mirror of
https://github.com/coder/code-server.git
synced 2026-05-09 13:57:26 +02:00
feat(vscode): update to version 1.57.0 (#3544)
* Squashed 'lib/vscode/' changes from cfa2e218100..2064d4c301c 2064d4c301c fix issue reporter unit tests d99a697eb52 Fix #125503 b098b10a77e Merge branch 'notebook/dev' into main a57e41b7a43 notebooks: more accurately detect old style of renderers c8a447f93b8 Fix #125507 ae4124aa7e1 Fix #122570 2bcdb95e7df update distro 89d750cd687 add restricted mode to created issues (#125537) 5f7166fd915 Put run menu above output container, Fix #125188 d9cf45e0dba Fix #125526 ab0bd774c91 Pass profiles, defaultProfile, isWorkspaceTrusted from renderer to shared process (#125450) 061403c7458 Switches to new RemoteHub authority separator 0e6d07052c8 remove untrustedWorkspace search for now 1c5b5a632f9 Hide "Customize Notebooks" in getting started behind setting f843c51bca8 Update lock file 2456872bcaa Bump distro 12547c1de87 remove virtual workspace information from banner (#125512) 6de86dcd840 update notebook layout default values. 0f0abb3dc87 trust vfs (#125523) 8d87a664274 Bump es build bcdfe884a3c Fix window reload with an empty remote window a0bc15ff6fc Close Remote Connection shows up in local windows fe1eafc80c0 Merge pull request #125498 from microsoft/hediet/fix-125034 5eb636f22fc Renames InlineSuggestionActions back to InlineCompletionsActions. e8ad99de2b3 Merge branch 'main' into hediet/fix-125034 dcf62e3a1e4 Merge pull request #125501 from microsoft/hediet/add-accept-action fe6fc208e9d Puts EditorOption.inlineSuggest to the right spot. c6777d5c6c9 Reverts menu rename. c5bbe0a35ea Merge pull request #125484 from microsoft/hediet/fix-125478 20cb29b076b Fixes #125430: Add a lock around the extension registry. 383ae36449b restricted mode hover: added a link to manage trust. For #125408 1a8285a9654 Adds accept action to inline suggestion hover. Fixes #125472. 2ba1dcb0c9c Removes setting ghostTextExpanded. Fixes #125037. 3300006ee17 * Renames command ids, context keys and command titles to Inline Suggestion. * Renames menu item, makes it proposed API. f8c061e73d7 restricted trust status bar item should use rich hover. Fixes #125408 620434543f5 * renames editor.suggest.showSuggestionPreview to editor.suggest.preview * renames editor.suggest.showInlineCompletions to editor.inlineSuggest.enabled 4b0c17e1808 fixes #115535 93e61e267ca fix compile error 54dd1429b64 startup timings uses now `exit` and not `quit`, fyi @bpasero 922f866a282 run menu: more precise context keys f207b383071 inline `NotebookExecuteHandler` type fa1694c8afb Merge pull request #125465 from microsoft/menubarFix 5efc2e7abcd 💄 7954c27d2d9 Fixes #125478. f99e1c358f0 fixes issue with asExternalUri API 8163cfd8c54 Make sure we layout editors in sideBySideEditor after they are created (#125445) 387e2f39ebb Properly complete notebook profile step 11f86fefb46 Track opening notebook separately from showing notebook GS page a2ef11ff553 fixes context keys with menubar fd2ed91612c Fix wrong CKS on output toolbar Fix #125109 f95fb77f22a Fix cellstatusbar layout issues from "visibleAfterExecute" Fix #125403 0acf7df100e Fix showCellStatusbar default #125403 a244a4ccc65 improve messaging with remote 5d58be33080 switch to a switch for localization edge cases ae0e8f04468 uppercase h in localization 885ac7f1c11 fixes #125406 b519331a06b fix #125458. 4c12628b2b5 fix #125234. 0c7e7cf21d6 disable notebook layout getting started. 4cae6713714 fix #125452. 72d325b4bc9 fix #125027 df59dc8470a don't show status bar entry until files are opened d3db92b7a2e Fine tune image styles. Fix #122680 72a67899410 add settings links to improve understanding of workspace trust c9e683d6c78 fix #124405 827398db432 fix #125444. f004a398e0b Fix #125313 b26e1aba58c Remove hashes from override 41b1406d3a8 Revert parts of "Fix #125395" Re-enables GitHub extension in untrusted workspaces eab8d0403f2 Revert "Fixes #125350: disallow in untrusted ws" 0006964091a Change cell type contextkey to markup Fix #125378 5ea26044187 Fix #124531 0a5d652bdfd Fixes #125332. f73f263c6bf Needed localization changes for converting xlfs to json (#125442) 0f0f066b392 Remove codespaces walkthrough from Getting Started 96004bc2c97 icon hover: add pointer for element hovers 11debc38469 Merge branch 'notebook/dev' into main b80011a819c Fix #124049 fd430418a01 Refine `CellInfo` type (#125351) b2cc8487aed Flip positioning of steps and media in narrow layouts. Closes #125246 5966e9dfec0 Check to see if there are any artifacts that haven't been published (#125428) a56ef182474 notebooks: fix default selection of untrusted renderer 788e39aad86 #125395 Improve the prompt 062401745a2 Switch loglevel to trace 73de22af596 Close #119722 be1cf4bddb5 Focus contributed profile terminals after creation 60651a0c4de Fix #125030 8f9eedf0019 Merge branch 'main' into notebook/dev 01c6fb557d0 Fix #125395 41163014f8c 💄 490cef7c075 Revoking trust in the remote scenario will reload the window 3f8672be8a4 fixes #124051 6d66648c51b Add snippets for ports attributes to settings helper Fixes #125081 e43c3957f64 rename onDidChangeNotebookAssociation to onDidChangeSelectedNotebooks 732769073a2 Workspace trust - add --disable-workspace-trust flag (#124998) 499e4948c7d Fix typo 41f117753ed debug: update js-debug 20df87725d3 fix build again 15aca96cd5c build - unblock lauching 8b4bc01166a Removes RemoteHub 0f2f7c7f04b Update notebook getting started images b2a64b1379c Merge pull request #125352 from microsoft/tyriar/migrate 515b7125255 revert a change fa19cd28adb fix build errors 6f0ce2e7508 Fix #125250 884b1e7a0d0 Fix "Trying to add a disposable to a DisposableStore that has already been disposed of." ref #125354 c8b7aaf2c29 Merge pull request #125340 from microsoft/merogge/onExit 7fd324944d3 Fix selected notebook profile highlights 99665d0b4ac Merge cell statusbar settings Fix #125173 2974dcbd985 Fix bugs causing walkthoughs to not open on install ab1c9202003 Try setting gettingStarted as default editor (#125321) ebde979a485 fix cell top/bottom border always active. 7dacb10b591 fix #125339. e6aaf493989 Move notebook getting started walkthrough to its own section f473e3dc9f8 fixes #125047 6cc80fea815 fix #125338. 66917f41795 fix #125334. 2356d7c5b76 fix init layout interrupted by editor group resize. a2a61127667 Hide execute actions from markdown toolbar Fix #125336 0f8c499d101 Migrate profile setting and fallback properly 272208523b5 register the extension request handler once workspace is resolved fixes #125342 4c57cf40313 fix #125175. bbc4995eca7 fix #125176. 195477a0e96 remove unused import 68e862e1d62 skip flaky window test. 9bd7cbd1403 notebook.insertToolbarLocation 769e7080f4b Fixes #125350: disallow in untrusted ws ae2f0b39e63 Fix #122741 8f3a47cc88c add singleTerminal argument 6ef81b30370 add safeDisposeTerminal to terminal service b295408c303 tweak wording fa8a7a84a13 Merge branch 'main' into merogge/onExit 895dbf4dfd0 fix #125069 60dd761211b update the layout based on treatment. 26cd18321bd remove backwards compatible tests. d215fc72380 Fallback to active editor if no webview is focused c5a4158a651 fixes #125247 3d0d203bb02 fix #124849 509906cd916 Merge branch 'notebook/dev' into main 4d4f0b528a2 fixes #125319 093b03bb39e fix #124854 Co-Authored-By: Daniel Imms <daimms@microsoft.com c4da8f4ab15 Improve terminal drag feedback 55f01b11fa0 fix #125067 fix #125039 Co-authored-by: Daniel Imms <daimms@microsoft.com> 941a603ccd0 Revert "Try fix getting started as default crashing integration tests" 0206b2ee43f Try fix getting started as default crashing integration tests d0ba023c71e Fix #122680 48fcde4048c Fix #123476 0543065c41a Don't commit inline completions on tab when "tabMovesFocus" is active. e2ad6d2c9a8 Merge pull request #125126 from microsoft/hediet/make-hover-unselectable b49731160d3 Add henning to my-endgame notebook 1fee13180b7 Vertically center notebook profile list 09a3fa687a9 Resets context keys properly when editor is disposed. c324c61ad57 add closing tags e92e3d1a9ea Reset context keys when Ghost Text Controller is disposed. ec5bf6733ca Fixes "Ctrl+Z doesn't retrigger inline suggestions" 26ff863e3d4 Merge pull request #125221 from jeanp413/fix-125035 ce8d2e86028 make IHoverDelegate.placement optional 32e6054985c Adds more documentation to the proposed inline completion API. Fixes #125267. 20be2c8eaa2 icon label hover: allow element placement. Fixes #125090 232412ff87b adopt terminalTabList 60a36219813 Revert "icon label hover: allow element placement. FIxes #125090" 4aa9f8271b8 Merge pull request #125292 from microsoft/tyriar/themeicon 8223d3d5c7f Fix #125156 0a32357e6d7 Fix filter.filterQuery initialization 3099c6f1c1b ios: trigger list/tree/table context menus 3a388466b85 tree viewer hover delegate: command handing now comes from hover service 1d471214fb3 icon label hover: allow element placement. FIxes #125090 0ee6895a6b8 Convert vscode ThemeIcon to internal type e040c0881f7 Workspace trust - calculate trust before extension host starts (#125283) ea339694fc7 fixes #125272 727caa7f238 Merge branch 'main' into notebook/dev e0131062c84 bail on standard start when workspace isn't trusted 0bf73ff15ab make sash work better on ios 9764a02c5d3 sash: use DomEmitter df709ddd3d5 Fix otherPortsAttributes protocol not updating correctly Fixes #125079 ef0fb8afb89 Revert "use associatedResource instead of untitledResource, https://github.com/microsoft/vscode/issues/125028" 073c4ffd7da remove deprecated notebook-namespace, notebooks is the future a24c1495208 Merge pull request #125281 from microsoft/hediet/allow-inline-completion-array 2e53913b637 Separator in remote indicator can appear/disappear. Fixes #122309 d71e5718c90 Fix ctrl+click to open link in ports view Fixes #125076 f32b38aa216 Allows to directly return an array of inline completions instead of an InlineCompletionList. b5bdc48d553 use vscode.notebooks not vscode.notebook 3d2309d4e74 Set icon for custom pty terminals e537c47c63c Fix double extension terminal profile creation a3337d01bb0 more api polish 17da5e37217 use associatedResource instead of untitledResource, https://github.com/microsoft/vscode/issues/125028 7051f7db889 Sort content of XLF files 109c9d9486b adopt viewType everywhere and drop support for it, fixes https://github.com/microsoft/vscode/issues/125163 b1e613481bb fix https://github.com/microsoft/vscode/issues/125163 9796ac82c3f (re)move NotebookCellOutput#id into proposal, also remove ctor overload that allows to set id a14600c325f editor trust - limit trust validation in `openEditors` to the places we want 39e7fd6a779 Merge pull request #125128 from microsoft/alex/ghost-text a1e7ce9cde2 tackle more todos, change ctor of NotebookCellData 0c3646199aa editors trust - support opening diffs into new window (#124618) 592ae5a1f53 editors trust - make sure diff editors bring up trust dialog too a286059b90d macOS - workaround fullscreen window regression (#125122) 82acbe807f2 updated todo search file ac05ae7b6f0 move file'ish things into workspace namespace e57462fd1f6 dnd - only add file system resources (for #125187) 0b6e70302f8 remove deprecated API dfc47c79571 rename notebookViewType context key to notebookType 542655758b4 fix https://github.com/microsoft/vscode/issues/125138 f2d6da27d01 make sure appendOutputItems and replaceOutputItems return something ee87b2bd432 missing adopting for end-call d487b379bae Merge branch 'main' into notebook/dev 7865b99b44e Fixes #125035 febc20e8d39 safari - disable clipboard error notification e7b9f3a1a40 Show a confirmation notification when installing/uninstalling shell commands (fix #125145) 217f1a2f60a Workspace trust - extension enablement (#125179) b154a3d3043 skip view column test. 322c81122dd Add "move cell up/down" to the cell context menu when drag and drop is disabled a90729cb6f0 show notebook profile the first time when a notebook is opened. c55d8a0e16c select notebook layout action. b2da4119928 Add undo/redo per cell for jupyter profile. 7aa8c5ec4a7 Fix profile contribution schema 2d97edd98f2 Update QuickPick api doc (#124485) 8df0f068588 Group startTime and endTime into a single object ef28d1663d9 Inline parameters to execution task start and end 5c6ae4fc5ac Update NotebookCellStatusBarItem constructor 872bbcc8c1c Update provideCellStatusBarItems 28bf10376c3 Add 'notebookLayout' tag to suggestions 4dd5ccb1566 Make registerWalkthrough public d2561813a8d Merge branch 'notebook/dev' into main dc9dc66109d tweak fix ac7bb556e8f Disable "consolidated run button" in Jupyter profile 4449461cae4 fix #125121 a296485f007 fix #125045 8e86a3c6b16 Enable consolidated run button by default, now that Jupyter has caught up 515c64f05e6 fix #125125 5e27fb3ba85 Fix to make @recommended:languages search work 84196c8b8d3 fix: update colors 950ad4ffec5 colors: add support for exporting colors 2c503281922 Add import to pull notebook getting started content into build 9bd78a8bf53 notebooks: restore renderer messaging generic 088e0d282ee Revert "fix #125137" d6d3091cbd3 fix #125137 c7f8b301938 Revert "fix #125137" 67e64a8fc86 Merge pull request #125083 from microsoft/ben/69349 80a899d8d67 fix #125040 aeecc7c03ff notebook: messaging api polish 4df863a28da Remove extra notebook images 092a2242ce5 fix #125137 2e8e888989a editors - more tests for capabilities b743bf63de1 fix #125124 d6d9200832a notebook: fix mimetype switcher not working between custom renderers ebcbe001591 update todo search file 6dbbcc2eee7 fix compile issues... e8d8e53752a update/tackle todos 6fe84c402bb Merge pull request #125106 from microsoft/joh/api/noMetadataType 553893e940a Merge pull request #125104 from microsoft/joh/api/noItemMetadata 632f2156f2e remote indicator: remove vscode-vfs workaround 5a49e6a283e Focuses the editor after showing next/previous inline completion. 1bab611b73b Sets user-select: none to the hover actions so they cannot be selected anymore. 103d3edfdc0 Fix text replace case operation false positive Fix #123483 a3bad5878ef Add a protocol property to tunnels that corresponds to the attributes for that tunnel and is reflected in the UI. bd8a0fb3d76 Fix #124779 0115c74d4af update todo@api search file 8dd2e53e247 bump versions a476c9d4f08 fixes #124850 0580a6bae45 Settings: remove notification that changes are saved (fix #69349) 545b931b96b remove dedicated types for notebook and cell metadata b1457fa4b6d remove NotebookCellOutputItem#metadata e7b775adebf Revert "fixes #115535" ae86512bf07 Fix automatic task timing Fixes #125044 6a02e8333c1 small jsdoc and todo tweaks 952e39f9d2d fixes #125026 d163f3f3d45 tooltip text grabbing broken. Fixes #124457 e9d05647b3b update distro 122df438a80 Run OSS Tool 89157c1a87a notebooks - tweak description for untitled files 1e446dfadf5 Don't hide suggest widget when expanding ghost text ab80101db2d Revert "Fix regression with extension enablement" 24e1234559d [json/css/html] update services a8a57c3fc67 Merge pull request #124288 from huszkacs/bug/issues_with_backspace a4e1a259a2b Fix tests that were not updated 91b7e6027ad Move vscode.newWindow & vscode.removeFromRecentlyOpened out of apiCommands. For #110583 2ae32273fd2 update my-endgame notebook 0a1046ada41 Merge branch 'notebook/dev' into main 2f253fcc220 more todos 4093effb9b0 Merge pull request #124964 from microsoft/alex/ghost-text 79048992ef1 Adds leftTrim function, fixes bug in inlineCompletionToGhostText. b1da1fbc8c4 Merge pull request #124972 from microsoft/hediet/fix-negative-overwriteAfter 3a259a7a6ae The replaced text does only need to be a prefix of the insert text after removing all leading whitespace. 1176faf27f2 Fix #119265 78c865d70d4 polish remote menu control actions 4ebf68103e6 readonly [] over ReadonlyArray... 67a85487125 Expose the inline completions provider in the monaco editor. 1eef15f0d54 more todos for notebook API... 552d457b718 updated code search file f700cab7c36 💄 2031df26538 rename hasExecutionOrder to supportsExecutionOrder f2fb2b8cbe3 rename viewType to notebookType, https://github.com/microsoft/vscode/issues/122922 ae17cc95591 Improve autocompletion for statusBar/remoteIndicator contribution point. Fixes #122566 efcf0eea9bc Remove default support from vscode-translations-import b55d4384260 Fix regression with extension enablement b7be98705fd Update descriptions 220f89ba3c6 jsdoc and todos 1e57955c798 Remove handling of Default inno setup file e1928efbb63 notebook API finalization part1 cc8ff11f574 Fix recursion exit condition in variable resolver 2e026cf7c28 more API todos 0fde806bf85 Add limited support for variables that resolve to other variables Fixes microsoft/vscode-remote-release#5007 355df0eccb6 move renderer script and IPC into proposed and merge with general renderer IPC, https://github.com/microsoft/vscode/issues/123601 da851abcd6f jsdoc 💄 8d244362832 remove resolved todo 83d5e2bda06 rename namespace to notebooks, https://github.com/microsoft/vscode/issues/122922 594fc814bca update untitled hint to work, use the correct setting name f68ee48c524 Fixes #124742 by ensuring that overwriteAfter is not negative. edf85f1711d fixes #122402 3b407e0b6c1 move onDidChangeNotebookCellExecutionState into proposed, https://github.com/microsoft/vscode/issues/124970 e9579534bb2 Only trigger session when typing, not when the model context changes. 6832b7ec4b0 Disables rendering visible whitespace due to feedback. This makes ghost text less real. 7cd53df7d72 Swaps previous and next inline completion actions in the hover menu. 427f48b8d17 Add prebuild commands dea978dd1c1 custom hover for remote indicator 8d41153ffc5 support custom hover on status bar entries d4161177d98 don't use console.trace for all... 692fb8ab211 print trace for potential cycle, https://github.com/microsoft/vscode/issues/124723#issuecomment-850674813 f55a5243a43 don't use console.trace for all... 910d70bb88d print trace for potential cycle, https://github.com/microsoft/vscode/issues/124723#issuecomment-850674813 8b04a825b49 Merge branch 'main' into notebook/dev 94f47f44c98 Merge branch 'main' into alex/ghost-text d776f0c1eba Fixes border color. Renames to ghostTextBorder/Foreground. 85f9447b720 Call adjustWhitespace on snippet. db1bd650dcc Don't compute ghost text that is not supported. Thus, no space is reserved for unsupported ghost text. 4f3d865a6b4 refactor for cleaner initialization (#124904) ccb0a8c2d93 Revert change in default due to build failures 6737ea008ff Change executionState to state 6949856b195 Remove API todo 038bac01c41 jsdoc for notebook cell statusbar API 667e3dd844a Tune getting startred color picker bb5b6afdce4 Remove "primary" in favor of alway opening the first applicable walkthrough 6adfa8b3135 Enable getting started as default startup editor b2f203113c1 editors - more alignments across editor inputs ada71479023 Open getting started the first time a notebook is opened 08eedafbd60 Finish getting started content b0862e99074 Don't include our loader in notebooks (#124864) 26b9218d64f Merge branch 'josola-edit-devcontainer-readme-markdown' into main ad52b3f4583 Fix merge conflicts 09d1935b4cd Correct dev container info in README 1605d16ee3d Refresh dev container content d7a79f0393f Add notebook profiles to Getting Started 887ff91a14a Merge branch 'notebook/dev' into main 5e7cd998762 Bump TS build version e3354e26eff Whitespace characters are near invisible in High Contrast theme (Fixes #124612) bb4e83a19a8 Improve hover stability and handle case where mouse is directly over inline ghosted text aada268951a update distro 5fe858a229c Workspace trust explainer in getting started f568f5aec3a Avoid scaling down SVGs in Getting Started eb65a93f4bb Include import ref to markdown so it gets included in bundle bbc7af71930 fixes #115535 3772aeec10b Typo 24143e91320 Merge pull request #124828 from microsoft/alex/ghost-text 868b84ceb99 fixes #124850 f509e7552a9 update classifier 676340ffad9 Get expected service worker version from renderer instead of main.js 3bfa3455a6b Fix incorrect reference when 404 on webview resource 445dc50f737 Use unique id for releaseNotes webview 1380c0c5e0b Remove unused code for rewriting endpoint 8058ab1a966 Disable consolidated run button by default 15616f8d936 refactor codeExchangeProxy to use same route and use it for refresh token as well a6f4d96b826 Fix #124049 44a24280846 editors - unset preferred mode/contents before attempting to resolve c784a74b8ba send Output metadata, not just OutputItem metadata, to renderer ea02f214c74 Merge remote-tracking branch 'origin/main' into alex/ghost-text dca2b9e2db8 Show hover over multiline ghost text ab1cf30d883 add api-todo code search file affbf49ccca Introduce `HoverAnchor` to allow hovering on something other than a range 4202ab071a6 rename NotebookCellOutput#outputs to #items b16b45fecb8 Tweaks the effect of showSuggestionPreview and introduces showInlineCompletions. Explicitly triggering inline completions will now work even if settings are disabled. d2854fd7877 fix #124842 d9f1e27aeb9 more API todos 394a1ce2dbc Merge pull request #124752 from microsoft/tyriar/term_trust 2f0a9160d0a more jsdoc, also more API-todos... 9eb940e243f Remove padding left (#124823) 42af32d16a0 do not use shadow dom for editor context menu for iOS 6c10a5334ee add jsdoc placeholder everywhere e7776ccdcc3 Setting for specifiying the local host for port forwarding Fixes #124581 e15f4026511 Add `mightBeForeignElement` detail for `CONTENT_TEXT` mouse target types a2d1bd0d158 Adds menu 'editor/inlineCompletions/actions' that extensions can contribute to. 098dfd56e3c Changes shortcuts of next/previous inline completion to Alt + Open/Close Square Bracket. 00de32d013a Keep track of the span node used for hit testing 8e54aed67b7 Remove `IHoverPart.equals` ccfda77c8e6 Reorder participants to improve stability 77bc8745b29 jsdoc for NotebookController 71fafe39464 update jsdoc for `NotebookCellExecution` 541a5d4fa70 smoke: trust dialog should appear in 5 seconds, and we can ignore if it doesn't 5b642616ef3 Use loading + spin codicon for task terminals status Part of https://github.com/microsoft/vscode/issues/121659 04e09dacdfd refine output modifications of NotebookCellExecution 3a857fc436d editors - add tests for capability change events 004f6609f8c Fix problems with port protocol 903e219ccb8 files - need to track individual readonly stats everywhere (#124524) 466dd4e490e files - provide access to stat object from not modified error 872fac207ee Merge branch 'main' into notebook/dev d5c73cc952d fix https://github.com/microsoft/vscode/issues/123570 3ac57c71329 Fix NPEs caused by extension that is not in the marketplace df03e4caadc Removed unused MarkdownString. e5f70e8e2a2 fix smoke tests (#124814) 47d3d743cad set max zoom to 8 (#124769) b2da15ea2d8 Merge pull request #124745 from microsoft/alex/ghost-text a9c54044e50 Refactors inline completions model. ccedcbdf5ec Do not accept suggestion on tab if indentation is suggested. a2944c32ea9 Fixes rendering of whitespace in the inline decoration. 8a3f351eb9c Use explicit context when the user cycles through suggestions. 1fa3397c48c fix post-merge hickup a97ebdbabc8 Merge branch 'main' into notebook/dev 8715d80695f fixes #124809 7a767570e85 editors - more tweaks to dnd behaviour 07ff28c58d2 Dont center markdown content 3ea0bf8fbca Implement run button with extra actions, and "run above"/"run below" actions 970858ef6dd Add first pass of color theme selection markdown content 29cad8fb5e6 Merge pull request #124754 from microsoft/tyriar/profile_api e0c8a76b7ad don't sort scope list. Fixes Microsoft/vscode-pull-request-github#2751 c1448f3161e Remove ansi escapes from localized string 9eeb092206f Re-enable skipped test 9b609ab1e11 Clean up profile provider api e8758933d80 Disallow custom profiles as default e037348d272 Support launching contributed profiles from dropdown 280e0070733 Add undocumented always syntax only TS server mode 866ecdd45a0 Merge branch 'notebook/dev' into main 1c3cc87bccb Add an `InlineCompletionsHoverParticipant` 7ce379e3f2b Merge branch 'main' into tyriar/term_trust d9e043161fb Bump sw version 73ff24e451d Fix de/encoding for rewritten vscode-resoruce uris b8f11107f72 Use @types/vscode-webview decb1ab6a89 update left aligned plus button margin. 484b04df64b fix #124774 902bb43b3b1 testing: move back to proposed 31aef10814f testing: hotkey for opening output peek 14a013e7f3c Support contributed profiles in dropdown dd61b26f33a enable workspace trust by default #wt 5338cc32da7 fix build. 4403b9010e5 Tweak remote workspace trust calculation e3d4313714f support editor options override. 384f42bca76 Tweak wording, fix #124724 0dd52e2d77f Update distro b157bc7e5c3 Support for @recommended:languages search (#124546) 9224159b004 Migrate users from shell -> profile settings (#124615) d2a0bfb2866 editors - log error when opening fails 61f3ac6e07b support context keys in menu bar (#124726) 0de0faecee5 fix https://github.com/microsoft/vscode/issues/122376 f523f65f398 Add a `description` field to decorations to be able to find leaks 4c5a061df61 rename NotebookKernelPreload to NotebookRendererScript 9ef57b5c216 remove NotebookCellOutputItem#value, https://github.com/microsoft/vscode/issues/123884 4ad3265cbed Support workspace trust in terminal a56fe2b397e use isVirtualWorkspace ed611d0ec39 Minor tweaks 4edb0110f6b Small tweaks 7bd0337d940 Move parts of the triggering logic into the widget 2508b33f0d3 Wait for listen when making tunnels 7a83e5aa627 workbench API: assumeGalleryExtensionsAreAddressable e94e8ed3998 Show default profile on top of term dropdown 693677c8a24 babel.config.json language mode should be 'jsonc'. Fixes #124683 cd501b2e5bb Merge pull request #124741 from jeanp413/fix-124735 9dd89100b49 Improve editor DND to work with untyped resource editor inputs (#124749) 14a4548e4ab editors - use preferred content only once da1193950a1 some jsdoc for NotebookCellExecution 30074591eea editors - input 💄 55d91bbed87 editors dnd - more 💄 9d907212baf rename NotebookCellExecutionTask to NotebookCellExecution 5d534b94f83 dart: fix language configuration location d0d80ec337f fix build 23fb4207373 Merge branch 'main' into notebook/dev 0637663fcfe fix isNoCorsEnvironment 9095b835bbf editors dnd - fix dirty contents in files diff c870d75473c editors dnd - enable diff editor transfer 70418f0aced editors dnd - fix untitled transfer bba59424f56 Merge branch 'main' into ben/better-dnd 367a0b809ab editors dnd - towards allowing any untyped editor input 6ba7093a8a1 Fix window/folder/workspace terminology for status bar entry and banner 9638534dd51 Fixes #124735 c1d4497a31e Merge pull request #124664 from microsoft/aeschli/dart 1082913dd01 fix config default value. 433c801dcc3 editors dnd - some more cleanup cbbdb17b80d editors dnd - rework to allow to set contents to untyped editors b5b059d2e2b fix typo in files.ts (#124693) 5eec3c86e3c smoke test - disable experiments 85d8f223c70 Remove keymaps from getting started walkthrough 02968bc2503 Call post message to all parent clients 1fd2b4ed1e3 Rename keys and support full context key expressions for check offs 14b31425b90 Fix #124531 bfb94c85fa3 fix build. c95af9ae460 fix build. 8519a42c5f6 notebook options respond to view type specific cell toolbar position. e3c8ff57849 try showing the banner only after some files have been opened 561b8364008 testing: fix hidden tests not being un-hidable ade83f05895 update context based on outputs. 574696980c9 notebooks: update state of messaging spec internally c4fda24034a testing: fix terminal being shown inappropriately, use beaker 0feee9edefc Remove `instanceof` check 641c2b175ca Merge pull request #124707 from microsoft/alex/ghost-text e0a52df1695 Merge branch 'notebook/dev' into main c3d63c1a1b2 fixes #124692 8e9405e7a9b Keep the `ColorPickerModel` alive between repaints in the `ColorHover` 35b43a410b0 Help TS understand the code ebdd548be22 fixes #124702 c538781d780 add notification if tsserver logs are left on for more than 7 days (#124149) 363e8f25f6f bump distro 244b48af73d testing: additional actions and better theming for peek a5bbc53e882 Extract more color picker related logic to `ColorHoverParticipant` 4e6fe2dc51e Extract pieces of the Color Picker logic to a new `ColorHoverParticipant` fd7c71a2eea 💄 c88d5756213 update compact view default value. 1e6b6cb6843 Pick up TS 4.3 final 6a12866c3e9 💄 1abeaf97753 Workspace trust - remote workspace (#124617) a430e8f9692 Fix settings dropdown hover style Fix #124207 b6acb191fac Fixes #124665. 99bcc8e2458 Show clear outputs only when there is kernel. 9252be4f90a distro 21bf10d4977 Removed unused member. 54ebd819176 distro f1a5d40aa75 refactor getCallbackEnvironment to return authority by default 5eddbd9d207 Properly support splitting ext profiles 08cf3df7457 Pass object instead of unwrapped args for term create d7779916566 move buildExtensionMedia to lib 62e8b545f7a missing build 51a5caf3f23 Simplify terminal creation bf5f7dd5ec1 Get split terminals working d1ed9c8bf35 Adds proposed API for window.getInlineCompletionItemController(...).onDidShowCompletionItem. 3ff91e7621b Merge remote-tracking branch 'origin/main' into tyriar/profile_api 7f21b1af485 Revert "Allow extensions to create multiple sessions from the same provider (#124640)" (#124705) 4583ef442b0 Fixes bug that "show previous inline completion" actually shows the next one. 8f0589da177 Set suggestionPreviewExpanded to true by default. 9464d14f317 Make notebook renderer activate potentially async (#124647) 5eb64c54ca4 Modified markdown preview nested list styling (#124445) 23e1f261dc8 Remove `HoverPartInfo` 84bc432011c Add `IHoverPart.owner` 31a59b5970d bump distro e6a1cc38437 Update commands.json e29194ad0d3 Activation event, register api 6a7c700a12d file working copies - do not throw from ctor anymore when schemes are unexpected bfccdcb9581 disable fullscreen on ios 764d8fdc3dd test: add test for event Relay (#119070) a7ab347c2b3 remove old notification d662cc56549 remove old license notification c704e43d5dc Check that the /build/ folder compiles 0e3459b0b9f expose Grid.onDidScroll 0a4bfb31c90 fix naming a628237458f Web: "Open Folder" on empty workspace does nothing 4cda850edf5 disable welcome editor in smoke tests (fix #124674) 0688745e82e Color.Format.Css.format does not return null, so the return type can be made more strict. (#124568) dff33d73244 use actual object 0d44b63c1c1 fixes #124407 9cba86888c9 update node version in optimize 0a7a69f1908 fix build compilation 9eb4eff77b8 some more jsdoc, https://github.com/microsoft/vscode/issues/124357 d63a69abf3d some enforcement of unique mime types in the extension host, https://github.com/microsoft/vscode/issues/124357 24ca9e0d7c7 Merge pull request #124654 from ValidMelvin/main a961587ecf7 dart as built-in language 98dc73a3296 unnecessary space in in julia cgmanifest c97189d9d06 some more :lipstsick: and simplifications a48d92d0fd8 only pick the first occurrence of an item per mimetype, ignore others, https://github.com/microsoft/vscode/issues/124357 18b6620bc39 fixes #123543 c2cc9b11afe Update README.md 14f61093f43 fixes #124507 69259e84a0a some 💄 for https://github.com/microsoft/vscode/issues/124357 44b470c99ff fixes #124576 47d00215aff editors - cleanup untyped editor interfaces for text capabilities 04f585a580a cleanup interfaces e12f21f498d Merge pull request #124549 from DonJayamanne/createNb 5e146d257b5 Update classifier.json d0884f4a1ec webview - clear group listener when it gets disposed fe1547c251b Merge pull request #124621 from microsoft/roblou/diffNotificationMessage 5cc9ec944d8 sandbox - bring in iframe based webview service 3084319750b Get rid of classes for workbench editor options (#124589) b82b90c1000 editor options - more 💄 before merge a18ea9c9ec2 Allow extensions to create multiple sessions from the same provider (#124640) 21162bfb68c Shift execution count label down just a bit 68efd480d0e Explicitly convert properties to strings before uploading ad41b3a12b5 Mark a few more props as readonly 0a7061ed002 ReadonlyArray -> readonly arr[] 24a23a8ea05 Mark array params in vscode.d.ts readonly (#124599) 7d50ce2061e fix tests 2521499104b testing: add clear test button to peek view title 57af60a7b7f testing: initial test message split view 473cfe28bfa Revert "Revert "Merge branch 'main' of https://github.com/microsoft/vscode into main"" 8822790908b Merge pull request #124622 from microsoft/gettingstarted/navigateToStep 7c01395da16 Revert "Merge branch 'main' of https://github.com/microsoft/vscode into main" 7a976501eb3 Merge branch 'main' of https://github.com/microsoft/vscode into main 22576768342 Applied new style to first getting started walkthrough 8ccc1243fc0 Fix notebook unit test b0f67df65d7 Fix build bb19c28fe2c Getting started layout polish 2d9aa1868ae Fix broken @tag in settings editor Better fix for #124520 0b2d890624c fixes #124619 #wt 20ce9d57629 fix weird border rendering on windows. b4f4839da5e fix integration test 6bcd590a706 Merge branch 'notebook/dev' into main e1731e91048 fix build f378cff1a8e dropAndDrop -> dragAndDrop 032920fa2ee vfs -> virtualfs 72c4bd69c04 remote menu: sort entries of current remote first 757fd91f040 Hide TestReolver.newWindow when in virtual workspace cf2a866b7dd test resolver: supports untrusted workspaces 68ba0141fbd fixed notebook focus indicator margin 5926c50d6c4 fix missing markdown height update 33209f104db update markdown height on options change. 7fd8f828c02 fix pwsh profile test failures (#124613) 8f18f3c5ff2 oops 771446e4a21 Make showCellStatusBarAfterExecute not experimental 71cc917274f Add option to show cell statusbar after execution 3ccbe2a6b1b Fixes #124247 789a91a487d wip db6ffb43b90 fix #124583 c351b396cac Support terminal renaming inline in the tabs view (#124533) 33eb149bada Add reset to default to terminal tab color selector 18b30e5b494 Fix default shell args for debugging a8b63f564af Merge pull request #124585 from microsoft/alex/ghost-text 19c88bc3c7a editors - more EditorOptions cleanup 5254e33173d Removes unused const. af0078d44d5 editors - more EditorOptions cleanup de08dcdf276 remove folding classes first when config change. c17917e6ba4 Enable notebook renderers on web (#124557) f2b1f78ad45 editors - remove EditorOptions a21b824704b Enable notebook renderers on web (#124557) 0249ae32871 clarify that NotebookController#id should be stable, https://github.com/microsoft/vscode/issues/124535 080f8b5e35f editors - remove TextEditorOptions 8adc16d6c50 editors - remove TextOptions#create b10db1828d0 editors - remove TextOptions#from 9bdb8f88816 editors - remove TextOptions#fromEditor 593a25d41e1 editors - remove TextOptions#apply 741beb8f264 TestResolver, use 127.0.0.1, not localhost 3de3141ff56 Implements basic cycling through completions. 4d4ebbee5fc Clear inline suggestions cache on commit. f89fc476de3 Fixes colors for dark & white theme. a78bc1aa96d icon label: supportIcons implies LabelWithHighlights d4307f8d534 missing validation for top-level token color object b6551f8e4e3 Workspace trust - no need to call setWorkspaceTrust (#124566) afc2b062703 editors - start to remove typed editor options (notebooks, search) 64b0c65b8cb editors - fix cyclic dependencies 5880a6a4eeb Merge pull request #124570 from microsoft/hediet/ghost-text-expanded-by-default-setting ce8b0049619 Merge pull request #124353 from microsoft/chrmarti/localinterface 97d576cb085 editors - more cleanup of large files 43b7187c3df Force inheritEnv in ext debug terminals 0685a4af28b Default bash, zsh and fish to login shells on macOS a26ecef44b5 Add self to workbench-diagnostics 8a1de4b022a Merge pull request #124567 from microsoft/hediet/fix-after-decoration 0efc03fb168 Removes unused import. 5364a9351fd Adds option "editor.suggest.suggestionPreviewExpanded" to toggle whether suggestion previews are expanded by default. 8bd49ffd50b update distro eff82a3cef1 rename trusted types CSP from notebookOutputRenderer to just notebookRenderer ffd1f84ea9f rename notebookOutputRenderer to notebookRenderer, https://github.com/microsoft/vscode/issues/121819 fd212e712ea validation 💄 https://github.com/microsoft/vscode/issues/121819 52df34dcf19 Fixes invalid cursor position around after decorations. eebf8e876ab editors - extract input and side by side to own classes 42e59bd3777 more complete cell info for markup renderers e11764f8420 add ICellInfo#data to eventually replace bytes b5fc2b0a398 NotebookCellOutputItem factories allow for metadata, expose "bytes" as data property, deprecate value-property, fix converter issues, https://github.com/microsoft/vscode/issues/123884 856fb393cb4 editors - more dnd code polish towards any editor dnd f2248508a68 fix tests on windows ab793cf02ac editors - make dnd code ready to delegate serialisation to respective editors 59f5bbaf253 editors - cleanup editor descriptors and 💄 dee37c6e765 web - offer upload action only for editable folders e0f751f1287 editors - introduce and adopt capabilities 71d00b3b993 Merge branch 'main' into notebook/dev 0bada155580 more API todos 76523deb91e Bump browserslist from 4.16.1 to 4.16.6 (#124550) 963f30f0871 💄 b1823157d54 Fall back to existing published webview commit (#119295) 286b643ecd2 chore: bump electron@12.0.9 c3c2113c368 Preserve execution summary when converting dto 8173a0e0398 Fix hang when typing incomplete @feature in settings editor Fix #124520 28803975a1a Add @feature/notebook e9f3fe9f301 rename markdown layout section. fe41fcc58d6 💄 fe3cab25c5f compute position/width of output action bar. af14bf673ab remove kernel picker config in menu contribution 1da9f2d2e6a Update subscribers.json d28d8802e42 testing: add default keybindings 8b9f8595cc2 Remove unused 6cfad71ce61 notebooks: remove deprecated vscode css vars from webview 196e79256dc Add workspacePlatform context key for getting started items Closes #123824 6ed2584f390 fix unit tests. 46ef54c20e9 Merge branch 'notebook/dev' into main 8a41d85d1be notebook open layout settings. f88c007f6c6 👋 Rachel, 👋 Tyler d7ead317dca Close #122570 41d869f5514 move nb layout settings out of experiments. 79762396a06 Respect soft revert for custom editors (#115658) 78251f8e016 Update distro 7e0d8cd80ee Merge pull request #124511 from microsoft/tyriar/color_config 9993eb9d1ae testing: improve support for markdown messages b168ece8de3 markdown: add nbsp to unescaped sequences b04c9668927 Fix test resolver with trusted workspaces dbdc7a259e2 added bottom spacing for table in markdown preview (#124385) 5cbd4bc4691 Fix color of single tab status icon a8d2a3a87c5 Resolve todos 47b7320c1f4 Show tab prefix before icon is ready a31d9ca6993 Don't show icon until its resolved f7f6956554a Share profile property schema 17e82898ed1 Remove show all colors, add settings intellisense a8e7253de09 fix scroll in table widget #wt fixes #124314 2b9f22631f5 Support terminal link word wrapping (#124514) cdbf46815c6 update distro fe1af896d5d Use remote reconnection constants (#124517) 2b6564c1015 insert toolbar on the left. df3c2c48755 testing: move apis to stable b6dad5d1437 Support any theme color 3c2ffadf29c Dynamically generate terminal color css 9eaba8944ff Support colors in terminal profile setting 7a0ce574da6 Fix test output pty usage 777f09d4569 Fix safe config provider fallback to default d63078aaf04 Merge pull request #124317 from jeanp413/fix-124127 a8397d15299 Merge pull request #124480 from nrayburn-tech/fix-122348 934a4244e27 Remove overrides from editor service (#124375) b47569400a3 option to hide terminal tabs if there is only a single group fixes #122348 5319757634f Add fallback webviewExternalEndpoint in code 8f11975c47c Close #119722 d6b5df5e199 retry logic for setting password (#124390) 12fa7b04257 Increase timeout for webview focus change 4c4ec402e7e return early b314536e56a Move find file references into the search group 18296326a9a Set proper context on new cell output toolbar 28767c88f0a Close #122795 cd8bf7ba536 fix zero height output height. 7bc2019b8d5 Updating pinning test be8745ac9e0 Fixes #123228 (#123584) 6ee883bfa32 Split href before decoding instead of after 6af49913c2a title for notebook label in notebook toolbar 77ff6eb03bc Add image specific link normalizer (#124400) e9b8c129155 Add types a03daaf8506 Merge branch 'notebook/dev' into main 4ba27602740 fix #119214. 8afcdfaad8b extract complex options parsing. 9996e32bc9c testing: improvements when running a previously un-run test 6507d05310f freeze layout config. 6cda8d2373d extract configuration update f38f3bb8e78 Merge pull request #124044 from microsoft/alex/ghost-text 51aa5402d63 Allow single line decorations that are not at the end of the line 06a86116b73 Fix firstHostSegment logic 827dec3a0df Use better logic for getting scheme and avoid extra decoding for authority 44fe867eddd fix tests c23c9e448e6 Implement dragAndDrop enable setting 021071ff74a Avoid double encoding authority 22dc518a210 Remove unused type 97bbacd8087 Remove extra call to with 82d18a42fa2 testing: fix auto run triggering test multiple times 17f3a69e243 Allow toSide #119725 86cb6d6b337 Close #119725 4d53b454283 Fix local term warning in remote 5ac4f5f5be4 Fix didn't work af3fbd968c9 Make sure we decode uri path 84f5040b3db Remove unused member 91fdd52d17b Merge remote-tracking branch 'origin/main' into alex/ghost-text 8b709a2f55a testing: provide test IDs in menu contributions, rather than internal elements 231c155d31f Refactors the inline completion feature. 852b70eafed Try fix (#124384) c5da7f33a70 Fix wsl detection in profiles tests b6387e477be Tweak output toolbar position b78754e1e46 fix incorrect links (#124203) a0d7f6292e9 Remove a few instances of 'vs code' in d.ts b0683f58a5a fix #124360 9847783f62d Remove uuid from webview resource uris 79dea51e79a Rewrite webview urls to be more url-ish 8a4bf3081a8 update folding on mouse over 70c87f0db9b Fix #124240 adf68a52d56 editors - some type 💄 around setInput effac5be7f9 drop as-prefix for ICreateCellInfo 8f093359f07 editors - simplify some serialized editor input 8aff8020dae Improve guard against unexpected URI call 82cd4f027ad fix leaking of NotebookCellOutputTextModel and NotebookCellOutputTextModel#_onDidChange, fyi @rebornix c5b9b6c48c7 Fix missing async 5807530e3ae more jsdoc 85f518b2552 Add `RemoteAuthorityResolver.getCanonicalURI` c650993dd39 Add `ResolvedOptions.isTrusted` 49e96be2fc1 💄 prefer `readonly` over `public readonly` 1087876df86 Use vendor-prefix for notebook specific mime-types, e.g application/vnd.code.notebook.stdout 045e5d2f568 Store conflicting defaults in storage service (#124366) fc0b6f5e5ab fixes #122653 8b25f922b6b builtin support image/gif mime type 61aca51a322 consolidate SVGRendererContrib and HTMLRendererContrib - both were the same... fb6f0d95fad consolidate JSONRenderer and CodeRenderer 103892d1eb4 fix code renderer layouting issues 32a06b9be2c [typescript] add limited description bbe3b2266b5 allow to signal limited functionality in virtual workspaces d65dace8ea0 Merge pull request #123351 from nrayburn-tech/fix-77239 b9f20119393 Bump distro 815f217174a jsdoc for factories, some unit tests, https://github.com/microsoft/vscode/issues/123884 8268bd46e7f 💄 375a15f07e9 Merge branch 'main' into notebook/dev 996dfffd637 enforce proposed API for shortTitle proposal, https://github.com/microsoft/vscode/issues/124355 c5637229dda Merge pull request #124289 from microsoft/command/shortTitle 2b959fdadc1 Listen on local interface only (#124350) 494e827e351 expose output item bytes to renderers and provide util functions: asText, asJSON, asBytes, asBlob, fyi @connor4312, https://github.com/microsoft/vscode/issues/123884 100a70731db SVG images are not render in the tooltip (fix #123688) 949f60498ae Merge pull request #124346 from microsoft/ben/pfs-promises 063be236f34 scm do not auto focus input box on iPad f19843b0a3e editors - some 💄 and tests for workspace trust in openEditors 9086ecd9f08 Renames Suggestion to Completion and cleans up API a6f7aa5e4c5 handle workspace uris 73c6f34f9ec Do not recognize Debian's .install file as a shell script. Fixes #124295 90b9e04f951 try to use suffix for view type when creating untitled notebook, fyi @brettfo, https://github.com/microsoft/vscode/issues/121974 779f9876bc0 debug: properly read the debug.saveBeforeStart respecting activeEditorMode 79e642a5ddb move and simplify renderer registry bbc293839d4 declare IRenderMainframeOutput#dispose and adopt it for builtin renderer 55059ffe214 todo e6dd819bf08 first cut of "bytes only" output items 4b5db9098c2 smoke test - disable failing search test 17459a4abd1 notebooks - add test for NotebookWorkingCopyTypeIdentifier 43b51ce8e76 notebooks - fix type identifier compute 703ca68ea6b fix typo b8fe2db4392 fileworkingcopymanager2 => fileworkingcopymanager 0e58bef15cd file working copy => stored file working copy 78fbc51ad75 Close #124325 633ea857088 notebook working copy - some cleanup before refactorings d0c5675f6f2 editors - less group.openEditor usage e66c62f38a2 Generalize smoke text 89c8f919884 Add smoke test for #124146 4fd4e10e99e Fix #124146? bfb822e4cf0 fix #124284 0989449a055 Fixes #124127 2f2b6b528d0 testing: show duration for test results, handle state computation better 3e5faf69c14 Remove console.log 9e04a67d125 Switch to use vscode-webview.net as default webview endpoint acc07bd9591 Mark properties readonly bf4c7042a3a cell toolbar overlap with notebook toolbar. f29bd6f18dd Dont autofocus input on ipad Fix #122044 5b0fc94e6af fire event when trust is change in empty window #wt c1b809ef221 Fix #124307 cd27f1fcb91 update text for loose file button #wt 07a2aab8f2c Remove duplication around checking isRemote 55c2fc5604e add setting and checkbox for untrusted files #wt d3f040fe017 Fix notebook cell statusbar items duplicating. Cancel tokens on dispose, and dispose the timeout properly 894b7782cce Merge branch 'notebook/dev' into main 9627b4ea63e Emmet identify CDATA for wrap, fixes #123136 97740a7d253 Revert "Revert "Revert "Closes #122433""" ddf8cc42616 Dispose CKS 4c1474b458d debug: finalize parentSession 514d3162bc8 Persist title source and set after reconnect 604b27db1bf Fixes android screen-keyboard backspace issue. af0c01b1e36 Short title for command contribution. 18ea5ac15e6 Use isRemote instead of remote authority to determine where to load from 6f2381e33d7 Terminal tab icon API (#124004) d0cea47ae39 Merge pull request #124194 from microsoft/dev/t-andreamah/outline-reveal-editor-switch-fix dae2a9d4ebc do not auto focus on ios extensions input and keybindings 6b097212e69 Revert "Revert "Closes #122433"" bd934d89ea0 Use untyped editors for workspace trust 59abb887f6c notebook: address messaging api changes 5b17052a5b3 Fix webview tests 2270c36cff9 Use extension location instead of remote for asWebviewUri 9ffac783be4 Merge branch 'main' into dev/t-andreamah/outline-reveal-editor-switch-fix 22f7f470b69 Apply Logan's suggested changes 0b355ffc89b notebook: fix certain kernels/output not working f75152cd815 notebook: fix certain kernels/output not working ab5df442eb3 Merge pull request #123727 from microsoft/dev/t-andreamah/markdown-static-preview-scroll-state 18c254987cd Merge branch 'main' into dev/t-andreamah/markdown-static-preview-scroll-state 2ab6e9cdd26 Debounce invoking the inline suggestions provider 99be6bad750 Add a command to hide the inline suggestions 62bbbcc7bf4 Remove log 875ada9bfad experimentalUseTitleEvent -> titleMode 76154be4d4b Truncate Windows path from sequence, do title handling based on remote OS 8a6cc9cba7c Fix dynamic title for reconnected terminals 74849362b2e Render only one line of ghosted text by default 713d5861404 💄 less casts 1718a6e2ac8 Hide hover on target element(s) click 224b0f22922 Fix category and max-memory option args 5ae8db2ae0e Render the suggest widget above when rendering a multi-line preview 8c194abb5e7 Do not shrink the additional lines once a shorter suggestion is displayed da4fcc266fc file working copy - avoid ugly casts aea7c1a3343 Merge pull request #124196 from microsoft/tyriar/dnd_2 e538fd90c0e Hover hover when context menu is shown 4a679df5312 Hide ghosted text when it wants to be displayed in the middle of a line e91d5475acf Merge remote-tracking branch 'origin/main' into tyriar/dnd_2 c6d2254b2fd Create a `InlineSuggestionsSession` also when showing suggestions b5a9a026068 Invalidate current suggestion based on cursor position 49339aba513 Only start a session after a content change (with debouncing) fdbcef0a842 Add `editor.suggest.showSuggestionPreview` to turn on/off inline suggestions c8410ece147 node-debug@1.44.28 234136b6c28 💄 acceptsNonWorkspaceFiles -> acceptsOutOfWorkspaceFiles 91bf9326334 fixes #123469 2cc3b168fde Do not force the instantiation of the `SuggestWidget` 1543754dcab Tweak loose file dialog warning 669b0b3f719 working copy manager - clean up some types add some new APIs on manager2 for unified access: - get(resource): working copy - workingCopies: workingcopy[] - onDidCreate: Event<working copy> 3234403c5d2 Merge pull request #124191 from microsoft/aeschli/virtualWorkspacesInExtensionView 19574448d0e Fix rendering snippets 5f7cfa3a54e Introduces a GhostTextWidget model to enable data binding. a6f89f58a9e Workspace trust - empty workspace (#123811) 0c996a39b65 Implement consolidated output toolbar f583b4b3367 Merge branch 'main' into notebook/dev 29c61570a5b Revert "Closes #122433" 34180ac9bef Remove code allowing multiple ids for a command 35eafb78732 Add sort imports command e65a227c829 Test using readonly[] syntax in vscode.d.ts 1ddc623e585 Simplify logic for webview resource uris (#123740) 93be0a6fa03 Update simple service 46a1ca78249 notebook: initial renderer communication 1ecba0426a9 Make `reload webviews` support webview-views b3ed595dde7 Extract isRequestFromSafeContext 39d1a94e86f Allow loading svgs from xhr 4fbd548d72c Merge branch 'main' into dev/t-andreamah/markdown-static-preview-scroll-state 40592a274c9 fix test suite. 4fcc2720826 use custom shield dialog #wt a65d55e9c48 Strict null fixes in webview f822083cb70 Check `defaultPrevented` before showing built-in webview context menu f594bb47e59 removing more unecessary whitespace 41606da2dc0 removing unecessary spaces 920c9a3a0d5 improve banner for virtual workspace 9ee1906718d pr feedback and combining prevEditor info for preview and non-preview f433b4781a0 Pick up TS 4.3.1-rc d921cc41fc0 Update markdown grammar 1642d4cd0ad Merge groupBy and groupByNumber utils Fix #123569 c1d6e44262e Avoid listener leak warning due to reusing cancel token for many RPC calls 0d338068034 Add suiteRepeat test util 5b3cf7cc2ae Merge branch 'notebook/dev' into main c936add6013 hide kernel status bar item if it is already rendered in notebook toolbar. b8c7d75e465 Avoid illegal line numbers in case of undoing a completion at the end of the file 63348b4a395 Improve contrast de89605adfe Do not invoke provideInlineSuggestions when just moving the cursor 08f4a36de15 notebook toolbar container display none by default. 6956a38b674 Have the `InlineSuggestionsModel` listen to events directly 26194faa8be fix focus indicator default value. 2fbf0cd977e Respect the suggest widget only when there are suggestions 8c27c1f257b Update list focus highlight color in standaloned editor Refs #123703 0ec51535fa2 Rearrange actual group splits, not just instances 25bdb018483 Add theme key for terminal dnd 15f772fae32 notebook: include script url in back compat patch b79825e7c4e Clean up a3e72ce1d06 Reinit dnd observer after reattach 72b01fea379 Avoid selecting accepted text d7f6d7e735d Fix markdown cell drag indiciator having wrong position in scrolled documents de1c8ad93a9 Give clearer names and add comment 40a26850575 Fix shift for markdown cell selection only selecting current cell 3c417665643 Fix strict null errors in webview main b8a0123cfc9 Fallback to fetch if localhost resolve fails f0ef8dfd913 Add explicit null typings c63ac2f6388 Use searchParams to get id c13c6d8e468 Throw error when trying to reload/navigate within a webview 05f83d95d2b Fix issue reporter not debounce submissions (#123340) 8e87fea2f15 Support dragging to unsplit 40fbf5d915b insertToolbarPosition e071cefa4df Support creating splits in different groups f1a372c15f8 Support rearranging splits 2b30689ab00 Close #123935 25a12c75c5a use dummy uri 568bb89fadb Merge remote-tracking branch 'origin/main' into tyriar/dnd_2 5266a5fae3f Improved hover feedback 44d135e94f3 Merge branch 'main' into dev/t-andreamah/markdown-static-preview-scroll-state f4e05837d01 Remove custom height, use default from menu service #123869 134d9b187bb Closes #122433 a796ebfb8b4 update actions column width #wt f36c7a0860b Terminal dnd feedback 3804c98d6f7 fix padding for table #wt 5442f154ef6 fix issue when not connected to remote #wt 6356613d809 Update according to suggestion 49187c4e785 Replace flush with setFlushOn with loglevel info, fixes #123856 45e5c50dbfd Partially implement terminal drag and drop 283180b64b2 don't show action icons all the time wte 9716c27e068 slightly better uri for untitled notebooks, https://github.com/microsoft/vscode/issues/121974 1256b258a6f Honor the selection suggestion in the suggest widget (when it is visible) d553c21d5e0 fixes #123869 52c8fced385 focus indicator border or gutter 81c6572671c added default editor override for symbol reveal 9326ded502b show extensions limited due to virtual workspace b02acf39081 Merge pull request #123738 from microsoft/dev/mjbvz/unify-renderers-api 284c21a588e tweak terminology for wt entries 9a322a0d62a use folder terminology due to windows/linux limit dc8844925ab Part of #122996 b63ad124d2e mark untitled notebooks as dirty by default, don't hold on to untitled notebooks eagerly, https://github.com/microsoft/vscode/issues/121974 09ab8ad2029 wip eaf5a49200b status - update description of `name` property 1f912dfa3d5 Fix #123617 ff6d8771c94 fixes #124085 88d9a3aaf5c Support command links in trusted tree hovers Fixes #123723 eb5874237eb Improve task terminal status message Fixes #124062 53b58c89d44 Fix loop in port forwarding 37a11814295 do not include non configured deprecated restricted settings 53352a29540 add API to open an untitled notebook, https://github.com/microsoft/vscode/issues/121974 b7058688ad3 Fixes #124143 a0819ed4fea Add ending period to --sync faa3146a310 Add ending period fto prof-startup 159479eb5ae Allow to show status bar entries with an id and name (fix #74972) f23f011f552 untitled - clarify resolve methods 734b79dadae fix #124102 08618458110 fix ExtensionRecommendationsService test (for #124119) e5bf21393bf Additional navigation keys 99f3a3726eb Additional details in the aria label 06ee4764a4c untitled file working copy - test 💄 1cc2f71a1ff untitled file working copy - tests for new unified manager 8c6db097ac7 untitled file working copy - add new manager that unifies file and untitled working copies bc37b284431 untitled file working copy - shared dispose handling 0d92cb9dd88 untitled file working copy - poperly resolve target 37fbfa61251 untitled file working copy - set visibility 5835fbc9f1d untitled file working copy - some code 💄 a47fc96766a untitled file working copy - fix tests 1edef157350 untitled file working copy - wire in save support 8173bd132fb untitled file working copy - add workingcopyservice#get a3ee06b3e03 untitled file working copy - extract common super type for manager 02c72e2bd64 untitled file working copy - extract reusable interfaces 44dec56af8c untitled file working copy - 💄 103ba104a56 untitled file working copy - tests 9f990bbb6ec untitled file working copy - first cut manager 4173ced659c untitled file working copy - first cut 825f6c7ab84 valide URIs created via from, https://github.com/microsoft/vscode/issues/121198 b1349b64a64 update color registry names for inline values 677f2a3be12 remove excess whitespace 0e4159cb7aa fix localization key/description for debugInline colors df308a9a81b add color customizations for inline debug values a9c5e37ea0d fix #124102 c5611075cad fix ExtensionRecommendationsService test (for #124119) 501b691dd06 Additional navigation keys 6034e35043e Additional details in the aria label e878f5a3ee4 Add support for untitled file working copies (#124120) 13aff6aa7d3 untitled file working copy - test 💄 a48180b6e6d valide URIs created via from, https://github.com/microsoft/vscode/issues/121198 f22eb7ee29d untitled file working copy - tests for new unified manager 5f6d4786857 Merge pull request #123065 from nrayburn-tech/120936 2ecf53bf79a untitled file working copy - add new manager that unifies file and untitled working copies 32d4df9b823 untitled file working copy - shared dispose handling 3a1b950c147 Merge branch 'main' into ben/untitled 97518f5e869 Copy testRepeat helper to extension integration tests 721cdd6472b Fix data loss when renaming custom editors (#124057) bbb89b86f56 remove hover bg color on notebook toolbar icon. 8f140828107 fix mouse click double background c59f782cc7d adopt table widget for workspace trust editor 616e0fd9927 refactor: polish 4815c9e9954 fix notebook toolbar scrollbar a069768c9b6 Remove disposing of editor input in override service 0026416a7ea move showDeprecated suggest to be a CompletionOption option 2a9f02348f6 Make notebook toolbar cell visible on click (change notebook.cellToolbarVisibility default) df93e53dfb6 Merge branch 'notebook/dev' into main 4486788c81c allow the left toolbar to overflow. d501abe922d contribute actions to the toolbar. bf9f6837d14 NotebooKernelActionViewItem accepts notebook editor widget. 9063312f9c1 keep ref from cellVM to outputVM for now. 5913c5b070e Merge pull request #124125 from microsoft/tyriar/join_terminals 9b1ba3f31f0 Adds Memento.keys #87110 8b726fc9f51 update color registry names for inline values 25873bb34c4 Adds Uri.from #121198 5c4fae91762 Merge remote-tracking branch 'origin/notebook/dev' into dev/mjbvz/unify-renderers-api 79bd573d9e4 Move OpenIssueReporter api command registration out of extHostApiCommands (#124068) 73cce9a080e Fix vertical terminal margin left/right e657d422814 debug: expose parent session on DebugSessions 25967171f17 Delay workspace recommendations as remote extensions installations are ongoing For #124119 b84a8c4215a Re-enable selectionNavigation in terminal tabs 84c23ab5bd7 debug: bump node-debug2 version b3c56f52813 testing: bad rendering of inline decorations for markdown messages 2bc36bec9dc breakpoints select on stop 71f4934a0c6 use enum 21e0fab8b87 Don't show join in command palette 1243a51d59b Support join terminals 4e7029f593b workbench web api: builtinExtensionsFilter 34775b7d660 add requestOpenUris api fc76d8b6b44 fix #124113 455a8b6949b List focus colors are too dark (Fixes #123703) aee486cd293 untitled file working copy - poperly resolve target e02714b5775 Polish for some workspace reuse (#123519) daaa8a98bf3 debug: focus breakpoint on debug stop f113243a516 Close and reopen tunnel when protocol changes 960af85cd93 Reduce number of calls to get port attributes fb9d3f3e501 Fix all ports showing as user forwarded 4726abdcfa8 explorer: do not remove dotes at end of filenames before validation 026e6239be9 Merge pull request #123895 from danielgary/wmic-replacement 8f78655dbe3 untitled file working copy - set visibility 615bb082c11 Tunnel -> Port 0f5ceb2f1db Add context menu for tunnel protocol Fixes #123750 414e5dbf1f8 install additional debuggers only when at least one debugger present 1c6e481373e untitled file working copy - some code 💄 b0272010af8 Disable bell sound completely f3cca6236e0 remove todos about ipynb cell ids 3f6e29238c5 Show status icon in tooltip f8054f260ee Merge remote-tracking branch 'origin/main' into alex/ghost-text 2d80cb75c4b Fixes #124038: Render a `<span>` for each `::after` decoration at end of line cdeaebbd3be Show info terminal statuses in narrow view 3467760399e Merge pull request #123867 from hediet/hediet/fix-123178-leading-ws-in-wrapped-line 71e729d1222 Merge pull request #122785 from tejasvi/patch-1 1b591be32b2 Merge remote-tracking branch 'origin/main' into pr/tejasvi/122785 49ded4d7e11 Fixes #122825: Dispose proxies when the extension host terminates 7c07550dbc5 Log errors encountered during deactivate e4159c8f892 Add protocol to portsAttributes Part of #123750 b2c32980765 untitled file working copy - fix tests b94b2d2cf01 untitled file working copy - wire in save support fe2761a026e untitled file working copy - add workingcopyservice#get 956347c4edc better logic for "notebook open/close, notebook ready when cell-document open event is fired", https://github.com/microsoft/vscode/issues/123655 191ebfabe65 Merge branch 'main' into notebook/dev 2f2f8d7b0f4 untitled file working copy - extract common super type for manager 102433ffbef untitled file working copy - extract reusable interfaces 27d250fa0da Merge branch 'main' into ben/untitled 07cf22f6814 Fix ctrl+enter in code cell editor 45aafeb326d fix #123816 fd7d84a392c Merge branch 'notebook/dev' into main d51c7f66306 add experiment global toolbar. 079be5f0fd6 remove run all cells constraint 0031e783518 Store tab list width in global storage e270ff06255 Make default horizontal tabs width 120px 1bd7c329fb2 Move tabs list size constants into const enum e0ff23866ad 💄 49870fb9fca More accessibility improvements 47c96324ba6 Banner accessibility improvements 7c4a2cb6cc6 Add args to source profile schema 2bf81674ee7 refs #123976 a3fe790a45f Explicitly flush OutputAppender #123856 2c91cc8a7d2 Clean up decorations when hiding ghosted text ac85998e016 Pipe language edit counts into CES survey (#124006) 22350f3dc54 Change the order for ghost text decorations (use a class name that sorts before) 9916815a187 Merge pull request #124039 from microsoft/tyriar/121278 74372fe5663 Fixes #124038: Render a `<span>` for each `::after` decoration at end of line a1646e35c36 Remove try catch, clean up 6353aaac13a Share code between dispose instance and remove instance 9719ae6e725 untitled file working copy - 💄 1371a8e0d49 Bulk-edit veto dialog use the correct reason 327690a3cb9 Unsplit terminals 32db232a53a Scaffold ghost text 63b6e6c51d7 untitled file working copy - tests 45769b83189 debug issues assign to Andre f2640c349b0 untitled file working copy - first cut manager 38dda41eaa0 Merge pull request #123474 from microsoft/tyriar/local_wording e8dbf0cc9a1 Improve wording, separate for remote and virtual befed354684 Merge remote-tracking branch 'origin/main' into tyriar/local_wording 509926497c8 sandbox - make CLI commands a native host thing (#123899) dc85211b159 untitled file working copy - first cut a6db2795e36 Fix elevateIfNeeded port attribute Fixes microsoft/vscode-remote-release#5065 736896527ce Improve npm trust message d4ca8b7f3a3 icon contribution: add requires (for #117437) b2273e80745 Merge remote-tracking branch 'origin/main' into main d8b70e74a88 cleanup link styling 46596757d41 fix icon contribution id validation (for #117437) 9ef2514d701 Merge branch 'main' into pr/121835 aad39f1ea63 untitled editor names 💄 7320c8514c9 status bar - fix compile errors 381e7c2d0bc status bar - use secondary priority as sorting criteria (#123827) da374067139 state service - do not log expected file not found error 82767cc1d7b add aria description for workspace trust editor refs #122537 cfa977755b1 fixes #122537 5a5d1bc91f8 Add provider label in auth menus (#123922) ff59c24225a fix #123709 87476adaee7 Merge pull request #123919 from jeanp413/fix-123892 217261cf739 Mitigate #123856 055319001c6 Fixes #123892 802ba30de2e allow managing workspace trust from extension icon f3277a1e1e9 fix markdown editor position for compact view. f0e57a781af fix #123819. 12f273e3e07 fix #121056. 6d79421b349 Merge branch 'notebook/dev' into main 7b9bb17ce56 refresh styles after kernel is changed. e5c7b899cf1 compact view. d0b6c2d2276 fixes #123858 40d5e6796fb fix #123700. baccddcd459 Use Windows mode if build # is <= 19041 (#123725) dcfdc8d2e4e Merge pull request #123429 from jeanp413/file-drop-terminal-tab 18313e82b00 update markdown folding icon padding fcde284705f Merge pull request #123246 from vibhavsarraf/terminal_link_normalize_path 109f7feb3b0 Trim leading ../ or ./ from quick access query 4e79908dcff Merge remote-tracking branch 'origin/main' into pr/vibhavsarraf/123246 42b1e81d5d5 Merge remote-tracking branch 'origin/main' into pr/vibhavsarraf/123246 e5f3dd9ef82 simplify default view styles. f4691de9552 remove legacy comments. 1ccd6a07946 Merge pull request #123910 from jeanp413/fix-123891 36e2b3176ee Update distro c492f13efc4 xterm@4.13.0-beta.1 dff22a07121 Fix double border on vertical splits c33420d9872 Fixes #123891 8c0ba0b5d79 [remote menu] add command Install Additional Remote Development Extensions. Fixes #123905 6510b614c10 Consistent casing for `Install Additional ...` quick pick entries 22d7f210f79 Fix full path flashing in terminal tab 4946fee0dd1 Alt+click to split single tab f8a3cef533f debug colors: compress css selectors 828e83defca Merge pull request #123726 from suzmue/hoverText 0f7439bf2e7 Middle click to kill single tab 09b77ba6d8b added comment and removed empty lines 89b4b6c90d1 Replaced wmic call with windows-process-tree c6525283099 Remove dialog in ChangeLocalPortAction Part of microsoft/vscode-remote-release#4958 c8b4656197f Add requireLocalPort property to portsAttributes Fixes microsoft/vscode-remote-release#4958 6aec850c759 Multiroot workspaces on Windows with forward slashes are treated as relative paths. Fixes #123871 27966a2521d Merge pull request #123833 from gjsjohnmurray/fix-123831 b540874d21d Incorporate task terminal status feedback 67133f048d5 Add tooltips to task status Fixes #123730 5446… * chore(deps): fixup lockfiles * chore: restore .gitignore * fix(vscode): redo extra extension paths * fix(vscode): add setSocket to PersistentProtocol * fix(vscode): restore server files * fix(vscode): restore typings * fix(vscode): restore proxy_agent.ts symlink * fix(vscode): use ptyHost.getEnv * fix(vscode): uriTransformer refactoring * fix(vscode): add new terminal listener * fix(vscode): fix + enable layering lint * chore: format file * Squashed 'lib/vscode/' changes from 2064d4c301c..b4c1bd0a9b0 b4c1bd0a9b0 Merge pull request #125817 from microsoft/aeschli/125786 33d504f8455 clarify nls comment d5fbc9f45fa fix localization strings with `command:`. For #125786 f8b576c274b trust the empty window by default (#125788) c5e845182bf Dont use exp service for choosing first content behaviour (#125780) 78429fb8736 Use interpolation to keep special syntax out of strings to be translated (#125790) 47c2272a70d candy/125576 (#125787) 1e7b9242e89 Merge pull request #125773 from microsoft/roblou/fix122768 134cc8b7711 Update values for keybinding query context to match real keybindings. Fix #122768 26317e8dccd fixes #125722 (#125768) 629091038fc allow a 3rd character for first part of language id (#125761) 5e6379fd7a9 Merge pull request #125741 from microsoft/tyriar/r157_125729 098c99e2237 Set terminal title in ctor for custom pty terminals c7119ae8b63 Merge pull request #125679 from microsoft/isidorn/fixRunMenu 0ef242cb5cc Merge pull request #125728 from microsoft/joh/fix/125716 c188ea91a64 make sure to transform notebook data metadata e27b85b9958 fix #125702 (#125703) 008c3ecf4cd Port fix for git diff paths in webviews to 1.57 (#125704) c926ecf021a Clone object before applying migrated setting (#125696) 6d4a8af5cfc Hide terminal menu if no process support is registered 2c9833b549d better context key for run menu bf84ee619e0 #125527 (#125651) 2c4bd4f2b3c Merge pull request #125654 from microsoft/hediet/reduce-flickering-of-inline-completions 2e9321da48b Merge pull request #125657 from microsoft/hediet/adjust-inline-completions-color b915673f7b2 Fix #125569 (#125650) 1c5f331fa08 Merge pull request #125653 from microsoft/hediet/fix-no-focus-after-accept 9bde5523814 Adjusts inline completions color. Fixes #125646. 7b724945310 Reduces flickering of inline completions & fixes context key. 5ba2e425f31 Fixes #125524 by focusing editor after commit. 6cdc6160057 Merge pull request #125649 from microsoft/joh/fix/125550 b3d2cd902b7 re-throw and dispose reference when resolving failed dcc182fe3d2 Revert ESBuild updates and fix #125518 (#125574) bdafbc3b5f2 Merge pull request #125552 from microsoft/roblou/disableRunToolbar ce8505d5580 Add "hostRequirements" to schema (microsoft/vscode-remote-release#5144) de98ebbc81e only show notification once per window reload (#125548) 31a11d3b786 Disable consolidated run button by default. Fix #125544 git-subtree-dir: lib/vscode git-subtree-split: b4c1bd0a9b03c749ea011b06c6d2676c8091a70c * fix(lib/vscode): restore product.json * fix(vscode): backport resources hotfix * refactor(lib/vscode): reorganize code to fit vs rules * fix(ci): remove stray symlinks * cleanup(lib/vscode): format .gitignore * chore(vscode): fix formatting * refactor(vscode): remove parsePathArg in envService * fix(vscode): add missing terminal commands * fix(vscode): update .eslintignore paths * chore: document uriTransformer hacks * fix(lib/vscode): restore patches * fix(lib/vscode): restore more patches * Squashed 'lib/vscode/' changes from b4c1bd0a9b0..507ce72a446 507ce72a446 Merge pull request #126577 from microsoft/sandy081/recovery/fix125970 3f4baf46c6f move focus check while converting d95f6de445f - migrate only from active window - respect old value while reading 08bca485860 Fix race that causes auto port forwarding setting to be ignored (#126481) 2650c2e51d3 Merge pull request #126413 from microsoft/inline-suggest-prevent-auto-suggest 45c13db2195 Do not auto-trigger suggest when typing exactly the inline suggestion 20a512db915 Fix #126178; fixes markdown scrolling bug 3300069a2b7 separated editor scroll info by type and consume entry on access 92c259a77f5 Update package.json (#126283) d21d7d9d3e8 Port fix #126057 (#126215) d9b1d6b8c17 Merge pull request #126234 from microsoft/tyriar/157_125985 a16b9181915 Pass config to external terminal service 6bca69fc6fb macos - restore Cmd+W to close window when no editors opened (#126045) b9f4ab00ba8 linux - restore `--no-sandbox` for startup scripts (#126028) ca66517cf90 Revert back to Electron `12.0.7` (#126026) 6370d95598d Set visibility of markdown cells before rendering it (#126099) git-subtree-dir: lib/vscode git-subtree-split: 507ce72a4466fbb27b715c3722558bb15afa9f48 * fmt(docs): remove trailing whitespace * fix(test): fix e2e tests * chore(deps): update vulnerable vscode deps * fix(test): clean up terminal test * Retry menu navigation in e2e tests * Update VS Code loader with trusted types policy * Update build resources with renamed file * Restore proposed API patch Without this it is impossible to enable the proposed API for extensions. To replicate the issue run a build or search for enableProposedApiForAll and set it to false (it is set to true during development) then try running an extension that uses the proposed API like the GitHub pull request extension and look for an error in the browser console. * Restore async storage write patch This issue can be reliably replicated by creating an extension with the following code: await context.globalState.update("key", "value") vscode.commands.executeCommand("workbench.action.reloadWindow"); If you do this the global state will never update. This can be seen by logging with a notification or browsing the global state manually. This is probably a bug with upstream because without this patch it is impossible to wait for the actual storage write. This actually required more patching than we previously had; I don't think it was working in 1.56 either. * Remove duplicate telemetry service registration We no longer comment out the telemetry service in the workbench so ours is redundant. - logTelemetry call was slightly wrong (uses an object rather than an array) - No longer need the telemetry channel since the default uses the extension environment channel. - No longer need disableTelemetry since we don't create the setting. This does mean the setting is not toggled off when the disable flag is set (it does of course still prevent telemetry) but that appears to be the same with upstream. * Use service in terminal channel instead of interface This allows us to avoid checking whether some functions exist when we know they will. This means we can't use this for other pty services like the browser one but that won't be necessary as this strictly runs on the Node end anyway. * Rename uri transformer using camel case * Update yarn.lock I got changes when I ran yarn so I reverted to 1.57 then ran yarn again and here is the result. * Restore .yarnrc patch Delete .yarnrc and update the build process where that file is read since it no longer exists. This should resolve the issues we keep having with mismatched Node versions. This can be consistently replicated by spinning up a new environment (no previous modules or cache), running yarn, then running yarn watch. Also use process.versions.node instead of process.version as the latter has an extra v at the beginning. * Remove gulp-tar dependency It's pulling in a security vuln and we don't need it anyway. * Cancel current menu navigation on retry * chore(docs): update docs with patches * chore(ci): increase timeout to 15 minutes Co-authored-by: Asher <ash@coder.com>
This commit is contained in:
@@ -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 { IContextMenuProvider } from 'vs/base/browser/contextmenu';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
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 { Event } from 'vs/base/common/event';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem {
|
||||
private _primaryAction: ActionViewItem;
|
||||
private _dropdown: DropdownMenuActionViewItem;
|
||||
private _container: HTMLElement | null = null;
|
||||
private _dropdownContainer: HTMLElement | null = null;
|
||||
|
||||
get onDidChangeDropdownVisibility(): Event<boolean> {
|
||||
return this._dropdown.onDidChangeVisibility;
|
||||
}
|
||||
|
||||
constructor(
|
||||
primaryAction: MenuItemAction,
|
||||
dropdownAction: IAction,
|
||||
dropdownMenuActions: IAction[],
|
||||
className: string,
|
||||
private readonly _contextMenuProvider: IContextMenuProvider,
|
||||
_keybindingService: IKeybindingService,
|
||||
_notificationService: INotificationService
|
||||
) {
|
||||
super(null, primaryAction);
|
||||
this._primaryAction = new MenuEntryActionViewItem(primaryAction, _keybindingService, _notificationService);
|
||||
this._dropdown = new DropdownMenuActionViewItem(dropdownAction, dropdownMenuActions, this._contextMenuProvider, {
|
||||
menuAsChild: true,
|
||||
classNames: ['codicon', 'codicon-chevron-down']
|
||||
});
|
||||
}
|
||||
|
||||
override setActionContext(newContext: unknown): void {
|
||||
super.setActionContext(newContext);
|
||||
this._primaryAction.setActionContext(newContext);
|
||||
this._dropdown.setActionContext(newContext);
|
||||
}
|
||||
|
||||
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));
|
||||
this._dropdownContainer = DOM.$('.dropdown-action-container');
|
||||
this._dropdown.render(DOM.append(this._container, this._dropdownContainer));
|
||||
this._register(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._register(DOM.addDisposableListener(this._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);
|
||||
}
|
||||
}
|
||||
|
||||
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._dropdownContainer) {
|
||||
this._dropdown.render(this._dropdownContainer);
|
||||
}
|
||||
}
|
||||
|
||||
override dispose() {
|
||||
this._primaryAction.dispose();
|
||||
this._dropdown.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,16 @@ export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuAct
|
||||
const groups = menu.getActions(options);
|
||||
const modifierKeyEmitter = ModifierKeyEmitter.getInstance();
|
||||
const useAlternativeActions = modifierKeyEmitter.keyStatus.altKey || ((isWindows || isLinux) && modifierKeyEmitter.keyStatus.shiftKey);
|
||||
fillInActions(groups, target, useAlternativeActions, primaryGroup);
|
||||
fillInActions(groups, target, useAlternativeActions, primaryGroup ? actionGroup => actionGroup === primaryGroup : actionGroup => actionGroup === 'navigation');
|
||||
return asDisposable(groups);
|
||||
}
|
||||
|
||||
export function createAndFillInActionBarActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, primaryGroup?: string, primaryMaxCount?: number, shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean): IDisposable {
|
||||
export function createAndFillInActionBarActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, primaryGroup?: string | ((actionGroup: string) => boolean), primaryMaxCount?: number, shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, useSeparatorsInPrimaryActions?: boolean): IDisposable {
|
||||
const groups = menu.getActions(options);
|
||||
const isPrimaryAction = typeof primaryGroup === 'string' ? (actionGroup: string) => actionGroup === primaryGroup : primaryGroup;
|
||||
|
||||
// Action bars handle alternative actions on their own so the alternative actions should be ignored
|
||||
fillInActions(groups, target, false, primaryGroup, primaryMaxCount, shouldInlineSubmenu);
|
||||
fillInActions(groups, target, false, isPrimaryAction, primaryMaxCount, shouldInlineSubmenu, useSeparatorsInPrimaryActions);
|
||||
return asDisposable(groups);
|
||||
}
|
||||
|
||||
@@ -49,9 +51,10 @@ function asDisposable(groups: ReadonlyArray<[string, ReadonlyArray<MenuItemActio
|
||||
function fillInActions(
|
||||
groups: ReadonlyArray<[string, ReadonlyArray<MenuItemAction | SubmenuItemAction>]>, target: IAction[] | { primary: IAction[]; secondary: IAction[]; },
|
||||
useAlternativeActions: boolean,
|
||||
primaryGroup = 'navigation',
|
||||
isPrimaryAction: (actionGroup: string) => boolean = actionGroup => actionGroup === 'navigation',
|
||||
primaryMaxCount: number = Number.MAX_SAFE_INTEGER,
|
||||
shouldInlineSubmenu: (action: SubmenuAction, group: string, groupSize: number) => boolean = () => false
|
||||
shouldInlineSubmenu: (action: SubmenuAction, group: string, groupSize: number) => boolean = () => false,
|
||||
useSeparatorsInPrimaryActions: boolean = false
|
||||
): void {
|
||||
|
||||
let primaryBucket: IAction[];
|
||||
@@ -69,8 +72,11 @@ function fillInActions(
|
||||
for (const [group, actions] of groups) {
|
||||
|
||||
let target: IAction[];
|
||||
if (group === primaryGroup) {
|
||||
if (isPrimaryAction(group)) {
|
||||
target = primaryBucket;
|
||||
if (target.length > 0 && useSeparatorsInPrimaryActions) {
|
||||
target.push(new Separator());
|
||||
}
|
||||
} else {
|
||||
target = secondaryBucket;
|
||||
if (target.length > 0) {
|
||||
@@ -93,7 +99,7 @@ function fillInActions(
|
||||
// ask the outside if submenu should be inlined or not. only ask when
|
||||
// there would be enough space
|
||||
for (const { group, action, index } of submenuInfo) {
|
||||
const target = group === primaryGroup ? primaryBucket : secondaryBucket;
|
||||
const target = isPrimaryAction(group) ? primaryBucket : secondaryBucket;
|
||||
|
||||
// inlining submenus with length 0 or 1 is easy,
|
||||
// larger submenus need to be checked with the overall limit
|
||||
@@ -133,13 +139,15 @@ export class MenuEntryActionViewItem extends ActionViewItem {
|
||||
return this._wantsAltCommand && this._menuItemAction.alt || this._menuItemAction;
|
||||
}
|
||||
|
||||
override onClick(event: MouseEvent): void {
|
||||
override async onClick(event: MouseEvent): Promise<void> {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.actionRunner
|
||||
.run(this._commandAction, this._context)
|
||||
.catch(err => this._notificationService.error(err));
|
||||
try {
|
||||
await this.actionRunner.run(this._commandAction, this._context);
|
||||
} catch (err) {
|
||||
this._notificationService.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
override render(container: HTMLElement): void {
|
||||
|
||||
@@ -41,6 +41,7 @@ export type Icon = { dark?: URI; light?: URI; } | ThemeIcon;
|
||||
export interface ICommandAction {
|
||||
id: string;
|
||||
title: string | ICommandActionTitle;
|
||||
shortTitle?: string | ICommandActionTitle;
|
||||
category?: string | ILocalizedString;
|
||||
tooltip?: string;
|
||||
icon?: Icon;
|
||||
@@ -87,6 +88,7 @@ export class MenuId {
|
||||
static readonly DebugWatchContext = new MenuId('DebugWatchContext');
|
||||
static readonly DebugToolBar = new MenuId('DebugToolBar');
|
||||
static readonly EditorContext = new MenuId('EditorContext');
|
||||
static readonly SimpleEditorContext = new MenuId('SimpleEditorContext');
|
||||
static readonly EditorContextCopy = new MenuId('EditorContextCopy');
|
||||
static readonly EditorContextPeek = new MenuId('EditorContextPeek');
|
||||
static readonly EditorTitle = new MenuId('EditorTitle');
|
||||
@@ -96,6 +98,7 @@ export class MenuId {
|
||||
static readonly ExplorerContext = new MenuId('ExplorerContext');
|
||||
static readonly ExtensionContext = new MenuId('ExtensionContext');
|
||||
static readonly GlobalActivity = new MenuId('GlobalActivity');
|
||||
static readonly MenubarMainMenu = new MenuId('MenubarMainMenu');
|
||||
static readonly MenubarAppearanceMenu = new MenuId('MenubarAppearanceMenu');
|
||||
static readonly MenubarDebugMenu = new MenuId('MenubarDebugMenu');
|
||||
static readonly MenubarEditMenu = new MenuId('MenubarEditMenu');
|
||||
@@ -125,9 +128,12 @@ export class MenuId {
|
||||
static readonly StatusBarWindowIndicatorMenu = new MenuId('StatusBarWindowIndicatorMenu');
|
||||
static readonly StatusBarRemoteIndicatorMenu = new MenuId('StatusBarRemoteIndicatorMenu');
|
||||
static readonly TestItem = new MenuId('TestItem');
|
||||
static readonly TestPeekElement = new MenuId('TestPeekElement');
|
||||
static readonly TestPeekTitle = new MenuId('TestPeekTitle');
|
||||
static readonly TouchBarContext = new MenuId('TouchBarContext');
|
||||
static readonly TitleBarContext = new MenuId('TitleBarContext');
|
||||
static readonly TunnelContext = new MenuId('TunnelContext');
|
||||
static readonly TunnelProtocol = new MenuId('TunnelProtocol');
|
||||
static readonly TunnelPortInline = new MenuId('TunnelInline');
|
||||
static readonly TunnelTitle = new MenuId('TunnelTitle');
|
||||
static readonly TunnelLocalAddressInline = new MenuId('TunnelLocalAddressInline');
|
||||
@@ -142,6 +148,7 @@ export class MenuId {
|
||||
static readonly CommentTitle = new MenuId('CommentTitle');
|
||||
static readonly CommentActions = new MenuId('CommentActions');
|
||||
static readonly NotebookToolbar = new MenuId('NotebookToolbar');
|
||||
static readonly NotebookRightToolbar = new MenuId('NotebookRightToolbar');
|
||||
static readonly NotebookCellTitle = new MenuId('NotebookCellTitle');
|
||||
static readonly NotebookCellInsert = new MenuId('NotebookCellInsert');
|
||||
static readonly NotebookCellBetween = new MenuId('NotebookCellBetween');
|
||||
@@ -150,6 +157,7 @@ export class MenuId {
|
||||
static readonly NotebookDiffCellInputTitle = new MenuId('NotebookDiffCellInputTitle');
|
||||
static readonly NotebookDiffCellMetadataTitle = new MenuId('NotebookDiffCellMetadataTitle');
|
||||
static readonly NotebookDiffCellOutputsTitle = new MenuId('NotebookDiffCellOutputsTitle');
|
||||
static readonly NotebookOutputToolbar = new MenuId('NotebookOutputToolbar');
|
||||
static readonly BulkEditTitle = new MenuId('BulkEditTitle');
|
||||
static readonly BulkEditContext = new MenuId('BulkEditContext');
|
||||
static readonly TimelineItemContext = new MenuId('TimelineItemContext');
|
||||
@@ -157,11 +165,13 @@ export class MenuId {
|
||||
static readonly TimelineTitleContext = new MenuId('TimelineTitleContext');
|
||||
static readonly AccountsContext = new MenuId('AccountsContext');
|
||||
static readonly PanelTitle = new MenuId('PanelTitle');
|
||||
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');
|
||||
static readonly TerminalInstanceContext = new MenuId('TerminalInstanceContext');
|
||||
static readonly TerminalNewDropdownContext = new MenuId('TerminalNewDropdownContext');
|
||||
static readonly TerminalTabContext = new MenuId('TerminalTabContext');
|
||||
static readonly TerminalTabEmptyAreaContext = new MenuId('TerminalTabEmptyAreaContext');
|
||||
static readonly TerminalInlineTabContext = new MenuId('TerminalInlineTabContext');
|
||||
static readonly WebviewContext = new MenuId('WebviewContext');
|
||||
static readonly InlineCompletionsActions = new MenuId('InlineCompletionsActions');
|
||||
|
||||
readonly id: number;
|
||||
readonly _debugName: string;
|
||||
@@ -175,6 +185,7 @@ export class MenuId {
|
||||
export interface IMenuActionOptions {
|
||||
arg?: any;
|
||||
shouldForwardArgs?: boolean;
|
||||
renderShortTitle?: boolean;
|
||||
}
|
||||
|
||||
export interface IMenu extends IDisposable {
|
||||
@@ -326,7 +337,7 @@ export class ExecuteCommandAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
override run(...args: any[]): Promise<any> {
|
||||
override run(...args: any[]): Promise<void> {
|
||||
return this._commandService.executeCommand(this.id, ...args);
|
||||
}
|
||||
}
|
||||
@@ -384,7 +395,9 @@ export class MenuItemAction implements IAction {
|
||||
@ICommandService private _commandService: ICommandService
|
||||
) {
|
||||
this.id = item.id;
|
||||
this.label = typeof item.title === 'string' ? item.title : item.title.value;
|
||||
this.label = options?.renderShortTitle && item.shortTitle
|
||||
? (typeof item.shortTitle === 'string' ? item.shortTitle : item.shortTitle.value)
|
||||
: (typeof item.title === 'string' ? item.title : item.title.value);
|
||||
this.tooltip = item.tooltip ?? '';
|
||||
this.enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition);
|
||||
this.checked = false;
|
||||
@@ -417,7 +430,7 @@ export class MenuItemAction implements IAction {
|
||||
// to bridge into the rendering world.
|
||||
}
|
||||
|
||||
run(...args: any[]): Promise<any> {
|
||||
run(...args: any[]): Promise<void> {
|
||||
let runArgs: any[] = [];
|
||||
|
||||
if (this._options?.arg) {
|
||||
@@ -525,7 +538,7 @@ export interface IAction2Options extends ICommandAction {
|
||||
|
||||
export abstract class Action2 {
|
||||
constructor(readonly desc: Readonly<IAction2Options>) { }
|
||||
abstract run(accessor: ServicesAccessor, ...args: any[]): any;
|
||||
abstract run(accessor: ServicesAccessor, ...args: any[]): void;
|
||||
}
|
||||
|
||||
export function registerAction2(ctor: { new(): Action2 }): IDisposable {
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as fs from 'fs';
|
||||
import { createHash } from 'crypto';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { writeFileSync, writeFile, readdir, exists, rimraf, RimRafMode } from 'vs/base/node/pfs';
|
||||
import { writeFileSync, writeFile, readdir, exists, rimraf, RimRafMode, Promises } from 'vs/base/node/pfs';
|
||||
import { IBackupMainService, IWorkspaceBackupInfo, isWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup';
|
||||
import { IBackupWorkspacesFormat, IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup';
|
||||
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
|
||||
@@ -49,7 +49,7 @@ export class BackupMainService implements IBackupMainService {
|
||||
async initialize(): Promise<void> {
|
||||
let backups: IBackupWorkspacesFormat;
|
||||
try {
|
||||
backups = JSON.parse(await fs.promises.readFile(this.workspacesJsonPath, 'utf8')); // invalid JSON or permission issue can happen here
|
||||
backups = JSON.parse(await Promises.readFile(this.workspacesJsonPath, 'utf8')); // invalid JSON or permission issue can happen here
|
||||
} catch (error) {
|
||||
backups = Object.create(null);
|
||||
}
|
||||
@@ -328,7 +328,7 @@ export class BackupMainService implements IBackupMainService {
|
||||
// Rename backupPath to new empty window backup path
|
||||
const newEmptyWindowBackupPath = this.getBackupPath(newBackupFolder);
|
||||
try {
|
||||
await fs.promises.rename(backupPath, newEmptyWindowBackupPath);
|
||||
await Promises.rename(backupPath, newEmptyWindowBackupPath);
|
||||
} catch (error) {
|
||||
this.logService.error(`Backup: Could not rename backup folder: ${error.toString()}`);
|
||||
return false;
|
||||
|
||||
@@ -107,7 +107,7 @@ flakySuite('BackupMainService', () => {
|
||||
|
||||
environmentService = new EnvironmentMainService(parseArgs(process.argv, OPTIONS), { _serviceBrand: undefined, ...product });
|
||||
|
||||
await fs.promises.mkdir(backupHome, { recursive: true });
|
||||
await pfs.Promises.mkdir(backupHome, { recursive: true });
|
||||
|
||||
configService = new TestConfigurationService();
|
||||
service = new class TestBackupMainService extends BackupMainService {
|
||||
@@ -446,7 +446,7 @@ flakySuite('BackupMainService', () => {
|
||||
await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson));
|
||||
await service.initialize();
|
||||
|
||||
const buffer = await fs.promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
|
||||
assert.deepStrictEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]);
|
||||
});
|
||||
@@ -462,7 +462,7 @@ flakySuite('BackupMainService', () => {
|
||||
};
|
||||
await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson));
|
||||
await service.initialize();
|
||||
const buffer = await fs.promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
|
||||
assert.deepStrictEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]);
|
||||
});
|
||||
@@ -484,7 +484,7 @@ flakySuite('BackupMainService', () => {
|
||||
await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson));
|
||||
await service.initialize();
|
||||
|
||||
const buffer = await fs.promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
|
||||
assert.strictEqual(json.rootURIWorkspaces.length, platform.isLinux ? 3 : 1);
|
||||
if (platform.isLinux) {
|
||||
@@ -500,7 +500,7 @@ flakySuite('BackupMainService', () => {
|
||||
service.registerFolderBackupSync(fooFile);
|
||||
service.registerFolderBackupSync(barFile);
|
||||
assertEqualUris(service.getFolderBackupPaths(), [fooFile, barFile]);
|
||||
const buffer = await fs.promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
|
||||
assert.deepStrictEqual(json.folderURIWorkspaces, [fooFile.toString(), barFile.toString()]);
|
||||
});
|
||||
@@ -515,7 +515,7 @@ flakySuite('BackupMainService', () => {
|
||||
assert.strictEqual(ws1.workspace.id, service.getWorkspaceBackups()[0].workspace.id);
|
||||
assert.strictEqual(ws2.workspace.id, service.getWorkspaceBackups()[1].workspace.id);
|
||||
|
||||
const buffer = await fs.promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
|
||||
|
||||
assert.deepStrictEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [fooFile.toString(), barFile.toString()]);
|
||||
@@ -528,7 +528,7 @@ flakySuite('BackupMainService', () => {
|
||||
service.registerFolderBackupSync(URI.file(fooFile.fsPath.toUpperCase()));
|
||||
assertEqualUris(service.getFolderBackupPaths(), [URI.file(fooFile.fsPath.toUpperCase())]);
|
||||
|
||||
const buffer = await fs.promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
|
||||
assert.deepStrictEqual(json.folderURIWorkspaces, [URI.file(fooFile.fsPath.toUpperCase()).toString()]);
|
||||
});
|
||||
@@ -538,7 +538,7 @@ flakySuite('BackupMainService', () => {
|
||||
service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(upperFooPath));
|
||||
assertEqualUris(service.getWorkspaceBackups().map(b => b.workspace.configPath), [URI.file(upperFooPath)]);
|
||||
|
||||
const buffer = await fs.promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json = (<IBackupWorkspacesFormat>JSON.parse(buffer));
|
||||
assert.deepStrictEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [URI.file(upperFooPath).toString()]);
|
||||
});
|
||||
@@ -549,12 +549,12 @@ flakySuite('BackupMainService', () => {
|
||||
service.registerFolderBackupSync(barFile);
|
||||
service.unregisterFolderBackupSync(fooFile);
|
||||
|
||||
const buffer = await fs.promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json = (<IBackupWorkspacesFormat>JSON.parse(buffer));
|
||||
assert.deepStrictEqual(json.folderURIWorkspaces, [barFile.toString()]);
|
||||
service.unregisterFolderBackupSync(barFile);
|
||||
|
||||
const content = await fs.promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const content = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json2 = (<IBackupWorkspacesFormat>JSON.parse(content));
|
||||
assert.deepStrictEqual(json2.folderURIWorkspaces, []);
|
||||
});
|
||||
@@ -566,12 +566,12 @@ flakySuite('BackupMainService', () => {
|
||||
service.registerWorkspaceBackupSync(ws2);
|
||||
service.unregisterWorkspaceBackupSync(ws1.workspace);
|
||||
|
||||
const buffer = await fs.promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json = (<IBackupWorkspacesFormat>JSON.parse(buffer));
|
||||
assert.deepStrictEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [barFile.toString()]);
|
||||
service.unregisterWorkspaceBackupSync(ws2.workspace);
|
||||
|
||||
const content = await fs.promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const content = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json2 = (<IBackupWorkspacesFormat>JSON.parse(content));
|
||||
assert.deepStrictEqual(json2.rootURIWorkspaces, []);
|
||||
});
|
||||
@@ -581,12 +581,12 @@ flakySuite('BackupMainService', () => {
|
||||
service.registerEmptyWindowBackupSync('bar');
|
||||
service.unregisterEmptyWindowBackupSync('foo');
|
||||
|
||||
const buffer = await fs.promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json = (<IBackupWorkspacesFormat>JSON.parse(buffer));
|
||||
assert.deepStrictEqual(json.emptyWorkspaceInfos, [{ backupFolder: 'bar' }]);
|
||||
service.unregisterEmptyWindowBackupSync('bar');
|
||||
|
||||
const content = await fs.promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const content = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json2 = (<IBackupWorkspacesFormat>JSON.parse(content));
|
||||
assert.deepStrictEqual(json2.emptyWorkspaceInfos, []);
|
||||
});
|
||||
@@ -600,7 +600,7 @@ flakySuite('BackupMainService', () => {
|
||||
await service.initialize();
|
||||
service.unregisterFolderBackupSync(barFile);
|
||||
service.unregisterEmptyWindowBackupSync('test');
|
||||
const content = await fs.promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const content = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8');
|
||||
const json = (<IBackupWorkspacesFormat>JSON.parse(content));
|
||||
assert.deepStrictEqual(json.folderURIWorkspaces, [existingTestFolder1.toString()]);
|
||||
});
|
||||
@@ -670,8 +670,8 @@ flakySuite('BackupMainService', () => {
|
||||
assert.strictEqual(((await service.getDirtyWorkspaces()).length), 0);
|
||||
|
||||
try {
|
||||
await fs.promises.mkdir(path.join(folderBackupPath, Schemas.file), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(workspaceBackupPath, Schemas.untitled), { recursive: true });
|
||||
await pfs.Promises.mkdir(path.join(folderBackupPath, Schemas.file), { recursive: true });
|
||||
await pfs.Promises.mkdir(path.join(workspaceBackupPath, Schemas.untitled), { recursive: true });
|
||||
} catch (error) {
|
||||
// ignore - folder might exist already
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ STATIC_VALUES.set('isEdge', _userAgent.indexOf('Edg/') >= 0);
|
||||
STATIC_VALUES.set('isFirefox', _userAgent.indexOf('Firefox') >= 0);
|
||||
STATIC_VALUES.set('isChrome', _userAgent.indexOf('Chrome') >= 0);
|
||||
STATIC_VALUES.set('isSafari', _userAgent.indexOf('Safari') >= 0);
|
||||
STATIC_VALUES.set('isIPad', _userAgent.indexOf('iPad') >= 0);
|
||||
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform';
|
||||
import { isMacintosh, isLinux, isWindows, isWeb, isIOS } from 'vs/base/common/platform';
|
||||
|
||||
export const IsMacContext = new RawContextKey<boolean>('isMac', isMacintosh, localize('isMac', "Whether the operating system is macOS"));
|
||||
export const IsLinuxContext = new RawContextKey<boolean>('isLinux', isLinux, localize('isLinux', "Whether the operating system is Linux"));
|
||||
@@ -13,6 +13,7 @@ export const IsWindowsContext = new RawContextKey<boolean>('isWindows', isWindow
|
||||
|
||||
export const IsWebContext = new RawContextKey<boolean>('isWeb', isWeb, localize('isWeb', "Whether the platform is a web browser"));
|
||||
export const IsMacNativeContext = new RawContextKey<boolean>('isMacNative', isMacintosh && !isWeb, localize('isMacNative', "Whether the operating system is macOS on a non-browser platform"));
|
||||
export const IsIOSContext = new RawContextKey<boolean>('isIOS', isIOS, localize('isIOS', "Whether the operating system is IOS"));
|
||||
|
||||
export const IsDevelopmentContext = new RawContextKey<boolean>('isDevelopment', false, true);
|
||||
|
||||
|
||||
@@ -12,12 +12,15 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ModifierKeyEmitter } from 'vs/base/browser/dom';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export class ContextMenuService extends Disposable implements IContextMenuService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private contextMenuHandler: ContextMenuHandler;
|
||||
|
||||
readonly onDidShowContextMenu = new Emitter<void>().event;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
|
||||
import { AnchorAlignment, AnchorAxisAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export const IContextViewService = createDecorator<IContextViewService>('contextViewService');
|
||||
|
||||
@@ -40,5 +41,7 @@ export interface IContextMenuService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly onDidShowContextMenu: Event<void>;
|
||||
|
||||
showContextMenu(delegate: IContextMenuDelegate): void;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { MessageBoxOptions, MessageBoxReturnValue, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, dialog, FileFilter, BrowserWindow } from 'electron';
|
||||
import { Queue } from 'vs/base/common/async';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { IStateMainService } from 'vs/platform/state/electron-main/state';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { dirname } from 'vs/base/common/path';
|
||||
import { normalizeNFC } from 'vs/base/common/normalization';
|
||||
@@ -55,7 +55,7 @@ export class DialogMainService implements IDialogMainService {
|
||||
private readonly noWindowDialogueQueue = new Queue<MessageBoxReturnValue | SaveDialogReturnValue | OpenDialogReturnValue>();
|
||||
|
||||
constructor(
|
||||
@IStateService private readonly stateService: IStateService
|
||||
@IStateMainService private readonly stateMainService: IStateMainService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -89,8 +89,7 @@ export class DialogMainService implements IDialogMainService {
|
||||
};
|
||||
|
||||
// Ensure defaultPath
|
||||
dialogOptions.defaultPath = options.defaultPath || this.stateService.getItem<string>(DialogMainService.workingDirPickerStorageKey);
|
||||
|
||||
dialogOptions.defaultPath = options.defaultPath || this.stateMainService.getItem<string>(DialogMainService.workingDirPickerStorageKey);
|
||||
|
||||
// Ensure properties
|
||||
if (typeof options.pickFiles === 'boolean' || typeof options.pickFolders === 'boolean') {
|
||||
@@ -116,7 +115,7 @@ export class DialogMainService implements IDialogMainService {
|
||||
if (result && result.filePaths && result.filePaths.length > 0) {
|
||||
|
||||
// Remember path in storage for next time
|
||||
this.stateService.setItem(DialogMainService.workingDirPickerStorageKey, dirname(result.filePaths[0]));
|
||||
this.stateMainService.setItem(DialogMainService.workingDirPickerStorageKey, dirname(result.filePaths[0]));
|
||||
|
||||
return result.filePaths;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,23 @@ export class TestDialogService implements IDialogService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
confirm(_confirmation: IConfirmation): Promise<IConfirmationResult> { return Promise.resolve({ confirmed: false }); }
|
||||
show(_severity: Severity, _message: string, _buttons: string[], _options?: IDialogOptions): Promise<IShowResult> { return Promise.resolve({ choice: 0 }); }
|
||||
input(): Promise<IInputResult> { { return Promise.resolve({ choice: 0, values: [] }); } }
|
||||
about(): Promise<void> { return Promise.resolve(); }
|
||||
private confirmResult: IConfirmationResult | undefined = undefined;
|
||||
setConfirmResult(result: IConfirmationResult) {
|
||||
this.confirmResult = result;
|
||||
}
|
||||
|
||||
async confirm(confirmation: IConfirmation): Promise<IConfirmationResult> {
|
||||
if (this.confirmResult) {
|
||||
const confirmResult = this.confirmResult;
|
||||
this.confirmResult = undefined;
|
||||
|
||||
return confirmResult;
|
||||
}
|
||||
|
||||
return { confirmed: false };
|
||||
}
|
||||
|
||||
async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise<IShowResult> { return { choice: 0 }; }
|
||||
async input(): Promise<IInputResult> { { return { choice: 0, values: [] }; } }
|
||||
async about(): Promise<void> { }
|
||||
}
|
||||
|
||||
@@ -37,17 +37,17 @@ export interface IEditorModel {
|
||||
export interface IBaseResourceEditorInput {
|
||||
|
||||
/**
|
||||
* Optional options to use when opening the text input.
|
||||
* Optional options to use when opening the input.
|
||||
*/
|
||||
options?: ITextEditorOptions;
|
||||
options?: IEditorOptions;
|
||||
|
||||
/**
|
||||
* Label to show for the diff editor
|
||||
* Label to show for the input.
|
||||
*/
|
||||
readonly label?: string;
|
||||
|
||||
/**
|
||||
* Description to show for the diff editor
|
||||
* Description to show for the input.
|
||||
*/
|
||||
readonly description?: string;
|
||||
|
||||
@@ -70,6 +70,48 @@ export interface IBaseResourceEditorInput {
|
||||
readonly forceUntitled?: boolean;
|
||||
}
|
||||
|
||||
export interface IBaseTextResourceEditorInput extends IBaseResourceEditorInput {
|
||||
|
||||
/**
|
||||
* Optional options to use when opening the text input.
|
||||
*/
|
||||
options?: ITextEditorOptions;
|
||||
|
||||
/**
|
||||
* The contents of the text input if known. If provided,
|
||||
* the input will not attempt to load the contents from
|
||||
* disk and may appear dirty.
|
||||
*/
|
||||
contents?: string;
|
||||
|
||||
/**
|
||||
* The encoding of the text input if known.
|
||||
*/
|
||||
encoding?: string;
|
||||
|
||||
/**
|
||||
* The identifier of the language mode of the text input
|
||||
* if known to use when displaying the contents.
|
||||
*/
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
export interface IResourceEditorInput extends IBaseResourceEditorInput {
|
||||
|
||||
/**
|
||||
* The resource URI of the resource to open.
|
||||
*/
|
||||
readonly resource: URI;
|
||||
}
|
||||
|
||||
export interface ITextResourceEditorInput extends IResourceEditorInput, IBaseTextResourceEditorInput {
|
||||
|
||||
/**
|
||||
* Optional options to use when opening the text input.
|
||||
*/
|
||||
options?: ITextEditorOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* This identifier allows to uniquely identify an editor with a
|
||||
* resource and type identifier.
|
||||
@@ -87,25 +129,6 @@ export interface IResourceEditorInputIdentifier {
|
||||
readonly typeId: string;
|
||||
}
|
||||
|
||||
export interface IResourceEditorInput extends IBaseResourceEditorInput {
|
||||
|
||||
/**
|
||||
* The resource URI of the resource to open.
|
||||
*/
|
||||
readonly resource: URI;
|
||||
|
||||
/**
|
||||
* The encoding of the text input if known.
|
||||
*/
|
||||
readonly encoding?: string;
|
||||
|
||||
/**
|
||||
* The identifier of the language mode of the text input
|
||||
* if known to use when displaying the contents.
|
||||
*/
|
||||
readonly mode?: string;
|
||||
}
|
||||
|
||||
export enum EditorActivation {
|
||||
|
||||
/**
|
||||
@@ -169,7 +192,7 @@ export interface IEditorOptions {
|
||||
* Will also not activate the group the editor opens in unless the group is already
|
||||
* the active one. This behaviour can be overridden via the `activation` option.
|
||||
*/
|
||||
readonly preserveFocus?: boolean;
|
||||
preserveFocus?: boolean;
|
||||
|
||||
/**
|
||||
* This option is only relevant if an editor is opened into a group that is not active
|
||||
@@ -179,14 +202,14 @@ export interface IEditorOptions {
|
||||
* By default, the editor group will become active unless `preserveFocus` or `inactive`
|
||||
* is specified.
|
||||
*/
|
||||
readonly activation?: EditorActivation;
|
||||
activation?: EditorActivation;
|
||||
|
||||
/**
|
||||
* Tells the editor to reload the editor input in the editor even if it is identical to the one
|
||||
* already showing. By default, the editor will not reload the input if it is identical to the
|
||||
* one showing.
|
||||
*/
|
||||
readonly forceReload?: boolean;
|
||||
forceReload?: boolean;
|
||||
|
||||
/**
|
||||
* Will reveal the editor if it is already opened and visible in any of the opened editor groups.
|
||||
@@ -194,7 +217,7 @@ export interface IEditorOptions {
|
||||
* Note that this option is just a hint that might be ignored if the user wants to open an editor explicitly
|
||||
* to the side of another one or into a specific editor group.
|
||||
*/
|
||||
readonly revealIfVisible?: boolean;
|
||||
revealIfVisible?: boolean;
|
||||
|
||||
/**
|
||||
* Will reveal the editor if it is already opened (even when not visible) in any of the opened editor groups.
|
||||
@@ -202,24 +225,24 @@ export interface IEditorOptions {
|
||||
* Note that this option is just a hint that might be ignored if the user wants to open an editor explicitly
|
||||
* to the side of another one or into a specific editor group.
|
||||
*/
|
||||
readonly revealIfOpened?: boolean;
|
||||
revealIfOpened?: boolean;
|
||||
|
||||
/**
|
||||
* An editor that is pinned remains in the editor stack even when another editor is being opened.
|
||||
* An editor that is not pinned will always get replaced by another editor that is not pinned.
|
||||
*/
|
||||
readonly pinned?: boolean;
|
||||
pinned?: boolean;
|
||||
|
||||
/**
|
||||
* An editor that is sticky moves to the beginning of the editors list within the group and will remain
|
||||
* there unless explicitly closed. Operations such as "Close All" will not close sticky editors.
|
||||
*/
|
||||
readonly sticky?: boolean;
|
||||
sticky?: boolean;
|
||||
|
||||
/**
|
||||
* The index in the document stack where to insert the editor into when opening.
|
||||
*/
|
||||
readonly index?: number;
|
||||
index?: number;
|
||||
|
||||
/**
|
||||
* An active editor that is opened will show its contents directly. Set to true to open an editor
|
||||
@@ -228,13 +251,13 @@ export interface IEditorOptions {
|
||||
* Will also not activate the group the editor opens in unless the group is already
|
||||
* the active one. This behaviour can be overridden via the `activation` option.
|
||||
*/
|
||||
readonly inactive?: boolean;
|
||||
inactive?: boolean;
|
||||
|
||||
/**
|
||||
* Will not show an error in case opening the editor fails and thus allows to show a custom error
|
||||
* message as needed. By default, an error will be presented as notification if opening was not possible.
|
||||
*/
|
||||
readonly ignoreError?: boolean;
|
||||
ignoreError?: boolean;
|
||||
|
||||
/**
|
||||
* Allows to override the editor that should be used to display the input:
|
||||
@@ -242,7 +265,7 @@ export interface IEditorOptions {
|
||||
* - `string`: specific override by id
|
||||
* - `EditorOverride`: specific override handling
|
||||
*/
|
||||
readonly override?: string | EditorOverride;
|
||||
override?: string | EditorOverride;
|
||||
|
||||
/**
|
||||
* A optional hint to signal in which context the editor opens.
|
||||
@@ -254,7 +277,7 @@ export interface IEditorOptions {
|
||||
* some background task, the notification would show in the background,
|
||||
* not as a modal dialog.
|
||||
*/
|
||||
readonly context?: EditorOpenContext;
|
||||
context?: EditorOpenContext;
|
||||
}
|
||||
|
||||
export interface ITextEditorSelection {
|
||||
@@ -269,14 +292,17 @@ export const enum TextEditorSelectionRevealType {
|
||||
* Option to scroll vertically or horizontally as necessary and reveal a range centered vertically.
|
||||
*/
|
||||
Center = 0,
|
||||
|
||||
/**
|
||||
* Option to scroll vertically or horizontally as necessary and reveal a range centered vertically only if it lies outside the viewport.
|
||||
*/
|
||||
CenterIfOutsideViewport = 1,
|
||||
|
||||
/**
|
||||
* Option to scroll vertically or horizontally as necessary and reveal a range close to the top of the viewport, but not quite at the top.
|
||||
*/
|
||||
NearTop = 2,
|
||||
|
||||
/**
|
||||
* Option to scroll vertically or horizontally as necessary and reveal a range close to the top of the viewport, but not quite at the top.
|
||||
* Only if it lies outside the viewport
|
||||
@@ -289,16 +315,16 @@ export interface ITextEditorOptions extends IEditorOptions {
|
||||
/**
|
||||
* Text editor selection.
|
||||
*/
|
||||
readonly selection?: ITextEditorSelection;
|
||||
selection?: ITextEditorSelection;
|
||||
|
||||
/**
|
||||
* Text editor view state.
|
||||
*/
|
||||
readonly viewState?: object;
|
||||
viewState?: object;
|
||||
|
||||
/**
|
||||
* Option to control the text editor selection reveal type.
|
||||
* Defaults to TextEditorSelectionRevealType.Center
|
||||
*/
|
||||
readonly selectionRevealType?: TextEditorSelectionRevealType;
|
||||
selectionRevealType?: TextEditorSelectionRevealType;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
* A list of command line arguments we support natively.
|
||||
*/
|
||||
export interface NativeParsedArgs {
|
||||
'extra-extensions-dir'?: string[];
|
||||
'extra-builtin-extensions-dir'?: string[];
|
||||
_: string[];
|
||||
'folder-uri'?: string[]; // undefined or array of 1 or more
|
||||
'file-uri'?: string[]; // undefined or array of 1 or more
|
||||
@@ -41,6 +39,8 @@ export interface NativeParsedArgs {
|
||||
'extensions-dir'?: string;
|
||||
'extensions-download-dir'?: string;
|
||||
'builtin-extensions-dir'?: string;
|
||||
'extra-extensions-dir'?: string[]; // NOTE@coder: added extra extensions dir
|
||||
'extra-builtin-extensions-dir'?: string[]; // NOTE@coder: added extra builtin extensions dir
|
||||
extensionDevelopmentPath?: string[]; // undefined or array of 1 or more local paths or URIs
|
||||
extensionTestsPath?: string; // either a local path or a URI
|
||||
extensionDevelopmentKind?: string[];
|
||||
@@ -67,6 +67,7 @@ export interface NativeParsedArgs {
|
||||
'install-source'?: string;
|
||||
'disable-updates'?: boolean;
|
||||
'disable-keytar'?: boolean;
|
||||
'disable-workspace-trust'?: boolean;
|
||||
'disable-crash-reporter'?: boolean;
|
||||
'crash-reporter-directory'?: string;
|
||||
'crash-reporter-id'?: string;
|
||||
|
||||
@@ -66,6 +66,9 @@ export interface IEnvironmentService {
|
||||
extensionDevelopmentKind?: ExtensionKind[];
|
||||
extensionTestsLocationURI?: URI;
|
||||
|
||||
// --- workspace trust
|
||||
disableWorkspaceTrust: boolean;
|
||||
|
||||
// --- logging
|
||||
logsPath: string;
|
||||
logLevel?: string;
|
||||
@@ -121,8 +124,9 @@ export interface INativeEnvironmentService extends IEnvironmentService {
|
||||
extensionsPath: string;
|
||||
extensionsDownloadPath: string;
|
||||
builtinExtensionsPath: string;
|
||||
extraExtensionPaths: string[]
|
||||
extraBuiltinExtensionPaths: string[]
|
||||
// NOTE@coder: add extraExtensionPaths/extraBuiltinExtensionPaths
|
||||
extraExtensionPaths: string[];
|
||||
extraBuiltinExtensionPaths: string[];
|
||||
|
||||
// --- smoke test support
|
||||
driverHandle?: string;
|
||||
|
||||
@@ -16,22 +16,6 @@ import { ExtensionKind } from 'vs/platform/extensions/common/extensions';
|
||||
import { env } from 'vs/base/common/process';
|
||||
|
||||
|
||||
function parsePathArg(arg: string | undefined, process: NodeJS.Process): string | undefined {
|
||||
if (!arg) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Determine if the arg is relative or absolute, if relative use the original CWD
|
||||
// (VSCODE_CWD), not the potentially overridden one (process.cwd()).
|
||||
const resolved = resolve(arg);
|
||||
|
||||
if (normalize(arg) === resolved) {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
return resolve(process.env['VSCODE_CWD'] || process.cwd(), arg);
|
||||
}
|
||||
|
||||
export interface INativeEnvironmentPaths {
|
||||
|
||||
/**
|
||||
@@ -174,6 +158,19 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron
|
||||
return joinPath(this.userHome, this.productService.dataFolderName, 'extensions').fsPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE@coder: add extraExtensionPaths and extraBuiltinExtensionPaths
|
||||
*/
|
||||
@memoize
|
||||
get extraExtensionPaths(): string[] {
|
||||
return (this._args['extra-extensions-dir'] || []).map((p) => resolve(p));
|
||||
}
|
||||
|
||||
@memoize
|
||||
get extraBuiltinExtensionPaths(): string[] {
|
||||
return (this._args['extra-builtin-extensions-dir'] || []).map((p) => resolve(p));
|
||||
}
|
||||
|
||||
@memoize
|
||||
get extensionDevelopmentLocationURI(): URI[] | undefined {
|
||||
const extensionDevelopmentPaths = this.args.extensionDevelopmentPath;
|
||||
@@ -190,19 +187,6 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE@coder: add extraExtensionPaths and extraBuiltinExtensionPaths
|
||||
*/
|
||||
@memoize
|
||||
get extraExtensionPaths(): string[] {
|
||||
return (this._args['extra-extensions-dir'] || []).map((p) => <string>parsePathArg(p, process));
|
||||
}
|
||||
|
||||
@memoize
|
||||
get extraBuiltinExtensionPaths(): string[] {
|
||||
return (this._args['extra-builtin-extensions-dir'] || []).map((p) => <string>parsePathArg(p, process));
|
||||
}
|
||||
|
||||
@memoize
|
||||
get extensionDevelopmentKind(): ExtensionKind[] | undefined {
|
||||
return this.args.extensionDevelopmentKind?.map(kind => kind === 'ui' || kind === 'workspace' || kind === 'web' ? kind : 'workspace');
|
||||
@@ -261,6 +245,9 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron
|
||||
get telemetryLogResource(): URI { return URI.file(join(this.logsPath, 'telemetry.log')); }
|
||||
get disableTelemetry(): boolean { return !!this.args['disable-telemetry']; }
|
||||
|
||||
@memoize
|
||||
get disableWorkspaceTrust(): boolean { return !!this.args['disable-workspace-trust']; }
|
||||
|
||||
get args(): NativeParsedArgs { return this._args; }
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -52,11 +52,12 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
|
||||
'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
|
||||
'extensions-download-dir': { type: 'string' },
|
||||
'builtin-extensions-dir': { type: 'string' },
|
||||
'extra-builtin-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra builtin extension directory.' },
|
||||
// NOTE@coder: add extra-extensions-dir and extra-builtin-extensions-dir
|
||||
'extra-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra user extension directory.' },
|
||||
'extra-builtin-extensions-dir': { type: 'string[]', cat: 'o', description: 'Path to an extra builtin extension directory.' },
|
||||
'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") },
|
||||
'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extensions.") },
|
||||
'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extensions.") },
|
||||
'category': { type: 'string', cat: 'e', description: localize('category', "Filters installed extensions by provided category, when using --list-extensions."), args: 'category' },
|
||||
'install-extension': { type: 'string[]', cat: 'e', args: 'extension-id[@version] | path-to-vsix', description: localize('installExtension', "Installs or updates the extension. The identifier of an extension is always `${publisher}.${name}`. Use `--force` argument to update to latest version. To install a specific version provide `@${version}`. For example: 'vscode.csharp@1.2.3'.") },
|
||||
'uninstall-extension': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('uninstallExtension', "Uninstalls an extension.") },
|
||||
'enable-proposed-api': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") },
|
||||
@@ -65,18 +66,18 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
|
||||
'verbose': { type: 'boolean', cat: 't', description: localize('verbose', "Print verbose output (implies --wait).") },
|
||||
'log': { type: 'string', cat: 't', args: 'level', description: localize('log', "Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'.") },
|
||||
'status': { type: 'boolean', alias: 's', cat: 't', description: localize('status', "Print process usage and diagnostics information.") },
|
||||
'prof-startup': { type: 'boolean', cat: 't', description: localize('prof-startup', "Run CPU profiler during startup") },
|
||||
'prof-startup': { type: 'boolean', cat: 't', description: localize('prof-startup', "Run CPU profiler during startup.") },
|
||||
'prof-append-timers': { type: 'string' },
|
||||
'prof-startup-prefix': { type: 'string' },
|
||||
'prof-v8-extensions': { type: 'boolean' },
|
||||
'disable-extensions': { type: 'boolean', deprecates: 'disableExtensions', cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") },
|
||||
'disable-extension': { type: 'string[]', cat: 't', args: 'extension-id', description: localize('disableExtension', "Disable an extension.") },
|
||||
'sync': { type: 'string', cat: 't', description: localize('turn sync', "Turn sync on or off"), args: ['on', 'off'] },
|
||||
'sync': { type: 'string', cat: 't', description: localize('turn sync', "Turn sync on or off."), args: ['on', 'off'] },
|
||||
|
||||
'inspect-extensions': { type: 'string', deprecates: 'debugPluginHost', args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") },
|
||||
'inspect-brk-extensions': { type: 'string', deprecates: 'debugBrkPluginHost', args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") },
|
||||
'disable-gpu': { type: 'boolean', cat: 't', description: localize('disableGPU', "Disable GPU hardware acceleration.") },
|
||||
'max-memory': { type: 'string', cat: 't', description: localize('maxMemory', "Max memory size for a window (in Mbytes).") },
|
||||
'max-memory': { type: 'string', cat: 't', description: localize('maxMemory', "Max memory size for a window (in Mbytes)."), args: 'memory' },
|
||||
'telemetry': { type: 'boolean', cat: 't', description: localize('telemetry', "Shows all telemetry events which VS code collects.") },
|
||||
|
||||
'remote': { type: 'string' },
|
||||
@@ -99,6 +100,7 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
|
||||
'disable-telemetry': { type: 'boolean' },
|
||||
'disable-updates': { type: 'boolean' },
|
||||
'disable-keytar': { type: 'boolean' },
|
||||
'disable-workspace-trust': { type: 'boolean' },
|
||||
'disable-crash-reporter': { type: 'boolean' },
|
||||
'crash-reporter-directory': { type: 'string' },
|
||||
'crash-reporter-id': { type: 'string' },
|
||||
|
||||
@@ -25,48 +25,48 @@ import { optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
|
||||
interface IRawGalleryExtensionFile {
|
||||
assetType: string;
|
||||
source: string;
|
||||
readonly assetType: string;
|
||||
readonly source: string;
|
||||
}
|
||||
|
||||
interface IRawGalleryExtensionProperty {
|
||||
key: string;
|
||||
value: string;
|
||||
readonly key: string;
|
||||
readonly value: string;
|
||||
}
|
||||
|
||||
interface IRawGalleryExtensionVersion {
|
||||
version: string;
|
||||
lastUpdated: string;
|
||||
assetUri: string;
|
||||
fallbackAssetUri: string;
|
||||
files: IRawGalleryExtensionFile[];
|
||||
properties?: IRawGalleryExtensionProperty[];
|
||||
readonly version: string;
|
||||
readonly lastUpdated: string;
|
||||
readonly assetUri: string;
|
||||
readonly fallbackAssetUri: string;
|
||||
readonly files: IRawGalleryExtensionFile[];
|
||||
readonly properties?: IRawGalleryExtensionProperty[];
|
||||
}
|
||||
|
||||
interface IRawGalleryExtensionStatistics {
|
||||
statisticName: string;
|
||||
value: number;
|
||||
readonly statisticName: string;
|
||||
readonly value: number;
|
||||
}
|
||||
|
||||
interface IRawGalleryExtension {
|
||||
extensionId: string;
|
||||
extensionName: string;
|
||||
displayName: string;
|
||||
shortDescription: string;
|
||||
publisher: { displayName: string, publisherId: string, publisherName: string; };
|
||||
versions: IRawGalleryExtensionVersion[];
|
||||
statistics: IRawGalleryExtensionStatistics[];
|
||||
flags: string;
|
||||
readonly extensionId: string;
|
||||
readonly extensionName: string;
|
||||
readonly displayName: string;
|
||||
readonly shortDescription: string;
|
||||
readonly publisher: { displayName: string, publisherId: string, publisherName: string; };
|
||||
readonly versions: IRawGalleryExtensionVersion[];
|
||||
readonly statistics: IRawGalleryExtensionStatistics[];
|
||||
readonly flags: string;
|
||||
}
|
||||
|
||||
interface IRawGalleryQueryResult {
|
||||
results: {
|
||||
extensions: IRawGalleryExtension[];
|
||||
resultMetadata: {
|
||||
metadataType: string;
|
||||
metadataItems: {
|
||||
name: string;
|
||||
count: number;
|
||||
readonly results: {
|
||||
readonly extensions: IRawGalleryExtension[];
|
||||
readonly resultMetadata: {
|
||||
readonly metadataType: string;
|
||||
readonly metadataItems: {
|
||||
readonly name: string;
|
||||
readonly count: number;
|
||||
}[];
|
||||
}[]
|
||||
}[];
|
||||
@@ -121,20 +121,20 @@ const PropertyType = {
|
||||
};
|
||||
|
||||
interface ICriterium {
|
||||
filterType: FilterType;
|
||||
value?: string;
|
||||
readonly filterType: FilterType;
|
||||
readonly value?: string;
|
||||
}
|
||||
|
||||
const DefaultPageSize = 10;
|
||||
|
||||
interface IQueryState {
|
||||
pageNumber: number;
|
||||
pageSize: number;
|
||||
sortBy: SortBy;
|
||||
sortOrder: SortOrder;
|
||||
flags: Flags;
|
||||
criteria: ICriterium[];
|
||||
assetTypes: string[];
|
||||
readonly pageNumber: number;
|
||||
readonly pageSize: number;
|
||||
readonly sortBy: SortBy;
|
||||
readonly sortOrder: SortOrder;
|
||||
readonly flags: Flags;
|
||||
readonly criteria: ICriterium[];
|
||||
readonly assetTypes: string[];
|
||||
}
|
||||
|
||||
const DefaultQueryState: IQueryState = {
|
||||
@@ -148,32 +148,32 @@ const DefaultQueryState: IQueryState = {
|
||||
};
|
||||
|
||||
type GalleryServiceQueryClassification = {
|
||||
filterTypes: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
sortBy: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
sortOrder: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
duration: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', 'isMeasurement': true };
|
||||
success: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
requestBodySize: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
responseBodySize?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
statusCode?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
errorCode?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
count?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
readonly filterTypes: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
readonly sortBy: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
readonly sortOrder: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
readonly duration: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', 'isMeasurement': true };
|
||||
readonly success: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
readonly requestBodySize: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
readonly responseBodySize?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
readonly statusCode?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
readonly errorCode?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
readonly count?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
|
||||
type QueryTelemetryData = {
|
||||
filterTypes: string[];
|
||||
sortBy: string;
|
||||
sortOrder: string;
|
||||
readonly filterTypes: string[];
|
||||
readonly sortBy: string;
|
||||
readonly sortOrder: string;
|
||||
};
|
||||
|
||||
type GalleryServiceQueryEvent = QueryTelemetryData & {
|
||||
duration: number;
|
||||
success: boolean;
|
||||
requestBodySize: string;
|
||||
responseBodySize?: string;
|
||||
statusCode?: string;
|
||||
errorCode?: string;
|
||||
count?: string;
|
||||
readonly duration: number;
|
||||
readonly success: boolean;
|
||||
readonly requestBodySize: string;
|
||||
readonly responseBodySize?: string;
|
||||
readonly statusCode?: string;
|
||||
readonly errorCode?: string;
|
||||
readonly count?: string;
|
||||
};
|
||||
|
||||
class Query {
|
||||
@@ -358,13 +358,11 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
|
||||
/* __GDPR__FRAGMENT__
|
||||
"GalleryExtensionTelemetryData2" : {
|
||||
"index" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"searchText": { "classification": "CustomerContent", "purpose": "FeatureInsight" },
|
||||
"querySource": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
telemetryData: {
|
||||
index: ((query.pageNumber - 1) * query.pageSize) + index,
|
||||
searchText: query.searchText,
|
||||
querySource
|
||||
},
|
||||
preview: getIsPreview(galleryExtension.flags)
|
||||
@@ -435,7 +433,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
|
||||
private async getCompatibleExtensionByEngine(arg1: IExtensionIdentifier | IGalleryExtension, version?: string): Promise<IGalleryExtension | null> {
|
||||
const extension: IGalleryExtension | null = isIExtensionIdentifier(arg1) ? null : arg1;
|
||||
if (extension && extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version)) {
|
||||
if (extension && extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version, this.productService.date)) {
|
||||
return extension;
|
||||
}
|
||||
const { id, uuid } = extension ? extension.identifier : <IExtensionIdentifier>arg1;
|
||||
@@ -460,7 +458,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
const versionAsset = rawExtension.versions.filter(v => v.version === version)[0];
|
||||
if (versionAsset) {
|
||||
const extension = toExtension(rawExtension, versionAsset, 0, query);
|
||||
if (extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version)) {
|
||||
if (extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version, this.productService.date)) {
|
||||
return extension;
|
||||
}
|
||||
}
|
||||
@@ -713,7 +711,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
try {
|
||||
engine = await this.getEngine(v);
|
||||
} catch (error) { /* Ignore error and skip version */ }
|
||||
if (engine && isEngineValid(engine, this.productService.version)) {
|
||||
if (engine && isEngineValid(engine, this.productService.version, this.productService.date)) {
|
||||
result.push({ version: v!.version, date: v!.lastUpdated });
|
||||
}
|
||||
}));
|
||||
@@ -776,7 +774,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
if (!engine) {
|
||||
return null;
|
||||
}
|
||||
if (isEngineValid(engine, this.productService.version)) {
|
||||
if (isEngineValid(engine, this.productService.version, this.productService.date)) {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -811,13 +809,14 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
|
||||
const version = versions[0];
|
||||
const engine = await this.getEngine(version);
|
||||
if (!isEngineValid(engine, this.productService.version)) {
|
||||
if (!isEngineValid(engine, this.productService.version, this.productService.date)) {
|
||||
return this.getLastValidExtensionVersionRecursively(extension, versions.slice(1));
|
||||
}
|
||||
|
||||
version.properties = version.properties || [];
|
||||
version.properties.push({ key: PropertyType.Engine, value: engine });
|
||||
return version;
|
||||
return {
|
||||
...version,
|
||||
properties: [...(version.properties || []), { key: PropertyType.Engine, value: engine }]
|
||||
};
|
||||
}
|
||||
|
||||
async getExtensionsReport(): Promise<IReportedExtension[]> {
|
||||
|
||||
@@ -203,6 +203,7 @@ export class ExtensionManagementError extends Error {
|
||||
}
|
||||
|
||||
export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean };
|
||||
export type InstallVSIXOptions = InstallOptions & { installOnlyNewlyAddedFromExtensionPack?: boolean };
|
||||
export type UninstallOptions = { donotIncludePack?: boolean, donotCheckDependents?: boolean };
|
||||
|
||||
export const IExtensionManagementService = createDecorator<IExtensionManagementService>('extensionManagementService');
|
||||
@@ -217,7 +218,7 @@ export interface IExtensionManagementService {
|
||||
zip(extension: ILocalExtension): Promise<URI>;
|
||||
unzip(zipLocation: URI): Promise<IExtensionIdentifier>;
|
||||
getManifest(vsix: URI): Promise<IExtensionManifest>;
|
||||
install(vsix: URI, options?: InstallOptions): Promise<ILocalExtension>;
|
||||
install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension>;
|
||||
canInstall(extension: IGalleryExtension): Promise<boolean>;
|
||||
installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise<ILocalExtension>;
|
||||
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void>;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionTipsService, InstallOptions, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionTipsService, InstallOptions, UninstallOptions, InstallVSIXOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IURITransformer, DefaultURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc';
|
||||
@@ -62,7 +62,7 @@ export class ExtensionManagementChannel implements IServerChannel {
|
||||
switch (command) {
|
||||
case 'zip': return this.service.zip(transformIncomingExtension(args[0], uriTransformer)).then(uri => transformOutgoingURI(uri, uriTransformer));
|
||||
case 'unzip': return this.service.unzip(transformIncomingURI(args[0], uriTransformer));
|
||||
case 'install': return this.service.install(transformIncomingURI(args[0], uriTransformer));
|
||||
case 'install': return this.service.install(transformIncomingURI(args[0], uriTransformer), args[1]);
|
||||
case 'getManifest': return this.service.getManifest(transformIncomingURI(args[0], uriTransformer));
|
||||
case 'canInstall': return this.service.canInstall(args[0]);
|
||||
case 'installFromGallery': return this.service.installFromGallery(args[0], args[1]);
|
||||
@@ -112,8 +112,8 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
|
||||
return Promise.resolve(this.channel.call('unzip', [zipLocation]));
|
||||
}
|
||||
|
||||
install(vsix: URI): Promise<ILocalExtension> {
|
||||
return Promise.resolve(this.channel.call<ILocalExtension>('install', [vsix])).then(local => transformIncomingExtension(local, null));
|
||||
install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension> {
|
||||
return Promise.resolve(this.channel.call<ILocalExtension>('install', [vsix, options])).then(local => transformIncomingExtension(local, null));
|
||||
}
|
||||
|
||||
getManifest(vsix: URI): Promise<IExtensionManifest> {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { promises } from 'fs';
|
||||
import { Promises as FSPromises } from 'vs/base/node/pfs';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files';
|
||||
import { IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
@@ -77,7 +77,7 @@ export class ExtensionsDownloader extends Disposable {
|
||||
|
||||
private async rename(from: URI, to: URI, retryUntil: number): Promise<void> {
|
||||
try {
|
||||
await promises.rename(from.fsPath, to.fsPath);
|
||||
await FSPromises.rename(from.fsPath, to.fsPath);
|
||||
} catch (error) {
|
||||
if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) {
|
||||
this.logService.info(`Failed renaming ${from} to ${to} with 'EPERM' error. Trying again...`);
|
||||
@@ -90,7 +90,7 @@ export class ExtensionsDownloader extends Disposable {
|
||||
private async cleanUp(): Promise<void> {
|
||||
try {
|
||||
if (!(await this.fileService.exists(this.extensionsDownloadDir))) {
|
||||
this.logService.trace('Extension VSIX downloads cache dir does not exist');
|
||||
this.logService.trace('Extension VSIX downlads cache dir does not exist');
|
||||
return;
|
||||
}
|
||||
const folderStat = await this.fileService.resolve(this.extensionsDownloadDir, { resolveMetadata: true });
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
@@ -22,7 +21,8 @@ import {
|
||||
INSTALL_ERROR_INCOMPATIBLE,
|
||||
ExtensionManagementError,
|
||||
InstallOptions,
|
||||
UninstallOptions
|
||||
UninstallOptions,
|
||||
InstallVSIXOptions
|
||||
} from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions, getGalleryExtensionId, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@@ -140,7 +140,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
const collectFilesFromDirectory = async (dir: string): Promise<string[]> => {
|
||||
let entries = await pfs.readdir(dir);
|
||||
entries = entries.map(e => path.join(dir, e));
|
||||
const stats = await Promise.all(entries.map(e => fs.promises.stat(e)));
|
||||
const stats = await Promise.all(entries.map(e => pfs.Promises.stat(e)));
|
||||
let promise: Promise<string[]> = Promise.resolve([]);
|
||||
stats.forEach((stat, index) => {
|
||||
const entry = entries[index];
|
||||
@@ -160,7 +160,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
return files.map(f => (<IFile>{ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f }));
|
||||
}
|
||||
|
||||
async install(vsix: URI, options: InstallOptions = {}): Promise<ILocalExtension> {
|
||||
async install(vsix: URI, options: InstallVSIXOptions = {}): Promise<ILocalExtension> {
|
||||
this.logService.trace('ExtensionManagementService#install', vsix.toString());
|
||||
return createCancelablePromise(async token => {
|
||||
|
||||
@@ -170,7 +170,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
const manifest = await getManifest(zipPath);
|
||||
const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
|
||||
let operation: InstallOperation = InstallOperation.Install;
|
||||
if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, product.version)) {
|
||||
if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, product.version, product.date)) {
|
||||
throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", identifier.id, product.version));
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
} catch (e) { /* Ignore */ }
|
||||
|
||||
try {
|
||||
const local = await this.installFromZipPath(identifierWithVersion, zipPath, { ...(metadata || {}), ...options }, options, operation, token);
|
||||
const local = await this.installFromZipPath(identifierWithVersion, zipPath, options.installOnlyNewlyAddedFromExtensionPack ? existing : undefined, { ...(metadata || {}), ...options }, options, operation, token);
|
||||
this.logService.info('Successfully installed the extension:', identifier.id);
|
||||
return local;
|
||||
} catch (e) {
|
||||
@@ -235,11 +235,13 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
return downloadedLocation;
|
||||
}
|
||||
|
||||
private async installFromZipPath(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, metadata: IMetadata | undefined, options: InstallOptions, operation: InstallOperation, token: CancellationToken): Promise<ILocalExtension> {
|
||||
private async installFromZipPath(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, existing: ILocalExtension | undefined, metadata: IMetadata | undefined, options: InstallOptions, operation: InstallOperation, token: CancellationToken): Promise<ILocalExtension> {
|
||||
try {
|
||||
const local = await this.installExtension({ zipPath, identifierWithVersion, metadata }, token);
|
||||
try {
|
||||
await this.installDependenciesAndPackExtensions(local, undefined, options);
|
||||
if (!options.donotIncludePackAndDependencies) {
|
||||
await this.installDependenciesAndPackExtensions(local, existing, options);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isNonEmptyArray(local.manifest.extensionDependencies)) {
|
||||
this.logService.warn(`Cannot install dependencies of extension:`, local.identifier.id, error.message);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as semver from 'vs/base/common/semver/semver';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
@@ -98,7 +97,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
}
|
||||
|
||||
async scanAllUserExtensions(): Promise<ILocalExtension[]> {
|
||||
return this.scanExtensionsInDirs(this.extensionsPath, this.environmentService.extraExtensionPaths, ExtensionType.User);
|
||||
return this.scanExtensionsInDirs([this.extensionsPath, ...this.environmentService.extraExtensionPaths], ExtensionType.User);
|
||||
}
|
||||
|
||||
async extractUserExtension(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, token: CancellationToken): Promise<ILocalExtension> {
|
||||
@@ -159,7 +158,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
storedMetadata.isBuiltin = storedMetadata.isBuiltin || undefined;
|
||||
storedMetadata.installedTimestamp = storedMetadata.installedTimestamp || undefined;
|
||||
const manifestPath = path.join(local.location.fsPath, 'package.json');
|
||||
const raw = await fs.promises.readFile(manifestPath, 'utf8');
|
||||
const raw = await pfs.Promises.readFile(manifestPath, 'utf8');
|
||||
const { manifest } = await this.parseManifest(raw);
|
||||
(manifest as ILocalExtensionManifest).__metadata = storedMetadata;
|
||||
await pfs.writeFile(manifestPath, JSON.stringify(manifest, null, '\t'));
|
||||
@@ -192,7 +191,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
return this.uninstalledFileLimiter.queue(async () => {
|
||||
let raw: string | undefined;
|
||||
try {
|
||||
raw = await fs.promises.readFile(this.uninstalledPath, 'utf8');
|
||||
raw = await pfs.Promises.readFile(this.uninstalledPath, 'utf8');
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
@@ -251,7 +250,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
|
||||
private async rename(identifier: IExtensionIdentifier, extractPath: string, renamePath: string, retryUntil: number): Promise<void> {
|
||||
try {
|
||||
await fs.promises.rename(extractPath, renamePath);
|
||||
await pfs.Promises.rename(extractPath, renamePath);
|
||||
} catch (error) {
|
||||
if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) {
|
||||
this.logService.info(`Failed renaming ${extractPath} to ${renamePath} with 'EPERM' error. Trying again...`, identifier.id);
|
||||
@@ -315,7 +314,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
}
|
||||
|
||||
private async scanDefaultSystemExtensions(): Promise<ILocalExtension[]> {
|
||||
const result = await this.scanExtensionsInDirs(this.systemExtensionsPath, this.environmentService.extraBuiltinExtensionPaths, ExtensionType.System);
|
||||
const result = await this.scanExtensionsInDirs([this.systemExtensionsPath, ...this.environmentService.extraBuiltinExtensionPaths], ExtensionType.System);
|
||||
this.logService.trace('Scanned system extensions:', result.length);
|
||||
return result;
|
||||
}
|
||||
@@ -394,9 +393,9 @@ export class ExtensionsScanner extends Disposable {
|
||||
|
||||
private async readManifest(extensionPath: string): Promise<{ manifest: IExtensionManifest; metadata: IStoredMetadata | null; }> {
|
||||
const promises = [
|
||||
fs.promises.readFile(path.join(extensionPath, 'package.json'), 'utf8')
|
||||
pfs.Promises.readFile(path.join(extensionPath, 'package.json'), 'utf8')
|
||||
.then(raw => this.parseManifest(raw)),
|
||||
fs.promises.readFile(path.join(extensionPath, 'package.nls.json'), 'utf8')
|
||||
pfs.Promises.readFile(path.join(extensionPath, 'package.nls.json'), 'utf8')
|
||||
.then(undefined, err => err.code !== 'ENOENT' ? Promise.reject<string>(err) : '{}')
|
||||
.then(raw => JSON.parse(raw))
|
||||
];
|
||||
@@ -420,8 +419,8 @@ export class ExtensionsScanner extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private async scanExtensionsInDirs(dir: string, dirs: string[], type: ExtensionType): Promise<ILocalExtension[]>{
|
||||
const results = await Promise.all([dir, ...dirs].map((path) => this.scanExtensionsInDir(path, type)));
|
||||
private async scanExtensionsInDirs(dirs: string[], type: ExtensionType): Promise<ILocalExtension[]>{
|
||||
const results = await Promise.all(dirs.map((path) => this.scanExtensionsInDir(path, type)));
|
||||
return results.reduce((flat, current) => flat.concat(current), []);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,12 @@ export interface INormalizedVersion {
|
||||
minorMustEqual: boolean;
|
||||
patchBase: number;
|
||||
patchMustEqual: boolean;
|
||||
notBefore: number; /* milliseconds timestamp, or 0 */
|
||||
isMinimum: boolean;
|
||||
}
|
||||
|
||||
const VERSION_REGEXP = /^(\^|>=)?((\d+)|x)\.((\d+)|x)\.((\d+)|x)(\-.*)?$/;
|
||||
const NOT_BEFORE_REGEXP = /^-(\d{4})(\d{2})(\d{2})$/;
|
||||
|
||||
export function isValidVersionStr(version: string): boolean {
|
||||
version = version.trim();
|
||||
@@ -93,6 +95,15 @@ export function normalizeVersion(version: IParsedVersion | null): INormalizedVer
|
||||
}
|
||||
}
|
||||
|
||||
let notBefore = 0;
|
||||
if (version.preRelease) {
|
||||
const match = NOT_BEFORE_REGEXP.exec(version.preRelease);
|
||||
if (match) {
|
||||
const [, year, month, day] = match;
|
||||
notBefore = Date.UTC(Number(year), Number(month) - 1, Number(day));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
majorBase: majorBase,
|
||||
majorMustEqual: majorMustEqual,
|
||||
@@ -100,16 +111,24 @@ export function normalizeVersion(version: IParsedVersion | null): INormalizedVer
|
||||
minorMustEqual: minorMustEqual,
|
||||
patchBase: patchBase,
|
||||
patchMustEqual: patchMustEqual,
|
||||
isMinimum: version.hasGreaterEquals
|
||||
isMinimum: version.hasGreaterEquals,
|
||||
notBefore,
|
||||
};
|
||||
}
|
||||
|
||||
export function isValidVersion(_version: string | INormalizedVersion, _desiredVersion: string | INormalizedVersion): boolean {
|
||||
export function isValidVersion(_inputVersion: string | INormalizedVersion, _inputDate: ProductDate, _desiredVersion: string | INormalizedVersion): boolean {
|
||||
let version: INormalizedVersion | null;
|
||||
if (typeof _version === 'string') {
|
||||
version = normalizeVersion(parseVersion(_version));
|
||||
if (typeof _inputVersion === 'string') {
|
||||
version = normalizeVersion(parseVersion(_inputVersion));
|
||||
} else {
|
||||
version = _version;
|
||||
version = _inputVersion;
|
||||
}
|
||||
|
||||
let productTs: number | undefined;
|
||||
if (_inputDate instanceof Date) {
|
||||
productTs = _inputDate.getTime();
|
||||
} else if (typeof _inputDate === 'string') {
|
||||
productTs = new Date(_inputDate).getTime();
|
||||
}
|
||||
|
||||
let desiredVersion: INormalizedVersion | null;
|
||||
@@ -130,6 +149,7 @@ export function isValidVersion(_version: string | INormalizedVersion, _desiredVe
|
||||
let desiredMajorBase = desiredVersion.majorBase;
|
||||
let desiredMinorBase = desiredVersion.minorBase;
|
||||
let desiredPatchBase = desiredVersion.patchBase;
|
||||
let desiredNotBefore = desiredVersion.notBefore;
|
||||
|
||||
let majorMustEqual = desiredVersion.majorMustEqual;
|
||||
let minorMustEqual = desiredVersion.minorMustEqual;
|
||||
@@ -152,6 +172,10 @@ export function isValidVersion(_version: string | INormalizedVersion, _desiredVe
|
||||
return false;
|
||||
}
|
||||
|
||||
if (productTs && productTs < desiredNotBefore) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return patchBase >= desiredPatchBase;
|
||||
}
|
||||
|
||||
@@ -200,6 +224,11 @@ export function isValidVersion(_version: string | INormalizedVersion, _desiredVe
|
||||
}
|
||||
|
||||
// at this point, patchBase are equal
|
||||
|
||||
if (productTs && productTs < desiredNotBefore) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -211,22 +240,24 @@ export interface IReducedExtensionDescription {
|
||||
main?: string;
|
||||
}
|
||||
|
||||
export function isValidExtensionVersion(version: string, extensionDesc: IReducedExtensionDescription, notices: string[]): boolean {
|
||||
type ProductDate = string | Date | undefined;
|
||||
|
||||
export function isValidExtensionVersion(version: string, date: ProductDate, extensionDesc: IReducedExtensionDescription, notices: string[]): boolean {
|
||||
|
||||
if (extensionDesc.isBuiltin || typeof extensionDesc.main === 'undefined') {
|
||||
// No version check for builtin or declarative extensions
|
||||
return true;
|
||||
}
|
||||
|
||||
return isVersionValid(version, extensionDesc.engines.vscode, notices);
|
||||
return isVersionValid(version, date, extensionDesc.engines.vscode, notices);
|
||||
}
|
||||
|
||||
export function isEngineValid(engine: string, version: string): boolean {
|
||||
export function isEngineValid(engine: string, version: string, date: ProductDate): boolean {
|
||||
// TODO@joao: discuss with alex '*' doesn't seem to be a valid engine version
|
||||
return engine === '*' || isVersionValid(version, engine);
|
||||
return engine === '*' || isVersionValid(version, date, engine);
|
||||
}
|
||||
|
||||
export function isVersionValid(currentVersion: string, requestedVersion: string, notices: string[] = []): boolean {
|
||||
function isVersionValid(currentVersion: string, date: ProductDate, requestedVersion: string, notices: string[] = []): boolean {
|
||||
|
||||
let desiredVersion = normalizeVersion(parseVersion(requestedVersion));
|
||||
if (!desiredVersion) {
|
||||
@@ -251,7 +282,7 @@ export function isVersionValid(currentVersion: string, requestedVersion: string,
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValidVersion(currentVersion, desiredVersion)) {
|
||||
if (!isValidVersion(currentVersion, date, desiredVersion)) {
|
||||
notices.push(nls.localize('versionMismatch', "Extension is not compatible with Code {0}. Extension requires: {1}.", currentVersion, requestedVersion));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -116,10 +116,12 @@ export interface IAuthenticationContribution {
|
||||
export interface IWalkthroughStep {
|
||||
readonly id: string;
|
||||
readonly title: string;
|
||||
readonly description: string;
|
||||
readonly description: string | undefined;
|
||||
readonly media:
|
||||
| { path: string | { dark: string, light: string, hc: string }, altText: string }
|
||||
| { path: string, },
|
||||
| { image: string | { dark: string, light: string, hc: string }, altText: string, markdown?: never }
|
||||
| { markdown: string, image?: never }
|
||||
readonly completionEvents?: string[];
|
||||
/** @deprecated use `completionEvents: 'onCommand:...'` */
|
||||
readonly doneOn?: { command: string };
|
||||
readonly when?: string;
|
||||
}
|
||||
@@ -129,7 +131,6 @@ export interface IWalkthrough {
|
||||
readonly title: string;
|
||||
readonly description: string;
|
||||
readonly steps: IWalkthroughStep[];
|
||||
readonly primary?: boolean;
|
||||
readonly when?: string;
|
||||
}
|
||||
|
||||
@@ -166,13 +167,30 @@ export interface IExtensionContributions {
|
||||
}
|
||||
|
||||
export interface IExtensionCapabilities {
|
||||
readonly virtualWorkspaces?: boolean;
|
||||
readonly virtualWorkspaces?: ExtensionVirtualWorkpaceSupport;
|
||||
readonly untrustedWorkspaces?: ExtensionUntrustedWorkspaceSupport;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type ExtensionKind = 'ui' | 'workspace' | 'web';
|
||||
export type ExtensionUntrustedWorkpaceSupportType = boolean | 'limited';
|
||||
export type ExtensionUntrustedWorkspaceSupport = { supported: true; } | { supported: false, description: string } | { supported: 'limited', description: string, restrictedConfigurations?: string[] };
|
||||
|
||||
export type LimitedWorkpaceSupportType = 'limited';
|
||||
export type ExtensionUntrustedWorkpaceSupportType = boolean | LimitedWorkpaceSupportType;
|
||||
export type ExtensionUntrustedWorkspaceSupport = { supported: true; } | { supported: false, description: string } | { supported: LimitedWorkpaceSupportType, description: string, restrictedConfigurations?: string[] };
|
||||
|
||||
export type ExtensionVirtualWorkpaceSupportType = boolean | LimitedWorkpaceSupportType;
|
||||
export type ExtensionVirtualWorkpaceSupport = boolean | { supported: true; } | { supported: false | LimitedWorkpaceSupportType, description: string };
|
||||
|
||||
export function getWorkpaceSupportTypeMessage(supportType: ExtensionUntrustedWorkspaceSupport | ExtensionVirtualWorkpaceSupport | undefined): string | undefined {
|
||||
if (typeof supportType === 'object' && supportType !== null) {
|
||||
if (supportType.supported !== true) {
|
||||
return supportType.description;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifier {
|
||||
return thing
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as assert from 'assert';
|
||||
import { INormalizedVersion, IParsedVersion, IReducedExtensionDescription, isValidExtensionVersion, isValidVersion, isValidVersionStr, normalizeVersion, parseVersion } from 'vs/platform/extensions/common/extensionValidator';
|
||||
|
||||
suite('Extension Version Validator', () => {
|
||||
const productVersion = '2021-05-11T21:54:30.577Z';
|
||||
|
||||
test('isValidVersionStr', () => {
|
||||
assert.strictEqual(isValidVersionStr('0.10.0-dev'), true);
|
||||
@@ -53,13 +54,16 @@ suite('Extension Version Validator', () => {
|
||||
});
|
||||
|
||||
test('normalizeVersion', () => {
|
||||
function assertNormalizeVersion(version: string, majorBase: number, majorMustEqual: boolean, minorBase: number, minorMustEqual: boolean, patchBase: number, patchMustEqual: boolean, isMinimum: boolean): void {
|
||||
function assertNormalizeVersion(version: string, majorBase: number, majorMustEqual: boolean, minorBase: number, minorMustEqual: boolean, patchBase: number, patchMustEqual: boolean, isMinimum: boolean, notBefore = 0): void {
|
||||
const actual = normalizeVersion(parseVersion(version));
|
||||
const expected: INormalizedVersion = { majorBase, majorMustEqual, minorBase, minorMustEqual, patchBase, patchMustEqual, isMinimum };
|
||||
const expected: INormalizedVersion = { majorBase, majorMustEqual, minorBase, minorMustEqual, patchBase, patchMustEqual, isMinimum, notBefore };
|
||||
assert.deepStrictEqual(actual, expected, 'parseVersion for ' + version);
|
||||
}
|
||||
|
||||
assertNormalizeVersion('0.10.0-dev', 0, true, 10, true, 0, true, false);
|
||||
assertNormalizeVersion('0.10.0-dev', 0, true, 10, true, 0, true, false, 0);
|
||||
assertNormalizeVersion('0.10.0-222222222', 0, true, 10, true, 0, true, false, 0);
|
||||
assertNormalizeVersion('0.10.0-20210511', 0, true, 10, true, 0, true, false, new Date('2021-05-11T00:00:00Z').getTime());
|
||||
|
||||
assertNormalizeVersion('0.10.0', 0, true, 10, true, 0, true, false);
|
||||
assertNormalizeVersion('0.10.1', 0, true, 10, true, 1, true, false);
|
||||
assertNormalizeVersion('0.10.100', 0, true, 10, true, 100, true, false);
|
||||
@@ -75,11 +79,12 @@ suite('Extension Version Validator', () => {
|
||||
|
||||
assertNormalizeVersion('>=0.0.1', 0, true, 0, true, 1, true, true);
|
||||
assertNormalizeVersion('>=2.4.3', 2, true, 4, true, 3, true, true);
|
||||
assertNormalizeVersion('>=2.4.3', 2, true, 4, true, 3, true, true);
|
||||
});
|
||||
|
||||
test('isValidVersion', () => {
|
||||
function testIsValidVersion(version: string, desiredVersion: string, expectedResult: boolean): void {
|
||||
let actual = isValidVersion(version, desiredVersion);
|
||||
let actual = isValidVersion(version, productVersion, desiredVersion);
|
||||
assert.strictEqual(actual, expectedResult, 'extension - vscode: ' + version + ', desiredVersion: ' + desiredVersion + ' should be ' + expectedResult);
|
||||
}
|
||||
|
||||
@@ -211,7 +216,7 @@ suite('Extension Version Validator', () => {
|
||||
main: hasMain ? 'something' : undefined
|
||||
};
|
||||
let reasons: string[] = [];
|
||||
let actual = isValidExtensionVersion(version, desc, reasons);
|
||||
let actual = isValidExtensionVersion(version, productVersion, desc, reasons);
|
||||
|
||||
assert.strictEqual(actual, expectedResult, 'version: ' + version + ', desiredVersion: ' + desiredVersion + ', desc: ' + JSON.stringify(desc) + ', reasons: ' + JSON.stringify(reasons));
|
||||
}
|
||||
@@ -390,5 +395,12 @@ suite('Extension Version Validator', () => {
|
||||
testIsValidVersion('2.0.0', '^1.100.0', false);
|
||||
testIsValidVersion('2.0.0', '^2.0.0', true);
|
||||
testIsValidVersion('2.0.0', '*', false); // fails due to lack of specificity
|
||||
|
||||
// date tags
|
||||
testIsValidVersion('1.10.0', '^1.10.0-20210511', true); // current date
|
||||
testIsValidVersion('1.10.0', '^1.10.0-20210510', true); // before date
|
||||
testIsValidVersion('1.10.0', '^1.10.0-20210512', false); // future date
|
||||
testIsValidVersion('1.10.1', '^1.10.0-20200101', true); // before date, but ahead version
|
||||
testIsValidVersion('1.11.0', '^1.10.0-20200101', true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ITerminalEnvironment } from 'vs/platform/terminal/common/terminal';
|
||||
|
||||
export const IExternalTerminalService = createDecorator<IExternalTerminalService>('externalTerminal');
|
||||
|
||||
export interface IExternalTerminalSettings {
|
||||
linuxExec?: string;
|
||||
osxExec?: string;
|
||||
windowsExec?: string;
|
||||
}
|
||||
|
||||
export interface ITerminalForPlatform {
|
||||
windows: string,
|
||||
linux: string,
|
||||
osx: string
|
||||
}
|
||||
|
||||
export interface IExternalTerminalService {
|
||||
readonly _serviceBrand: undefined;
|
||||
openTerminal(configuration: IExternalTerminalSettings, path: string): Promise<void>;
|
||||
runInTerminal(title: string, cwd: string, args: string[], env: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise<number | undefined>;
|
||||
getDefaultTerminalForPlatforms(): Promise<ITerminalForPlatform>;
|
||||
}
|
||||
|
||||
export interface IExternalTerminalConfiguration {
|
||||
terminal: {
|
||||
explorerKind: 'integrated' | 'external',
|
||||
external: IExternalTerminalSettings;
|
||||
};
|
||||
}
|
||||
|
||||
export const DEFAULT_TERMINAL_OSX = 'Terminal.app';
|
||||
|
||||
export const IExternalTerminalMainService = createDecorator<IExternalTerminalMainService>('externalTerminal');
|
||||
|
||||
export interface IExternalTerminalMainService extends IExternalTerminalService {
|
||||
readonly _serviceBrand: undefined;
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { deepEqual, equal } from 'assert';
|
||||
import { DEFAULT_TERMINAL_OSX } from 'vs/platform/externalTerminal/common/externalTerminal';
|
||||
import { WindowsExternalTerminalService, MacExternalTerminalService, LinuxExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService';
|
||||
|
||||
suite('ExternalTerminalService', () => {
|
||||
let mockOnExit: Function;
|
||||
let mockOnError: Function;
|
||||
let mockConfig: any;
|
||||
|
||||
setup(() => {
|
||||
mockConfig = {
|
||||
terminal: {
|
||||
explorerKind: 'external',
|
||||
external: {
|
||||
windowsExec: 'testWindowsShell',
|
||||
osxExec: 'testOSXShell',
|
||||
linuxExec: 'testLinuxShell'
|
||||
}
|
||||
}
|
||||
};
|
||||
mockOnExit = (s: any) => s;
|
||||
mockOnError = (e: any) => e;
|
||||
});
|
||||
|
||||
test(`WinTerminalService - uses terminal from configuration`, done => {
|
||||
let testShell = 'cmd';
|
||||
let testCwd = 'path/to/workspace';
|
||||
let mockSpawner = {
|
||||
spawn: (command: any, args: any, opts: any) => {
|
||||
// assert
|
||||
equal(command, testShell, 'shell should equal expected');
|
||||
equal(args[args.length - 1], mockConfig.terminal.external.windowsExec, 'terminal should equal expected');
|
||||
equal(opts.cwd, testCwd, 'opts.cwd should equal expected');
|
||||
done();
|
||||
return {
|
||||
on: (evt: any) => evt
|
||||
};
|
||||
}
|
||||
};
|
||||
let testService = new WindowsExternalTerminalService();
|
||||
(<any>testService).spawnTerminal(
|
||||
mockSpawner,
|
||||
mockConfig,
|
||||
testShell,
|
||||
testCwd,
|
||||
mockOnExit,
|
||||
mockOnError
|
||||
);
|
||||
});
|
||||
|
||||
test(`WinTerminalService - uses default terminal when configuration.terminal.external.windowsExec is undefined`, done => {
|
||||
let testShell = 'cmd';
|
||||
let testCwd = 'path/to/workspace';
|
||||
let mockSpawner = {
|
||||
spawn: (command: any, args: any, opts: any) => {
|
||||
// assert
|
||||
equal(args[args.length - 1], WindowsExternalTerminalService.getDefaultTerminalWindows(), 'terminal should equal expected');
|
||||
done();
|
||||
return {
|
||||
on: (evt: any) => evt
|
||||
};
|
||||
}
|
||||
};
|
||||
mockConfig.terminal.external.windowsExec = undefined;
|
||||
let testService = new WindowsExternalTerminalService();
|
||||
(<any>testService).spawnTerminal(
|
||||
mockSpawner,
|
||||
mockConfig,
|
||||
testShell,
|
||||
testCwd,
|
||||
mockOnExit,
|
||||
mockOnError
|
||||
);
|
||||
});
|
||||
|
||||
test(`WinTerminalService - uses default terminal when configuration.terminal.external.windowsExec is undefined`, done => {
|
||||
let testShell = 'cmd';
|
||||
let testCwd = 'c:/foo';
|
||||
let mockSpawner = {
|
||||
spawn: (command: any, args: any, opts: any) => {
|
||||
// assert
|
||||
equal(opts.cwd, 'C:/foo', 'cwd should be uppercase regardless of the case that\'s passed in');
|
||||
done();
|
||||
return {
|
||||
on: (evt: any) => evt
|
||||
};
|
||||
}
|
||||
};
|
||||
let testService = new WindowsExternalTerminalService();
|
||||
(<any>testService).spawnTerminal(
|
||||
mockSpawner,
|
||||
mockConfig,
|
||||
testShell,
|
||||
testCwd,
|
||||
mockOnExit,
|
||||
mockOnError
|
||||
);
|
||||
});
|
||||
|
||||
test(`WinTerminalService - cmder should be spawned differently`, done => {
|
||||
let testShell = 'cmd';
|
||||
mockConfig.terminal.external.windowsExec = 'cmder';
|
||||
let testCwd = 'c:/foo';
|
||||
let mockSpawner = {
|
||||
spawn: (command: any, args: any, opts: any) => {
|
||||
// assert
|
||||
deepEqual(args, ['C:/foo']);
|
||||
equal(opts, undefined);
|
||||
done();
|
||||
return { on: (evt: any) => evt };
|
||||
}
|
||||
};
|
||||
let testService = new WindowsExternalTerminalService();
|
||||
(<any>testService).spawnTerminal(
|
||||
mockSpawner,
|
||||
mockConfig,
|
||||
testShell,
|
||||
testCwd,
|
||||
mockOnExit,
|
||||
mockOnError
|
||||
);
|
||||
});
|
||||
|
||||
test(`WinTerminalService - windows terminal should open workspace directory`, done => {
|
||||
let testShell = 'wt';
|
||||
let testCwd = 'c:/foo';
|
||||
let mockSpawner = {
|
||||
spawn: (command: any, args: any, opts: any) => {
|
||||
// assert
|
||||
equal(opts.cwd, 'C:/foo');
|
||||
done();
|
||||
return { on: (evt: any) => evt };
|
||||
}
|
||||
};
|
||||
let testService = new WindowsExternalTerminalService();
|
||||
(<any>testService).spawnTerminal(
|
||||
mockSpawner,
|
||||
mockConfig,
|
||||
testShell,
|
||||
testCwd,
|
||||
mockOnExit,
|
||||
mockOnError
|
||||
);
|
||||
});
|
||||
|
||||
test(`MacTerminalService - uses terminal from configuration`, done => {
|
||||
let testCwd = 'path/to/workspace';
|
||||
let mockSpawner = {
|
||||
spawn: (command: any, args: any, opts: any) => {
|
||||
// assert
|
||||
equal(args[1], mockConfig.terminal.external.osxExec, 'terminal should equal expected');
|
||||
done();
|
||||
return {
|
||||
on: (evt: any) => evt
|
||||
};
|
||||
}
|
||||
};
|
||||
let testService = new MacExternalTerminalService();
|
||||
(<any>testService).spawnTerminal(
|
||||
mockSpawner,
|
||||
mockConfig,
|
||||
testCwd,
|
||||
mockOnExit,
|
||||
mockOnError
|
||||
);
|
||||
});
|
||||
|
||||
test(`MacTerminalService - uses default terminal when configuration.terminal.external.osxExec is undefined`, done => {
|
||||
let testCwd = 'path/to/workspace';
|
||||
let mockSpawner = {
|
||||
spawn: (command: any, args: any, opts: any) => {
|
||||
// assert
|
||||
equal(args[1], DEFAULT_TERMINAL_OSX, 'terminal should equal expected');
|
||||
done();
|
||||
return {
|
||||
on: (evt: any) => evt
|
||||
};
|
||||
}
|
||||
};
|
||||
mockConfig.terminal.external.osxExec = undefined;
|
||||
let testService = new MacExternalTerminalService();
|
||||
(<any>testService).spawnTerminal(
|
||||
mockSpawner,
|
||||
mockConfig,
|
||||
testCwd,
|
||||
mockOnExit,
|
||||
mockOnError
|
||||
);
|
||||
});
|
||||
|
||||
test(`LinuxTerminalService - uses terminal from configuration`, done => {
|
||||
let testCwd = 'path/to/workspace';
|
||||
let mockSpawner = {
|
||||
spawn: (command: any, args: any, opts: any) => {
|
||||
// assert
|
||||
equal(command, mockConfig.terminal.external.linuxExec, 'terminal should equal expected');
|
||||
equal(opts.cwd, testCwd, 'opts.cwd should equal expected');
|
||||
done();
|
||||
return {
|
||||
on: (evt: any) => evt
|
||||
};
|
||||
}
|
||||
};
|
||||
let testService = new LinuxExternalTerminalService();
|
||||
(<any>testService).spawnTerminal(
|
||||
mockSpawner,
|
||||
mockConfig,
|
||||
testCwd,
|
||||
mockOnExit,
|
||||
mockOnError
|
||||
);
|
||||
});
|
||||
|
||||
test(`LinuxTerminalService - uses default terminal when configuration.terminal.external.linuxExec is undefined`, done => {
|
||||
LinuxExternalTerminalService.getDefaultTerminalLinuxReady().then(defaultTerminalLinux => {
|
||||
let testCwd = 'path/to/workspace';
|
||||
let mockSpawner = {
|
||||
spawn: (command: any, args: any, opts: any) => {
|
||||
// assert
|
||||
equal(command, defaultTerminalLinux, 'terminal should equal expected');
|
||||
done();
|
||||
return {
|
||||
on: (evt: any) => evt
|
||||
};
|
||||
}
|
||||
};
|
||||
mockConfig.terminal.external.linuxExec = undefined;
|
||||
let testService = new LinuxExternalTerminalService();
|
||||
(<any>testService).spawnTerminal(
|
||||
mockSpawner,
|
||||
mockConfig,
|
||||
testCwd,
|
||||
mockOnExit,
|
||||
mockOnError
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services';
|
||||
import { IExternalTerminalService } from 'vs/platform/externalTerminal/common/externalTerminal';
|
||||
|
||||
export const IExternalTerminalMainService = createDecorator<IExternalTerminalMainService>('externalTerminal');
|
||||
|
||||
export interface IExternalTerminalMainService extends IExternalTerminalService {
|
||||
readonly _serviceBrand: undefined;
|
||||
}
|
||||
|
||||
registerMainProcessRemoteService(IExternalTerminalMainService, 'externalTerminal', { supportsDelayedInstantiation: true });
|
||||
@@ -0,0 +1,336 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as cp from 'child_process';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as processes from 'vs/base/node/processes';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import * as env from 'vs/base/common/platform';
|
||||
import { IExternalTerminalSettings, DEFAULT_TERMINAL_OSX, ITerminalForPlatform, IExternalTerminalMainService } from 'vs/platform/externalTerminal/common/externalTerminal';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { ITerminalEnvironment } from 'vs/platform/terminal/common/terminal';
|
||||
import { sanitizeProcessEnvironment } from 'vs/base/common/processes';
|
||||
|
||||
const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console");
|
||||
|
||||
abstract class ExternalTerminalService {
|
||||
public _serviceBrand: undefined;
|
||||
|
||||
async getDefaultTerminalForPlatforms(): Promise<ITerminalForPlatform> {
|
||||
const linuxTerminal = await LinuxExternalTerminalService.getDefaultTerminalLinuxReady();
|
||||
return { windows: WindowsExternalTerminalService.getDefaultTerminalWindows(), linux: linuxTerminal, osx: 'xterm' };
|
||||
}
|
||||
}
|
||||
|
||||
export class WindowsExternalTerminalService extends ExternalTerminalService implements IExternalTerminalMainService {
|
||||
private static readonly CMD = 'cmd.exe';
|
||||
private static _DEFAULT_TERMINAL_WINDOWS: string;
|
||||
|
||||
public openTerminal(configuration: IExternalTerminalSettings, cwd?: string): Promise<void> {
|
||||
return this.spawnTerminal(cp, configuration, processes.getWindowsShell(), cwd);
|
||||
}
|
||||
|
||||
public spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalSettings, command: string, cwd?: string): Promise<void> {
|
||||
const exec = configuration.windowsExec || WindowsExternalTerminalService.getDefaultTerminalWindows();
|
||||
|
||||
// Make the drive letter uppercase on Windows (see #9448)
|
||||
if (cwd && cwd[1] === ':') {
|
||||
cwd = cwd[0].toUpperCase() + cwd.substr(1);
|
||||
}
|
||||
|
||||
// cmder ignores the environment cwd and instead opts to always open in %USERPROFILE%
|
||||
// unless otherwise specified
|
||||
const basename = path.basename(exec).toLowerCase();
|
||||
if (basename === 'cmder' || basename === 'cmder.exe') {
|
||||
spawner.spawn(exec, cwd ? [cwd] : undefined);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
const cmdArgs = ['/c', 'start', '/wait'];
|
||||
if (exec.indexOf(' ') >= 0) {
|
||||
// The "" argument is the window title. Without this, exec doesn't work when the path
|
||||
// contains spaces
|
||||
cmdArgs.push('""');
|
||||
}
|
||||
cmdArgs.push(exec);
|
||||
// Add starting directory parameter for Windows Terminal (see #90734)
|
||||
if (basename === 'wt' || basename === 'wt.exe') {
|
||||
cmdArgs.push('-d .');
|
||||
}
|
||||
|
||||
return new Promise<void>((c, e) => {
|
||||
const env = getSanitizedEnvironment(process);
|
||||
const child = spawner.spawn(command, cmdArgs, { cwd, env });
|
||||
child.on('error', e);
|
||||
child.on('exit', () => c());
|
||||
});
|
||||
}
|
||||
|
||||
public runInTerminal(title: string, dir: string, args: string[], envVars: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise<number | undefined> {
|
||||
const exec = 'windowsExec' in settings && settings.windowsExec ? settings.windowsExec : WindowsExternalTerminalService.getDefaultTerminalWindows();
|
||||
|
||||
return new Promise<number | undefined>((resolve, reject) => {
|
||||
|
||||
const title = `"${dir} - ${TERMINAL_TITLE}"`;
|
||||
const command = `""${args.join('" "')}" & pause"`; // use '|' to only pause on non-zero exit code
|
||||
|
||||
const cmdArgs = [
|
||||
'/c', 'start', title, '/wait', exec, '/c', command
|
||||
];
|
||||
|
||||
// merge environment variables into a copy of the process.env
|
||||
const env = Object.assign({}, getSanitizedEnvironment(process), envVars);
|
||||
|
||||
// delete environment variables that have a null value
|
||||
Object.keys(env).filter(v => env[v] === null).forEach(key => delete env[key]);
|
||||
|
||||
const options: any = {
|
||||
cwd: dir,
|
||||
env: env,
|
||||
windowsVerbatimArguments: true
|
||||
};
|
||||
|
||||
const cmd = cp.spawn(WindowsExternalTerminalService.CMD, cmdArgs, options);
|
||||
cmd.on('error', err => {
|
||||
reject(improveError(err));
|
||||
});
|
||||
|
||||
resolve(undefined);
|
||||
});
|
||||
}
|
||||
|
||||
public static getDefaultTerminalWindows(): string {
|
||||
if (!WindowsExternalTerminalService._DEFAULT_TERMINAL_WINDOWS) {
|
||||
const isWoW64 = !!process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
|
||||
WindowsExternalTerminalService._DEFAULT_TERMINAL_WINDOWS = `${process.env.windir ? process.env.windir : 'C:\\Windows'}\\${isWoW64 ? 'Sysnative' : 'System32'}\\cmd.exe`;
|
||||
}
|
||||
return WindowsExternalTerminalService._DEFAULT_TERMINAL_WINDOWS;
|
||||
}
|
||||
}
|
||||
|
||||
export class MacExternalTerminalService extends ExternalTerminalService implements IExternalTerminalMainService {
|
||||
private static readonly OSASCRIPT = '/usr/bin/osascript'; // osascript is the AppleScript interpreter on OS X
|
||||
|
||||
public openTerminal(configuration: IExternalTerminalSettings, cwd?: string): Promise<void> {
|
||||
return this.spawnTerminal(cp, configuration, cwd);
|
||||
}
|
||||
|
||||
public runInTerminal(title: string, dir: string, args: string[], envVars: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise<number | undefined> {
|
||||
|
||||
const terminalApp = settings.osxExec || DEFAULT_TERMINAL_OSX;
|
||||
|
||||
return new Promise<number | undefined>((resolve, reject) => {
|
||||
|
||||
if (terminalApp === DEFAULT_TERMINAL_OSX || terminalApp === 'iTerm.app') {
|
||||
|
||||
// On OS X we launch an AppleScript that creates (or reuses) a Terminal window
|
||||
// and then launches the program inside that window.
|
||||
|
||||
const script = terminalApp === DEFAULT_TERMINAL_OSX ? 'TerminalHelper' : 'iTermHelper';
|
||||
const scriptpath = FileAccess.asFileUri(`vs/workbench/contrib/externalTerminal/node/${script}.scpt`, require).fsPath;
|
||||
|
||||
const osaArgs = [
|
||||
scriptpath,
|
||||
'-t', title || TERMINAL_TITLE,
|
||||
'-w', dir,
|
||||
];
|
||||
|
||||
for (let a of args) {
|
||||
osaArgs.push('-a');
|
||||
osaArgs.push(a);
|
||||
}
|
||||
|
||||
if (envVars) {
|
||||
for (let key in envVars) {
|
||||
const value = envVars[key];
|
||||
if (value === null) {
|
||||
osaArgs.push('-u');
|
||||
osaArgs.push(key);
|
||||
} else {
|
||||
osaArgs.push('-e');
|
||||
osaArgs.push(`${key}=${value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let stderr = '';
|
||||
const osa = cp.spawn(MacExternalTerminalService.OSASCRIPT, osaArgs);
|
||||
osa.on('error', err => {
|
||||
reject(improveError(err));
|
||||
});
|
||||
osa.stderr.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
osa.on('exit', (code: number) => {
|
||||
if (code === 0) { // OK
|
||||
resolve(undefined);
|
||||
} else {
|
||||
if (stderr) {
|
||||
const lines = stderr.split('\n', 1);
|
||||
reject(new Error(lines[0]));
|
||||
} else {
|
||||
reject(new Error(nls.localize('mac.terminal.script.failed', "Script '{0}' failed with exit code {1}", script, code)));
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject(new Error(nls.localize('mac.terminal.type.not.supported', "'{0}' not supported", terminalApp)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalSettings, cwd?: string): Promise<void> {
|
||||
const terminalApp = configuration.osxExec || DEFAULT_TERMINAL_OSX;
|
||||
|
||||
return new Promise<void>((c, e) => {
|
||||
const args = ['-a', terminalApp];
|
||||
if (cwd) {
|
||||
args.push(cwd);
|
||||
}
|
||||
const child = spawner.spawn('/usr/bin/open', args);
|
||||
child.on('error', e);
|
||||
child.on('exit', () => c());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class LinuxExternalTerminalService extends ExternalTerminalService implements IExternalTerminalMainService {
|
||||
|
||||
private static readonly WAIT_MESSAGE = nls.localize('press.any.key', "Press any key to continue...");
|
||||
|
||||
public openTerminal(configuration: IExternalTerminalSettings, cwd?: string): Promise<void> {
|
||||
return this.spawnTerminal(cp, configuration, cwd);
|
||||
}
|
||||
|
||||
public runInTerminal(title: string, dir: string, args: string[], envVars: ITerminalEnvironment, settings: IExternalTerminalSettings): Promise<number | undefined> {
|
||||
|
||||
const execPromise = settings.linuxExec ? Promise.resolve(settings.linuxExec) : LinuxExternalTerminalService.getDefaultTerminalLinuxReady();
|
||||
|
||||
return new Promise<number | undefined>((resolve, reject) => {
|
||||
|
||||
let termArgs: string[] = [];
|
||||
//termArgs.push('--title');
|
||||
//termArgs.push(`"${TERMINAL_TITLE}"`);
|
||||
execPromise.then(exec => {
|
||||
if (exec.indexOf('gnome-terminal') >= 0) {
|
||||
termArgs.push('-x');
|
||||
} else {
|
||||
termArgs.push('-e');
|
||||
}
|
||||
termArgs.push('bash');
|
||||
termArgs.push('-c');
|
||||
|
||||
const bashCommand = `${quote(args)}; echo; read -p "${LinuxExternalTerminalService.WAIT_MESSAGE}" -n1;`;
|
||||
termArgs.push(`''${bashCommand}''`); // wrapping argument in two sets of ' because node is so "friendly" that it removes one set...
|
||||
|
||||
// merge environment variables into a copy of the process.env
|
||||
const env = Object.assign({}, process.env, envVars);
|
||||
|
||||
// delete environment variables that have a null value
|
||||
Object.keys(env).filter(v => env[v] === null).forEach(key => delete env[key]);
|
||||
|
||||
const options: any = {
|
||||
cwd: dir,
|
||||
env: env
|
||||
};
|
||||
|
||||
let stderr = '';
|
||||
const cmd = cp.spawn(exec, termArgs, options);
|
||||
cmd.on('error', err => {
|
||||
reject(improveError(err));
|
||||
});
|
||||
cmd.stderr.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
cmd.on('exit', (code: number) => {
|
||||
if (code === 0) { // OK
|
||||
resolve(undefined);
|
||||
} else {
|
||||
if (stderr) {
|
||||
const lines = stderr.split('\n', 1);
|
||||
reject(new Error(lines[0]));
|
||||
} else {
|
||||
reject(new Error(nls.localize('linux.term.failed', "'{0}' failed with exit code {1}", exec, code)));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static _DEFAULT_TERMINAL_LINUX_READY: Promise<string>;
|
||||
|
||||
public static async getDefaultTerminalLinuxReady(): Promise<string> {
|
||||
if (!LinuxExternalTerminalService._DEFAULT_TERMINAL_LINUX_READY) {
|
||||
LinuxExternalTerminalService._DEFAULT_TERMINAL_LINUX_READY = new Promise(async r => {
|
||||
if (env.isLinux) {
|
||||
const isDebian = await pfs.exists('/etc/debian_version');
|
||||
if (isDebian) {
|
||||
r('x-terminal-emulator');
|
||||
} else if (process.env.DESKTOP_SESSION === 'gnome' || process.env.DESKTOP_SESSION === 'gnome-classic') {
|
||||
r('gnome-terminal');
|
||||
} else if (process.env.DESKTOP_SESSION === 'kde-plasma') {
|
||||
r('konsole');
|
||||
} else if (process.env.COLORTERM) {
|
||||
r(process.env.COLORTERM);
|
||||
} else if (process.env.TERM) {
|
||||
r(process.env.TERM);
|
||||
} else {
|
||||
r('xterm');
|
||||
}
|
||||
} else {
|
||||
r('xterm');
|
||||
}
|
||||
});
|
||||
}
|
||||
return LinuxExternalTerminalService._DEFAULT_TERMINAL_LINUX_READY;
|
||||
}
|
||||
|
||||
spawnTerminal(spawner: typeof cp, configuration: IExternalTerminalSettings, cwd?: string): Promise<void> {
|
||||
const execPromise = configuration.linuxExec ? Promise.resolve(configuration.linuxExec) : LinuxExternalTerminalService.getDefaultTerminalLinuxReady();
|
||||
|
||||
return new Promise<void>((c, e) => {
|
||||
execPromise.then(exec => {
|
||||
const env = getSanitizedEnvironment(process);
|
||||
const child = spawner.spawn(exec, [], { cwd, env });
|
||||
child.on('error', e);
|
||||
child.on('exit', () => c());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getSanitizedEnvironment(process: NodeJS.Process) {
|
||||
const env = process.env;
|
||||
sanitizeProcessEnvironment(env);
|
||||
return env;
|
||||
}
|
||||
|
||||
/**
|
||||
* tries to turn OS errors into more meaningful error messages
|
||||
*/
|
||||
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']));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quote args if necessary and combine into a space separated string.
|
||||
*/
|
||||
function quote(args: string[]): string {
|
||||
let r = '';
|
||||
for (let a of args) {
|
||||
if (a.indexOf(' ') >= 0) {
|
||||
r += '"' + a + '"';
|
||||
} else {
|
||||
r += a;
|
||||
}
|
||||
r += ' ';
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@@ -23,7 +23,7 @@ const ERR_FILE_NOT_DIR = createFileSystemProviderError(localize('fileNotDirector
|
||||
const ERR_DIR_NOT_EMPTY = createFileSystemProviderError(localize('dirIsNotEmpty', "Directory is not empty"), FileSystemProviderErrorCode.Unknown);
|
||||
|
||||
// Arbitrary Internal Errors (should never be thrown in production)
|
||||
const ERR_UNKNOWN_INTERNAL = (message: string) => createFileSystemProviderError(localize('internal', "Internal error occured in IndexedDB File System Provider. ({0})", message), FileSystemProviderErrorCode.Unknown);
|
||||
const ERR_UNKNOWN_INTERNAL = (message: string) => createFileSystemProviderError(localize('internal', "Internal error occurred in IndexedDB File System Provider. ({0})", message), FileSystemProviderErrorCode.Unknown);
|
||||
|
||||
export class IndexedDB {
|
||||
|
||||
@@ -99,7 +99,7 @@ class IndexedDBFileSystemNode {
|
||||
}
|
||||
|
||||
|
||||
read(path: string) {
|
||||
read(path: string): IndexedDBFileSystemEntry | undefined {
|
||||
return this.doRead(path.split('/').filter(p => p.length));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { localize } from 'vs/nls';
|
||||
import { mark } from 'vs/base/common/performance';
|
||||
import { Disposable, IDisposable, toDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED, hasFileReadStreamCapability, IFileSystemProviderWithFileReadStreamCapability, ensureFileSystemProviderError, IFileSystemProviderCapabilitiesChangeEvent, IReadFileStreamOptions, FileDeleteOptions } from 'vs/platform/files/common/files';
|
||||
import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED, hasFileReadStreamCapability, IFileSystemProviderWithFileReadStreamCapability, ensureFileSystemProviderError, IFileSystemProviderCapabilitiesChangeEvent, IReadFileStreamOptions, FileDeleteOptions, FilePermission, NotModifiedSinceFileOperationError } from 'vs/platform/files/common/files';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IExtUri, extUri, extUriIgnorePathCase, isAbsolutePath } from 'vs/base/common/resources';
|
||||
@@ -234,6 +234,7 @@ export class FileService extends Disposable implements IFileService {
|
||||
mtime: stat.mtime,
|
||||
ctime: stat.ctime,
|
||||
size: stat.size,
|
||||
readonly: Boolean((stat.permissions ?? 0) & FilePermission.Readonly) || Boolean(provider.capabilities & FileSystemProviderCapabilities.Readonly),
|
||||
etag: etag({ mtime: stat.mtime, size: stat.size })
|
||||
};
|
||||
|
||||
@@ -401,6 +402,9 @@ export class FileService extends Disposable implements IFileService {
|
||||
throw new FileOperationError(localize('fileIsDirectoryWriteError', "Unable to write file '{0}' that is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options);
|
||||
}
|
||||
|
||||
// File cannot be readonly
|
||||
this.throwIfFileIsReadonly(resource, stat);
|
||||
|
||||
// Dirty write prevention: if the file on disk has been changed and does not match our expected
|
||||
// mtime and etag, we bail out to prevent dirty writing.
|
||||
//
|
||||
@@ -526,7 +530,14 @@ export class FileService extends Disposable implements IFileService {
|
||||
await consumeStream(fileStream);
|
||||
}
|
||||
|
||||
throw new FileOperationError(localize('err.read', "Unable to read file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options);
|
||||
// Re-throw errors as file operation errors but preserve
|
||||
// specific errors (such as not modified since)
|
||||
const message = localize('err.read', "Unable to read file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString());
|
||||
if (error instanceof NotModifiedSinceFileOperationError) {
|
||||
throw new NotModifiedSinceFileOperationError(message, error.stat, options);
|
||||
} else {
|
||||
throw new FileOperationError(message, toFileOperationResult(error), options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,7 +605,7 @@ export class FileService extends Disposable implements IFileService {
|
||||
|
||||
// Throw if file not modified since (unless disabled)
|
||||
if (options && typeof options.etag === 'string' && options.etag !== ETAG_DISABLED && options.etag === stat.etag) {
|
||||
throw new FileOperationError(localize('fileNotModifiedError', "File not modified since"), FileOperationResult.FILE_NOT_MODIFIED_SINCE, options);
|
||||
throw new NotModifiedSinceFileOperationError(localize('fileNotModifiedError', "File not modified since"), stat, options);
|
||||
}
|
||||
|
||||
// Throw if file is too large to load
|
||||
@@ -912,14 +923,22 @@ export class FileService extends Disposable implements IFileService {
|
||||
}
|
||||
|
||||
// Validate delete
|
||||
const exists = await this.exists(resource);
|
||||
if (!exists) {
|
||||
let stat: IStat | undefined = undefined;
|
||||
try {
|
||||
stat = await provider.stat(resource);
|
||||
} catch (error) {
|
||||
// Handled later
|
||||
}
|
||||
|
||||
if (stat) {
|
||||
this.throwIfFileIsReadonly(resource, stat);
|
||||
} else {
|
||||
throw new FileOperationError(localize('deleteFailedNotFound', "Unable to delete non-existing file '{0}'", this.resourceForError(resource)), FileOperationResult.FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Validate recursive
|
||||
const recursive = !!options?.recursive;
|
||||
if (!recursive && exists) {
|
||||
if (!recursive) {
|
||||
const stat = await this.resolve(resource);
|
||||
if (stat.isDirectory && Array.isArray(stat.children) && stat.children.length > 0) {
|
||||
throw new Error(localize('deleteFailedNonEmptyFolder', "Unable to delete non-empty folder '{0}'.", this.resourceForError(resource)));
|
||||
@@ -1227,6 +1246,12 @@ export class FileService extends Disposable implements IFileService {
|
||||
return provider;
|
||||
}
|
||||
|
||||
private throwIfFileIsReadonly(resource: URI, stat: IStat): void {
|
||||
if ((stat.permissions ?? 0) & FilePermission.Readonly) {
|
||||
throw new FileOperationError(localize('err.readonly', "Unable to modify readonly file '{0}'", this.resourceForError(resource)), FileOperationResult.FILE_PERMISSION_DENIED);
|
||||
}
|
||||
}
|
||||
|
||||
private resourceForError(resource: URI): string {
|
||||
if (resource.scheme === Schemas.file) {
|
||||
return resource.fsPath;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { startsWithIgnoreCase } from 'vs/base/common/strings';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isNumber, isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { isNumber } from 'vs/base/common/types';
|
||||
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';
|
||||
import { ReadableStreamEvents } from 'vs/base/common/stream';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
@@ -315,6 +315,14 @@ export enum FileType {
|
||||
SymbolicLink = 64
|
||||
}
|
||||
|
||||
export enum FilePermission {
|
||||
|
||||
/**
|
||||
* File is readonly.
|
||||
*/
|
||||
Readonly = 1
|
||||
}
|
||||
|
||||
export interface IStat {
|
||||
|
||||
/**
|
||||
@@ -335,7 +343,12 @@ export interface IStat {
|
||||
/**
|
||||
* The size of the file in bytes.
|
||||
*/
|
||||
size: number;
|
||||
readonly size: number;
|
||||
|
||||
/**
|
||||
* The file permissions.
|
||||
*/
|
||||
readonly permissions?: FilePermission;
|
||||
}
|
||||
|
||||
export interface IWatchOptions {
|
||||
@@ -400,7 +413,7 @@ export interface IFileSystemProvider {
|
||||
readonly capabilities: FileSystemProviderCapabilities;
|
||||
readonly onDidChangeCapabilities: Event<void>;
|
||||
|
||||
readonly onDidErrorOccur?: Event<string>; // TODO@bpasero remove once file watchers are solid
|
||||
readonly onDidErrorOccur?: Event<string>;
|
||||
|
||||
readonly onDidChangeFile: Event<readonly IFileChange[]>;
|
||||
watch(resource: URI, opts: IWatchOptions): IDisposable;
|
||||
@@ -475,7 +488,7 @@ export enum FileSystemProviderErrorCode {
|
||||
|
||||
export class FileSystemProviderError extends Error {
|
||||
|
||||
constructor(message: string, public readonly code: FileSystemProviderErrorCode) {
|
||||
constructor(message: string, readonly code: FileSystemProviderErrorCode) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -592,7 +605,7 @@ export class FileOperationEvent {
|
||||
|
||||
constructor(resource: URI, operation: FileOperation.DELETE);
|
||||
constructor(resource: URI, operation: FileOperation.CREATE | FileOperation.MOVE | FileOperation.COPY, target: IFileStatWithMetadata);
|
||||
constructor(public readonly resource: URI, public readonly operation: FileOperation, public readonly target?: IFileStatWithMetadata) { }
|
||||
constructor(readonly resource: URI, readonly operation: FileOperation, readonly target?: IFileStatWithMetadata) { }
|
||||
|
||||
isOperation(operation: FileOperation.DELETE): boolean;
|
||||
isOperation(operation: FileOperation.MOVE | FileOperation.COPY | FileOperation.CREATE): this is { readonly target: IFileStatWithMetadata };
|
||||
@@ -868,6 +881,11 @@ interface IBaseStat {
|
||||
* it is optional.
|
||||
*/
|
||||
readonly etag?: string;
|
||||
|
||||
/**
|
||||
* The file is read-only.
|
||||
*/
|
||||
readonly readonly?: boolean;
|
||||
}
|
||||
|
||||
export interface IBaseStatWithMetadata extends Required<IBaseStat> { }
|
||||
@@ -906,6 +924,7 @@ export interface IFileStatWithMetadata extends IFileStat, IBaseStatWithMetadata
|
||||
readonly ctime: number;
|
||||
readonly etag: string;
|
||||
readonly size: number;
|
||||
readonly readonly: boolean;
|
||||
readonly children?: IFileStatWithMetadata[];
|
||||
}
|
||||
|
||||
@@ -956,7 +975,7 @@ export interface IReadFileOptions extends IBaseReadFileOptions {
|
||||
*
|
||||
* Typically you should not need to use this flag but if
|
||||
* for example you are quickly reading a file right after
|
||||
* a file event occured and the file changes a lot, there
|
||||
* a file event occurred and the file changes a lot, there
|
||||
* is a chance that a read returns an empty or partial file
|
||||
* because a pending write has not finished yet.
|
||||
*
|
||||
@@ -1019,12 +1038,23 @@ export interface ICreateFileOptions {
|
||||
}
|
||||
|
||||
export class FileOperationError extends Error {
|
||||
constructor(message: string, public fileOperationResult: FileOperationResult, public options?: IReadFileOptions & IWriteFileOptions & ICreateFileOptions) {
|
||||
constructor(
|
||||
message: string,
|
||||
readonly fileOperationResult: FileOperationResult,
|
||||
readonly options?: IReadFileOptions & IWriteFileOptions & ICreateFileOptions
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
static isFileOperationError(obj: unknown): obj is FileOperationError {
|
||||
return obj instanceof Error && !isUndefinedOrNull((obj as FileOperationError).fileOperationResult);
|
||||
export class NotModifiedSinceFileOperationError extends FileOperationError {
|
||||
|
||||
constructor(
|
||||
message: string,
|
||||
readonly stat: IFileStatWithMetadata,
|
||||
options?: IReadFileOptions
|
||||
) {
|
||||
super(message, FileOperationResult.FILE_NOT_MODIFIED_SINCE, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { DiskFileSystemProvider as NodeDiskFileSystemProvider, IDiskFileSystemProviderOptions } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
import { FileDeleteOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { localize } from 'vs/nls';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
|
||||
@@ -34,8 +34,11 @@ export class DiskFileSystemProvider extends NodeDiskFileSystemProvider {
|
||||
return super.doDelete(filePath, opts);
|
||||
}
|
||||
|
||||
const result = await this.nativeHostService.moveItemToTrash(filePath);
|
||||
if (!result) {
|
||||
try {
|
||||
await this.nativeHostService.moveItemToTrash(filePath);
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
|
||||
throw new Error(isWindows ? localize('binFailed', "Failed to move '{0}' to the recycle bin", basename(filePath)) : localize('trashFailed', "Failed to move '{0}' to the trash", basename(filePath)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { open, close, read, write, fdatasync, Stats, promises } from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import { Stats } from 'fs';
|
||||
import { IDisposable, Disposable, toDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, FileReadStreamOptions, IFileSystemProviderWithFileFolderCopyCapability, isFileOpenForWriteOptions } from 'vs/platform/files/common/files';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { isLinux, isWindows } from 'vs/base/common/platform';
|
||||
import { SymlinkSupport, move, copy, rimraf, RimRafMode, exists, readdir, IDirent } from 'vs/base/node/pfs';
|
||||
import { SymlinkSupport, move, copy, rimraf, RimRafMode, exists, readdir, IDirent, Promises } from 'vs/base/node/pfs';
|
||||
import { normalize, basename, dirname } from 'vs/base/common/path';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { isEqual } from 'vs/base/common/extpath';
|
||||
@@ -47,7 +46,7 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
private readonly BUFFER_SIZE = this.options?.bufferSize || 64 * 1024;
|
||||
|
||||
constructor(
|
||||
private readonly logService: ILogService,
|
||||
protected readonly logService: ILogService,
|
||||
private readonly options?: IDiskFileSystemProviderOptions
|
||||
) {
|
||||
super();
|
||||
@@ -152,7 +151,7 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
try {
|
||||
const filePath = this.toFilePath(resource);
|
||||
|
||||
return await promises.readFile(filePath);
|
||||
return await Promises.readFile(filePath);
|
||||
} catch (error) {
|
||||
throw this.toFileSystemProviderError(error);
|
||||
}
|
||||
@@ -216,7 +215,7 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
try {
|
||||
const { stat } = await SymlinkSupport.stat(filePath);
|
||||
if (!(stat.mode & 0o200 /* File mode indicating writable by owner */)) {
|
||||
await promises.chmod(filePath, stat.mode | 0o200);
|
||||
await Promises.chmod(filePath, stat.mode | 0o200);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.trace(error); // ignore any errors here and try to just write
|
||||
@@ -232,7 +231,7 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
// by first truncating the file and then writing with r+ flag. This helps to save hidden files on Windows
|
||||
// (see https://github.com/microsoft/vscode/issues/931) and prevent removing alternate data streams
|
||||
// (see https://github.com/microsoft/vscode/issues/6363)
|
||||
await promises.truncate(filePath, 0);
|
||||
await Promises.truncate(filePath, 0);
|
||||
|
||||
// After a successful truncate() the flag can be set to 'r+' which will not truncate.
|
||||
flags = 'r+';
|
||||
@@ -256,7 +255,7 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
flags = 'r';
|
||||
}
|
||||
|
||||
const handle = await promisify(open)(filePath, flags);
|
||||
const handle = await Promises.open(filePath, flags);
|
||||
|
||||
// remember this handle to track file position of the handle
|
||||
// we init the position to 0 since the file descriptor was
|
||||
@@ -290,7 +289,7 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
// to flush the contents to disk if possible.
|
||||
if (this.writeHandles.delete(fd) && this.canFlush) {
|
||||
try {
|
||||
await promisify(fdatasync)(fd);
|
||||
await Promises.fdatasync(fd);
|
||||
} catch (error) {
|
||||
// In some exotic setups it is well possible that node fails to sync
|
||||
// In that case we disable flushing and log the error to our logger
|
||||
@@ -299,7 +298,7 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
}
|
||||
}
|
||||
|
||||
return await promisify(close)(fd);
|
||||
return await Promises.close(fd);
|
||||
} catch (error) {
|
||||
throw this.toFileSystemProviderError(error);
|
||||
}
|
||||
@@ -310,7 +309,7 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
|
||||
let bytesRead: number | null = null;
|
||||
try {
|
||||
const result = await promisify(read)(fd, data, offset, length, normalizedPos);
|
||||
const result = await Promises.read(fd, data, offset, length, normalizedPos);
|
||||
|
||||
if (typeof result === 'number') {
|
||||
bytesRead = result; // node.d.ts fail
|
||||
@@ -396,7 +395,7 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
|
||||
let bytesWritten: number | null = null;
|
||||
try {
|
||||
const result = await promisify(write)(fd, data, offset, length, normalizedPos);
|
||||
const result = await Promises.write(fd, data, offset, length, normalizedPos);
|
||||
|
||||
if (typeof result === 'number') {
|
||||
bytesWritten = result; // node.d.ts fail
|
||||
@@ -418,7 +417,7 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
|
||||
async mkdir(resource: URI): Promise<void> {
|
||||
try {
|
||||
await promises.mkdir(this.toFilePath(resource));
|
||||
await Promises.mkdir(this.toFilePath(resource));
|
||||
} catch (error) {
|
||||
throw this.toFileSystemProviderError(error);
|
||||
}
|
||||
@@ -438,7 +437,7 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
if (opts.recursive) {
|
||||
await rimraf(filePath, RimRafMode.MOVE);
|
||||
} else {
|
||||
await promises.unlink(filePath);
|
||||
await Promises.unlink(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,7 +541,7 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
return this.watchRecursive(resource, opts.excludes);
|
||||
}
|
||||
|
||||
return this.watchNonRecursive(resource); // TODO@bpasero ideally the same watcher can be used in both cases
|
||||
return this.watchNonRecursive(resource);
|
||||
}
|
||||
|
||||
private watchRecursive(resource: URI, excludes: string[]): IDisposable {
|
||||
|
||||
@@ -10,10 +10,10 @@ import { Schemas } from 'vs/base/common/network';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
import { flakySuite, getRandomTestPath, getPathFromAmdModule } from 'vs/base/test/node/testUtils';
|
||||
import { join, basename, dirname, posix } from 'vs/base/common/path';
|
||||
import { copy, rimraf, rimrafSync } from 'vs/base/node/pfs';
|
||||
import { copy, Promises, rimraf, rimrafSync } from 'vs/base/node/pfs';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync, createReadStream, promises } from 'fs';
|
||||
import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent, FileOperationError, etag, IStat, IFileStatWithMetadata, IReadFileOptions } from 'vs/platform/files/common/files';
|
||||
import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync, createReadStream } from 'fs';
|
||||
import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent, FileOperationError, etag, IStat, IFileStatWithMetadata, IReadFileOptions, FilePermission, NotModifiedSinceFileOperationError } from 'vs/platform/files/common/files';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { isLinux, isWindows } from 'vs/base/common/platform';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
@@ -56,6 +56,7 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider {
|
||||
|
||||
private invalidStatSize: boolean = false;
|
||||
private smallStatSize: boolean = false;
|
||||
private readonly: boolean = false;
|
||||
|
||||
private _testCapabilities!: FileSystemProviderCapabilities;
|
||||
override get capabilities(): FileSystemProviderCapabilities {
|
||||
@@ -88,13 +89,19 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider {
|
||||
this.smallStatSize = enabled;
|
||||
}
|
||||
|
||||
setReadonly(readonly: boolean): void {
|
||||
this.readonly = readonly;
|
||||
}
|
||||
|
||||
override async stat(resource: URI): Promise<IStat> {
|
||||
const res = await super.stat(resource);
|
||||
|
||||
if (this.invalidStatSize) {
|
||||
res.size = String(res.size) as any; // for https://github.com/microsoft/vscode/issues/72909
|
||||
(res as any).size = String(res.size) as any; // for https://github.com/microsoft/vscode/issues/72909
|
||||
} else if (this.smallStatSize) {
|
||||
res.size = 1;
|
||||
(res as any).size = 1;
|
||||
} else if (this.readonly) {
|
||||
(res as any).permissions = FilePermission.Readonly;
|
||||
}
|
||||
|
||||
return res;
|
||||
@@ -213,6 +220,7 @@ flakySuite('Disk File Service', function () {
|
||||
assert.strictEqual(resolved.name, 'index.html');
|
||||
assert.strictEqual(resolved.isFile, true);
|
||||
assert.strictEqual(resolved.isDirectory, false);
|
||||
assert.strictEqual(resolved.readonly, false);
|
||||
assert.strictEqual(resolved.isSymbolicLink, false);
|
||||
assert.strictEqual(resolved.resource.toString(), resource.toString());
|
||||
assert.strictEqual(resolved.children, undefined);
|
||||
@@ -233,6 +241,7 @@ flakySuite('Disk File Service', function () {
|
||||
assert.ok(result.children);
|
||||
assert.ok(result.children!.length > 0);
|
||||
assert.ok(result!.isDirectory);
|
||||
assert.strictEqual(result.readonly, false);
|
||||
assert.ok(result.mtime! > 0);
|
||||
assert.ok(result.ctime! > 0);
|
||||
assert.strictEqual(result.children!.length, testsElements.length);
|
||||
@@ -408,7 +417,7 @@ flakySuite('Disk File Service', function () {
|
||||
|
||||
test('resolve - folder symbolic link', async () => {
|
||||
const link = URI.file(join(testDir, 'deep-link'));
|
||||
await promises.symlink(join(testDir, 'deep'), link.fsPath, 'junction');
|
||||
await Promises.symlink(join(testDir, 'deep'), link.fsPath, 'junction');
|
||||
|
||||
const resolved = await service.resolve(link);
|
||||
assert.strictEqual(resolved.children!.length, 4);
|
||||
@@ -418,7 +427,7 @@ flakySuite('Disk File Service', function () {
|
||||
|
||||
(isWindows ? test.skip /* windows: cannot create file symbolic link without elevated context */ : test)('resolve - file symbolic link', async () => {
|
||||
const link = URI.file(join(testDir, 'lorem.txt-linked'));
|
||||
await promises.symlink(join(testDir, 'lorem.txt'), link.fsPath);
|
||||
await Promises.symlink(join(testDir, 'lorem.txt'), link.fsPath);
|
||||
|
||||
const resolved = await service.resolve(link);
|
||||
assert.strictEqual(resolved.isDirectory, false);
|
||||
@@ -426,7 +435,7 @@ flakySuite('Disk File Service', function () {
|
||||
});
|
||||
|
||||
test('resolve - symbolic link pointing to non-existing file does not break', async () => {
|
||||
await promises.symlink(join(testDir, 'foo'), join(testDir, 'bar'), 'junction');
|
||||
await Promises.symlink(join(testDir, 'foo'), join(testDir, 'bar'), 'junction');
|
||||
|
||||
const resolved = await service.resolve(URI.file(testDir));
|
||||
assert.strictEqual(resolved.isDirectory, true);
|
||||
@@ -477,7 +486,7 @@ flakySuite('Disk File Service', function () {
|
||||
(isWindows ? test.skip /* windows: cannot create file symbolic link without elevated context */ : test)('deleteFile - symbolic link (exists)', async () => {
|
||||
const target = URI.file(join(testDir, 'lorem.txt'));
|
||||
const link = URI.file(join(testDir, 'lorem.txt-linked'));
|
||||
await promises.symlink(target.fsPath, link.fsPath);
|
||||
await Promises.symlink(target.fsPath, link.fsPath);
|
||||
|
||||
const source = await service.resolve(link);
|
||||
|
||||
@@ -499,7 +508,7 @@ flakySuite('Disk File Service', function () {
|
||||
(isWindows ? test.skip /* windows: cannot create file symbolic link without elevated context */ : test)('deleteFile - symbolic link (pointing to non-existing file)', async () => {
|
||||
const target = URI.file(join(testDir, 'foo'));
|
||||
const link = URI.file(join(testDir, 'bar'));
|
||||
await promises.symlink(target.fsPath, link.fsPath);
|
||||
await Promises.symlink(target.fsPath, link.fsPath);
|
||||
|
||||
let event: FileOperationEvent;
|
||||
disposables.add(service.onDidRunOperation(e => event = e));
|
||||
@@ -1482,6 +1491,7 @@ flakySuite('Disk File Service', function () {
|
||||
|
||||
assert.ok(error);
|
||||
assert.strictEqual(error!.fileOperationResult, FileOperationResult.FILE_NOT_MODIFIED_SINCE);
|
||||
assert.ok(error instanceof NotModifiedSinceFileOperationError && error.stat);
|
||||
assert.strictEqual(fileProvider.totalBytesRead, 0);
|
||||
}
|
||||
|
||||
@@ -1592,7 +1602,7 @@ flakySuite('Disk File Service', function () {
|
||||
|
||||
(isWindows ? test.skip /* windows: cannot create file symbolic link without elevated context */ : test)('readFile - dangling symbolic link - https://github.com/microsoft/vscode/issues/116049', async () => {
|
||||
const link = URI.file(join(testDir, 'small.js-link'));
|
||||
await promises.symlink(join(testDir, 'small.js'), link.fsPath);
|
||||
await Promises.symlink(join(testDir, 'small.js'), link.fsPath);
|
||||
|
||||
let error: FileOperationError | undefined = undefined;
|
||||
try {
|
||||
@@ -1928,8 +1938,8 @@ flakySuite('Disk File Service', function () {
|
||||
|
||||
await service.writeFile(lockedFile, VSBuffer.fromString('Locked File'));
|
||||
|
||||
const stats = await promises.stat(lockedFile.fsPath);
|
||||
await promises.chmod(lockedFile.fsPath, stats.mode & ~0o200);
|
||||
const stats = await Promises.stat(lockedFile.fsPath);
|
||||
await Promises.chmod(lockedFile.fsPath, stats.mode & ~0o200);
|
||||
|
||||
let error;
|
||||
const newContent = 'Updates to locked file';
|
||||
@@ -2091,7 +2101,7 @@ flakySuite('Disk File Service', function () {
|
||||
|
||||
(runWatchTests && !isWindows /* windows: cannot create file symbolic link without elevated context */ ? test : test.skip)('watch - file symbolic link', async () => {
|
||||
const toWatch = URI.file(join(testDir, 'lorem.txt-linked'));
|
||||
await promises.symlink(join(testDir, 'lorem.txt'), toWatch.fsPath);
|
||||
await Promises.symlink(join(testDir, 'lorem.txt'), toWatch.fsPath);
|
||||
|
||||
const promise = assertWatch(toWatch, [[FileChangeType.UPDATED, toWatch]]);
|
||||
setTimeout(() => writeFileSync(toWatch.fsPath, 'Changes'), 50);
|
||||
@@ -2219,7 +2229,7 @@ flakySuite('Disk File Service', function () {
|
||||
|
||||
(runWatchTests ? test : test.skip)('watch - folder (non recursive) - symbolic link - change file', async () => {
|
||||
const watchDir = URI.file(join(testDir, 'deep-link'));
|
||||
await promises.symlink(join(testDir, 'deep'), watchDir.fsPath, 'junction');
|
||||
await Promises.symlink(join(testDir, 'deep'), watchDir.fsPath, 'junction');
|
||||
|
||||
const file = URI.file(join(watchDir.fsPath, 'index.html'));
|
||||
writeFileSync(file.fsPath, 'Init');
|
||||
@@ -2390,4 +2400,32 @@ flakySuite('Disk File Service', function () {
|
||||
await fileProvider.close(fdWrite);
|
||||
await fileProvider.close(fdRead);
|
||||
});
|
||||
|
||||
test('readonly - is handled properly for a single resource', async () => {
|
||||
fileProvider.setReadonly(true);
|
||||
|
||||
const resource = URI.file(join(testDir, 'index.html'));
|
||||
|
||||
const resolveResult = await service.resolve(resource);
|
||||
assert.strictEqual(resolveResult.readonly, true);
|
||||
|
||||
const readResult = await service.readFile(resource);
|
||||
assert.strictEqual(readResult.readonly, true);
|
||||
|
||||
let writeFileError: Error | undefined = undefined;
|
||||
try {
|
||||
await service.writeFile(resource, VSBuffer.fromString('Hello Test'));
|
||||
} catch (error) {
|
||||
writeFileError = error;
|
||||
}
|
||||
assert.ok(writeFileError);
|
||||
|
||||
let deleteFileError: Error | undefined = undefined;
|
||||
try {
|
||||
await service.del(resource);
|
||||
} catch (error) {
|
||||
deleteFileError = error;
|
||||
}
|
||||
assert.ok(deleteFileError);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,6 +58,7 @@ export interface IssueReporterData extends WindowData {
|
||||
issueType?: IssueType;
|
||||
extensionId?: string;
|
||||
experiments?: string;
|
||||
restrictedMode: boolean;
|
||||
githubAccessToken: string;
|
||||
readonly issueTitle?: string;
|
||||
readonly issueBody?: string;
|
||||
@@ -70,8 +71,14 @@ export interface ISettingSearchResult {
|
||||
}
|
||||
|
||||
export interface ProcessExplorerStyles extends WindowStyles {
|
||||
hoverBackground?: string;
|
||||
hoverForeground?: string;
|
||||
listHoverBackground?: string;
|
||||
listHoverForeground?: string;
|
||||
listFocusBackground?: string;
|
||||
listFocusForeground?: string;
|
||||
listFocusOutline?: string;
|
||||
listActiveSelectionBackground?: string;
|
||||
listActiveSelectionForeground?: string;
|
||||
listHoverOutline?: string;
|
||||
}
|
||||
|
||||
export interface ProcessExplorerData extends WindowData {
|
||||
|
||||
@@ -25,6 +25,13 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
|
||||
export const IIssueMainService = createDecorator<IIssueMainService>('issueMainService');
|
||||
|
||||
interface IBrowserWindowOptions {
|
||||
backgroundColor: string | undefined;
|
||||
title: string;
|
||||
zoomLevel: number;
|
||||
alwaysOnTop: boolean;
|
||||
}
|
||||
|
||||
export interface IIssueMainService extends ICommonIssueService { }
|
||||
|
||||
export class IssueMainService implements ICommonIssueService {
|
||||
@@ -189,7 +196,12 @@ export class IssueMainService implements ICommonIssueService {
|
||||
const issueReporterWindowConfigUrl = issueReporterDisposables.add(this.protocolMainService.createIPCObjectUrl<IssueReporterWindowConfiguration>());
|
||||
const position = this.getWindowPosition(this.issueReporterParentWindow, 700, 800);
|
||||
|
||||
this.issueReporterWindow = this.createBrowserWindow(position, issueReporterWindowConfigUrl, data.styles.backgroundColor, localize('issueReporter', "Issue Reporter"), data.zoomLevel);
|
||||
this.issueReporterWindow = this.createBrowserWindow(position, issueReporterWindowConfigUrl, {
|
||||
backgroundColor: data.styles.backgroundColor,
|
||||
title: localize('issueReporter', "Issue Reporter"),
|
||||
zoomLevel: data.zoomLevel,
|
||||
alwaysOnTop: false
|
||||
});
|
||||
|
||||
// Store into config object URL
|
||||
issueReporterWindowConfigUrl.update({
|
||||
@@ -239,7 +251,12 @@ export class IssueMainService implements ICommonIssueService {
|
||||
const processExplorerWindowConfigUrl = processExplorerDisposables.add(this.protocolMainService.createIPCObjectUrl<ProcessExplorerWindowConfiguration>());
|
||||
const position = this.getWindowPosition(this.processExplorerParentWindow, 800, 500);
|
||||
|
||||
this.processExplorerWindow = this.createBrowserWindow(position, processExplorerWindowConfigUrl, data.styles.backgroundColor, localize('processExplorer', "Process Explorer"), data.zoomLevel);
|
||||
this.processExplorerWindow = this.createBrowserWindow(position, processExplorerWindowConfigUrl, {
|
||||
backgroundColor: data.styles.backgroundColor,
|
||||
title: localize('processExplorer', "Process Explorer"),
|
||||
zoomLevel: data.zoomLevel,
|
||||
alwaysOnTop: true
|
||||
});
|
||||
|
||||
// Store into config object URL
|
||||
processExplorerWindowConfigUrl.update({
|
||||
@@ -273,7 +290,7 @@ export class IssueMainService implements ICommonIssueService {
|
||||
this.processExplorerWindow?.focus();
|
||||
}
|
||||
|
||||
private createBrowserWindow<T>(position: IWindowState, ipcObjectUrl: IIPCObjectUrl<T>, backgroundColor: string | undefined, title: string, zoomLevel: number): BrowserWindow {
|
||||
private createBrowserWindow<T>(position: IWindowState, ipcObjectUrl: IIPCObjectUrl<T>, options: IBrowserWindowOptions): BrowserWindow {
|
||||
const window = new BrowserWindow({
|
||||
fullscreen: false,
|
||||
skipTaskbar: true,
|
||||
@@ -284,8 +301,8 @@ export class IssueMainService implements ICommonIssueService {
|
||||
minHeight: 200,
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
title,
|
||||
backgroundColor: backgroundColor || IssueMainService.DEFAULT_BACKGROUND_COLOR,
|
||||
title: options.title,
|
||||
backgroundColor: options.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) */],
|
||||
@@ -294,10 +311,11 @@ export class IssueMainService implements ICommonIssueService {
|
||||
enableRemoteModule: false,
|
||||
spellcheck: false,
|
||||
nativeWindowOpen: true,
|
||||
zoomFactor: zoomLevelToZoomFactor(zoomLevel),
|
||||
zoomFactor: zoomLevelToZoomFactor(options.zoomLevel),
|
||||
sandbox: true,
|
||||
contextIsolation: true
|
||||
}
|
||||
contextIsolation: true,
|
||||
},
|
||||
alwaysOnTop: options.alwaysOnTop
|
||||
});
|
||||
|
||||
window.setMenuBarVisibility(false);
|
||||
|
||||
@@ -107,8 +107,8 @@ export abstract class AbstractKeybindingService extends Disposable implements IK
|
||||
);
|
||||
}
|
||||
|
||||
public lookupKeybinding(commandId: string): ResolvedKeybinding | undefined {
|
||||
const result = this._getResolver().lookupPrimaryKeybinding(commandId);
|
||||
public lookupKeybinding(commandId: string, context?: IContextKeyService): ResolvedKeybinding | undefined {
|
||||
const result = this._getResolver().lookupPrimaryKeybinding(commandId, context);
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { Keybinding, KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver';
|
||||
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
|
||||
@@ -85,7 +85,7 @@ export interface IKeybindingService {
|
||||
* Look up the preferred (last defined) keybinding for a command.
|
||||
* @returns The preferred keybinding or null if the command is not bound.
|
||||
*/
|
||||
lookupKeybinding(commandId: string): ResolvedKeybinding | undefined;
|
||||
lookupKeybinding(commandId: string, context?: IContextKeyService): ResolvedKeybinding | undefined;
|
||||
|
||||
getDefaultKeybindingsContent(): string;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IContext, ContextKeyExpression, ContextKeyExprType } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ContextKeyExpression, ContextKeyExprType, IContext, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
|
||||
|
||||
export interface IResolveResult {
|
||||
@@ -247,13 +247,15 @@ export class KeybindingResolver {
|
||||
return result;
|
||||
}
|
||||
|
||||
public lookupPrimaryKeybinding(commandId: string): ResolvedKeybindingItem | null {
|
||||
public lookupPrimaryKeybinding(commandId: string, context?: IContextKeyService): ResolvedKeybindingItem | null {
|
||||
let items = this._lookupMap.get(commandId);
|
||||
if (typeof items === 'undefined' || items.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return items[items.length - 1];
|
||||
const itemMatchingContext = context &&
|
||||
Array.from(items).reverse().find(item => context.contextMatchesRules(item.when));
|
||||
return itemMatchingContext ?? items[items.length - 1];
|
||||
}
|
||||
|
||||
public resolve(context: IContext, currentChord: string | null, keypress: string): IResolveResult | null {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { ipcMain, app, BrowserWindow } from 'electron';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { IStateMainService } from 'vs/platform/state/electron-main/state';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
@@ -115,12 +115,12 @@ export interface ILifecycleMainService {
|
||||
/**
|
||||
* Restart the application with optional arguments (CLI). All lifecycle event handlers are triggered.
|
||||
*/
|
||||
relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): void;
|
||||
relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): Promise<void>;
|
||||
|
||||
/**
|
||||
* Shutdown the application normally. All lifecycle event handlers are triggered.
|
||||
*/
|
||||
quit(fromUpdate?: boolean): Promise<boolean /* veto */>;
|
||||
quit(willRestart?: boolean): Promise<boolean /* veto */>;
|
||||
|
||||
/**
|
||||
* Forcefully shutdown the application. No livecycle event handlers are triggered.
|
||||
@@ -158,7 +158,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private static readonly QUIT_FROM_RESTART_MARKER = 'quit.from.restart'; // use a marker to find out if the session was restarted
|
||||
private static readonly QUIT_AND_RESTART_KEY = 'lifecycle.quitAndRestart';
|
||||
|
||||
private readonly _onBeforeShutdown = this._register(new Emitter<void>());
|
||||
readonly onBeforeShutdown = this._onBeforeShutdown.event;
|
||||
@@ -188,28 +188,29 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe
|
||||
private oneTimeListenerTokenGenerator = 0;
|
||||
private windowCounter = 0;
|
||||
|
||||
private pendingQuitPromise: Promise<boolean> | null = null;
|
||||
private pendingQuitPromiseResolve: { (veto: boolean): void } | null = null;
|
||||
private pendingQuitPromise: Promise<boolean> | undefined = undefined;
|
||||
private pendingQuitPromiseResolve: { (veto: boolean): void } | undefined = undefined;
|
||||
|
||||
private pendingWillShutdownPromise: Promise<void> | null = null;
|
||||
private pendingWillShutdownPromise: Promise<void> | undefined = undefined;
|
||||
|
||||
private readonly phaseWhen = new Map<LifecycleMainPhase, Barrier>();
|
||||
|
||||
constructor(
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IStateService private readonly stateService: IStateService
|
||||
@IStateMainService private readonly stateMainService: IStateMainService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.handleRestarted();
|
||||
this.resolveRestarted();
|
||||
this.when(LifecycleMainPhase.Ready).then(() => this.registerListeners());
|
||||
}
|
||||
|
||||
private handleRestarted(): void {
|
||||
this._wasRestarted = !!this.stateService.getItem(LifecycleMainService.QUIT_FROM_RESTART_MARKER);
|
||||
private resolveRestarted(): void {
|
||||
this._wasRestarted = !!this.stateMainService.getItem(LifecycleMainService.QUIT_AND_RESTART_KEY);
|
||||
|
||||
if (this._wasRestarted) {
|
||||
this.stateService.removeItem(LifecycleMainService.QUIT_FROM_RESTART_MARKER); // remove the marker right after if found
|
||||
// remove the marker right after if found
|
||||
this.stateMainService.removeItem(LifecycleMainService.QUIT_AND_RESTART_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,7 +295,23 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe
|
||||
}
|
||||
});
|
||||
|
||||
this.pendingWillShutdownPromise = Promises.settled(joiners).then(() => undefined, err => this.logService.error(err));
|
||||
this.pendingWillShutdownPromise = (async () => {
|
||||
|
||||
// Settle all shutdown event joiners
|
||||
try {
|
||||
await Promises.settled(joiners);
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
|
||||
// Then, always make sure at the end
|
||||
// the state service is flushed.
|
||||
try {
|
||||
await this.stateMainService.close();
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
})();
|
||||
|
||||
return this.pendingWillShutdownPromise;
|
||||
}
|
||||
@@ -454,8 +471,8 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe
|
||||
private resolvePendingQuitPromise(veto: boolean): void {
|
||||
if (this.pendingQuitPromiseResolve) {
|
||||
this.pendingQuitPromiseResolve(veto);
|
||||
this.pendingQuitPromiseResolve = null;
|
||||
this.pendingQuitPromise = null;
|
||||
this.pendingQuitPromiseResolve = undefined;
|
||||
this.pendingQuitPromise = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,16 +519,16 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe
|
||||
});
|
||||
}
|
||||
|
||||
quit(fromUpdate?: boolean): Promise<boolean /* veto */> {
|
||||
quit(willRestart?: boolean): Promise<boolean /* veto */> {
|
||||
if (this.pendingQuitPromise) {
|
||||
return this.pendingQuitPromise;
|
||||
}
|
||||
|
||||
this.logService.trace(`Lifecycle#quit() - from update: ${fromUpdate}`);
|
||||
this.logService.trace(`Lifecycle#quit() - will restart: ${willRestart}`);
|
||||
|
||||
// Remember the reason for quit was to restart
|
||||
if (fromUpdate) {
|
||||
this.stateService.setItem(LifecycleMainService.QUIT_FROM_RESTART_MARKER, true);
|
||||
// Remember if we are about to restart
|
||||
if (willRestart) {
|
||||
this.stateMainService.setItem(LifecycleMainService.QUIT_AND_RESTART_KEY, true);
|
||||
}
|
||||
|
||||
this.pendingQuitPromise = new Promise(resolve => {
|
||||
@@ -528,7 +545,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe
|
||||
return this.pendingQuitPromise;
|
||||
}
|
||||
|
||||
relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): void {
|
||||
async relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): Promise<void> {
|
||||
this.logService.trace('Lifecycle#relaunch()');
|
||||
|
||||
const args = process.argv.slice(1);
|
||||
@@ -545,37 +562,34 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe
|
||||
}
|
||||
}
|
||||
|
||||
let quitVetoed = false;
|
||||
app.once('quit', () => {
|
||||
if (!quitVetoed) {
|
||||
|
||||
// Remember the reason for quit was to restart
|
||||
this.stateService.setItem(LifecycleMainService.QUIT_FROM_RESTART_MARKER, true);
|
||||
|
||||
// Windows: we are about to restart and as such we need to restore the original
|
||||
// current working directory we had on startup to get the exact same startup
|
||||
// behaviour. As such, we briefly change back to that directory and then when
|
||||
// Code starts it will set it back to the installation directory again.
|
||||
try {
|
||||
if (isWindows) {
|
||||
const currentWorkingDir = cwd();
|
||||
if (currentWorkingDir !== process.cwd()) {
|
||||
process.chdir(currentWorkingDir);
|
||||
}
|
||||
const quitListener = () => {
|
||||
// Windows: we are about to restart and as such we need to restore the original
|
||||
// current working directory we had on startup to get the exact same startup
|
||||
// behaviour. As such, we briefly change back to that directory and then when
|
||||
// Code starts it will set it back to the installation directory again.
|
||||
try {
|
||||
if (isWindows) {
|
||||
const currentWorkingDir = cwd();
|
||||
if (currentWorkingDir !== process.cwd()) {
|
||||
process.chdir(currentWorkingDir);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logService.error(err);
|
||||
}
|
||||
|
||||
// relaunch after we are sure there is no veto
|
||||
this.logService.trace('Lifecycle#relaunch() - calling app.relaunch()');
|
||||
app.relaunch({ args });
|
||||
} catch (err) {
|
||||
this.logService.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
// relaunch after we are sure there is no veto
|
||||
this.logService.trace('Lifecycle#relaunch() - calling app.relaunch()');
|
||||
app.relaunch({ args });
|
||||
};
|
||||
app.once('quit', quitListener);
|
||||
|
||||
// app.relaunch() does not quit automatically, so we quit first,
|
||||
// check for vetoes and then relaunch from the app.on('quit') event
|
||||
this.quit().then(veto => quitVetoed = veto);
|
||||
const veto = await this.quit(true /* will restart */);
|
||||
if (veto) {
|
||||
app.removeListener('quit', quitListener);
|
||||
}
|
||||
}
|
||||
|
||||
async kill(code?: number): Promise<void> {
|
||||
|
||||
@@ -183,7 +183,7 @@ function toWorkbenchListOptions<T>(options: IListOptions<T>, configurationServic
|
||||
}
|
||||
};
|
||||
|
||||
result.smoothScrolling = configurationService.getValue<boolean>(listSmoothScrolling);
|
||||
result.smoothScrolling = Boolean(configurationService.getValue<boolean>(listSmoothScrolling));
|
||||
|
||||
return [result, disposables];
|
||||
}
|
||||
@@ -221,7 +221,7 @@ export class WorkbenchList<T> extends List<T> {
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IKeybindingService keybindingService: IKeybindingService
|
||||
) {
|
||||
const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : configurationService.getValue<boolean>(horizontalScrollingKey);
|
||||
const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : Boolean(configurationService.getValue<boolean>(horizontalScrollingKey));
|
||||
const [workbenchListOptions, workbenchListOptionsDisposable] = toWorkbenchListOptions(options, configurationService, keybindingService);
|
||||
|
||||
super(user, container, delegate, renderers,
|
||||
@@ -282,11 +282,11 @@ export class WorkbenchList<T> extends List<T> {
|
||||
let options: IListOptionsUpdate = {};
|
||||
|
||||
if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) {
|
||||
const horizontalScrolling = configurationService.getValue<boolean>(horizontalScrollingKey);
|
||||
const horizontalScrolling = Boolean(configurationService.getValue<boolean>(horizontalScrollingKey));
|
||||
options = { ...options, horizontalScrolling };
|
||||
}
|
||||
if (e.affectsConfiguration(listSmoothScrolling)) {
|
||||
const smoothScrolling = configurationService.getValue<boolean>(listSmoothScrolling);
|
||||
const smoothScrolling = Boolean(configurationService.getValue<boolean>(listSmoothScrolling));
|
||||
options = { ...options, smoothScrolling };
|
||||
}
|
||||
if (Object.keys(options).length > 0) {
|
||||
@@ -348,7 +348,7 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IKeybindingService keybindingService: IKeybindingService
|
||||
) {
|
||||
const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : configurationService.getValue<boolean>(horizontalScrollingKey);
|
||||
const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : Boolean(configurationService.getValue<boolean>(horizontalScrollingKey));
|
||||
const [workbenchListOptions, workbenchListOptionsDisposable] = toWorkbenchListOptions(options, configurationService, keybindingService);
|
||||
super(user, container, delegate, renderers,
|
||||
{
|
||||
@@ -394,11 +394,11 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
|
||||
let options: IListOptionsUpdate = {};
|
||||
|
||||
if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) {
|
||||
const horizontalScrolling = configurationService.getValue<boolean>(horizontalScrollingKey);
|
||||
const horizontalScrolling = Boolean(configurationService.getValue<boolean>(horizontalScrollingKey));
|
||||
options = { ...options, horizontalScrolling };
|
||||
}
|
||||
if (e.affectsConfiguration(listSmoothScrolling)) {
|
||||
const smoothScrolling = configurationService.getValue<boolean>(listSmoothScrolling);
|
||||
const smoothScrolling = Boolean(configurationService.getValue<boolean>(listSmoothScrolling));
|
||||
options = { ...options, smoothScrolling };
|
||||
}
|
||||
if (Object.keys(options).length > 0) {
|
||||
@@ -469,7 +469,7 @@ export class WorkbenchTable<TRow> extends Table<TRow> {
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IKeybindingService keybindingService: IKeybindingService
|
||||
) {
|
||||
const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : configurationService.getValue<boolean>(horizontalScrollingKey);
|
||||
const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : Boolean(configurationService.getValue<boolean>(horizontalScrollingKey));
|
||||
const [workbenchListOptions, workbenchListOptionsDisposable] = toWorkbenchListOptions(options, configurationService, keybindingService);
|
||||
|
||||
super(user, container, delegate, columns, renderers,
|
||||
@@ -531,11 +531,11 @@ export class WorkbenchTable<TRow> extends Table<TRow> {
|
||||
let options: IListOptionsUpdate = {};
|
||||
|
||||
if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) {
|
||||
const horizontalScrolling = configurationService.getValue<boolean>(horizontalScrollingKey);
|
||||
const horizontalScrolling = Boolean(configurationService.getValue<boolean>(horizontalScrollingKey));
|
||||
options = { ...options, horizontalScrolling };
|
||||
}
|
||||
if (e.affectsConfiguration(listSmoothScrolling)) {
|
||||
const smoothScrolling = configurationService.getValue<boolean>(listSmoothScrolling);
|
||||
const smoothScrolling = Boolean(configurationService.getValue<boolean>(listSmoothScrolling));
|
||||
options = { ...options, smoothScrolling };
|
||||
}
|
||||
if (Object.keys(options).length > 0) {
|
||||
@@ -999,10 +999,10 @@ function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTre
|
||||
|
||||
const getAutomaticKeyboardNavigation = () => {
|
||||
// give priority to the context key value to disable this completely
|
||||
let automaticKeyboardNavigation = contextKeyService.getContextKeyValue<boolean>(WorkbenchListAutomaticKeyboardNavigationKey);
|
||||
let automaticKeyboardNavigation = Boolean(contextKeyService.getContextKeyValue<boolean>(WorkbenchListAutomaticKeyboardNavigationKey));
|
||||
|
||||
if (automaticKeyboardNavigation) {
|
||||
automaticKeyboardNavigation = configurationService.getValue<boolean>(automaticKeyboardNavigationSettingKey);
|
||||
automaticKeyboardNavigation = Boolean(configurationService.getValue<boolean>(automaticKeyboardNavigationSettingKey));
|
||||
}
|
||||
|
||||
return automaticKeyboardNavigation;
|
||||
@@ -1010,7 +1010,7 @@ function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTre
|
||||
|
||||
const accessibilityOn = accessibilityService.isScreenReaderOptimized();
|
||||
const keyboardNavigation = options.simpleKeyboardNavigation || accessibilityOn ? 'simple' : configurationService.getValue<string>(keyboardNavigationSettingKey);
|
||||
const horizontalScrolling = options.horizontalScrolling !== undefined ? options.horizontalScrolling : configurationService.getValue<boolean>(horizontalScrollingKey);
|
||||
const horizontalScrolling = options.horizontalScrolling !== undefined ? options.horizontalScrolling : Boolean(configurationService.getValue<boolean>(horizontalScrollingKey));
|
||||
const [workbenchListOptions, disposable] = toWorkbenchListOptions(options, configurationService, keybindingService);
|
||||
const additionalScrollHeight = options.additionalScrollHeight;
|
||||
|
||||
@@ -1023,7 +1023,7 @@ function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTre
|
||||
...workbenchListOptions,
|
||||
indent: configurationService.getValue<number>(treeIndentKey),
|
||||
renderIndentGuides: configurationService.getValue<RenderIndentGuides>(treeRenderIndentGuidesKey),
|
||||
smoothScrolling: configurationService.getValue<boolean>(listSmoothScrolling),
|
||||
smoothScrolling: Boolean(configurationService.getValue<boolean>(listSmoothScrolling)),
|
||||
automaticKeyboardNavigation: getAutomaticKeyboardNavigation(),
|
||||
simpleKeyboardNavigation: keyboardNavigation === 'simple',
|
||||
filterOnType: keyboardNavigation === 'filter',
|
||||
@@ -1120,7 +1120,7 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
|
||||
newOptions = { ...newOptions, renderIndentGuides };
|
||||
}
|
||||
if (e.affectsConfiguration(listSmoothScrolling)) {
|
||||
const smoothScrolling = configurationService.getValue<boolean>(listSmoothScrolling);
|
||||
const smoothScrolling = Boolean(configurationService.getValue<boolean>(listSmoothScrolling));
|
||||
newOptions = { ...newOptions, smoothScrolling };
|
||||
}
|
||||
if (e.affectsConfiguration(keyboardNavigationSettingKey)) {
|
||||
@@ -1130,7 +1130,7 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
|
||||
newOptions = { ...newOptions, automaticKeyboardNavigation: getAutomaticKeyboardNavigation() };
|
||||
}
|
||||
if (e.affectsConfiguration(horizontalScrollingKey) && options.horizontalScrolling === undefined) {
|
||||
const horizontalScrolling = configurationService.getValue<boolean>(horizontalScrollingKey);
|
||||
const horizontalScrolling = Boolean(configurationService.getValue<boolean>(horizontalScrollingKey));
|
||||
newOptions = { ...newOptions, horizontalScrolling };
|
||||
}
|
||||
if (e.affectsConfiguration(treeExpandMode) && options.expandOnlyOnTwistieClick === undefined) {
|
||||
@@ -1171,20 +1171,20 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
|
||||
configurationRegistry.registerConfiguration({
|
||||
'id': 'workbench',
|
||||
'order': 7,
|
||||
'title': localize('workbenchConfigurationTitle', "Workbench"),
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
id: 'workbench',
|
||||
order: 7,
|
||||
title: localize('workbenchConfigurationTitle', "Workbench"),
|
||||
type: 'object',
|
||||
properties: {
|
||||
[multiSelectModifierSettingKey]: {
|
||||
'type': 'string',
|
||||
'enum': ['ctrlCmd', 'alt'],
|
||||
'enumDescriptions': [
|
||||
type: 'string',
|
||||
enum: ['ctrlCmd', 'alt'],
|
||||
enumDescriptions: [
|
||||
localize('multiSelectModifier.ctrlCmd', "Maps to `Control` on Windows and Linux and to `Command` on macOS."),
|
||||
localize('multiSelectModifier.alt', "Maps to `Alt` on Windows and Linux and to `Option` on macOS.")
|
||||
],
|
||||
'default': 'ctrlCmd',
|
||||
'description': localize({
|
||||
default: 'ctrlCmd',
|
||||
description: localize({
|
||||
key: 'multiSelectModifier',
|
||||
comment: [
|
||||
'- `ctrlCmd` refers to a value the setting can take and should not be localized.',
|
||||
@@ -1193,25 +1193,25 @@ configurationRegistry.registerConfiguration({
|
||||
}, "The modifier to be used to add an item in trees and lists to a multi-selection with the mouse (for example in the explorer, open editors and scm view). The 'Open to Side' mouse gestures - if supported - will adapt such that they do not conflict with the multiselect modifier.")
|
||||
},
|
||||
[openModeSettingKey]: {
|
||||
'type': 'string',
|
||||
'enum': ['singleClick', 'doubleClick'],
|
||||
'default': 'singleClick',
|
||||
'description': localize({
|
||||
type: 'string',
|
||||
enum: ['singleClick', 'doubleClick'],
|
||||
default: 'singleClick',
|
||||
description: localize({
|
||||
key: 'openModeModifier',
|
||||
comment: ['`singleClick` and `doubleClick` refers to a value the setting can take and should not be localized.']
|
||||
}, "Controls how to open items in trees and lists using the mouse (if supported). Note that some trees and lists might choose to ignore this setting if it is not applicable.")
|
||||
},
|
||||
[horizontalScrollingKey]: {
|
||||
'type': 'boolean',
|
||||
'default': false,
|
||||
'description': localize('horizontalScrolling setting', "Controls whether lists and trees support horizontal scrolling in the workbench. Warning: turning on this setting has a performance implication.")
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: localize('horizontalScrolling setting', "Controls whether lists and trees support horizontal scrolling in the workbench. Warning: turning on this setting has a performance implication.")
|
||||
},
|
||||
[treeIndentKey]: {
|
||||
'type': 'number',
|
||||
'default': 8,
|
||||
type: 'number',
|
||||
default: 8,
|
||||
minimum: 0,
|
||||
maximum: 40,
|
||||
'description': localize('tree indent setting', "Controls tree indentation in pixels.")
|
||||
description: localize('tree indent setting', "Controls tree indentation in pixels.")
|
||||
},
|
||||
[treeRenderIndentGuidesKey]: {
|
||||
type: 'string',
|
||||
@@ -1225,19 +1225,19 @@ configurationRegistry.registerConfiguration({
|
||||
description: localize('list smoothScrolling setting', "Controls whether lists and trees have smooth scrolling."),
|
||||
},
|
||||
[keyboardNavigationSettingKey]: {
|
||||
'type': 'string',
|
||||
'enum': ['simple', 'highlight', 'filter'],
|
||||
'enumDescriptions': [
|
||||
type: 'string',
|
||||
enum: ['simple', 'highlight', 'filter'],
|
||||
enumDescriptions: [
|
||||
localize('keyboardNavigationSettingKey.simple', "Simple keyboard navigation focuses elements which match the keyboard input. Matching is done only on prefixes."),
|
||||
localize('keyboardNavigationSettingKey.highlight', "Highlight keyboard navigation highlights elements which match the keyboard input. Further up and down navigation will traverse only the highlighted elements."),
|
||||
localize('keyboardNavigationSettingKey.filter', "Filter keyboard navigation will filter out and hide all the elements which do not match the keyboard input.")
|
||||
],
|
||||
'default': 'highlight',
|
||||
'description': localize('keyboardNavigationSettingKey', "Controls the keyboard navigation style for lists and trees in the workbench. Can be simple, highlight and filter.")
|
||||
default: 'highlight',
|
||||
description: localize('keyboardNavigationSettingKey', "Controls the keyboard navigation style for lists and trees in the workbench. Can be simple, highlight and filter.")
|
||||
},
|
||||
[automaticKeyboardNavigationSettingKey]: {
|
||||
'type': 'boolean',
|
||||
'default': true,
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: localize('automatic keyboard navigation setting', "Controls whether keyboard navigation in lists and trees is automatically triggered simply by typing. If set to `false`, keyboard navigation is only triggered when executing the `list.toggleKeyboardNavigation` command, for which you can assign a keyboard shortcut.")
|
||||
},
|
||||
[treeExpandMode]: {
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { writeFile } from 'vs/base/node/pfs';
|
||||
import { promises } from 'fs';
|
||||
import { Promises, writeFile } from 'vs/base/node/pfs';
|
||||
import { createHash } from 'crypto';
|
||||
import { IExtensionManagementService, ILocalExtension, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
@@ -158,7 +157,7 @@ class LanguagePacksCache extends Disposable {
|
||||
private withLanguagePacks<T>(fn: (languagePacks: { [language: string]: ILanguagePack }) => T | null = () => null): Promise<T> {
|
||||
return this.languagePacksFileLimiter.queue(() => {
|
||||
let result: T | null = null;
|
||||
return promises.readFile(this.languagePacksFilePath, 'utf8')
|
||||
return Promises.readFile(this.languagePacksFilePath, 'utf8')
|
||||
.then(undefined, err => err.code === 'ENOENT' ? Promise.resolve('{}') : Promise.reject(err))
|
||||
.then<{ [language: string]: ILanguagePack }>(raw => { try { return JSON.parse(raw); } catch (e) { return {}; } })
|
||||
.then(languagePacks => { result = fn(languagePacks); return languagePacks; })
|
||||
|
||||
@@ -7,20 +7,21 @@ import { LogLevel, ILogger, AbstractMessageLogger } from 'vs/platform/log/common
|
||||
import * as spdlog from 'spdlog';
|
||||
import { ByteSize } from 'vs/platform/files/common/files';
|
||||
|
||||
async function createSpdLogLogger(name: string, logfilePath: string, filesize: number, filecount: number): Promise<spdlog.RotatingLogger | null> {
|
||||
async function createSpdLogLogger(name: string, logfilePath: string, filesize: number, filecount: number): Promise<spdlog.Logger | null> {
|
||||
// Do not crash if spdlog cannot be loaded
|
||||
try {
|
||||
const _spdlog = await import('spdlog');
|
||||
_spdlog.setAsyncMode(8192, 500);
|
||||
return _spdlog.createRotatingLoggerAsync(name, logfilePath, filesize, filecount);
|
||||
_spdlog.setFlushOn(LogLevel.Trace);
|
||||
return _spdlog.createAsyncRotatingLogger(name, logfilePath, filesize, filecount);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function createRotatingLogger(name: string, filename: string, filesize: number, filecount: number): spdlog.RotatingLogger {
|
||||
export function createRotatingLogger(name: string, filename: string, filesize: number, filecount: number): Promise<spdlog.Logger> {
|
||||
const _spdlog: typeof spdlog = require.__$__nodeRequire('spdlog');
|
||||
_spdlog.setFlushOn(LogLevel.Trace);
|
||||
return _spdlog.createRotatingLogger(name, filename, filesize, filecount);
|
||||
}
|
||||
|
||||
@@ -29,7 +30,7 @@ interface ILog {
|
||||
message: string;
|
||||
}
|
||||
|
||||
function log(logger: spdlog.RotatingLogger, level: LogLevel, message: string): void {
|
||||
function log(logger: spdlog.Logger, level: LogLevel, message: string): void {
|
||||
switch (level) {
|
||||
case LogLevel.Trace: logger.trace(message); break;
|
||||
case LogLevel.Debug: logger.debug(message); break;
|
||||
@@ -45,7 +46,7 @@ export class SpdLogLogger extends AbstractMessageLogger implements ILogger {
|
||||
|
||||
private buffer: ILog[] = [];
|
||||
private readonly _loggerCreationPromise: Promise<void>;
|
||||
private _logger: spdlog.RotatingLogger | undefined;
|
||||
private _logger: spdlog.Logger | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly name: string,
|
||||
|
||||
@@ -19,7 +19,7 @@ import { IWindowsMainService, IWindowsCountChangedEvent, OpenContext } from 'vs/
|
||||
import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
|
||||
import { IMenubarData, IMenubarKeybinding, MenubarMenuItem, isMenubarMenuItemSeparator, isMenubarMenuItemSubmenu, isMenubarMenuItemAction, IMenubarMenu, isMenubarMenuItemRecentAction, IMenubarMenuRecentItemAction } from 'vs/platform/menubar/common/menubar';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { IStateMainService } from 'vs/platform/state/electron-main/state';
|
||||
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
|
||||
import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
|
||||
@@ -70,7 +70,7 @@ export class Menubar {
|
||||
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService,
|
||||
@IStateService private readonly stateService: IStateService,
|
||||
@IStateMainService private readonly stateMainService: IStateMainService,
|
||||
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@INativeHostMainService private readonly nativeHostMainService: INativeHostMainService,
|
||||
@@ -100,7 +100,7 @@ export class Menubar {
|
||||
}
|
||||
|
||||
private restoreCachedMenubarData() {
|
||||
const menubarData = this.stateService.getItem<IMenubarData>(Menubar.lastKnownMenubarStorageKey);
|
||||
const menubarData = this.stateMainService.getItem<IMenubarData>(Menubar.lastKnownMenubarStorageKey);
|
||||
if (menubarData) {
|
||||
if (menubarData.menus) {
|
||||
this.menubarMenus = menubarData.menus;
|
||||
@@ -200,7 +200,7 @@ export class Menubar {
|
||||
this.keybindings = menubarData.keybindings;
|
||||
|
||||
// Save off new menu and keybindings
|
||||
this.stateService.setItem(Menubar.lastKnownMenubarStorageKey, menubarData);
|
||||
this.stateMainService.setItem(Menubar.lastKnownMenubarStorageKey, menubarData);
|
||||
|
||||
this.scheduleUpdateMenu();
|
||||
}
|
||||
@@ -285,53 +285,60 @@ export class Menubar {
|
||||
}
|
||||
|
||||
// File
|
||||
const fileMenu = new Menu();
|
||||
const fileMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File")), submenu: fileMenu });
|
||||
|
||||
this.setMenuById(fileMenu, 'File');
|
||||
menubar.append(fileMenuItem);
|
||||
if (this.shouldDrawMenu('File')) {
|
||||
const fileMenu = new Menu();
|
||||
const fileMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File")), submenu: fileMenu });
|
||||
this.setMenuById(fileMenu, 'File');
|
||||
menubar.append(fileMenuItem);
|
||||
}
|
||||
|
||||
// Edit
|
||||
const editMenu = new Menu();
|
||||
const editMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit")), submenu: editMenu });
|
||||
|
||||
this.setMenuById(editMenu, 'Edit');
|
||||
menubar.append(editMenuItem);
|
||||
if (this.shouldDrawMenu('Edit')) {
|
||||
const editMenu = new Menu();
|
||||
const editMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit")), submenu: editMenu });
|
||||
this.setMenuById(editMenu, 'Edit');
|
||||
menubar.append(editMenuItem);
|
||||
}
|
||||
|
||||
// Selection
|
||||
const selectionMenu = new Menu();
|
||||
const selectionMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection")), submenu: selectionMenu });
|
||||
|
||||
this.setMenuById(selectionMenu, 'Selection');
|
||||
menubar.append(selectionMenuItem);
|
||||
if (this.shouldDrawMenu('Selection')) {
|
||||
const selectionMenu = new Menu();
|
||||
const selectionMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection")), submenu: selectionMenu });
|
||||
this.setMenuById(selectionMenu, 'Selection');
|
||||
menubar.append(selectionMenuItem);
|
||||
}
|
||||
|
||||
// View
|
||||
const viewMenu = new Menu();
|
||||
const viewMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View")), submenu: viewMenu });
|
||||
|
||||
this.setMenuById(viewMenu, 'View');
|
||||
menubar.append(viewMenuItem);
|
||||
if (this.shouldDrawMenu('View')) {
|
||||
const viewMenu = new Menu();
|
||||
const viewMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View")), submenu: viewMenu });
|
||||
this.setMenuById(viewMenu, 'View');
|
||||
menubar.append(viewMenuItem);
|
||||
}
|
||||
|
||||
// Go
|
||||
const gotoMenu = new Menu();
|
||||
const gotoMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")), submenu: gotoMenu });
|
||||
|
||||
this.setMenuById(gotoMenu, 'Go');
|
||||
menubar.append(gotoMenuItem);
|
||||
if (this.shouldDrawMenu('Go')) {
|
||||
const gotoMenu = new Menu();
|
||||
const gotoMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")), submenu: gotoMenu });
|
||||
this.setMenuById(gotoMenu, 'Go');
|
||||
menubar.append(gotoMenuItem);
|
||||
}
|
||||
|
||||
// Debug
|
||||
const debugMenu = new Menu();
|
||||
const debugMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mRun', comment: ['&& denotes a mnemonic'] }, "&&Run")), submenu: debugMenu });
|
||||
|
||||
this.setMenuById(debugMenu, 'Run');
|
||||
menubar.append(debugMenuItem);
|
||||
if (this.shouldDrawMenu('Run')) {
|
||||
const debugMenu = new Menu();
|
||||
const debugMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mRun', comment: ['&& denotes a mnemonic'] }, "&&Run")), submenu: debugMenu });
|
||||
this.setMenuById(debugMenu, 'Run');
|
||||
menubar.append(debugMenuItem);
|
||||
}
|
||||
|
||||
// Terminal
|
||||
const terminalMenu = new Menu();
|
||||
const terminalMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal")), submenu: terminalMenu });
|
||||
|
||||
this.setMenuById(terminalMenu, 'Terminal');
|
||||
menubar.append(terminalMenuItem);
|
||||
if (this.shouldDrawMenu('Terminal')) {
|
||||
const terminalMenu = new Menu();
|
||||
const terminalMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal")), submenu: terminalMenu });
|
||||
this.setMenuById(terminalMenu, 'Terminal');
|
||||
menubar.append(terminalMenuItem);
|
||||
}
|
||||
|
||||
// Mac: Window
|
||||
let macWindowMenuItem: MenuItem | undefined;
|
||||
@@ -346,11 +353,12 @@ export class Menubar {
|
||||
}
|
||||
|
||||
// Help
|
||||
const helpMenu = new Menu();
|
||||
const helpMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help")), submenu: helpMenu, role: 'help' });
|
||||
|
||||
this.setMenuById(helpMenu, 'Help');
|
||||
menubar.append(helpMenuItem);
|
||||
if (this.shouldDrawMenu('Help')) {
|
||||
const helpMenu = new Menu();
|
||||
const helpMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help")), submenu: helpMenu, role: 'help' });
|
||||
this.setMenuById(helpMenu, 'Help');
|
||||
menubar.append(helpMenuItem);
|
||||
}
|
||||
|
||||
if (menubar.items && menubar.items.length > 0) {
|
||||
Menu.setApplicationMenu(menubar);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes';
|
||||
import { IOpenedWindow, IWindowOpenable, IOpenEmptyWindowOptions, IOpenWindowOptions, IColorScheme } from 'vs/platform/windows/common/windows';
|
||||
import { IOpenedWindow, IWindowOpenable, IOpenEmptyWindowOptions, IOpenWindowOptions, IColorScheme, IPartsSplash } from 'vs/platform/windows/common/windows';
|
||||
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -72,6 +72,8 @@ export interface ICommonNativeHostService {
|
||||
|
||||
setMinimumSize(width: number | undefined, height: number | undefined): Promise<void>;
|
||||
|
||||
saveWindowSplash(splash: IPartsSplash): Promise<void>;
|
||||
|
||||
/**
|
||||
* Make the window focused.
|
||||
*
|
||||
@@ -97,7 +99,7 @@ export interface ICommonNativeHostService {
|
||||
setRepresentedFilename(path: string): Promise<void>;
|
||||
setDocumentEdited(edited: boolean): Promise<void>;
|
||||
openExternal(url: string): Promise<boolean>;
|
||||
moveItemToTrash(fullPath: string, deleteOnFail?: boolean): Promise<boolean>;
|
||||
moveItemToTrash(fullPath: string): Promise<void>;
|
||||
|
||||
isAdmin(): Promise<boolean>;
|
||||
writeElevated(source: URI, target: URI, options?: { unlock?: boolean }): Promise<void>;
|
||||
@@ -127,6 +129,10 @@ export interface ICommonNativeHostService {
|
||||
toggleWindowTabsBar(): Promise<void>;
|
||||
updateTouchBar(items: ISerializableCommandAction[][]): Promise<void>;
|
||||
|
||||
// macOS Shell command
|
||||
installShellCommand(): Promise<void>;
|
||||
uninstallShellCommand(): Promise<void>;
|
||||
|
||||
// Lifecycle
|
||||
notifyReady(): Promise<void>
|
||||
relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): Promise<void>;
|
||||
|
||||
@@ -3,11 +3,15 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { localize } from 'vs/nls';
|
||||
import { realpath } from 'vs/base/node/extpath';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IWindowsMainService, ICodeWindow, OpenContext } from 'vs/platform/windows/electron-main/windows';
|
||||
import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, Menu, BrowserWindow, app, clipboard, powerMonitor, nativeTheme, screen, Display } from 'electron';
|
||||
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { IOpenedWindow, IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions, IColorScheme } from 'vs/platform/windows/common/windows';
|
||||
import { IOpenedWindow, IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions, IColorScheme, IPartsSplash } from 'vs/platform/windows/common/windows';
|
||||
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { isMacintosh, isWindows, isLinux, isLinuxSnap } from 'vs/base/common/platform';
|
||||
import { ICommonNativeHostService, IOSProperties, IOSStatistics } from 'vs/platform/native/common/native';
|
||||
@@ -15,7 +19,7 @@ import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
|
||||
import { AddFirstParameterToFunctions } from 'vs/base/common/types';
|
||||
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService';
|
||||
import { SymlinkSupport } from 'vs/base/node/pfs';
|
||||
import { exists, Promises, SymlinkSupport } from 'vs/base/node/pfs';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -23,11 +27,12 @@ import { MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes';
|
||||
import { arch, totalmem, release, platform, type, loadavg, freemem, cpus } from 'os';
|
||||
import { virtualMachineHint } from 'vs/base/node/id';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { dirname, join } from 'vs/base/common/path';
|
||||
import { dirname, join, resolve } from 'vs/base/common/path';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ISharedProcess } from 'vs/platform/sharedProcess/node/sharedProcess';
|
||||
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
|
||||
|
||||
export interface INativeHostMainService extends AddFirstParameterToFunctions<ICommonNativeHostService, Promise<unknown> /* only methods, not events */, number | undefined /* window ID */> { }
|
||||
|
||||
@@ -50,7 +55,8 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
||||
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IProductService private readonly productService: IProductService
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IThemeMainService private readonly themeMainService: IThemeMainService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -247,9 +253,106 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
||||
}
|
||||
}
|
||||
|
||||
async saveWindowSplash(windowId: number | undefined, splash: IPartsSplash): Promise<void> {
|
||||
this.themeMainService.saveWindowSplash(windowId, splash);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region macOS Shell Command
|
||||
|
||||
async installShellCommand(windowId: number | undefined): Promise<void> {
|
||||
const { source, target } = await this.getShellCommandLink();
|
||||
|
||||
// Only install unless already existing
|
||||
try {
|
||||
const { symbolicLink } = await SymlinkSupport.stat(source);
|
||||
if (symbolicLink && !symbolicLink.dangling) {
|
||||
const linkTargetRealPath = await realpath(source);
|
||||
if (target === linkTargetRealPath) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Different source, delete it first
|
||||
await Promises.unlink(source);
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') {
|
||||
throw error; // throw on any error but file not found
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await Promises.symlink(target, source);
|
||||
} catch (error) {
|
||||
if (error.code !== 'EACCES' && error.code !== 'ENOENT') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const { response } = await this.showMessageBox(windowId, {
|
||||
type: 'info',
|
||||
message: localize('warnEscalation', "{0} will now prompt with 'osascript' for Administrator privileges to install the shell command.", this.productService.nameShort),
|
||||
buttons: [localize('ok', "OK"), localize('cancel', "Cancel")],
|
||||
cancelId: 1
|
||||
});
|
||||
|
||||
if (response === 0 /* OK */) {
|
||||
try {
|
||||
const command = `osascript -e "do shell script \\"mkdir -p /usr/local/bin && ln -sf \'${target}\' \'${source}\'\\" with administrator privileges"`;
|
||||
await promisify(exec)(command);
|
||||
} catch (error) {
|
||||
throw new Error(localize('cantCreateBinFolder', "Unable to install the shell command '{0}'.", source));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async uninstallShellCommand(windowId: number | undefined): Promise<void> {
|
||||
const { source } = await this.getShellCommandLink();
|
||||
|
||||
try {
|
||||
await Promises.unlink(source);
|
||||
} catch (error) {
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
const { response } = await this.showMessageBox(windowId, {
|
||||
type: 'info',
|
||||
message: localize('warnEscalationUninstall', "{0} will now prompt with 'osascript' for Administrator privileges to uninstall the shell command.", this.productService.nameShort),
|
||||
buttons: [localize('ok', "OK"), localize('cancel', "Cancel")],
|
||||
cancelId: 1
|
||||
});
|
||||
|
||||
if (response === 0 /* OK */) {
|
||||
try {
|
||||
const command = `osascript -e "do shell script \\"rm \'${source}\'\\" with administrator privileges"`;
|
||||
await promisify(exec)(command);
|
||||
} catch (error) {
|
||||
throw new Error(localize('cantUninstall', "Unable to uninstall the shell command '{0}'.", source));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'ENOENT':
|
||||
break; // ignore file not found
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async getShellCommandLink(): Promise<{ readonly source: string, readonly target: string }> {
|
||||
const target = resolve(this.environmentMainService.appRoot, 'bin', 'code');
|
||||
const source = `/usr/local/bin/${this.productService.applicationName}`;
|
||||
|
||||
// Ensure source exists
|
||||
const sourceExists = await exists(target);
|
||||
if (!sourceExists) {
|
||||
throw new Error(localize('sourceMissing', "Unable to find shell script in '{0}'", target));
|
||||
}
|
||||
|
||||
return { source, target };
|
||||
}
|
||||
|
||||
//#region Dialog
|
||||
|
||||
async showMessageBox(windowId: number | undefined, options: MessageBoxOptions): Promise<MessageBoxReturnValue> {
|
||||
@@ -376,8 +479,8 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
||||
process.env['GDK_PIXBUF_MODULEDIR'] = gdkPixbufModuleDir;
|
||||
}
|
||||
|
||||
async moveItemToTrash(windowId: number | undefined, fullPath: string): Promise<boolean> {
|
||||
return shell.moveItemToTrash(fullPath);
|
||||
moveItemToTrash(windowId: number | undefined, fullPath: string): Promise<void> {
|
||||
return shell.trashItem(fullPath);
|
||||
}
|
||||
|
||||
async isAdmin(): Promise<boolean> {
|
||||
@@ -599,9 +702,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
||||
|
||||
// Otherwise: normal quit
|
||||
else {
|
||||
setTimeout(() => {
|
||||
this.lifecycleMainService.quit();
|
||||
}, 10 /* delay to unwind callback stack (IPC) */);
|
||||
this.lifecycleMainService.quit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,6 +812,27 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
||||
|
||||
async setPassword(windowId: number | undefined, service: string, account: string, password: string): Promise<void> {
|
||||
const keytar = await this.withKeytar();
|
||||
const MAX_SET_ATTEMPTS = 3;
|
||||
|
||||
// Sometimes Keytar has a problem talking to the keychain on the OS. To be more resilient, we retry a few times.
|
||||
const setPasswordWithRetry = async (service: string, account: string, password: string) => {
|
||||
let attempts = 0;
|
||||
let error: any;
|
||||
while (attempts < MAX_SET_ATTEMPTS) {
|
||||
try {
|
||||
await keytar.setPassword(service, account, password);
|
||||
return;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
this.logService.warn('Error attempting to set a password: ', e);
|
||||
attempts++;
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
}
|
||||
}
|
||||
|
||||
// throw last error
|
||||
throw error;
|
||||
};
|
||||
|
||||
if (isWindows && password.length > NativeHostMainService.MAX_PASSWORD_LENGTH) {
|
||||
let index = 0;
|
||||
@@ -726,12 +848,12 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
||||
hasNextChunk: hasNextChunk
|
||||
};
|
||||
|
||||
await keytar.setPassword(service, chunk ? `${account}-${chunk}` : account, JSON.stringify(content));
|
||||
await setPasswordWithRetry(service, chunk ? `${account}-${chunk}` : account, JSON.stringify(content));
|
||||
chunk++;
|
||||
}
|
||||
|
||||
} else {
|
||||
await keytar.setPassword(service, account, password);
|
||||
await setPasswordWithRetry(service, account, password);
|
||||
}
|
||||
|
||||
this._onDidChangePassword.fire({ service, account });
|
||||
|
||||
@@ -12,7 +12,7 @@ export const INativeHostService = createDecorator<INativeHostService>('nativeHos
|
||||
* A set of methods specific to a native host, i.e. unsupported in web
|
||||
* environments.
|
||||
*
|
||||
* @see `IHostService` for methods that can be used in native and web
|
||||
* @see {@link IHostService} for methods that can be used in native and web
|
||||
* hosts.
|
||||
*/
|
||||
export interface INativeHostService extends ICommonNativeHostService { }
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { $, EventHelper, EventLike } from 'vs/base/browser/dom';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { DomEmitter, domEvent } from 'vs/base/browser/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
export interface ILinkDescriptor {
|
||||
readonly label: string;
|
||||
@@ -18,75 +19,86 @@ export interface ILinkDescriptor {
|
||||
readonly title?: string;
|
||||
}
|
||||
|
||||
export interface ILinkStyles {
|
||||
readonly textLinkForeground?: Color;
|
||||
readonly disabled?: boolean;
|
||||
export interface ILinkOptions {
|
||||
readonly opener?: (href: string) => void;
|
||||
readonly textLinkForeground?: string;
|
||||
}
|
||||
|
||||
export class Link extends Disposable {
|
||||
|
||||
readonly el: HTMLAnchorElement;
|
||||
private disabled: boolean;
|
||||
private styles: ILinkStyles = {
|
||||
textLinkForeground: Color.fromHex('#006AB1')
|
||||
};
|
||||
private _enabled: boolean = true;
|
||||
|
||||
get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
set enabled(enabled: boolean) {
|
||||
if (enabled) {
|
||||
this.el.setAttribute('aria-disabled', 'false');
|
||||
this.el.tabIndex = 0;
|
||||
this.el.style.pointerEvents = 'auto';
|
||||
this.el.style.opacity = '1';
|
||||
this.el.style.cursor = 'pointer';
|
||||
this._enabled = false;
|
||||
} else {
|
||||
this.el.setAttribute('aria-disabled', 'true');
|
||||
this.el.tabIndex = -1;
|
||||
this.el.style.pointerEvents = 'none';
|
||||
this.el.style.opacity = '0.4';
|
||||
this.el.style.cursor = 'default';
|
||||
this._enabled = true;
|
||||
}
|
||||
|
||||
this._enabled = enabled;
|
||||
}
|
||||
|
||||
constructor(
|
||||
link: ILinkDescriptor,
|
||||
options: ILinkOptions | undefined = undefined,
|
||||
@IOpenerService openerService: IOpenerService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.el = $<HTMLAnchorElement>('a', {
|
||||
this.el = $<HTMLAnchorElement>('a.monaco-link', {
|
||||
tabIndex: 0,
|
||||
href: link.href,
|
||||
title: link.title
|
||||
}, link.label);
|
||||
|
||||
const onClick = domEvent(this.el, 'click');
|
||||
const onClickEmitter = this._register(new DomEmitter(this.el, 'click'));
|
||||
const onEnterPress = Event.chain(domEvent(this.el, 'keypress'))
|
||||
.map(e => new StandardKeyboardEvent(e))
|
||||
.filter(e => e.keyCode === KeyCode.Enter)
|
||||
.event;
|
||||
const onOpen = Event.any<EventLike>(onClick, onEnterPress);
|
||||
const onOpen = Event.any<EventLike>(onClickEmitter.event, onEnterPress);
|
||||
|
||||
this._register(onOpen(e => {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
EventHelper.stop(e, true);
|
||||
if (!this.disabled) {
|
||||
|
||||
if (options?.opener) {
|
||||
options.opener(link.href);
|
||||
} else {
|
||||
openerService.open(link.href, { allowCommands: true });
|
||||
}
|
||||
}));
|
||||
|
||||
this.disabled = false;
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
style(styles: ILinkStyles): void {
|
||||
this.styles = styles;
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
private applyStyles(): void {
|
||||
const color = this.styles.textLinkForeground?.toString();
|
||||
if (color) {
|
||||
this.el.style.color = color;
|
||||
}
|
||||
if (typeof this.styles.disabled === 'boolean' && this.styles.disabled !== this.disabled) {
|
||||
if (this.styles.disabled) {
|
||||
this.el.setAttribute('aria-disabled', 'true');
|
||||
this.el.tabIndex = -1;
|
||||
this.el.style.pointerEvents = 'none';
|
||||
this.el.style.opacity = '0.4';
|
||||
this.el.style.cursor = 'default';
|
||||
this.disabled = true;
|
||||
} else {
|
||||
this.el.setAttribute('aria-disabled', 'false');
|
||||
this.el.tabIndex = 0;
|
||||
this.el.style.pointerEvents = 'auto';
|
||||
this.el.style.opacity = '1';
|
||||
this.el.style.cursor = 'pointer';
|
||||
this.disabled = false;
|
||||
}
|
||||
}
|
||||
this.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const textLinkForegroundColor = theme.getColor(textLinkForeground);
|
||||
if (textLinkForegroundColor) {
|
||||
collector.addRule(`.monaco-link { color: ${textLinkForegroundColor}; }`);
|
||||
}
|
||||
|
||||
const textLinkActiveForegroundColor = theme.getColor(textLinkActiveForeground);
|
||||
if (textLinkActiveForegroundColor) {
|
||||
collector.addRule(`.monaco-link:hover { color: ${textLinkActiveForegroundColor}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -109,6 +109,7 @@ export interface IOpenerService {
|
||||
|
||||
/**
|
||||
* Resolve a resource to its external form.
|
||||
* @throws whenever resolvers couldn't resolve this resource externally.
|
||||
*/
|
||||
resolveExternalUri(resource: URI, options?: ResolveExternalUriOptions): Promise<IResolvedExternalUri>;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes
|
||||
let product: IProductConfiguration;
|
||||
|
||||
// Native sandbox environment
|
||||
if (typeof globals.vscode !== 'undefined') {
|
||||
if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.context !== 'undefined') {
|
||||
const configuration: ISandboxConfiguration | undefined = globals.vscode.context.configuration();
|
||||
if (configuration) {
|
||||
product = configuration.product;
|
||||
@@ -54,7 +54,7 @@ else {
|
||||
// Running out of sources
|
||||
if (Object.keys(product).length === 0) {
|
||||
Object.assign(product, {
|
||||
version: '1.56.0-dev',
|
||||
version: '1.57.0-dev',
|
||||
nameShort: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev',
|
||||
nameLong: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev',
|
||||
applicationName: 'code-oss',
|
||||
@@ -66,13 +66,14 @@ else {
|
||||
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'
|
||||
'ms-vscode.remotehub-insiders',
|
||||
'GitHub.remotehub',
|
||||
'GitHub.remotehub-insiders'
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// NOTE@coder: Add the ability to inject settings from the server.
|
||||
const el = document.getElementById('vscode-remote-product-configuration');
|
||||
const rawProductConfiguration = el && el.getAttribute('data-settings');
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface IProgressService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
withProgress<R>(
|
||||
options: IProgressOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions,
|
||||
options: IProgressOptions | IProgressDialogOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions,
|
||||
task: (progress: IProgress<IProgressStep>) => Promise<R>,
|
||||
onDidCancel?: (choice?: number) => void
|
||||
): Promise<R>;
|
||||
@@ -66,6 +66,11 @@ export interface IProgressNotificationOptions extends IProgressOptions {
|
||||
readonly silent?: boolean;
|
||||
}
|
||||
|
||||
export interface IProgressDialogOptions extends IProgressOptions {
|
||||
readonly delay?: number;
|
||||
readonly detail?: string;
|
||||
}
|
||||
|
||||
export interface IProgressWindowOptions extends IProgressOptions {
|
||||
readonly location: ProgressLocation.Window;
|
||||
readonly command?: string;
|
||||
@@ -134,7 +139,7 @@ export class UnmanagedProgress extends Disposable {
|
||||
private lastStep?: IProgressStep;
|
||||
|
||||
constructor(
|
||||
options: IProgressOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions,
|
||||
options: IProgressOptions | IProgressDialogOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions,
|
||||
@IProgressService progressService: IProgressService,
|
||||
) {
|
||||
super();
|
||||
@@ -159,7 +164,6 @@ export class UnmanagedProgress extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class LongRunningOperation extends Disposable {
|
||||
private currentOperationId = 0;
|
||||
private readonly currentOperationDisposables = this._register(new DisposableStore());
|
||||
|
||||
@@ -34,12 +34,8 @@ export interface IProtocolMainService {
|
||||
/**
|
||||
* 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<T>(obj?: T): IIPCObjectUrl<T>;
|
||||
createIPCObjectUrl<T>(): IIPCObjectUrl<T>;
|
||||
|
||||
/**
|
||||
* Adds a `URI` as root to the list of allowed
|
||||
|
||||
@@ -22,7 +22,7 @@ export class ProtocolMainService extends Disposable implements IProtocolMainServ
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly validRoots = TernarySearchTree.forUris<boolean>(() => !isLinux);
|
||||
private readonly validExtensions = new Set(['.png', '.jpg', '.jpeg', '.gif', '.bmp']); // https://github.com/microsoft/vscode/issues/119384
|
||||
private readonly validExtensions = new Set(['.svg', '.png', '.jpg', '.jpeg', '.gif', '.bmp']); // https://github.com/microsoft/vscode/issues/119384
|
||||
|
||||
constructor(
|
||||
@INativeEnvironmentService environmentService: INativeEnvironmentService,
|
||||
@@ -47,10 +47,10 @@ export class ProtocolMainService extends Disposable implements IProtocolMainServ
|
||||
const { defaultSession } = session;
|
||||
|
||||
// Register vscode-file:// handler
|
||||
defaultSession.protocol.registerFileProtocol(Schemas.vscodeFileResource, (request, callback) => this.handleResourceRequest(request, callback as unknown as ProtocolCallback));
|
||||
defaultSession.protocol.registerFileProtocol(Schemas.vscodeFileResource, (request, callback) => this.handleResourceRequest(request, callback));
|
||||
|
||||
// Intercept any file:// access
|
||||
defaultSession.protocol.interceptFileProtocol(Schemas.file, (request, callback) => this.handleFileRequest(request, callback as unknown as ProtocolCallback));
|
||||
defaultSession.protocol.interceptFileProtocol(Schemas.file, (request, callback) => this.handleFileRequest(request, callback));
|
||||
|
||||
// Cleanup
|
||||
this._register(toDisposable(() => {
|
||||
@@ -142,7 +142,8 @@ export class ProtocolMainService extends Disposable implements IProtocolMainServ
|
||||
|
||||
//#region IPC Object URLs
|
||||
|
||||
createIPCObjectUrl<T>(obj: T): IIPCObjectUrl<T> {
|
||||
createIPCObjectUrl<T>(): IIPCObjectUrl<T> {
|
||||
let obj: T | undefined = undefined;
|
||||
|
||||
// Create unique URI
|
||||
const resource = URI.from({
|
||||
@@ -152,7 +153,7 @@ export class ProtocolMainService extends Disposable implements IProtocolMainServ
|
||||
|
||||
// Install IPC handler
|
||||
const channel = resource.toString();
|
||||
const handler = async (): Promise<T> => obj;
|
||||
const handler = async (): Promise<T | undefined> => obj;
|
||||
ipcMain.handle(channel, handler);
|
||||
|
||||
this.logService.trace(`IPC Object URL: Registered new channel ${channel}.`);
|
||||
|
||||
@@ -19,7 +19,8 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
|
||||
export interface ICommandQuickPick extends IPickerQuickAccessItem {
|
||||
@@ -47,14 +48,14 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@INotificationService private readonly notificationService: INotificationService
|
||||
@IDialogService private readonly dialogService: IDialogService
|
||||
) {
|
||||
super(AbstractCommandsQuickAccessProvider.PREFIX, options);
|
||||
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
protected async getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise<Array<ICommandQuickPick | IQuickPickSeparator>> {
|
||||
protected async _getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise<Array<ICommandQuickPick | IQuickPickSeparator>> {
|
||||
|
||||
// Ask subclass for all command picks
|
||||
const allCommandPicks = await this.getCommandPicks(disposables, token);
|
||||
@@ -162,7 +163,7 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc
|
||||
await this.commandService.executeCommand(commandPick.commandId);
|
||||
} catch (error) {
|
||||
if (!isPromiseCanceledError(error)) {
|
||||
this.notificationService.error(localize('canNotRun', "Command '{0}' resulted in an error ({1})", commandPick.label, toErrorMessage(error)));
|
||||
this.dialogService.show(Severity.Error, localize('canNotRun', "Command '{0}' resulted in an error ({1})", commandPick.label, toErrorMessage(error)), [localize('ok', 'OK')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent } from 'vs/base/parts/quickinput/common/quickInput';
|
||||
import { IQuickPickSeparator, IKeyMods, IQuickPickDidAcceptEvent } from 'vs/base/parts/quickinput/common/quickInput';
|
||||
import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
|
||||
import { IDisposable, DisposableStore, Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
@@ -42,7 +42,7 @@ export interface IPickerQuickAccessItem extends IQuickPickItem {
|
||||
* @param keyMods the state of modifier keys when the item was accepted.
|
||||
* @param event the underlying event that caused the accept to trigger.
|
||||
*/
|
||||
accept?(keyMods: IKeyMods, event: IQuickPickAcceptEvent): void;
|
||||
accept?(keyMods: IKeyMods, event: IQuickPickDidAcceptEvent): void;
|
||||
|
||||
/**
|
||||
* A method that will be executed when a button of the pick item was
|
||||
@@ -122,7 +122,7 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
|
||||
// Collect picks and support both long running and short or combined
|
||||
const picksToken = picksCts.token;
|
||||
const picksFilter = picker.value.substr(this.prefix.length).trim();
|
||||
const providedPicks = this.getPicks(picksFilter, picksDisposables, picksToken);
|
||||
const providedPicks = this._getPicks(picksFilter, picksDisposables, picksToken);
|
||||
|
||||
const applyPicks = (picks: Picks<T>, skipEmpty?: boolean): boolean => {
|
||||
let items: readonly Pick<T>[];
|
||||
@@ -330,5 +330,5 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
|
||||
* @returns the picks either directly, as promise or combined fast and slow results.
|
||||
* Pickers can return `null` to signal that no change in picks is needed.
|
||||
*/
|
||||
protected abstract getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Picks<T> | Promise<Picks<T>> | FastAndSlowPicks<T> | null;
|
||||
protected abstract _getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Picks<T> | Promise<Picks<T>> | FastAndSlowPicks<T> | null;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,17 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
|
||||
super();
|
||||
}
|
||||
|
||||
pick(value = '', options?: IQuickAccessOptions): Promise<IQuickPickItem[] | undefined> {
|
||||
return this.doShowOrPick(value, true, options);
|
||||
}
|
||||
|
||||
show(value = '', options?: IQuickAccessOptions): void {
|
||||
this.doShowOrPick(value, false, options);
|
||||
}
|
||||
|
||||
private doShowOrPick(value: string, pick: true, options?: IQuickAccessOptions): Promise<IQuickPickItem[] | undefined>;
|
||||
private doShowOrPick(value: string, pick: false, options?: IQuickAccessOptions): void;
|
||||
private doShowOrPick(value: string, pick: boolean, options?: IQuickAccessOptions): Promise<IQuickPickItem[] | undefined> | void {
|
||||
|
||||
// Find provider for the value to show
|
||||
const [provider, descriptor] = this.getOrInstantiateProvider(value);
|
||||
@@ -99,6 +109,18 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
|
||||
picker.ariaLabel = descriptor?.placeholder;
|
||||
}
|
||||
|
||||
// Pick mode: setup a promise that can be resolved
|
||||
// with the selected items and prevent execution
|
||||
let pickPromise: Promise<IQuickPickItem[]> | undefined = undefined;
|
||||
let pickResolve: Function | undefined = undefined;
|
||||
if (pick) {
|
||||
pickPromise = new Promise<IQuickPickItem[]>(resolve => pickResolve = resolve);
|
||||
disposables.add(once(picker.onWillAccept)(e => {
|
||||
e.veto();
|
||||
picker.hide();
|
||||
}));
|
||||
}
|
||||
|
||||
// Register listeners
|
||||
disposables.add(this.registerPickerListeners(picker, provider, descriptor, value));
|
||||
|
||||
@@ -119,12 +141,20 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
|
||||
|
||||
// Start to dispose once picker hides
|
||||
disposables.dispose();
|
||||
|
||||
// Resolve pick promise with selected items
|
||||
pickResolve?.(picker.selectedItems);
|
||||
});
|
||||
|
||||
// Finally, show the picker. This is important because a provider
|
||||
// may not call this and then our disposables would leak that rely
|
||||
// on the onDidHide event.
|
||||
picker.show();
|
||||
|
||||
// Pick mode: return with promise
|
||||
if (pick) {
|
||||
return pickPromise;
|
||||
}
|
||||
}
|
||||
|
||||
private adjustValueSelection(picker: IQuickPick<IQuickPickItem>, descriptor?: IQuickAccessProviderDescriptor, options?: IQuickAccessOptions): void {
|
||||
|
||||
@@ -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, keybindingLabelBackground, keybindingLabelForeground, keybindingLabelBorder, keybindingLabelBottomBorder } 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, activeContrastBorder, pickerGroupBorder, pickerGroupForeground, quickInputForeground, quickInputBackground, quickInputTitleBackground, quickInputListFocusBackground, keybindingLabelBackground, keybindingLabelForeground, keybindingLabelBorder, keybindingLabelBottomBorder, quickInputListFocusForeground } 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';
|
||||
@@ -219,7 +219,7 @@ export class QuickInputService extends Themable implements IQuickInputService {
|
||||
list: computeStyles(this.theme, {
|
||||
listBackground: quickInputBackground,
|
||||
// Look like focused when inactive.
|
||||
listInactiveFocusForeground: listFocusForeground,
|
||||
listInactiveFocusForeground: quickInputListFocusForeground,
|
||||
listInactiveFocusBackground: quickInputListFocusBackground,
|
||||
listFocusOutline: activeContrastBorder,
|
||||
listInactiveFocusOutline: activeContrastBorder,
|
||||
|
||||
@@ -36,6 +36,13 @@ export interface IQuickAccessController {
|
||||
* Open the quick access picker with the optional value prefilled.
|
||||
*/
|
||||
show(value?: string, options?: IQuickAccessOptions): void;
|
||||
|
||||
/**
|
||||
* Same as `show()` but instead of executing the selected pick item,
|
||||
* it will be returned. May return `undefined` in case no item was
|
||||
* picked by the user.
|
||||
*/
|
||||
pick(value?: string, options?: IQuickAccessOptions): Promise<IQuickPickItem[] | undefined>;
|
||||
}
|
||||
|
||||
export enum DefaultQuickAccessFilterValue {
|
||||
|
||||
@@ -217,3 +217,6 @@ export class BrowserSocketFactory implements ISocketFactory {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -40,6 +40,10 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
||||
return this._cache.get(authority)!;
|
||||
}
|
||||
|
||||
async getCanonicalURI(uri: URI): Promise<URI> {
|
||||
return uri;
|
||||
}
|
||||
|
||||
getConnectionData(authority: string): IRemoteConnectionData | null {
|
||||
if (!this._cache.has(authority)) {
|
||||
return null;
|
||||
@@ -76,4 +80,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
||||
RemoteAuthorities.setConnectionToken(authority, connectionToken);
|
||||
this._onDidChangeConnectionData.fire();
|
||||
}
|
||||
|
||||
_setCanonicalURIProvider(provider: (uri: URI) => Promise<URI>): void {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export const IRemoteAuthorityResolverService = createDecorator<IRemoteAuthorityResolverService>('remoteAuthorityResolverService');
|
||||
|
||||
@@ -15,15 +16,9 @@ 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;
|
||||
readonly isTrusted?: boolean;
|
||||
}
|
||||
|
||||
export interface TunnelDescription {
|
||||
@@ -98,9 +93,18 @@ export interface IRemoteAuthorityResolverService {
|
||||
|
||||
resolveAuthority(authority: string): Promise<ResolverResult>;
|
||||
getConnectionData(authority: string): IRemoteConnectionData | null;
|
||||
/**
|
||||
* Get the canonical URI for a `vscode-remote://` URI.
|
||||
*
|
||||
* **NOTE**: This can throw e.g. in cases where there is no resolver installed for the specific remote authority.
|
||||
*
|
||||
* @param uri The `vscode-remote://` URI
|
||||
*/
|
||||
getCanonicalURI(uri: URI): Promise<URI>;
|
||||
|
||||
_clearResolvedAuthority(authority: string): void;
|
||||
_setResolvedAuthority(resolvedAuthority: ResolvedAuthority, resolvedOptions?: ResolvedOptions): void;
|
||||
_setResolvedAuthorityError(authority: string, err: any): void;
|
||||
_setAuthorityConnectionToken(authority: string, connectionToken: string): void;
|
||||
_setCanonicalURIProvider(provider: (uri: URI) => Promise<URI>): void;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export function getRemoteName(authority: string | undefined): string | undefined
|
||||
return authority.substr(0, pos);
|
||||
}
|
||||
|
||||
function isVirtualResource(resource: URI) {
|
||||
export function isVirtualResource(resource: URI) {
|
||||
return resource.scheme !== Schemas.file && resource.scheme !== Schemas.vscodeRemote;
|
||||
}
|
||||
|
||||
@@ -42,3 +42,7 @@ export function getVirtualWorkspaceLocation(workspace: IWorkspace): { scheme: st
|
||||
export function getVirtualWorkspaceScheme(workspace: IWorkspace): string | undefined {
|
||||
return getVirtualWorkspaceLocation(workspace)?.scheme;
|
||||
}
|
||||
|
||||
export function isVirtualWorkspace(workspace: IWorkspace): boolean {
|
||||
return getVirtualWorkspaceLocation(workspace) !== undefined;
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ export interface ITunnelService {
|
||||
readonly onTunnelOpened: Event<RemoteTunnel>;
|
||||
readonly onTunnelClosed: Event<{ host: string, port: number; }>;
|
||||
readonly canElevate: boolean;
|
||||
readonly hasTunnelProvider: boolean;
|
||||
|
||||
canTunnel(uri: URI): boolean;
|
||||
openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number, elevateIfNeeded?: boolean, isPublic?: boolean): Promise<RemoteTunnel | undefined> | undefined;
|
||||
@@ -141,6 +142,10 @@ export abstract class AbstractTunnelService implements ITunnelService {
|
||||
@ILogService protected readonly logService: ILogService
|
||||
) { }
|
||||
|
||||
get hasTunnelProvider(): boolean {
|
||||
return !!this._tunnelProvider;
|
||||
}
|
||||
|
||||
setTunnelProvider(provider: ITunnelProvider | undefined, features: TunnelProviderFeatures): IDisposable {
|
||||
this._tunnelProvider = provider;
|
||||
if (!provider) {
|
||||
|
||||
@@ -8,22 +8,27 @@ import * as errors from 'vs/base/common/errors';
|
||||
import { RemoteAuthorities } from 'vs/base/common/network';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
class PendingResolveAuthorityRequest {
|
||||
class PendingPromise<I, R> {
|
||||
public readonly promise: Promise<R>;
|
||||
public readonly input: I;
|
||||
public result: R | null;
|
||||
private _resolve!: (value: R) => void;
|
||||
private _reject!: (err: any) => void;
|
||||
|
||||
public value: ResolverResult | null;
|
||||
|
||||
constructor(
|
||||
private readonly _resolve: (value: ResolverResult) => void,
|
||||
private readonly _reject: (err: any) => void,
|
||||
public readonly promise: Promise<ResolverResult>,
|
||||
) {
|
||||
this.value = null;
|
||||
constructor(request: I) {
|
||||
this.input = request;
|
||||
this.promise = new Promise<R>((resolve, reject) => {
|
||||
this._resolve = resolve;
|
||||
this._reject = reject;
|
||||
});
|
||||
this.result = null;
|
||||
}
|
||||
|
||||
resolve(value: ResolverResult): void {
|
||||
this.value = value;
|
||||
this._resolve(this.value);
|
||||
resolve(result: R): void {
|
||||
this.result = result;
|
||||
this._resolve(this.result);
|
||||
}
|
||||
|
||||
reject(err: any): void {
|
||||
@@ -38,40 +43,50 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
||||
private readonly _onDidChangeConnectionData = this._register(new Emitter<void>());
|
||||
public readonly onDidChangeConnectionData = this._onDidChangeConnectionData.event;
|
||||
|
||||
private readonly _resolveAuthorityRequests: Map<string, PendingResolveAuthorityRequest>;
|
||||
private readonly _resolveAuthorityRequests: Map<string, PendingPromise<string, ResolverResult>>;
|
||||
private readonly _connectionTokens: Map<string, string>;
|
||||
private readonly _canonicalURIRequests: Map<string, PendingPromise<URI, URI>>;
|
||||
private _canonicalURIProvider: ((uri: URI) => Promise<URI>) | null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._resolveAuthorityRequests = new Map<string, PendingResolveAuthorityRequest>();
|
||||
this._resolveAuthorityRequests = new Map<string, PendingPromise<string, ResolverResult>>();
|
||||
this._connectionTokens = new Map<string, string>();
|
||||
this._canonicalURIRequests = new Map<string, PendingPromise<URI, URI>>();
|
||||
this._canonicalURIProvider = null;
|
||||
}
|
||||
|
||||
resolveAuthority(authority: string): Promise<ResolverResult> {
|
||||
if (!this._resolveAuthorityRequests.has(authority)) {
|
||||
let resolve: (value: ResolverResult) => void;
|
||||
let reject: (err: any) => void;
|
||||
const promise = new Promise<ResolverResult>((_resolve, _reject) => {
|
||||
resolve = _resolve;
|
||||
reject = _reject;
|
||||
});
|
||||
this._resolveAuthorityRequests.set(authority, new PendingResolveAuthorityRequest(resolve!, reject!, promise));
|
||||
this._resolveAuthorityRequests.set(authority, new PendingPromise<string, ResolverResult>(authority));
|
||||
}
|
||||
return this._resolveAuthorityRequests.get(authority)!.promise;
|
||||
}
|
||||
|
||||
async getCanonicalURI(uri: URI): Promise<URI> {
|
||||
const key = uri.toString();
|
||||
if (!this._canonicalURIRequests.has(key)) {
|
||||
const request = new PendingPromise<URI, URI>(uri);
|
||||
if (this._canonicalURIProvider) {
|
||||
this._canonicalURIProvider(request.input).then((uri) => request.resolve(uri), (err) => request.reject(err));
|
||||
}
|
||||
this._canonicalURIRequests.set(key, request);
|
||||
}
|
||||
return this._canonicalURIRequests.get(key)!.promise;
|
||||
}
|
||||
|
||||
getConnectionData(authority: string): IRemoteConnectionData | null {
|
||||
if (!this._resolveAuthorityRequests.has(authority)) {
|
||||
return null;
|
||||
}
|
||||
const request = this._resolveAuthorityRequests.get(authority)!;
|
||||
if (!request.value) {
|
||||
if (!request.result) {
|
||||
return null;
|
||||
}
|
||||
const connectionToken = this._connectionTokens.get(authority);
|
||||
return {
|
||||
host: request.value.authority.host,
|
||||
port: request.value.authority.port,
|
||||
host: request.result.authority.host,
|
||||
port: request.result.authority.port,
|
||||
connectionToken: connectionToken
|
||||
};
|
||||
}
|
||||
@@ -107,4 +122,11 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot
|
||||
RemoteAuthorities.setConnectionToken(authority, connectionToken);
|
||||
this._onDidChangeConnectionData.fire();
|
||||
}
|
||||
|
||||
_setCanonicalURIProvider(provider: (uri: URI) => Promise<URI>): void {
|
||||
this._canonicalURIProvider = provider;
|
||||
this._canonicalURIRequests.forEach((value) => {
|
||||
this._canonicalURIProvider!(value.input).then((uri) => value.resolve(uri), (err) => value.reject(err));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Barrier } from 'vs/base/common/async';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { findFreePortFaster } from 'vs/base/node/ports';
|
||||
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { connectRemoteAgentTunnel, IConnectionOptions, IAddressProvider, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
@@ -15,8 +16,8 @@ import { AbstractTunnelService, RemoteTunnel } from 'vs/platform/remote/common/t
|
||||
import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
|
||||
async function createRemoteTunnel(options: IConnectionOptions, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise<RemoteTunnel> {
|
||||
const tunnel = new NodeRemoteTunnel(options, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort);
|
||||
async function createRemoteTunnel(options: IConnectionOptions, defaultTunnelHost: string, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise<RemoteTunnel> {
|
||||
const tunnel = new NodeRemoteTunnel(options, defaultTunnelHost, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort);
|
||||
return tunnel.waitForReady();
|
||||
}
|
||||
|
||||
@@ -38,7 +39,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
|
||||
|
||||
private readonly _socketsDispose: Map<string, () => void> = new Map();
|
||||
|
||||
constructor(options: IConnectionOptions, tunnelRemoteHost: string, tunnelRemotePort: number, private readonly suggestedLocalPort?: number) {
|
||||
constructor(options: IConnectionOptions, private readonly defaultTunnelHost: string, tunnelRemoteHost: string, tunnelRemotePort: number, private readonly suggestedLocalPort?: number) {
|
||||
super();
|
||||
this._options = options;
|
||||
this._server = net.createServer();
|
||||
@@ -76,17 +77,19 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
|
||||
|
||||
// if that fails, the method above returns 0, which works out fine below...
|
||||
let address: string | net.AddressInfo | null = null;
|
||||
address = (<net.AddressInfo>this._server.listen(localPort).address());
|
||||
this._server.listen(localPort, this.defaultTunnelHost);
|
||||
await this._barrier.wait();
|
||||
address = <net.AddressInfo>this._server.address();
|
||||
|
||||
// It is possible for findFreePortFaster to return a port that there is already a server listening on. This causes the previous listen call to error out.
|
||||
if (!address) {
|
||||
localPort = 0;
|
||||
address = (<net.AddressInfo>this._server.listen(localPort).address());
|
||||
this._server.listen(localPort, this.defaultTunnelHost);
|
||||
await this._barrier.wait();
|
||||
address = <net.AddressInfo>this._server.address();
|
||||
}
|
||||
|
||||
this.tunnelLocalPort = address.port;
|
||||
|
||||
await this._barrier.wait();
|
||||
this.localAddress = `${this.tunnelRemoteHost === '127.0.0.1' ? '127.0.0.1' : 'localhost'}:${address.port}`;
|
||||
return this;
|
||||
}
|
||||
@@ -135,11 +138,16 @@ export class BaseTunnelService extends AbstractTunnelService {
|
||||
private readonly socketFactory: ISocketFactory,
|
||||
@ILogService logService: ILogService,
|
||||
@ISignService private readonly signService: ISignService,
|
||||
@IProductService private readonly productService: IProductService
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super(logService);
|
||||
}
|
||||
|
||||
private get defaultTunnelHost(): string {
|
||||
return (this.configurationService.getValue('remote.localPortHost') === 'localhost') ? '127.0.0.1' : '0.0.0.0';
|
||||
}
|
||||
|
||||
protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, isPublic: boolean): Promise<RemoteTunnel | undefined> | undefined {
|
||||
const existing = this.getTunnelFromMap(remoteHost, remotePort);
|
||||
if (existing) {
|
||||
@@ -160,7 +168,7 @@ export class BaseTunnelService extends AbstractTunnelService {
|
||||
ipcLogger: null
|
||||
};
|
||||
|
||||
const tunnel = createRemoteTunnel(options, remoteHost, remotePort, localPort);
|
||||
const tunnel = createRemoteTunnel(options, this.defaultTunnelHost, remoteHost, remotePort, localPort);
|
||||
this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created without provider.');
|
||||
this.addTunnelToMap(remoteHost, remotePort, tunnel);
|
||||
return tunnel;
|
||||
@@ -172,8 +180,9 @@ export class TunnelService extends BaseTunnelService {
|
||||
public constructor(
|
||||
@ILogService logService: ILogService,
|
||||
@ISignService signService: ISignService,
|
||||
@IProductService productService: IProductService
|
||||
@IProductService productService: IProductService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super(nodeSocketFactory, logService, signService, productService);
|
||||
super(nodeSocketFactory, logService, signService, productService, configurationService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import { connect as connectMessagePort } from 'vs/base/parts/ipc/electron-main/i
|
||||
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 {
|
||||
@@ -139,9 +138,6 @@ export class SharedProcess extends Disposable implements ISharedProcess {
|
||||
// Always wait for first window asking for connection
|
||||
await this.firstWindowConnectionBarrier.wait();
|
||||
|
||||
// Resolve shell environment
|
||||
this.userEnv = { ...this.userEnv, ...(await resolveShellEnv(this.logService, this.environmentMainService.args, process.env)) };
|
||||
|
||||
// Create window for shared process
|
||||
this.createWindow();
|
||||
|
||||
|
||||
@@ -5,15 +5,19 @@
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const IStateService = createDecorator<IStateService>('stateService');
|
||||
export const IStateMainService = createDecorator<IStateMainService>('stateMainService');
|
||||
|
||||
export interface IStateMainService {
|
||||
|
||||
export interface IStateService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
getItem<T>(key: string, defaultValue: T): T;
|
||||
getItem<T>(key: string, defaultValue?: T): T | undefined;
|
||||
|
||||
setItem(key: string, data?: object | string | number | boolean | undefined | null): void;
|
||||
setItems(items: readonly { key: string, data?: object | string | number | boolean | undefined | null }[]): void;
|
||||
|
||||
removeItem(key: string): void;
|
||||
|
||||
close(): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { IStateMainService } from 'vs/platform/state/electron-main/state';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
|
||||
import { ThrottledDelayer } from 'vs/base/common/async';
|
||||
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
type StorageDatabase = { [key: string]: unknown; };
|
||||
|
||||
export class FileStorage {
|
||||
|
||||
private storage: StorageDatabase = Object.create(null);
|
||||
private lastSavedStorageContents = '';
|
||||
|
||||
private readonly flushDelayer = new ThrottledDelayer<void>(100 /* buffer saves over a short time */);
|
||||
|
||||
private initializing: Promise<void> | undefined = undefined;
|
||||
private closing: Promise<void> | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
private readonly storagePath: URI,
|
||||
private readonly logService: ILogService,
|
||||
private readonly fileService: IFileService
|
||||
) {
|
||||
}
|
||||
|
||||
init(): Promise<void> {
|
||||
if (!this.initializing) {
|
||||
this.initializing = this.doInit();
|
||||
}
|
||||
|
||||
return this.initializing;
|
||||
}
|
||||
|
||||
private async doInit(): Promise<void> {
|
||||
try {
|
||||
this.lastSavedStorageContents = (await this.fileService.readFile(this.storagePath)).value.toString();
|
||||
this.storage = JSON.parse(this.lastSavedStorageContents);
|
||||
} catch (error) {
|
||||
if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getItem<T>(key: string, defaultValue: T): T;
|
||||
getItem<T>(key: string, defaultValue?: T): T | undefined;
|
||||
getItem<T>(key: string, defaultValue?: T): T | undefined {
|
||||
const res = this.storage[key];
|
||||
if (isUndefinedOrNull(res)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return res as T;
|
||||
}
|
||||
|
||||
setItem(key: string, data?: object | string | number | boolean | undefined | null): void {
|
||||
this.setItems([{ key, data }]);
|
||||
}
|
||||
|
||||
setItems(items: readonly { key: string, data?: object | string | number | boolean | undefined | null }[]): void {
|
||||
let save = false;
|
||||
|
||||
for (const { key, data } of items) {
|
||||
|
||||
// Shortcut for data that did not change
|
||||
if (this.storage[key] === data) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove items when they are undefined or null
|
||||
if (isUndefinedOrNull(data)) {
|
||||
if (!isUndefined(this.storage[key])) {
|
||||
this.storage[key] = undefined;
|
||||
save = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise add an item
|
||||
else {
|
||||
this.storage[key] = data;
|
||||
save = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (save) {
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
|
||||
removeItem(key: string): void {
|
||||
|
||||
// Only update if the key is actually present (not undefined)
|
||||
if (!isUndefined(this.storage[key])) {
|
||||
this.storage[key] = undefined;
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
|
||||
private async save(delay?: number): Promise<void> {
|
||||
if (this.closing) {
|
||||
return; // already about to close
|
||||
}
|
||||
|
||||
return this.flushDelayer.trigger(() => this.doSave(), delay);
|
||||
}
|
||||
|
||||
private async doSave(): Promise<void> {
|
||||
if (!this.initializing) {
|
||||
return; // if we never initialized, we should not save our state
|
||||
}
|
||||
|
||||
// Make sure to wait for init to finish first
|
||||
await this.initializing;
|
||||
|
||||
// Return early if the database has not changed
|
||||
const serializedDatabase = JSON.stringify(this.storage, null, 4);
|
||||
if (serializedDatabase === this.lastSavedStorageContents) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Write to disk
|
||||
try {
|
||||
await this.fileService.writeFile(this.storagePath, VSBuffer.fromString(serializedDatabase));
|
||||
this.lastSavedStorageContents = serializedDatabase;
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
if (!this.closing) {
|
||||
this.closing = this.flushDelayer.trigger(() => this.doSave(), 0 /* as soon as possible */);
|
||||
}
|
||||
|
||||
return this.closing;
|
||||
}
|
||||
}
|
||||
|
||||
export class StateMainService implements IStateMainService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private static readonly STATE_FILE = 'storage.json';
|
||||
|
||||
private readonly fileStorage: FileStorage;
|
||||
|
||||
constructor(
|
||||
@IEnvironmentMainService environmentMainService: IEnvironmentMainService,
|
||||
@ILogService logService: ILogService,
|
||||
@IFileService fileService: IFileService
|
||||
) {
|
||||
this.fileStorage = new FileStorage(URI.file(join(environmentMainService.userDataPath, StateMainService.STATE_FILE)), logService, fileService);
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
return this.fileStorage.init();
|
||||
}
|
||||
|
||||
getItem<T>(key: string, defaultValue: T): T;
|
||||
getItem<T>(key: string, defaultValue?: T): T | undefined;
|
||||
getItem<T>(key: string, defaultValue?: T): T | undefined {
|
||||
return this.fileStorage.getItem(key, defaultValue);
|
||||
}
|
||||
|
||||
setItem(key: string, data?: object | string | number | boolean | undefined | null): void {
|
||||
this.fileStorage.setItem(key, data);
|
||||
}
|
||||
|
||||
setItems(items: readonly { key: string, data?: object | string | number | boolean | undefined | null }[]): void {
|
||||
this.fileStorage.setItems(items);
|
||||
}
|
||||
|
||||
removeItem(key: string): void {
|
||||
this.fileStorage.removeItem(key);
|
||||
}
|
||||
|
||||
close(): Promise<void> {
|
||||
return this.fileStorage.close();
|
||||
}
|
||||
}
|
||||
@@ -1,158 +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 path from 'vs/base/common/path';
|
||||
import * as fs from 'fs';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { writeFileSync } from 'vs/base/node/pfs';
|
||||
import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
type StorageDatabase = { [key: string]: any; };
|
||||
|
||||
export class FileStorage {
|
||||
|
||||
private _database: StorageDatabase | null = null;
|
||||
private lastFlushedSerializedDatabase: string | null = null;
|
||||
|
||||
constructor(private dbPath: string, private onError: (error: Error) => void) { }
|
||||
|
||||
private get database(): StorageDatabase {
|
||||
if (!this._database) {
|
||||
this._database = this.loadSync();
|
||||
}
|
||||
|
||||
return this._database;
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
if (this._database) {
|
||||
return; // return if database was already loaded
|
||||
}
|
||||
|
||||
const database = await this.loadAsync();
|
||||
|
||||
if (this._database) {
|
||||
return; // return if database was already loaded
|
||||
}
|
||||
|
||||
this._database = database;
|
||||
}
|
||||
|
||||
private loadSync(): StorageDatabase {
|
||||
try {
|
||||
this.lastFlushedSerializedDatabase = fs.readFileSync(this.dbPath).toString();
|
||||
|
||||
return JSON.parse(this.lastFlushedSerializedDatabase);
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') {
|
||||
this.onError(error);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
private async loadAsync(): Promise<StorageDatabase> {
|
||||
try {
|
||||
this.lastFlushedSerializedDatabase = (await fs.promises.readFile(this.dbPath)).toString();
|
||||
|
||||
return JSON.parse(this.lastFlushedSerializedDatabase);
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') {
|
||||
this.onError(error);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
getItem<T>(key: string, defaultValue: T): T;
|
||||
getItem<T>(key: string, defaultValue?: T): T | undefined;
|
||||
getItem<T>(key: string, defaultValue?: T): T | undefined {
|
||||
const res = this.database[key];
|
||||
if (isUndefinedOrNull(res)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
setItem(key: string, data?: object | string | number | boolean | undefined | null): void {
|
||||
|
||||
// Remove an item when it is undefined or null
|
||||
if (isUndefinedOrNull(data)) {
|
||||
return this.removeItem(key);
|
||||
}
|
||||
|
||||
// Shortcut for primitives that did not change
|
||||
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
|
||||
if (this.database[key] === data) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.database[key] = data;
|
||||
this.saveSync();
|
||||
}
|
||||
|
||||
removeItem(key: string): void {
|
||||
|
||||
// Only update if the key is actually present (not undefined)
|
||||
if (!isUndefined(this.database[key])) {
|
||||
this.database[key] = undefined;
|
||||
this.saveSync();
|
||||
}
|
||||
}
|
||||
|
||||
private saveSync(): void {
|
||||
const serializedDatabase = JSON.stringify(this.database, null, 4);
|
||||
if (serializedDatabase === this.lastFlushedSerializedDatabase) {
|
||||
return; // return early if the database has not changed
|
||||
}
|
||||
|
||||
try {
|
||||
writeFileSync(this.dbPath, serializedDatabase); // permission issue can happen here
|
||||
this.lastFlushedSerializedDatabase = serializedDatabase;
|
||||
} catch (error) {
|
||||
this.onError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class StateService implements IStateService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private static readonly STATE_FILE = 'storage.json';
|
||||
|
||||
private fileStorage: FileStorage;
|
||||
|
||||
constructor(
|
||||
@INativeEnvironmentService environmentService: INativeEnvironmentService,
|
||||
@ILogService logService: ILogService
|
||||
) {
|
||||
this.fileStorage = new FileStorage(path.join(environmentService.userDataPath, StateService.STATE_FILE), error => logService.error(error));
|
||||
}
|
||||
|
||||
init(): Promise<void> {
|
||||
return this.fileStorage.init();
|
||||
}
|
||||
|
||||
getItem<T>(key: string, defaultValue: T): T;
|
||||
getItem<T>(key: string, defaultValue?: T): T | undefined;
|
||||
getItem<T>(key: string, defaultValue?: T): T | undefined {
|
||||
return this.fileStorage.getItem(key, defaultValue);
|
||||
}
|
||||
|
||||
setItem(key: string, data?: object | string | number | boolean | undefined | null): void {
|
||||
this.fileStorage.setItem(key, data);
|
||||
}
|
||||
|
||||
removeItem(key: string): void {
|
||||
this.fileStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { tmpdir } from 'os';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { FileStorage } from 'vs/platform/state/electron-main/stateMainService';
|
||||
import { Promises, rimraf, writeFileSync } from 'vs/base/node/pfs';
|
||||
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
flakySuite('StateMainService', () => {
|
||||
|
||||
let testDir: string;
|
||||
let fileService: IFileService;
|
||||
let logService: ILogService;
|
||||
let diskFileSystemProvider: DiskFileSystemProvider;
|
||||
|
||||
setup(() => {
|
||||
testDir = getRandomTestPath(tmpdir(), 'vsctests', 'statemainservice');
|
||||
|
||||
logService = new NullLogService();
|
||||
|
||||
fileService = new FileService(logService);
|
||||
diskFileSystemProvider = new DiskFileSystemProvider(logService);
|
||||
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
|
||||
|
||||
return Promises.mkdir(testDir, { recursive: true });
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
fileService.dispose();
|
||||
diskFileSystemProvider.dispose();
|
||||
|
||||
return rimraf(testDir);
|
||||
});
|
||||
|
||||
test('Basics', async function () {
|
||||
const storageFile = join(testDir, 'storage.json');
|
||||
writeFileSync(storageFile, '');
|
||||
|
||||
let service = new FileStorage(URI.file(storageFile), logService, fileService);
|
||||
await service.init();
|
||||
|
||||
service.setItem('some.key', 'some.value');
|
||||
assert.strictEqual(service.getItem('some.key'), 'some.value');
|
||||
|
||||
service.removeItem('some.key');
|
||||
assert.strictEqual(service.getItem('some.key', 'some.default'), 'some.default');
|
||||
|
||||
assert.ok(!service.getItem('some.unknonw.key'));
|
||||
|
||||
service.setItem('some.other.key', 'some.other.value');
|
||||
|
||||
await service.close();
|
||||
|
||||
service = new FileStorage(URI.file(storageFile), logService, fileService);
|
||||
await service.init();
|
||||
|
||||
assert.strictEqual(service.getItem('some.other.key'), 'some.other.value');
|
||||
|
||||
service.setItem('some.other.key', 'some.other.value');
|
||||
assert.strictEqual(service.getItem('some.other.key'), 'some.other.value');
|
||||
|
||||
service.setItem('some.undefined.key', undefined);
|
||||
assert.strictEqual(service.getItem('some.undefined.key', 'some.default'), 'some.default');
|
||||
|
||||
service.setItem('some.null.key', null);
|
||||
assert.strictEqual(service.getItem('some.null.key', 'some.default'), 'some.default');
|
||||
|
||||
service.setItems([
|
||||
{ key: 'some.setItems.key1', data: 'some.value' },
|
||||
{ key: 'some.setItems.key2', data: 0 },
|
||||
{ key: 'some.setItems.key3', data: true },
|
||||
{ key: 'some.setItems.key4', data: null },
|
||||
{ key: 'some.setItems.key5', data: undefined }
|
||||
]);
|
||||
|
||||
assert.strictEqual(service.getItem('some.setItems.key1'), 'some.value');
|
||||
assert.strictEqual(service.getItem('some.setItems.key2'), 0);
|
||||
assert.strictEqual(service.getItem('some.setItems.key3'), true);
|
||||
assert.strictEqual(service.getItem('some.setItems.key4'), undefined);
|
||||
assert.strictEqual(service.getItem('some.setItems.key5'), undefined);
|
||||
|
||||
service.setItems([
|
||||
{ key: 'some.setItems.key1', data: undefined },
|
||||
{ key: 'some.setItems.key2', data: undefined },
|
||||
{ key: 'some.setItems.key3', data: undefined },
|
||||
{ key: 'some.setItems.key4', data: null },
|
||||
{ key: 'some.setItems.key5', data: undefined }
|
||||
]);
|
||||
|
||||
assert.strictEqual(service.getItem('some.setItems.key1'), undefined);
|
||||
assert.strictEqual(service.getItem('some.setItems.key2'), undefined);
|
||||
assert.strictEqual(service.getItem('some.setItems.key3'), undefined);
|
||||
assert.strictEqual(service.getItem('some.setItems.key4'), undefined);
|
||||
assert.strictEqual(service.getItem('some.setItems.key5'), undefined);
|
||||
});
|
||||
|
||||
test('Multiple ops are buffered and applied', async function () {
|
||||
const storageFile = join(testDir, 'storage.json');
|
||||
writeFileSync(storageFile, '');
|
||||
|
||||
let service = new FileStorage(URI.file(storageFile), logService, fileService);
|
||||
await service.init();
|
||||
|
||||
service.setItem('some.key1', 'some.value1');
|
||||
service.setItem('some.key2', 'some.value2');
|
||||
service.setItem('some.key3', 'some.value3');
|
||||
service.setItem('some.key4', 'some.value4');
|
||||
service.removeItem('some.key4');
|
||||
|
||||
assert.strictEqual(service.getItem('some.key1'), 'some.value1');
|
||||
assert.strictEqual(service.getItem('some.key2'), 'some.value2');
|
||||
assert.strictEqual(service.getItem('some.key3'), 'some.value3');
|
||||
assert.strictEqual(service.getItem('some.key4'), undefined);
|
||||
|
||||
await service.close();
|
||||
|
||||
service = new FileStorage(URI.file(storageFile), logService, fileService);
|
||||
await service.init();
|
||||
|
||||
assert.strictEqual(service.getItem('some.key1'), 'some.value1');
|
||||
assert.strictEqual(service.getItem('some.key2'), 'some.value2');
|
||||
assert.strictEqual(service.getItem('some.key3'), 'some.value3');
|
||||
assert.strictEqual(service.getItem('some.key4'), undefined);
|
||||
});
|
||||
|
||||
test('Used before init', async function () {
|
||||
const storageFile = join(testDir, 'storage.json');
|
||||
writeFileSync(storageFile, '');
|
||||
|
||||
let service = new FileStorage(URI.file(storageFile), logService, fileService);
|
||||
|
||||
service.setItem('some.key1', 'some.value1');
|
||||
service.setItem('some.key2', 'some.value2');
|
||||
service.setItem('some.key3', 'some.value3');
|
||||
service.setItem('some.key4', 'some.value4');
|
||||
service.removeItem('some.key4');
|
||||
|
||||
assert.strictEqual(service.getItem('some.key1'), 'some.value1');
|
||||
assert.strictEqual(service.getItem('some.key2'), 'some.value2');
|
||||
assert.strictEqual(service.getItem('some.key3'), 'some.value3');
|
||||
assert.strictEqual(service.getItem('some.key4'), undefined);
|
||||
|
||||
await service.init();
|
||||
|
||||
assert.strictEqual(service.getItem('some.key1'), 'some.value1');
|
||||
assert.strictEqual(service.getItem('some.key2'), 'some.value2');
|
||||
assert.strictEqual(service.getItem('some.key3'), 'some.value3');
|
||||
assert.strictEqual(service.getItem('some.key4'), undefined);
|
||||
});
|
||||
|
||||
test('Used after close', async function () {
|
||||
const storageFile = join(testDir, 'storage.json');
|
||||
writeFileSync(storageFile, '');
|
||||
|
||||
const service = new FileStorage(URI.file(storageFile), logService, fileService);
|
||||
|
||||
await service.init();
|
||||
|
||||
service.setItem('some.key1', 'some.value1');
|
||||
service.setItem('some.key2', 'some.value2');
|
||||
service.setItem('some.key3', 'some.value3');
|
||||
service.setItem('some.key4', 'some.value4');
|
||||
|
||||
await service.close();
|
||||
|
||||
service.setItem('some.key5', 'some.marker');
|
||||
|
||||
const contents = readFileSync(storageFile).toString();
|
||||
assert.ok(contents.includes('some.value1'));
|
||||
assert.ok(!contents.includes('some.marker'));
|
||||
|
||||
await service.close();
|
||||
});
|
||||
|
||||
test('Closed before init', async function () {
|
||||
const storageFile = join(testDir, 'storage.json');
|
||||
writeFileSync(storageFile, '');
|
||||
|
||||
const service = new FileStorage(URI.file(storageFile), logService, fileService);
|
||||
|
||||
service.setItem('some.key1', 'some.value1');
|
||||
service.setItem('some.key2', 'some.value2');
|
||||
service.setItem('some.key3', 'some.value3');
|
||||
service.setItem('some.key4', 'some.value4');
|
||||
|
||||
await service.close();
|
||||
|
||||
const contents = readFileSync(storageFile).toString();
|
||||
assert.strictEqual(contents.length, 0);
|
||||
});
|
||||
});
|
||||
@@ -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 * as assert from 'assert';
|
||||
import { tmpdir } from 'os';
|
||||
import { promises } from 'fs';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { FileStorage } from 'vs/platform/state/node/stateService';
|
||||
import { rimraf, writeFileSync } from 'vs/base/node/pfs';
|
||||
|
||||
flakySuite('StateService', () => {
|
||||
|
||||
let testDir: string;
|
||||
|
||||
setup(() => {
|
||||
testDir = getRandomTestPath(tmpdir(), 'vsctests', 'stateservice');
|
||||
|
||||
return promises.mkdir(testDir, { recursive: true });
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
return rimraf(testDir);
|
||||
});
|
||||
|
||||
test('Basics', async function () {
|
||||
const storageFile = join(testDir, 'storage.json');
|
||||
writeFileSync(storageFile, '');
|
||||
|
||||
let service = new FileStorage(storageFile, () => null);
|
||||
|
||||
service.setItem('some.key', 'some.value');
|
||||
assert.strictEqual(service.getItem('some.key'), 'some.value');
|
||||
|
||||
service.removeItem('some.key');
|
||||
assert.strictEqual(service.getItem('some.key', 'some.default'), 'some.default');
|
||||
|
||||
assert.ok(!service.getItem('some.unknonw.key'));
|
||||
|
||||
service.setItem('some.other.key', 'some.other.value');
|
||||
|
||||
service = new FileStorage(storageFile, () => null);
|
||||
|
||||
assert.strictEqual(service.getItem('some.other.key'), 'some.other.value');
|
||||
|
||||
service.setItem('some.other.key', 'some.other.value');
|
||||
assert.strictEqual(service.getItem('some.other.key'), 'some.other.value');
|
||||
|
||||
service.setItem('some.undefined.key', undefined);
|
||||
assert.strictEqual(service.getItem('some.undefined.key', 'some.default'), 'some.default');
|
||||
|
||||
service.setItem('some.null.key', null);
|
||||
assert.strictEqual(service.getItem('some.null.key', 'some.default'), 'some.default');
|
||||
});
|
||||
});
|
||||
@@ -105,6 +105,8 @@ export interface IStorageService {
|
||||
*
|
||||
* @param target allows to define the target of the storage operation
|
||||
* to either the current machine or user.
|
||||
*
|
||||
* NOTE@coder: Add a promise so extensions can await storage writes.
|
||||
*/
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope, target: StorageTarget): Promise<void> | void;
|
||||
|
||||
@@ -333,46 +335,49 @@ export abstract class AbstractStorageService extends Disposable implements IStor
|
||||
return this.getStorage(scope)?.getNumber(key, fallbackValue);
|
||||
}
|
||||
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope, target: StorageTarget): void {
|
||||
// NOTE@coder: Make a promise so extensions can await storage writes.
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope, target: StorageTarget): Promise<void> | void {
|
||||
|
||||
// We remove the key for undefined/null values
|
||||
if (isUndefinedOrNull(value)) {
|
||||
this.remove(key, scope);
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Update our datastructures but send events only after
|
||||
this.withPausedEmitters(() => {
|
||||
return this.withPausedEmitters(() => {
|
||||
|
||||
// Update key-target map
|
||||
this.updateKeyTarget(key, scope, target);
|
||||
|
||||
// Store actual value
|
||||
this.getStorage(scope)?.set(key, value);
|
||||
return this.getStorage(scope)?.set(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
remove(key: string, scope: StorageScope): void {
|
||||
// NOTE@coder: Make a promise so extensions can await the storage write.
|
||||
remove(key: string, scope: StorageScope): Promise<void> | void {
|
||||
|
||||
// Update our datastructures but send events only after
|
||||
this.withPausedEmitters(() => {
|
||||
return this.withPausedEmitters(() => {
|
||||
|
||||
// Update key-target map
|
||||
this.updateKeyTarget(key, scope, undefined);
|
||||
|
||||
// Remove actual key
|
||||
this.getStorage(scope)?.delete(key);
|
||||
return this.getStorage(scope)?.delete(key);
|
||||
});
|
||||
}
|
||||
|
||||
private withPausedEmitters(fn: Function): void {
|
||||
// NOTE@coder: Return the function's return so extensions can await the storage write.
|
||||
private withPausedEmitters<T>(fn: () => T): T {
|
||||
|
||||
// Pause emitters
|
||||
this._onDidChangeValue.pause();
|
||||
this._onDidChangeTarget.pause();
|
||||
|
||||
try {
|
||||
fn();
|
||||
return fn();
|
||||
} finally {
|
||||
|
||||
// Resume emitters
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { promises } from 'fs';
|
||||
import { exists, writeFile } from 'vs/base/node/pfs';
|
||||
import { exists, Promises, writeFile } from 'vs/base/node/pfs';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
|
||||
@@ -276,7 +275,7 @@ export class WorkspaceStorageMain extends BaseStorageMain implements IStorageMai
|
||||
}
|
||||
|
||||
// Ensure storage folder exists
|
||||
await promises.mkdir(workspaceStorageFolderPath, { recursive: true });
|
||||
await Promises.mkdir(workspaceStorageFolderPath, { recursive: true });
|
||||
|
||||
// Write metadata into folder (but do not await)
|
||||
this.ensureWorkspaceStorageFolderMeta(workspaceStorageFolderPath);
|
||||
|
||||
@@ -66,8 +66,8 @@ suite('StorageMainService', function () {
|
||||
registerWindow(window: ICodeWindow): void { }
|
||||
async reload(window: ICodeWindow, cli?: NativeParsedArgs): Promise<void> { }
|
||||
async unload(window: ICodeWindow, reason: UnloadReason): Promise<boolean> { return true; }
|
||||
relaunch(options?: { addArgs?: string[] | undefined; removeArgs?: string[] | undefined; }): void { }
|
||||
async quit(fromUpdate?: boolean): Promise<boolean> { return true; }
|
||||
async relaunch(options?: { addArgs?: string[] | undefined; removeArgs?: string[] | undefined; }): Promise<void> { }
|
||||
async quit(willRestart?: boolean): Promise<boolean> { return true; }
|
||||
async kill(code?: number): Promise<void> { }
|
||||
async when(phase: LifecycleMainPhase): Promise<void> { }
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ export async function resolveCommonProperties(
|
||||
return result;
|
||||
}
|
||||
|
||||
function verifyMicrosoftInternalDomain(domainList: readonly string[]): boolean {
|
||||
export function verifyMicrosoftInternalDomain(domainList: readonly string[]): boolean {
|
||||
const userDnsDomain = env['USERDNSDOMAIN'];
|
||||
if (!userDnsDomain) {
|
||||
return false;
|
||||
|
||||
@@ -8,6 +8,85 @@ import { Event } from 'vs/base/common/event';
|
||||
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';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export const enum TerminalSettingPrefix {
|
||||
Shell = 'terminal.integrated.shell.',
|
||||
ShellArgs = 'terminal.integrated.shellArgs.',
|
||||
DefaultProfile = 'terminal.integrated.defaultProfile.',
|
||||
Profiles = 'terminal.integrated.profiles.'
|
||||
}
|
||||
|
||||
export const enum TerminalSettingId {
|
||||
ShellLinux = 'terminal.integrated.shell.linux',
|
||||
ShellMacOs = 'terminal.integrated.shell.osx',
|
||||
ShellWindows = 'terminal.integrated.shell.windows',
|
||||
SendKeybindingsToShell = 'terminal.integrated.sendKeybindingsToShell',
|
||||
AutomationShellLinux = 'terminal.integrated.automationShell.linux',
|
||||
AutomationShellMacOs = 'terminal.integrated.automationShell.osx',
|
||||
AutomationShellWindows = 'terminal.integrated.automationShell.windows',
|
||||
ShellArgsLinux = 'terminal.integrated.shellArgs.linux',
|
||||
ShellArgsMacOs = 'terminal.integrated.shellArgs.osx',
|
||||
ShellArgsWindows = 'terminal.integrated.shellArgs.windows',
|
||||
ProfilesWindows = 'terminal.integrated.profiles.windows',
|
||||
ProfilesMacOs = 'terminal.integrated.profiles.osx',
|
||||
ProfilesLinux = 'terminal.integrated.profiles.linux',
|
||||
DefaultProfileLinux = 'terminal.integrated.defaultProfile.linux',
|
||||
DefaultProfileMacOs = 'terminal.integrated.defaultProfile.osx',
|
||||
DefaultProfileWindows = 'terminal.integrated.defaultProfile.windows',
|
||||
UseWslProfiles = 'terminal.integrated.useWslProfiles',
|
||||
TabsEnabled = 'terminal.integrated.tabs.enabled',
|
||||
TabsHideCondition = 'terminal.integrated.tabs.hideCondition',
|
||||
TabsShowActiveTerminal = 'terminal.integrated.tabs.showActiveTerminal',
|
||||
TabsLocation = 'terminal.integrated.tabs.location',
|
||||
TabsFocusMode = 'terminal.integrated.tabs.focusMode',
|
||||
MacOptionIsMeta = 'terminal.integrated.macOptionIsMeta',
|
||||
MacOptionClickForcesSelection = 'terminal.integrated.macOptionClickForcesSelection',
|
||||
AltClickMovesCursor = 'terminal.integrated.altClickMovesCursor',
|
||||
CopyOnSelection = 'terminal.integrated.copyOnSelection',
|
||||
DrawBoldTextInBrightColors = 'terminal.integrated.drawBoldTextInBrightColors',
|
||||
FontFamily = 'terminal.integrated.fontFamily',
|
||||
FontSize = 'terminal.integrated.fontSize',
|
||||
LetterSpacing = 'terminal.integrated.letterSpacing',
|
||||
LineHeight = 'terminal.integrated.lineHeight',
|
||||
MinimumContrastRatio = 'terminal.integrated.minimumContrastRatio',
|
||||
FastScrollSensitivity = 'terminal.integrated.fastScrollSensitivity',
|
||||
MouseWheelScrollSensitivity = 'terminal.integrated.mouseWheelScrollSensitivity',
|
||||
BellDuration = 'terminal.integrated.bellDuration',
|
||||
FontWeight = 'terminal.integrated.fontWeight',
|
||||
FontWeightBold = 'terminal.integrated.fontWeightBold',
|
||||
CursorBlinking = 'terminal.integrated.cursorBlinking',
|
||||
CursorStyle = 'terminal.integrated.cursorStyle',
|
||||
CursorWidth = 'terminal.integrated.cursorWidth',
|
||||
Scrollback = 'terminal.integrated.scrollback',
|
||||
DetectLocale = 'terminal.integrated.detectLocale',
|
||||
GpuAcceleration = 'terminal.integrated.gpuAcceleration',
|
||||
RightClickBehavior = 'terminal.integrated.rightClickBehavior',
|
||||
Cwd = 'terminal.integrated.cwd',
|
||||
ConfirmOnExit = 'terminal.integrated.confirmOnExit',
|
||||
EnableBell = 'terminal.integrated.enableBell',
|
||||
CommandsToSkipShell = 'terminal.integrated.commandsToSkipShell',
|
||||
AllowChords = 'terminal.integrated.allowChords',
|
||||
AllowMnemonics = 'terminal.integrated.allowMnemonics',
|
||||
EnvMacOs = 'terminal.integrated.env.osx',
|
||||
EnvLinux = 'terminal.integrated.env.linux',
|
||||
EnvWindows = 'terminal.integrated.env.windows',
|
||||
EnvironmentChangesIndicator = 'terminal.integrated.environmentChangesIndicator',
|
||||
EnvironmentChangesRelaunch = 'terminal.integrated.environmentChangesRelaunch',
|
||||
ShowExitAlert = 'terminal.integrated.showExitAlert',
|
||||
SplitCwd = 'terminal.integrated.splitCwd',
|
||||
WindowsEnableConpty = 'terminal.integrated.windowsEnableConpty',
|
||||
WordSeparators = 'terminal.integrated.wordSeparators',
|
||||
TitleMode = 'terminal.integrated.titleMode',
|
||||
EnableFileLinks = 'terminal.integrated.enableFileLinks',
|
||||
UnicodeVersion = 'terminal.integrated.unicodeVersion',
|
||||
ExperimentalLinkProvider = 'terminal.integrated.experimentalLinkProvider',
|
||||
LocalEchoLatencyThreshold = 'terminal.integrated.localEchoLatencyThreshold',
|
||||
LocalEchoExcludePrograms = 'terminal.integrated.localEchoExcludePrograms',
|
||||
LocalEchoStyle = 'terminal.integrated.localEchoStyle',
|
||||
EnablePersistentSessions = 'terminal.integrated.enablePersistentSessions',
|
||||
InheritEnv = 'terminal.integrated.inheritEnv'
|
||||
}
|
||||
|
||||
export enum WindowsShellType {
|
||||
CommandPrompt = 'cmd',
|
||||
@@ -30,7 +109,6 @@ export interface IRawTerminalTabLayoutInfo<T> {
|
||||
}
|
||||
|
||||
export type ITerminalTabLayoutInfoById = IRawTerminalTabLayoutInfo<number>;
|
||||
export type ITerminalTabLayoutInfo = IRawTerminalTabLayoutInfo<IPtyHostAttachTarget | null>;
|
||||
|
||||
export interface IRawTerminalsLayoutInfo<T> {
|
||||
tabs: IRawTerminalTabLayoutInfo<T>[];
|
||||
@@ -40,11 +118,21 @@ export interface IPtyHostAttachTarget {
|
||||
id: number;
|
||||
pid: number;
|
||||
title: string;
|
||||
titleSource: TitleEventSource;
|
||||
cwd: string;
|
||||
workspaceId: string;
|
||||
workspaceName: string;
|
||||
isOrphan: boolean;
|
||||
icon: string | undefined;
|
||||
icon: TerminalIcon | undefined;
|
||||
}
|
||||
|
||||
export enum TitleEventSource {
|
||||
/** From the API or the rename command that overrides any other type */
|
||||
Api,
|
||||
/** From the process name property*/
|
||||
Process,
|
||||
/** From the VT sequence */
|
||||
Sequence
|
||||
}
|
||||
|
||||
export type ITerminalsLayoutInfo = IRawTerminalsLayoutInfo<IPtyHostAttachTarget | null>;
|
||||
@@ -96,8 +184,13 @@ export interface IOffProcessTerminalService {
|
||||
attachToProcess(id: number): Promise<ITerminalChildProcess | undefined>;
|
||||
listProcesses(): Promise<IProcessDetails[]>;
|
||||
getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string>;
|
||||
getShellEnvironment(): Promise<IProcessEnvironment>;
|
||||
getProfiles(profiles: unknown, defaultProfile: unknown, includeDetectedProfiles?: boolean): Promise<ITerminalProfile[]>;
|
||||
getWslPath(original: string): Promise<string>;
|
||||
getEnvironment(): Promise<IProcessEnvironment>;
|
||||
getShellEnvironment(): Promise<IProcessEnvironment | undefined>;
|
||||
setTerminalLayoutInfo(layoutInfo?: ITerminalsLayoutInfoById): Promise<void>;
|
||||
updateTitle(id: number, title: string, titleSource: TitleEventSource): Promise<void>;
|
||||
updateIcon(id: number, icon: TerminalIcon, color?: string): Promise<void>;
|
||||
getTerminalLayoutInfo(): Promise<ITerminalsLayoutInfo | undefined>;
|
||||
reduceConnectionGraceTime(): Promise<void>;
|
||||
}
|
||||
@@ -115,6 +208,8 @@ export interface IPtyService {
|
||||
readonly onPtyHostStart?: Event<void>;
|
||||
readonly onPtyHostUnresponsive?: Event<void>;
|
||||
readonly onPtyHostResponsive?: Event<void>;
|
||||
readonly onPtyHostRequestResolveVariables?: Event<IRequestResolveVariablesEvent>;
|
||||
|
||||
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 } }>;
|
||||
@@ -127,6 +222,7 @@ export interface IPtyService {
|
||||
|
||||
restartPtyHost?(): Promise<void>;
|
||||
shutdownAll?(): Promise<void>;
|
||||
acceptPtyHostResolvedVariables?(id: number, resolved: string[]): Promise<void>;
|
||||
|
||||
createProcess(
|
||||
shellLaunchConfig: IShellLaunchConfig,
|
||||
@@ -159,14 +255,22 @@ export interface IPtyService {
|
||||
processBinary(id: number, data: string): Promise<void>;
|
||||
/** Confirm the process is _not_ an orphan. */
|
||||
orphanQuestionReply(id: number): Promise<void>;
|
||||
|
||||
updateTitle(id: number, title: string, titleSource: TitleEventSource): Promise<void>;
|
||||
updateIcon(id: number, icon: TerminalIcon, color?: string): Promise<void>;
|
||||
getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string>;
|
||||
getShellEnvironment(): Promise<IProcessEnvironment>;
|
||||
getProfiles?(profiles: unknown, defaultProfile: unknown, includeDetectedProfiles?: boolean): Promise<ITerminalProfile[]>;
|
||||
getEnvironment(): Promise<IProcessEnvironment>;
|
||||
getWslPath(original: string): Promise<string>;
|
||||
setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise<void>;
|
||||
getTerminalLayoutInfo(args: IGetTerminalLayoutInfoArgs): Promise<ITerminalsLayoutInfo | undefined>;
|
||||
reduceConnectionGraceTime(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IRequestResolveVariablesEvent {
|
||||
id: number;
|
||||
originalText: string[];
|
||||
}
|
||||
|
||||
export enum HeartbeatConstants {
|
||||
/**
|
||||
* The duration between heartbeats
|
||||
@@ -260,7 +364,7 @@ export interface IShellLaunchConfig {
|
||||
/**
|
||||
* This is a terminal that attaches to an already running terminal.
|
||||
*/
|
||||
attachPersistentProcess?: { id: number; pid: number; title: string; cwd: string; icon?: string; };
|
||||
attachPersistentProcess?: { id: number; pid: number; title: string; titleSource: TitleEventSource; cwd: string; icon?: TerminalIcon; color?: string };
|
||||
|
||||
/**
|
||||
* Whether the terminal process environment should be exactly as provided in
|
||||
@@ -271,6 +375,14 @@ export interface IShellLaunchConfig {
|
||||
*/
|
||||
strictEnv?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the terminal process environment will inherit VS Code's "shell environment" that may
|
||||
* get sourced from running a login shell depnding on how the application was launched.
|
||||
* Consumers that rely on development tools being present in the $PATH should set this to true.
|
||||
* This will overwrite the value of the inheritEnv setting.
|
||||
*/
|
||||
useShellEnvironment?: boolean;
|
||||
|
||||
/**
|
||||
* When enabled the terminal will run the process as normal but not be surfaced to the user
|
||||
* until `Terminal.show` is called. The typical usage for this is when you need to run
|
||||
@@ -292,18 +404,25 @@ export interface IShellLaunchConfig {
|
||||
isExtensionOwnedTerminal?: boolean;
|
||||
|
||||
/**
|
||||
* The codicon ID to use for this terminal. If not specified it will use the default fallback
|
||||
* icon.
|
||||
* The icon for the terminal, used primarily in the terminal tab.
|
||||
*/
|
||||
icon?: string;
|
||||
icon?: TerminalIcon;
|
||||
|
||||
/**
|
||||
* The color ID to use for this terminal. If not specified it will use the default fallback
|
||||
*/
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export type TerminalIcon = ThemeIcon | URI | { light: URI; dark: URI };
|
||||
|
||||
export interface IShellLaunchConfigDto {
|
||||
name?: string;
|
||||
executable?: string;
|
||||
args?: string[] | string;
|
||||
cwd?: string | UriComponents;
|
||||
env?: ITerminalEnvironment;
|
||||
useShellEnvironment?: boolean;
|
||||
hideFromUser?: boolean;
|
||||
}
|
||||
|
||||
@@ -316,6 +435,12 @@ export interface ITerminalLaunchError {
|
||||
code?: number;
|
||||
}
|
||||
|
||||
export interface IProcessReadyEvent {
|
||||
pid: number,
|
||||
cwd: string,
|
||||
requiresWindowsMode?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface representing a raw terminal child process, this contains a subset of the
|
||||
* child_process.ChildProcess node.js interface.
|
||||
@@ -335,7 +460,7 @@ export interface ITerminalChildProcess {
|
||||
|
||||
onProcessData: Event<IProcessDataEvent | string>;
|
||||
onProcessExit: Event<number | undefined>;
|
||||
onProcessReady: Event<{ pid: number, cwd: string }>;
|
||||
onProcessReady: Event<IProcessReadyEvent>;
|
||||
onProcessTitleChanged: Event<string>;
|
||||
onProcessOverrideDimensions?: Event<ITerminalDimensionsOverride | undefined>;
|
||||
onProcessResolvedShellLaunchConfig?: Event<IShellLaunchConfig>;
|
||||
@@ -378,15 +503,20 @@ export interface ITerminalChildProcess {
|
||||
getLatency(): Promise<number>;
|
||||
}
|
||||
|
||||
export interface IReconnectConstants {
|
||||
GraceTime: number,
|
||||
ShortGraceTime: number
|
||||
}
|
||||
|
||||
export const enum LocalReconnectConstants {
|
||||
/**
|
||||
* If there is no reconnection within this time-frame, consider the connection permanently closed...
|
||||
*/
|
||||
ReconnectionGraceTime = 60000, // 60 seconds
|
||||
GraceTime = 60000, // 60 seconds
|
||||
/**
|
||||
* Maximal grace time between the first and the last reconnection...
|
||||
*/
|
||||
ReconnectionShortGraceTime = 6000, // 6 seconds
|
||||
ShortGraceTime = 6000, // 6 seconds
|
||||
}
|
||||
|
||||
export const enum FlowControlConstants {
|
||||
@@ -433,6 +563,18 @@ export interface ITerminalDimensions {
|
||||
rows: number;
|
||||
}
|
||||
|
||||
export interface ITerminalProfile {
|
||||
profileName: string;
|
||||
path: string;
|
||||
isDefault: boolean;
|
||||
isAutoDetected?: boolean;
|
||||
args?: string | string[] | undefined;
|
||||
env?: ITerminalEnvironment;
|
||||
overrideName?: boolean;
|
||||
color?: string;
|
||||
icon?: ThemeIcon | URI | { light: URI, dark: URI };
|
||||
}
|
||||
|
||||
export interface ITerminalDimensionsOverride extends Readonly<ITerminalDimensions> {
|
||||
/**
|
||||
* indicate that xterm must receive these exact dimensions, even if they overflow the ui!
|
||||
@@ -440,4 +582,26 @@ export interface ITerminalDimensionsOverride extends Readonly<ITerminalDimension
|
||||
forceExactSize?: boolean;
|
||||
}
|
||||
|
||||
export type SafeConfigProvider = <T>(key: string) => T | undefined;
|
||||
export const enum ProfileSource {
|
||||
GitBash = 'Git Bash',
|
||||
Pwsh = 'PowerShell'
|
||||
}
|
||||
|
||||
export interface IBaseUnresolvedTerminalProfile {
|
||||
args?: string | string[] | undefined;
|
||||
isAutoDetected?: boolean;
|
||||
overrideName?: boolean;
|
||||
icon?: ThemeIcon | URI | { light: URI, dark: URI };
|
||||
color?: string;
|
||||
env?: ITerminalEnvironment;
|
||||
}
|
||||
|
||||
export interface ITerminalExecutable extends IBaseUnresolvedTerminalProfile {
|
||||
path: string | string[];
|
||||
}
|
||||
|
||||
export interface ITerminalProfileSource extends IBaseUnresolvedTerminalProfile {
|
||||
source: ProfileSource;
|
||||
}
|
||||
|
||||
export type ITerminalProfileObject = ITerminalExecutable | ITerminalProfileSource | null;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export function escapeNonWindowsPath(path: string): string {
|
||||
let newPath = path;
|
||||
if (newPath.indexOf('\\') !== 0) {
|
||||
newPath = newPath.replace(/\\/g, '\\\\');
|
||||
}
|
||||
const bannedChars = /[\`\$\|\&\>\~\#\!\^\*\;\<\"\']/g;
|
||||
newPath = newPath.replace(bannedChars, '');
|
||||
return `'${newPath}'`;
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ITerminalProfile, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Codicon, iconRegistry } from 'vs/base/common/codicons';
|
||||
import { OperatingSystem } from 'vs/base/common/platform';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
const terminalProfileBaseProperties: IJSONSchemaMap = {
|
||||
args: {
|
||||
description: localize('terminalProfile.args', 'An optional set of arguments to run the shell executable with.'),
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
overrideName: {
|
||||
description: localize('terminalProfile.overrideName', 'Controls whether or not the profile name overrides the auto detected one.'),
|
||||
type: 'boolean'
|
||||
},
|
||||
icon: {
|
||||
description: localize('terminalProfile.icon', 'A codicon ID to associate with this terminal.'),
|
||||
type: 'string',
|
||||
enum: Array.from(iconRegistry.all, icon => icon.id),
|
||||
markdownEnumDescriptions: Array.from(iconRegistry.all, icon => `$(${icon.id})`),
|
||||
},
|
||||
color: {
|
||||
description: localize('terminalProfile.color', 'A theme color ID to associate with this terminal.'),
|
||||
type: ['string', 'null'],
|
||||
enum: [
|
||||
'terminal.ansiBlack',
|
||||
'terminal.ansiRed',
|
||||
'terminal.ansiGreen',
|
||||
'terminal.ansiYellow',
|
||||
'terminal.ansiBlue',
|
||||
'terminal.ansiMagenta',
|
||||
'terminal.ansiCyan',
|
||||
'terminal.ansiWhite'
|
||||
],
|
||||
default: null
|
||||
},
|
||||
env: {
|
||||
markdownDescription: localize('terminalProfile.env', "An object with environment variables that will be added to the terminal profile process. Set to `null` to delete environment variables from the base environment."),
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: ['string', 'null']
|
||||
},
|
||||
default: {}
|
||||
}
|
||||
};
|
||||
|
||||
const terminalProfileSchema: IJSONSchema = {
|
||||
type: 'object',
|
||||
required: ['path'],
|
||||
properties: {
|
||||
path: {
|
||||
description: localize('terminalProfile.path', 'A single path to a shell executable or an array of paths that will be used as fallbacks when one fails.'),
|
||||
type: ['string', 'array'],
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
...terminalProfileBaseProperties
|
||||
}
|
||||
};
|
||||
|
||||
const shellDeprecationMessageLinux = localize('terminal.integrated.shell.linux.deprecation', "This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in {0} and setting its profile name as the default in {1}. This will currently take priority over the new profiles settings but that will change in the future.", '`#terminal.integrated.profiles.linux#`', '`#terminal.integrated.defaultProfile.linux#`');
|
||||
const shellDeprecationMessageOsx = localize('terminal.integrated.shell.osx.deprecation', "This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in {0} and setting its profile name as the default in {1}. This will currently take priority over the new profiles settings but that will change in the future.", '`#terminal.integrated.profiles.osx#`', '`#terminal.integrated.defaultProfile.osx#`');
|
||||
const shellDeprecationMessageWindows = localize('terminal.integrated.shell.windows.deprecation', "This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in {0} and setting its profile name as the default in {1}. This will currently take priority over the new profiles settings but that will change in the future.", '`#terminal.integrated.profiles.windows#`', '`#terminal.integrated.defaultProfile.windows#`');
|
||||
|
||||
const terminalPlatformConfiguration: IConfigurationNode = {
|
||||
id: 'terminal',
|
||||
order: 100,
|
||||
title: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"),
|
||||
type: 'object',
|
||||
properties: {
|
||||
[TerminalSettingId.AutomationShellLinux]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize({
|
||||
key: 'terminal.integrated.automationShell.linux',
|
||||
comment: ['{0} and {1} are the `shell` and `shellArgs` settings keys']
|
||||
}, "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.linux`', '`shellArgs`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
[TerminalSettingId.AutomationShellMacOs]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize({
|
||||
key: 'terminal.integrated.automationShell.osx',
|
||||
comment: ['{0} and {1} are the `shell` and `shellArgs` settings keys']
|
||||
}, "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.osx`', '`shellArgs`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
[TerminalSettingId.AutomationShellWindows]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize({
|
||||
key: 'terminal.integrated.automationShell.windows',
|
||||
comment: ['{0} and {1} are the `shell` and `shellArgs` settings keys']
|
||||
}, "A path that when set will override {0} and ignore {1} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.windows`', '`shellArgs`'),
|
||||
type: ['string', 'null'],
|
||||
default: null
|
||||
},
|
||||
[TerminalSettingId.ShellLinux]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: ['string', 'null'],
|
||||
default: null,
|
||||
markdownDeprecationMessage: shellDeprecationMessageLinux
|
||||
},
|
||||
[TerminalSettingId.ShellMacOs]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: ['string', 'null'],
|
||||
default: null,
|
||||
markdownDeprecationMessage: shellDeprecationMessageOsx
|
||||
},
|
||||
[TerminalSettingId.ShellWindows]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: ['string', 'null'],
|
||||
default: null,
|
||||
markdownDeprecationMessage: shellDeprecationMessageWindows
|
||||
},
|
||||
[TerminalSettingId.ShellArgsLinux]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shellArgs.linux', "The command line arguments to use when on the Linux terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
default: [],
|
||||
markdownDeprecationMessage: shellDeprecationMessageLinux
|
||||
},
|
||||
[TerminalSettingId.ShellArgsMacOs]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shellArgs.osx', "The command line arguments to use when on the macOS terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
// Unlike on Linux, ~/.profile is not sourced when logging into a macOS session. This
|
||||
// is the reason terminals on macOS typically run login shells by default which set up
|
||||
// the environment. See http://unix.stackexchange.com/a/119675/115410
|
||||
default: ['-l'],
|
||||
markdownDeprecationMessage: shellDeprecationMessageOsx
|
||||
},
|
||||
[TerminalSettingId.ShellArgsWindows]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."),
|
||||
'anyOf': [
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
markdownDescription: localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).")
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
markdownDescription: localize('terminal.integrated.shellArgs.windows.string', "The command line arguments in [command-line format](https://msdn.microsoft.com/en-au/08dfcab2-eb6e-49a4-80eb-87d4076c98c6) to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).")
|
||||
}
|
||||
],
|
||||
default: [],
|
||||
markdownDeprecationMessage: shellDeprecationMessageWindows
|
||||
},
|
||||
[TerminalSettingId.ProfilesWindows]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize(
|
||||
{
|
||||
key: 'terminal.integrated.profiles.windows',
|
||||
comment: ['{0}, {1}, and {2} are the `source`, `path` and optional `args` settings keys']
|
||||
},
|
||||
"The Windows profiles to present when creating a new terminal via the terminal dropdown. Set to null to exclude them, use the {0} property to use the default detected configuration. Or, set the {1} and optional {2}", '`source`', '`path`', '`args`.'
|
||||
),
|
||||
type: 'object',
|
||||
default: {
|
||||
'PowerShell': {
|
||||
source: 'PowerShell',
|
||||
icon: 'terminal-powershell'
|
||||
},
|
||||
'Command Prompt': {
|
||||
path: [
|
||||
'${env:windir}\\Sysnative\\cmd.exe',
|
||||
'${env:windir}\\System32\\cmd.exe'
|
||||
],
|
||||
args: [],
|
||||
icon: 'terminal-cmd'
|
||||
},
|
||||
'Git Bash': {
|
||||
source: 'Git Bash'
|
||||
}
|
||||
},
|
||||
additionalProperties: {
|
||||
'anyOf': [
|
||||
{
|
||||
type: 'object',
|
||||
required: ['source'],
|
||||
properties: {
|
||||
source: {
|
||||
description: localize('terminalProfile.windowsSource', 'A profile source that will auto detect the paths to the shell.'),
|
||||
enum: ['PowerShell', 'Git Bash']
|
||||
},
|
||||
...terminalProfileBaseProperties
|
||||
}
|
||||
},
|
||||
{ type: 'null' },
|
||||
terminalProfileSchema
|
||||
]
|
||||
}
|
||||
},
|
||||
[TerminalSettingId.ProfilesMacOs]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize(
|
||||
{
|
||||
key: 'terminal.integrated.profile.osx',
|
||||
comment: ['{0} and {1} are the `path` and optional `args` settings keys']
|
||||
},
|
||||
"The macOS profiles to present when creating a new terminal via the terminal dropdown. When set, these will override the default detected profiles. They are comprised of a {0} and optional {1}", '`path`', '`args`.'
|
||||
),
|
||||
type: 'object',
|
||||
default: {
|
||||
'bash': {
|
||||
path: 'bash',
|
||||
args: ['-l'],
|
||||
icon: 'terminal-bash'
|
||||
},
|
||||
'zsh': {
|
||||
path: 'zsh',
|
||||
args: ['-l']
|
||||
},
|
||||
'fish': {
|
||||
path: 'fish',
|
||||
args: ['-l']
|
||||
},
|
||||
'tmux': {
|
||||
path: 'tmux',
|
||||
icon: 'terminal-tmux'
|
||||
},
|
||||
'pwsh': {
|
||||
path: 'pwsh',
|
||||
icon: 'terminal-powershell'
|
||||
}
|
||||
},
|
||||
additionalProperties: {
|
||||
'anyOf': [
|
||||
{ type: 'null' },
|
||||
terminalProfileSchema
|
||||
]
|
||||
}
|
||||
},
|
||||
[TerminalSettingId.ProfilesLinux]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize(
|
||||
{
|
||||
key: 'terminal.integrated.profile.linux',
|
||||
comment: ['{0} and {1} are the `path` and optional `args` settings keys']
|
||||
},
|
||||
"The Linux profiles to present when creating a new terminal via the terminal dropdown. When set, these will override the default detected profiles. They are comprised of a {0} and optional {1}", '`path`', '`args`.'
|
||||
),
|
||||
type: 'object',
|
||||
default: {
|
||||
'bash': {
|
||||
path: 'bash'
|
||||
},
|
||||
'zsh': {
|
||||
path: 'zsh'
|
||||
},
|
||||
'fish': {
|
||||
path: 'fish'
|
||||
},
|
||||
'tmux': {
|
||||
path: 'tmux',
|
||||
icon: 'terminal-tmux'
|
||||
},
|
||||
'pwsh': {
|
||||
path: 'pwsh',
|
||||
icon: 'terminal-powershell'
|
||||
}
|
||||
},
|
||||
additionalProperties: {
|
||||
'anyOf': [
|
||||
{ type: 'null' },
|
||||
terminalProfileSchema
|
||||
]
|
||||
}
|
||||
},
|
||||
[TerminalSettingId.UseWslProfiles]: {
|
||||
description: localize('terminal.integrated.useWslProfiles', 'Controls whether or not WSL distros are shown in the terminal dropdown'),
|
||||
type: 'boolean',
|
||||
default: true
|
||||
},
|
||||
[TerminalSettingId.InheritEnv]: {
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
description: localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from VS Code which may source a login shell to ensure $PATH and other development variables are initialized. This has no effect on Windows."),
|
||||
type: 'boolean',
|
||||
default: true
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers terminal configurations required by shared process and remote server.
|
||||
*/
|
||||
export function registerTerminalPlatformConfiguration() {
|
||||
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration(terminalPlatformConfiguration);
|
||||
registerTerminalDefaultProfileConfiguration();
|
||||
}
|
||||
|
||||
let lastDefaultProfilesConfiguration: IConfigurationNode | undefined;
|
||||
export function registerTerminalDefaultProfileConfiguration(detectedProfiles?: { os: OperatingSystem, profiles: ITerminalProfile[] }) {
|
||||
const registry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
|
||||
if (lastDefaultProfilesConfiguration) {
|
||||
registry.deregisterConfigurations([lastDefaultProfilesConfiguration]);
|
||||
}
|
||||
let enumValues: string[] | undefined = undefined;
|
||||
let enumDescriptions: string[] | undefined = undefined;
|
||||
if (detectedProfiles) {
|
||||
const result = detectedProfiles.profiles.map(e => {
|
||||
return {
|
||||
name: e.profileName,
|
||||
description: createProfileDescription(e)
|
||||
};
|
||||
});
|
||||
enumValues = result.map(e => e.name);
|
||||
enumDescriptions = result.map(e => e.description);
|
||||
}
|
||||
lastDefaultProfilesConfiguration = {
|
||||
id: 'terminal',
|
||||
order: 100,
|
||||
title: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"),
|
||||
type: 'object',
|
||||
properties: {
|
||||
[TerminalSettingId.DefaultProfileLinux]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.defaultProfile.linux', "The default profile used on Linux. This setting will currently be ignored if either {0} or {1} are set.", '`#terminal.integrated.shell.linux#`', '`#terminal.integrated.shellArgs.linux#`'),
|
||||
type: ['string', 'null'],
|
||||
default: null,
|
||||
enum: detectedProfiles?.os === OperatingSystem.Linux ? enumValues : undefined,
|
||||
markdownEnumDescriptions: detectedProfiles?.os === OperatingSystem.Linux ? enumDescriptions : undefined
|
||||
},
|
||||
[TerminalSettingId.DefaultProfileMacOs]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.defaultProfile.osx', "The default profile used on macOS. This setting will currently be ignored if either {0} or {1} are set.", '`#terminal.integrated.shell.osx#`', '`#terminal.integrated.shellArgs.osx#`'),
|
||||
type: ['string', 'null'],
|
||||
default: null,
|
||||
enum: detectedProfiles?.os === OperatingSystem.Macintosh ? enumValues : undefined,
|
||||
markdownEnumDescriptions: detectedProfiles?.os === OperatingSystem.Macintosh ? enumDescriptions : undefined
|
||||
},
|
||||
[TerminalSettingId.DefaultProfileWindows]: {
|
||||
restricted: true,
|
||||
markdownDescription: localize('terminal.integrated.defaultProfile.windows', "The default profile used on Windows. This setting will currently be ignored if either {0} or {1} are set.", '`#terminal.integrated.shell.windows#`', '`#terminal.integrated.shellArgs.windows#`'),
|
||||
type: ['string', 'null'],
|
||||
default: null,
|
||||
enum: detectedProfiles?.os === OperatingSystem.Windows ? enumValues : undefined,
|
||||
markdownEnumDescriptions: detectedProfiles?.os === OperatingSystem.Windows ? enumDescriptions : undefined
|
||||
},
|
||||
}
|
||||
};
|
||||
registry.registerConfiguration(lastDefaultProfilesConfiguration);
|
||||
}
|
||||
|
||||
function createProfileDescription(profile: ITerminalProfile): string {
|
||||
let description = `$(${ThemeIcon.isThemeIcon(profile.icon) ? profile.icon.id : profile.icon ? profile.icon : Codicon.terminal.id}) ${profile.profileName}\n- path: ${profile.path}`;
|
||||
if (profile.args) {
|
||||
if (typeof profile.args === 'string') {
|
||||
description += `\n- args: "${profile.args}"`;
|
||||
} else {
|
||||
description += `\n- args: [${profile.args.length === 0 ? '' : profile.args.join(`','`)}]`;
|
||||
}
|
||||
}
|
||||
if (profile.overrideName !== undefined) {
|
||||
description += `\n- overrideName: ${profile.overrideName}`;
|
||||
}
|
||||
if (profile.color) {
|
||||
description += `\n- color: ${profile.color}`;
|
||||
}
|
||||
if (profile.env) {
|
||||
description += `\n- env: ${JSON.stringify(profile.env)}`;
|
||||
}
|
||||
return description;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { UriComponents } from 'vs/base/common/uri';
|
||||
import { IRawTerminalTabLayoutInfo, ITerminalEnvironment, ITerminalTabLayoutInfoById } from 'vs/platform/terminal/common/terminal';
|
||||
import { IRawTerminalTabLayoutInfo, ITerminalEnvironment, ITerminalTabLayoutInfoById, TerminalIcon, TitleEventSource } from 'vs/platform/terminal/common/terminal';
|
||||
import { ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable';
|
||||
|
||||
export interface ISingleTerminalConfiguration<T> {
|
||||
@@ -26,7 +26,6 @@ export interface ICompleteTerminalConfiguration {
|
||||
'terminal.integrated.env.windows': ISingleTerminalConfiguration<ITerminalEnvironment>;
|
||||
'terminal.integrated.env.osx': ISingleTerminalConfiguration<ITerminalEnvironment>;
|
||||
'terminal.integrated.env.linux': ISingleTerminalConfiguration<ITerminalEnvironment>;
|
||||
'terminal.integrated.inheritEnv': boolean;
|
||||
'terminal.integrated.cwd': string;
|
||||
'terminal.integrated.detectLocale': 'auto' | 'off' | 'on';
|
||||
}
|
||||
@@ -52,11 +51,13 @@ export interface IProcessDetails {
|
||||
id: number;
|
||||
pid: number;
|
||||
title: string;
|
||||
titleSource: TitleEventSource;
|
||||
cwd: string;
|
||||
workspaceId: string;
|
||||
workspaceName: string;
|
||||
isOrphan: boolean;
|
||||
icon: string | undefined;
|
||||
icon: TerminalIcon | undefined;
|
||||
color: string | undefined;
|
||||
}
|
||||
|
||||
export type ITerminalTabLayoutInfoDto = IRawTerminalTabLayoutInfo<IProcessDetails>;
|
||||
|
||||
@@ -26,7 +26,7 @@ export class TerminalRecorder {
|
||||
this._entries = [{ cols, rows, data: [] }];
|
||||
}
|
||||
|
||||
public recordResize(cols: number, rows: number): void {
|
||||
recordResize(cols: number, rows: number): void {
|
||||
if (this._entries.length > 0) {
|
||||
const lastEntry = this._entries[this._entries.length - 1];
|
||||
if (lastEntry.data.length === 0) {
|
||||
@@ -52,7 +52,7 @@ export class TerminalRecorder {
|
||||
this._entries.push({ cols, rows, data: [] });
|
||||
}
|
||||
|
||||
public recordData(data: string): void {
|
||||
recordData(data: string): void {
|
||||
const lastEntry = this._entries[this._entries.length - 1];
|
||||
lastEntry.data.push(data);
|
||||
|
||||
@@ -76,7 +76,7 @@ export class TerminalRecorder {
|
||||
}
|
||||
}
|
||||
|
||||
public generateReplayEvent(): IPtyHostProcessReplayEvent {
|
||||
generateReplayEvent(): IPtyHostProcessReplayEvent {
|
||||
// normalize entries to one element per data array
|
||||
this._entries.forEach((entry) => {
|
||||
if (entry.data.length > 0) {
|
||||
|
||||
@@ -23,7 +23,11 @@ server.registerChannel(TerminalIpcChannels.Log, logChannel);
|
||||
const heartbeatService = new HeartbeatService();
|
||||
server.registerChannel(TerminalIpcChannels.Heartbeat, ProxyChannel.fromService(heartbeatService));
|
||||
|
||||
const ptyService = new PtyService(lastPtyId, logService);
|
||||
const reconnectConstants = { GraceTime: parseInt(process.env.VSCODE_RECONNECT_GRACE_TIME || '0'), ShortGraceTime: parseInt(process.env.VSCODE_RECONNECT_SHORT_GRACE_TIME || '0') };
|
||||
delete process.env.VSCODE_RECONNECT_GRACE_TIME;
|
||||
delete process.env.VSCODE_RECONNECT_SHORT_GRACE_TIME;
|
||||
|
||||
const ptyService = new PtyService(lastPtyId, logService, reconnectConstants);
|
||||
server.registerChannel(TerminalIpcChannels.PtyHost, ProxyChannel.fromService(ptyService));
|
||||
|
||||
process.once('exit', () => {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalsLayoutInfo, TerminalIpcChannels, IHeartbeatService, HeartbeatConstants, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { IPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalsLayoutInfo, TerminalIpcChannels, IHeartbeatService, HeartbeatConstants, TerminalShellType, ITerminalProfile, IRequestResolveVariablesEvent, TitleEventSource, TerminalIcon, IReconnectConstants } from 'vs/platform/terminal/common/terminal';
|
||||
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
@@ -14,6 +14,9 @@ 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';
|
||||
import { detectAvailableProfiles } from 'vs/platform/terminal/node/terminalProfiles';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { registerTerminalPlatformConfiguration } from 'vs/platform/terminal/common/terminalPlatformConfiguration';
|
||||
|
||||
enum Constants {
|
||||
MaxRestarts = 5
|
||||
@@ -25,6 +28,8 @@ enum Constants {
|
||||
*/
|
||||
let lastPtyId = 0;
|
||||
|
||||
let lastResolveVariablesRequestId = 0;
|
||||
|
||||
/**
|
||||
* This service implements IPtyService by launching a pty host process, forwarding messages to and
|
||||
* from the pty host process and manages the connection.
|
||||
@@ -51,6 +56,9 @@ export class PtyHostService extends Disposable implements IPtyService {
|
||||
readonly onPtyHostUnresponsive = this._onPtyHostUnresponsive.event;
|
||||
private readonly _onPtyHostResponsive = this._register(new Emitter<void>());
|
||||
readonly onPtyHostResponsive = this._onPtyHostResponsive.event;
|
||||
private readonly _onPtyHostRequestResolveVariables = this._register(new Emitter<IRequestResolveVariablesEvent>());
|
||||
readonly onPtyHostRequestResolveVariables = this._onPtyHostRequestResolveVariables.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 }>());
|
||||
@@ -71,11 +79,17 @@ export class PtyHostService extends Disposable implements IPtyService {
|
||||
readonly onProcessOrphanQuestion = this._onProcessOrphanQuestion.event;
|
||||
|
||||
constructor(
|
||||
private readonly _reconnectConstants: IReconnectConstants,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Platform configuration is required on the process running the pty host (shared process or
|
||||
// remote server).
|
||||
registerTerminalPlatformConfiguration();
|
||||
|
||||
this._register(toDisposable(() => this._disposePtyHost()));
|
||||
|
||||
[this._client, this._proxy] = this._startPtyHost();
|
||||
@@ -91,7 +105,9 @@ export class PtyHostService extends Disposable implements IPtyService {
|
||||
VSCODE_LAST_PTY_ID: lastPtyId,
|
||||
VSCODE_AMD_ENTRYPOINT: 'vs/platform/terminal/node/ptyHostMain',
|
||||
VSCODE_PIPE_LOGGING: 'true',
|
||||
VSCODE_VERBOSE_LOGGING: 'true' // transmit console logs from server to client
|
||||
VSCODE_VERBOSE_LOGGING: 'true', // transmit console logs from server to client,
|
||||
VSCODE_RECONNECT_GRACE_TIME: this._reconnectConstants.GraceTime,
|
||||
VSCODE_RECONNECT_SHORT_GRACE_TIME: this._reconnectConstants.ShortGraceTime
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -122,6 +138,7 @@ export class PtyHostService extends Disposable implements IPtyService {
|
||||
|
||||
// Setup logging
|
||||
const logChannel = client.getChannel(TerminalIpcChannels.Log);
|
||||
LogLevelChannelClient.setLevel(logChannel, this._logService.getLevel());
|
||||
this._register(this._logService.onDidChangeLogLevel(() => {
|
||||
LogLevelChannelClient.setLevel(logChannel, this._logService.getLevel());
|
||||
}));
|
||||
@@ -153,6 +170,12 @@ export class PtyHostService extends Disposable implements IPtyService {
|
||||
lastPtyId = Math.max(lastPtyId, id);
|
||||
return id;
|
||||
}
|
||||
updateTitle(id: number, title: string, titleSource: TitleEventSource): Promise<void> {
|
||||
return this._proxy.updateTitle(id, title, titleSource);
|
||||
}
|
||||
updateIcon(id: number, icon: TerminalIcon, color?: string): Promise<void> {
|
||||
return this._proxy.updateIcon(id, icon, color);
|
||||
}
|
||||
attachToProcess(id: number): Promise<void> {
|
||||
return this._proxy.attachToProcess(id);
|
||||
}
|
||||
@@ -199,8 +222,14 @@ export class PtyHostService extends Disposable implements IPtyService {
|
||||
getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string> {
|
||||
return this._proxy.getDefaultSystemShell(osOverride);
|
||||
}
|
||||
getShellEnvironment(): Promise<IProcessEnvironment> {
|
||||
return this._proxy.getShellEnvironment();
|
||||
async getProfiles(profiles: unknown, defaultProfile: unknown, includeDetectedProfiles: boolean = false): Promise<ITerminalProfile[]> {
|
||||
return detectAvailableProfiles(profiles, defaultProfile, includeDetectedProfiles, this._configurationService, undefined, this._logService, this._resolveVariables.bind(this));
|
||||
}
|
||||
getEnvironment(): Promise<IProcessEnvironment> {
|
||||
return this._proxy.getEnvironment();
|
||||
}
|
||||
getWslPath(original: string): Promise<string> {
|
||||
return this._proxy.getWslPath(original);
|
||||
}
|
||||
|
||||
setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise<void> {
|
||||
@@ -279,4 +308,22 @@ export class PtyHostService extends Disposable implements IPtyService {
|
||||
this._heartbeatSecondTimeout = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _pendingResolveVariablesRequests: Map<number, (resolved: string[]) => void> = new Map();
|
||||
private _resolveVariables(text: string[]): Promise<string[]> {
|
||||
return new Promise<string[]>(resolve => {
|
||||
const id = ++lastResolveVariablesRequestId;
|
||||
this._pendingResolveVariablesRequests.set(id, resolve);
|
||||
this._onPtyHostRequestResolveVariables.fire({ id, originalText: text });
|
||||
});
|
||||
}
|
||||
async acceptPtyHostResolvedVariables(id: number, resolved: string[]) {
|
||||
const request = this._pendingResolveVariablesRequests.get(id);
|
||||
if (request) {
|
||||
request(resolved);
|
||||
this._pendingResolveVariablesRequests.delete(id);
|
||||
} else {
|
||||
this._logService.warn(`Resolved variables received without matching request ${id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
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 { IProcessEnvironment, isWindows, OperatingSystem, OS } from 'vs/base/common/platform';
|
||||
import { IPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, ITerminalsLayoutInfo, IRawTerminalInstanceLayoutInfo, ITerminalTabLayoutInfoById, ITerminalInstanceLayoutInfoById, TerminalShellType, IProcessReadyEvent, TitleEventSource, TerminalIcon, IReconnectConstants } from 'vs/platform/terminal/common/terminal';
|
||||
import { AutoOpenBarrier, Queue, RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { TerminalRecorder } from 'vs/platform/terminal/common/terminalRecorder';
|
||||
@@ -14,6 +14,10 @@ import { ISetTerminalLayoutInfoArgs, ITerminalTabLayoutInfoDto, IProcessDetails,
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering';
|
||||
import { getSystemShell } from 'vs/base/node/shell';
|
||||
import { getWindowsBuildNumber } from 'vs/platform/terminal/node/terminalEnvironment';
|
||||
import { execFile } from 'child_process';
|
||||
import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
type WorkspaceId = string;
|
||||
|
||||
@@ -47,7 +51,8 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
|
||||
constructor(
|
||||
private _lastPtyId: number,
|
||||
private readonly _logService: ILogService
|
||||
private readonly _logService: ILogService,
|
||||
private readonly _reconnectConstants: IReconnectConstants
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -88,7 +93,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, shellLaunchConfig.icon);
|
||||
const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, this._reconnectConstants, this._logService, shellLaunchConfig.icon);
|
||||
process.onProcessExit(() => {
|
||||
persistentProcess.dispose();
|
||||
this._ptys.delete(id);
|
||||
@@ -111,6 +116,14 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
}
|
||||
}
|
||||
|
||||
async updateTitle(id: number, title: string, titleSource: TitleEventSource): Promise<void> {
|
||||
this._throwIfNoPty(id).setTitle(title, titleSource);
|
||||
}
|
||||
|
||||
async updateIcon(id: number, icon: URI | { light: URI; dark: URI } | { id: string, color?: { id: string } }, color?: string): Promise<void> {
|
||||
this._throwIfNoPty(id).setIcon(icon, color);
|
||||
}
|
||||
|
||||
async detachFromProcess(id: number): Promise<void> {
|
||||
this._throwIfNoPty(id).detach();
|
||||
}
|
||||
@@ -166,10 +179,25 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
return getSystemShell(osOverride, process.env);
|
||||
}
|
||||
|
||||
async getShellEnvironment(): Promise<IProcessEnvironment> {
|
||||
async getEnvironment(): Promise<IProcessEnvironment> {
|
||||
return { ...process.env };
|
||||
}
|
||||
|
||||
async getWslPath(original: string): Promise<string> {
|
||||
if (!isWindows) {
|
||||
return original;
|
||||
}
|
||||
if (getWindowsBuildNumber() < 17063) {
|
||||
return original.replace(/\\/g, '/');
|
||||
}
|
||||
return new Promise<string>(c => {
|
||||
const proc = execFile('bash.exe', ['-c', `wslpath ${escapeNonWindowsPath(original)}`], {}, (error, stdout, stderr) => {
|
||||
c(escapeNonWindowsPath(stdout.trim()));
|
||||
});
|
||||
proc.stdin!.end();
|
||||
});
|
||||
}
|
||||
|
||||
async setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise<void> {
|
||||
this._workspaceLayoutInfos.set(args.workspaceId, args);
|
||||
}
|
||||
@@ -178,10 +206,8 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
const layout = this._workspaceLayoutInfos.get(args.workspaceId);
|
||||
if (layout) {
|
||||
const expandedTabs = await Promise.all(layout.tabs.map(async tab => this._expandTerminalTab(tab)));
|
||||
const filtered = expandedTabs.filter(t => t.terminals.length > 0);
|
||||
return {
|
||||
tabs: filtered
|
||||
};
|
||||
const tabs = expandedTabs.filter(t => t.terminals.length > 0);
|
||||
return { tabs };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -219,12 +245,14 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
return {
|
||||
id,
|
||||
title: persistentProcess.title,
|
||||
titleSource: persistentProcess.titleSource,
|
||||
pid: persistentProcess.pid,
|
||||
workspaceId: persistentProcess.workspaceId,
|
||||
workspaceName: persistentProcess.workspaceName,
|
||||
cwd,
|
||||
isOrphan,
|
||||
icon: persistentProcess.icon
|
||||
icon: persistentProcess.icon,
|
||||
color: persistentProcess.color
|
||||
};
|
||||
}
|
||||
|
||||
@@ -254,7 +282,7 @@ export class PersistentTerminalProcess extends Disposable {
|
||||
|
||||
private readonly _onProcessReplay = this._register(new Emitter<IPtyHostProcessReplayEvent>());
|
||||
readonly onProcessReplay = this._onProcessReplay.event;
|
||||
private readonly _onProcessReady = this._register(new Emitter<{ pid: number, cwd: string }>());
|
||||
private readonly _onProcessReady = this._register(new Emitter<IProcessReadyEvent>());
|
||||
readonly onProcessReady = this._onProcessReady.event;
|
||||
private readonly _onProcessTitleChanged = this._register(new Emitter<string>());
|
||||
readonly onProcessTitleChanged = this._onProcessTitleChanged.event;
|
||||
@@ -271,33 +299,49 @@ export class PersistentTerminalProcess extends Disposable {
|
||||
|
||||
private _pid = -1;
|
||||
private _cwd = '';
|
||||
private _title: string | undefined;
|
||||
private _titleSource: TitleEventSource = TitleEventSource.Process;
|
||||
|
||||
get pid(): number { return this._pid; }
|
||||
get title(): string { return this._terminalProcess.currentTitle; }
|
||||
get icon(): string | undefined { return this._icon; }
|
||||
get title(): string { return this._title || this._terminalProcess.currentTitle; }
|
||||
get titleSource(): TitleEventSource { return this._titleSource; }
|
||||
get icon(): TerminalIcon | undefined { return this._icon; }
|
||||
get color(): string | undefined { return this._color; }
|
||||
|
||||
setTitle(title: string, titleSource: TitleEventSource): void {
|
||||
this._title = title;
|
||||
this._titleSource = titleSource;
|
||||
}
|
||||
|
||||
setIcon(icon: TerminalIcon, color?: string): void {
|
||||
this._icon = icon;
|
||||
this._color = color;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _persistentProcessId: number,
|
||||
private readonly _terminalProcess: TerminalProcess,
|
||||
public readonly workspaceId: string,
|
||||
public readonly workspaceName: string,
|
||||
public readonly shouldPersistTerminal: boolean,
|
||||
readonly workspaceId: string,
|
||||
readonly workspaceName: string,
|
||||
readonly shouldPersistTerminal: boolean,
|
||||
cols: number, rows: number,
|
||||
reconnectConstants: IReconnectConstants,
|
||||
private readonly _logService: ILogService,
|
||||
private readonly _icon?: string
|
||||
private _icon?: TerminalIcon,
|
||||
private _color?: string
|
||||
) {
|
||||
super();
|
||||
this._recorder = new TerminalRecorder(cols, rows);
|
||||
this._orphanQuestionBarrier = null;
|
||||
this._orphanQuestionReplyTime = 0;
|
||||
this._disconnectRunner1 = this._register(new RunOnceScheduler(() => {
|
||||
this._logService.info(`Persistent process "${this._persistentProcessId}": The reconnection grace time of ${printTime(LocalReconnectConstants.ReconnectionGraceTime)} has expired, shutting down pid "${this._pid}"`);
|
||||
this._logService.info(`Persistent process "${this._persistentProcessId}": The reconnection grace time of ${printTime(reconnectConstants.GraceTime)} has expired, shutting down pid "${this._pid}"`);
|
||||
this.shutdown(true);
|
||||
}, LocalReconnectConstants.ReconnectionGraceTime));
|
||||
}, reconnectConstants.GraceTime));
|
||||
this._disconnectRunner2 = this._register(new RunOnceScheduler(() => {
|
||||
this._logService.info(`Persistent process "${this._persistentProcessId}": The short reconnection grace time of ${printTime(LocalReconnectConstants.ReconnectionShortGraceTime)} has expired, shutting down pid ${this._pid}`);
|
||||
this._logService.info(`Persistent process "${this._persistentProcessId}": The short reconnection grace time of ${printTime(reconnectConstants.ShortGraceTime)} has expired, shutting down pid ${this._pid}`);
|
||||
this.shutdown(true);
|
||||
}, LocalReconnectConstants.ReconnectionShortGraceTime));
|
||||
}, reconnectConstants.ShortGraceTime));
|
||||
|
||||
this._register(this._terminalProcess.onProcessReady(e => {
|
||||
this._pid = e.pid;
|
||||
@@ -337,7 +381,7 @@ export class PersistentTerminalProcess extends Disposable {
|
||||
}
|
||||
this._isStarted = true;
|
||||
} else {
|
||||
this._onProcessReady.fire({ pid: this._pid, cwd: this._cwd });
|
||||
this._onProcessReady.fire({ pid: this._pid, cwd: this._cwd, requiresWindowsMode: isWindows && getWindowsBuildNumber() < 21376 });
|
||||
this._onProcessTitleChanged.fire(this._terminalProcess.currentTitle);
|
||||
this._onProcessShellTypeChanged.fire(this._terminalProcess.shellType);
|
||||
this.triggerReplay();
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IShellLaunchConfig, ITerminalLaunchError, FlowControlConstants, ITerminalChildProcess, ITerminalDimensionsOverride, TerminalShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { IShellLaunchConfig, ITerminalLaunchError, FlowControlConstants, ITerminalChildProcess, ITerminalDimensionsOverride, TerminalShellType, IProcessReadyEvent } from 'vs/platform/terminal/common/terminal';
|
||||
import { exec } from 'child_process';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { findExecutable, getWindowsBuildNumber } from 'vs/platform/terminal/node/terminalEnvironment';
|
||||
@@ -18,6 +18,7 @@ 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';
|
||||
import { Promises } from 'vs/base/node/pfs';
|
||||
|
||||
// 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
|
||||
@@ -90,21 +91,21 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
|
||||
private _isPtyPaused: boolean = false;
|
||||
private _unacknowledgedCharCount: number = 0;
|
||||
public get exitMessage(): string | undefined { return this._exitMessage; }
|
||||
get exitMessage(): string | undefined { return this._exitMessage; }
|
||||
|
||||
public get currentTitle(): string { return this._windowsShellHelper?.shellTitle || this._currentTitle; }
|
||||
public get shellType(): TerminalShellType { return this._windowsShellHelper ? this._windowsShellHelper.shellType : undefined; }
|
||||
get currentTitle(): string { return this._windowsShellHelper?.shellTitle || this._currentTitle; }
|
||||
get shellType(): TerminalShellType { return this._windowsShellHelper ? this._windowsShellHelper.shellType : undefined; }
|
||||
|
||||
private readonly _onProcessData = this._register(new Emitter<string>());
|
||||
public get onProcessData(): Event<string> { return this._onProcessData.event; }
|
||||
get onProcessData(): Event<string> { return this._onProcessData.event; }
|
||||
private readonly _onProcessExit = this._register(new Emitter<number>());
|
||||
public get onProcessExit(): Event<number> { return this._onProcessExit.event; }
|
||||
private readonly _onProcessReady = this._register(new Emitter<{ pid: number, cwd: string }>());
|
||||
public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; }
|
||||
get onProcessExit(): Event<number> { return this._onProcessExit.event; }
|
||||
private readonly _onProcessReady = this._register(new Emitter<IProcessReadyEvent>());
|
||||
get onProcessReady(): Event<IProcessReadyEvent> { return this._onProcessReady.event; }
|
||||
private readonly _onProcessTitleChanged = this._register(new Emitter<string>());
|
||||
public get onProcessTitleChanged(): Event<string> { return this._onProcessTitleChanged.event; }
|
||||
get onProcessTitleChanged(): Event<string> { return this._onProcessTitleChanged.event; }
|
||||
private readonly _onProcessShellTypeChanged = this._register(new Emitter<TerminalShellType>());
|
||||
public readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event;
|
||||
readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event;
|
||||
|
||||
constructor(
|
||||
private readonly _shellLaunchConfig: IShellLaunchConfig,
|
||||
@@ -164,7 +165,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
onProcessOverrideDimensions?: Event<ITerminalDimensionsOverride | undefined> | undefined;
|
||||
onProcessResolvedShellLaunchConfig?: Event<IShellLaunchConfig> | undefined;
|
||||
|
||||
public async start(): Promise<ITerminalLaunchError | undefined> {
|
||||
async start(): Promise<ITerminalLaunchError | undefined> {
|
||||
const results = await Promise.all([this._validateCwd(), this._validateExecutable()]);
|
||||
const firstError = results.find(r => r !== undefined);
|
||||
if (firstError) {
|
||||
@@ -182,7 +183,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
|
||||
private async _validateCwd(): Promise<undefined | ITerminalLaunchError> {
|
||||
try {
|
||||
const result = await fs.promises.stat(this._initialCwd);
|
||||
const result = await Promises.stat(this._initialCwd);
|
||||
if (!result.isDirectory()) {
|
||||
return { message: localize('launchFail.cwdNotDirectory', "Starting directory (cwd) \"{0}\" is not a directory", this._initialCwd.toString()) };
|
||||
}
|
||||
@@ -200,7 +201,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
throw new Error('IShellLaunchConfig.executable not set');
|
||||
}
|
||||
try {
|
||||
const result = await fs.promises.stat(slc.executable);
|
||||
const result = await Promises.stat(slc.executable);
|
||||
if (!result.isFile() && !result.isSymbolicLink()) {
|
||||
return { message: localize('launchFail.executableIsNotFileOrSymlink', "Path to shell executable \"{0}\" is not a file of a symlink", slc.executable) };
|
||||
}
|
||||
@@ -239,7 +240,6 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
ptyProcess.pause();
|
||||
}
|
||||
|
||||
|
||||
// Refire the data event
|
||||
this._onProcessData.fire(data);
|
||||
if (this._closeTimeout) {
|
||||
@@ -251,11 +251,11 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
this._exitCode = e.exitCode;
|
||||
this._queueProcessExit();
|
||||
});
|
||||
this._setupTitlePolling(ptyProcess);
|
||||
this._sendProcessId(ptyProcess.pid);
|
||||
this._setupTitlePolling(ptyProcess);
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
override dispose(): void {
|
||||
this._isDisposed = true;
|
||||
if (this._titleInterval) {
|
||||
clearInterval(this._titleInterval);
|
||||
@@ -266,7 +266,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
|
||||
private _setupTitlePolling(ptyProcess: pty.IPty) {
|
||||
// Send initial timeout async to give event listeners a chance to init
|
||||
setTimeout(() => this._sendProcessTitle(ptyProcess), 0);
|
||||
setTimeout(() => this._sendProcessTitle(ptyProcess));
|
||||
// Setup polling for non-Windows, for Windows `process` doesn't change
|
||||
if (!isWindows) {
|
||||
this._titleInterval = setInterval(() => {
|
||||
@@ -325,7 +325,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
}
|
||||
|
||||
private _sendProcessId(pid: number) {
|
||||
this._onProcessReady.fire({ pid, cwd: this._initialCwd });
|
||||
this._onProcessReady.fire({ pid, cwd: this._initialCwd, requiresWindowsMode: isWindows && getWindowsBuildNumber() < 21376 });
|
||||
}
|
||||
|
||||
private _sendProcessTitle(ptyProcess: pty.IPty): void {
|
||||
@@ -336,7 +336,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
this._onProcessTitleChanged.fire(this._currentTitle);
|
||||
}
|
||||
|
||||
public shutdown(immediate: boolean): void {
|
||||
shutdown(immediate: boolean): void {
|
||||
// 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.
|
||||
@@ -356,7 +356,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
}
|
||||
}
|
||||
|
||||
public input(data: string, isBinary?: boolean): void {
|
||||
input(data: string, isBinary?: boolean): void {
|
||||
if (this._isDisposed || !this._ptyProcess) {
|
||||
return;
|
||||
}
|
||||
@@ -370,7 +370,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
this._startWrite();
|
||||
}
|
||||
|
||||
public async processBinary(data: string): Promise<void> {
|
||||
async processBinary(data: string): Promise<void> {
|
||||
this.input(data, true);
|
||||
}
|
||||
|
||||
@@ -404,7 +404,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
}
|
||||
}
|
||||
|
||||
public resize(cols: number, rows: number): void {
|
||||
resize(cols: number, rows: number): void {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
@@ -437,7 +437,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
}
|
||||
}
|
||||
|
||||
public acknowledgeDataEvent(charCount: number): void {
|
||||
acknowledgeDataEvent(charCount: number): void {
|
||||
// Prevent lower than 0 to heal from errors
|
||||
this._unacknowledgedCharCount = Math.max(this._unacknowledgedCharCount - charCount, 0);
|
||||
this._logService.trace(`Flow control: Ack ${charCount} chars (unacknowledged: ${this._unacknowledgedCharCount})`);
|
||||
@@ -448,7 +448,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
}
|
||||
}
|
||||
|
||||
public clearUnacknowledgedChars(): void {
|
||||
clearUnacknowledgedChars(): void {
|
||||
this._unacknowledgedCharCount = 0;
|
||||
this._logService.trace(`Flow control: Cleared all unacknowledged chars, forcing resume`);
|
||||
if (this._isPtyPaused) {
|
||||
@@ -457,11 +457,11 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
}
|
||||
}
|
||||
|
||||
public getInitialCwd(): Promise<string> {
|
||||
getInitialCwd(): Promise<string> {
|
||||
return Promise.resolve(this._initialCwd);
|
||||
}
|
||||
|
||||
public getCwd(): Promise<string> {
|
||||
getCwd(): Promise<string> {
|
||||
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
|
||||
@@ -506,7 +506,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
});
|
||||
}
|
||||
|
||||
public getLatency(): Promise<number> {
|
||||
getLatency(): Promise<number> {
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
}
|
||||
@@ -515,12 +515,12 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
* Tracks the latest resize event to be trigger at a later point.
|
||||
*/
|
||||
class DelayedResizer extends Disposable {
|
||||
public rows: number | undefined;
|
||||
public cols: number | undefined;
|
||||
rows: number | undefined;
|
||||
cols: number | undefined;
|
||||
private _timeout: NodeJS.Timeout;
|
||||
|
||||
private readonly _onTrigger = this._register(new Emitter<{ rows?: number, cols?: number }>());
|
||||
public get onTrigger(): Event<{ rows?: number, cols?: number }> { return this._onTrigger.event; }
|
||||
get onTrigger(): Event<{ rows?: number, cols?: number }> { return this._onTrigger.event; }
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
362
lib/vscode/src/vs/platform/terminal/node/terminalProfiles.ts
Normal file
362
lib/vscode/src/vs/platform/terminal/node/terminalProfiles.ts
Normal file
@@ -0,0 +1,362 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { normalize, basename, delimiter } from 'vs/base/common/path';
|
||||
import { enumeratePowerShellInstallations } from 'vs/base/node/powershell';
|
||||
import { findExecutable, getWindowsBuildNumber } from 'vs/platform/terminal/node/terminalEnvironment';
|
||||
import * as cp from 'child_process';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { ITerminalEnvironment, ITerminalProfile, ITerminalProfileObject, ProfileSource, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { isLinux, isWindows } from 'vs/base/common/platform';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
let profileSources: Map<string, IPotentialTerminalProfile> | undefined;
|
||||
|
||||
export function detectAvailableProfiles(
|
||||
profiles: unknown,
|
||||
defaultProfile: unknown,
|
||||
includeDetectedProfiles: boolean,
|
||||
configurationService: IConfigurationService,
|
||||
fsProvider?: IFsProvider,
|
||||
logService?: ILogService,
|
||||
variableResolver?: (text: string[]) => Promise<string[]>,
|
||||
testPaths?: string[]
|
||||
): Promise<ITerminalProfile[]> {
|
||||
fsProvider = fsProvider || {
|
||||
existsFile: pfs.SymlinkSupport.existsFile,
|
||||
readFile: pfs.Promises.readFile
|
||||
};
|
||||
if (isWindows) {
|
||||
return detectAvailableWindowsProfiles(
|
||||
includeDetectedProfiles,
|
||||
fsProvider,
|
||||
logService,
|
||||
configurationService.getValue<boolean>(TerminalSettingId.UseWslProfiles) !== false,
|
||||
profiles && typeof profiles === 'object' ? { ...profiles } : configurationService.getValue<{ [key: string]: ITerminalProfileObject }>(TerminalSettingId.ProfilesWindows),
|
||||
typeof defaultProfile === 'string' ? defaultProfile : configurationService.getValue<string>(TerminalSettingId.DefaultProfileWindows),
|
||||
testPaths,
|
||||
variableResolver
|
||||
);
|
||||
}
|
||||
return detectAvailableUnixProfiles(
|
||||
fsProvider,
|
||||
logService,
|
||||
includeDetectedProfiles,
|
||||
profiles && typeof profiles === 'object' ? { ...profiles } : configurationService.getValue<{ [key: string]: ITerminalProfileObject }>(isLinux ? TerminalSettingId.ProfilesLinux : TerminalSettingId.ProfilesMacOs),
|
||||
typeof defaultProfile === 'string' ? defaultProfile : configurationService.getValue<string>(isLinux ? TerminalSettingId.DefaultProfileLinux : TerminalSettingId.DefaultProfileMacOs),
|
||||
testPaths,
|
||||
variableResolver
|
||||
);
|
||||
}
|
||||
|
||||
async function detectAvailableWindowsProfiles(
|
||||
includeDetectedProfiles: boolean,
|
||||
fsProvider: IFsProvider,
|
||||
logService?: ILogService,
|
||||
useWslProfiles?: boolean,
|
||||
configProfiles?: { [key: string]: ITerminalProfileObject },
|
||||
defaultProfileName?: string,
|
||||
testPaths?: string[],
|
||||
variableResolver?: (text: string[]) => Promise<string[]>
|
||||
): Promise<ITerminalProfile[]> {
|
||||
// Determine the correct System32 path. We want to point to Sysnative
|
||||
// when the 32-bit version of VS Code is running on a 64-bit machine.
|
||||
// The reason for this is because PowerShell's important PSReadline
|
||||
// module doesn't work if this is not the case. See #27915.
|
||||
const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
|
||||
const system32Path = `${process.env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}`;
|
||||
|
||||
let useWSLexe = false;
|
||||
|
||||
if (getWindowsBuildNumber() >= 16299) {
|
||||
useWSLexe = true;
|
||||
}
|
||||
|
||||
await initializeWindowsProfiles(testPaths);
|
||||
|
||||
const detectedProfiles: Map<string, ITerminalProfileObject> = new Map();
|
||||
|
||||
// Add auto detected profiles
|
||||
if (includeDetectedProfiles) {
|
||||
detectedProfiles.set('PowerShell', {
|
||||
source: ProfileSource.Pwsh,
|
||||
icon: Codicon.terminalPowershell,
|
||||
isAutoDetected: true
|
||||
});
|
||||
detectedProfiles.set('Windows PowerShell', {
|
||||
path: `${system32Path}\\WindowsPowerShell\\v1.0\\powershell.exe`,
|
||||
icon: Codicon.terminalPowershell,
|
||||
isAutoDetected: true
|
||||
});
|
||||
detectedProfiles.set('Git Bash', {
|
||||
source: ProfileSource.GitBash,
|
||||
isAutoDetected: true
|
||||
});
|
||||
detectedProfiles.set('Cygwin', {
|
||||
path: [
|
||||
`${process.env['HOMEDRIVE']}\\cygwin64\\bin\\bash.exe`,
|
||||
`${process.env['HOMEDRIVE']}\\cygwin\\bin\\bash.exe`
|
||||
],
|
||||
args: ['--login'],
|
||||
isAutoDetected: true
|
||||
});
|
||||
detectedProfiles.set('Command Prompt', {
|
||||
path: `${system32Path}\\cmd.exe`,
|
||||
icon: Codicon.terminalCmd,
|
||||
isAutoDetected: true
|
||||
});
|
||||
}
|
||||
|
||||
applyConfigProfilesToMap(configProfiles, detectedProfiles);
|
||||
|
||||
const resultProfiles: ITerminalProfile[] = await transformToTerminalProfiles(detectedProfiles.entries(), defaultProfileName, fsProvider, logService, variableResolver);
|
||||
|
||||
if (includeDetectedProfiles || (!includeDetectedProfiles && useWslProfiles)) {
|
||||
try {
|
||||
const result = await getWslProfiles(`${system32Path}\\${useWSLexe ? 'wsl' : 'bash'}.exe`, defaultProfileName);
|
||||
for (const wslProfile of result) {
|
||||
if (!configProfiles || !(wslProfile.profileName in configProfiles)) {
|
||||
resultProfiles.push(wslProfile);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logService?.info('WSL is not installed, so could not detect WSL profiles');
|
||||
}
|
||||
}
|
||||
|
||||
return resultProfiles;
|
||||
}
|
||||
|
||||
async function transformToTerminalProfiles(
|
||||
entries: IterableIterator<[string, ITerminalProfileObject]>,
|
||||
defaultProfileName: string | undefined,
|
||||
fsProvider: IFsProvider,
|
||||
logService?: ILogService,
|
||||
variableResolver?: (text: string[]) => Promise<string[]>
|
||||
): Promise<ITerminalProfile[]> {
|
||||
const resultProfiles: ITerminalProfile[] = [];
|
||||
for (const [profileName, profile] of entries) {
|
||||
if (profile === null) { continue; }
|
||||
let originalPaths: string[];
|
||||
let args: string[] | string | undefined;
|
||||
let icon: ThemeIcon | URI | { light: URI, dark: URI } | undefined = undefined;
|
||||
if ('source' in profile) {
|
||||
const source = profileSources?.get(profile.source);
|
||||
if (!source) {
|
||||
continue;
|
||||
}
|
||||
originalPaths = source.paths;
|
||||
|
||||
// if there are configured args, override the default ones
|
||||
args = profile.args || source.args;
|
||||
if (profile.icon) {
|
||||
icon = profile.icon;
|
||||
} else if (source.icon) {
|
||||
icon = source.icon;
|
||||
}
|
||||
} else {
|
||||
originalPaths = Array.isArray(profile.path) ? profile.path : [profile.path];
|
||||
args = isWindows ? profile.args : Array.isArray(profile.args) ? profile.args : undefined;
|
||||
icon = profile.icon || undefined;
|
||||
}
|
||||
|
||||
const paths = (await variableResolver?.(originalPaths)) || originalPaths.slice();
|
||||
const validatedProfile = await validateProfilePaths(profileName, defaultProfileName, paths, fsProvider, args, profile.env, profile.overrideName, profile.isAutoDetected, logService);
|
||||
if (validatedProfile) {
|
||||
validatedProfile.isAutoDetected = profile.isAutoDetected;
|
||||
validatedProfile.icon = icon;
|
||||
validatedProfile.color = profile.color;
|
||||
resultProfiles.push(validatedProfile);
|
||||
} else {
|
||||
logService?.trace('profile not validated', profileName, originalPaths);
|
||||
}
|
||||
}
|
||||
return resultProfiles;
|
||||
}
|
||||
|
||||
async function initializeWindowsProfiles(testPaths?: string[]): Promise<void> {
|
||||
if (profileSources) {
|
||||
return;
|
||||
}
|
||||
|
||||
profileSources = new Map();
|
||||
profileSources.set(
|
||||
'Git Bash', {
|
||||
profileName: 'Git Bash',
|
||||
paths: [
|
||||
`${process.env['ProgramW6432']}\\Git\\bin\\bash.exe`,
|
||||
`${process.env['ProgramW6432']}\\Git\\usr\\bin\\bash.exe`,
|
||||
`${process.env['ProgramFiles']}\\Git\\bin\\bash.exe`,
|
||||
`${process.env['ProgramFiles']}\\Git\\usr\\bin\\bash.exe`,
|
||||
`${process.env['LocalAppData']}\\Programs\\Git\\bin\\bash.exe`,
|
||||
`${process.env['UserProfile']}\\scoop\\apps\\git-with-openssh\\current\\bin\\bash.exe`,
|
||||
`${process.env['AllUsersProfile']}\\scoop\\apps\\git-with-openssh\\current\\bin\\bash.exe`
|
||||
],
|
||||
args: ['--login']
|
||||
});
|
||||
profileSources.set('PowerShell', {
|
||||
profileName: 'PowerShell',
|
||||
paths: testPaths || await getPowershellPaths(),
|
||||
icon: ThemeIcon.asThemeIcon(Codicon.terminalPowershell)
|
||||
});
|
||||
}
|
||||
|
||||
async function getPowershellPaths(): Promise<string[]> {
|
||||
const paths: string[] = [];
|
||||
// Add all of the different kinds of PowerShells
|
||||
for await (const pwshExe of enumeratePowerShellInstallations()) {
|
||||
paths.push(pwshExe.exePath);
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
async function getWslProfiles(wslPath: string, defaultProfileName: string | undefined): Promise<ITerminalProfile[]> {
|
||||
const profiles: ITerminalProfile[] = [];
|
||||
const distroOutput = await new Promise<string>((resolve, reject) => {
|
||||
// wsl.exe output is encoded in utf16le (ie. A -> 0x4100)
|
||||
cp.exec('wsl.exe -l -q', { encoding: 'utf16le' }, (err, stdout) => {
|
||||
if (err) {
|
||||
return reject('Problem occurred when getting wsl distros');
|
||||
}
|
||||
resolve(stdout);
|
||||
});
|
||||
});
|
||||
if (!distroOutput) {
|
||||
return [];
|
||||
}
|
||||
const regex = new RegExp(/[\r?\n]/);
|
||||
const distroNames = distroOutput.split(regex).filter(t => t.trim().length > 0 && t !== '');
|
||||
for (const distroName of distroNames) {
|
||||
// Skip empty lines
|
||||
if (distroName === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// docker-desktop and docker-desktop-data are treated as implementation details of
|
||||
// Docker Desktop for Windows and therefore not exposed
|
||||
if (distroName.startsWith('docker-desktop')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create the profile, adding the icon depending on the distro
|
||||
const profileName = `${distroName} (WSL)`;
|
||||
const profile: ITerminalProfile = {
|
||||
profileName,
|
||||
path: wslPath,
|
||||
args: [`-d`, `${distroName}`],
|
||||
isDefault: profileName === defaultProfileName,
|
||||
icon: getWslIcon(distroName)
|
||||
};
|
||||
// Add the profile
|
||||
profiles.push(profile);
|
||||
}
|
||||
return profiles;
|
||||
}
|
||||
|
||||
function getWslIcon(distroName: string): ThemeIcon {
|
||||
if (distroName.includes('Ubuntu')) {
|
||||
return ThemeIcon.asThemeIcon(Codicon.terminalUbuntu);
|
||||
} else if (distroName.includes('Debian')) {
|
||||
return ThemeIcon.asThemeIcon(Codicon.terminalDebian);
|
||||
} else {
|
||||
return ThemeIcon.asThemeIcon(Codicon.terminalLinux);
|
||||
}
|
||||
}
|
||||
|
||||
async function detectAvailableUnixProfiles(
|
||||
fsProvider: IFsProvider,
|
||||
logService?: ILogService,
|
||||
includeDetectedProfiles?: boolean,
|
||||
configProfiles?: { [key: string]: ITerminalProfileObject },
|
||||
defaultProfileName?: string,
|
||||
testPaths?: string[],
|
||||
variableResolver?: (text: string[]) => Promise<string[]>
|
||||
): Promise<ITerminalProfile[]> {
|
||||
const detectedProfiles: Map<string, ITerminalProfileObject> = new Map();
|
||||
|
||||
// Add non-quick launch profiles
|
||||
if (includeDetectedProfiles) {
|
||||
const contents = (await fsProvider.readFile('/etc/shells')).toString();
|
||||
const profiles = testPaths || contents.split('\n').filter(e => e.trim().indexOf('#') !== 0 && e.trim().length > 0);
|
||||
const counts: Map<string, number> = new Map();
|
||||
for (const profile of profiles) {
|
||||
let profileName = basename(profile);
|
||||
let count = counts.get(profileName) || 0;
|
||||
count++;
|
||||
if (count > 1) {
|
||||
profileName = `${profileName} (${count})`;
|
||||
}
|
||||
counts.set(profileName, count);
|
||||
detectedProfiles.set(profileName, { path: profile, isAutoDetected: true });
|
||||
}
|
||||
}
|
||||
|
||||
applyConfigProfilesToMap(configProfiles, detectedProfiles);
|
||||
|
||||
return await transformToTerminalProfiles(detectedProfiles.entries(), defaultProfileName, fsProvider, logService, variableResolver);
|
||||
}
|
||||
|
||||
function applyConfigProfilesToMap(configProfiles: { [key: string]: ITerminalProfileObject } | undefined, profilesMap: Map<string, ITerminalProfileObject>) {
|
||||
if (!configProfiles) {
|
||||
return;
|
||||
}
|
||||
for (const [profileName, value] of Object.entries(configProfiles)) {
|
||||
if (value === null || (!('path' in value) && !('source' in value))) {
|
||||
profilesMap.delete(profileName);
|
||||
} else {
|
||||
profilesMap.set(profileName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function validateProfilePaths(profileName: string, defaultProfileName: string | undefined, potentialPaths: string[], fsProvider: IFsProvider, args?: string[] | string, env?: ITerminalEnvironment, overrideName?: boolean, isAutoDetected?: boolean, logService?: ILogService): Promise<ITerminalProfile | undefined> {
|
||||
if (potentialPaths.length === 0) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
const path = potentialPaths.shift()!;
|
||||
if (path === '') {
|
||||
return validateProfilePaths(profileName, defaultProfileName, potentialPaths, fsProvider, args, env, overrideName, isAutoDetected);
|
||||
}
|
||||
|
||||
const profile: ITerminalProfile = { profileName, path, args, env, overrideName, isAutoDetected, isDefault: profileName === defaultProfileName };
|
||||
|
||||
// For non-absolute paths, check if it's available on $PATH
|
||||
if (basename(path) === path) {
|
||||
// The executable isn't an absolute path, try find it on the PATH
|
||||
const envPaths: string[] | undefined = process.env.PATH ? process.env.PATH.split(delimiter) : undefined;
|
||||
const executable = await findExecutable(path, undefined, envPaths, undefined, fsProvider.existsFile);
|
||||
if (!executable) {
|
||||
return validateProfilePaths(profileName, defaultProfileName, potentialPaths, fsProvider, args);
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
const result = await fsProvider.existsFile(normalize(path));
|
||||
if (result) {
|
||||
return profile;
|
||||
}
|
||||
|
||||
return validateProfilePaths(profileName, defaultProfileName, potentialPaths, fsProvider, args, env, overrideName, isAutoDetected);
|
||||
}
|
||||
|
||||
export interface IFsProvider {
|
||||
existsFile(path: string): Promise<boolean>,
|
||||
readFile(path: string): Promise<Buffer>;
|
||||
}
|
||||
|
||||
export interface IProfileVariableResolver {
|
||||
resolve(text: string[]): Promise<string[]>;
|
||||
}
|
||||
|
||||
interface IPotentialTerminalProfile {
|
||||
profileName: string;
|
||||
paths: string[];
|
||||
args?: string[];
|
||||
icon?: ThemeIcon | URI | { light: URI, dark: URI };
|
||||
}
|
||||
@@ -38,15 +38,15 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe
|
||||
private _isDisposed: boolean;
|
||||
private _currentRequest: Promise<string> | undefined;
|
||||
private _shellType: TerminalShellType | undefined;
|
||||
public get shellType(): TerminalShellType | undefined { return this._shellType; }
|
||||
get shellType(): TerminalShellType | undefined { return this._shellType; }
|
||||
private _shellTitle: string = '';
|
||||
public get shellTitle(): string { return this._shellTitle; }
|
||||
get shellTitle(): string { return this._shellTitle; }
|
||||
private readonly _onShellNameChanged = new Emitter<string>();
|
||||
public get onShellNameChanged(): Event<string> { return this._onShellNameChanged.event; }
|
||||
get onShellNameChanged(): Event<string> { return this._onShellNameChanged.event; }
|
||||
private readonly _onShellTypeChanged = new Emitter<TerminalShellType>();
|
||||
public get onShellTypeChanged(): Event<TerminalShellType> { return this._onShellTypeChanged.event; }
|
||||
get onShellTypeChanged(): Event<TerminalShellType> { return this._onShellTypeChanged.event; }
|
||||
|
||||
public constructor(
|
||||
constructor(
|
||||
private _rootProcessId: number
|
||||
) {
|
||||
super();
|
||||
@@ -112,7 +112,7 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe
|
||||
return this.traverseTree(tree.children[favouriteChild]);
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
override dispose(): void {
|
||||
this._isDisposed = true;
|
||||
super.dispose();
|
||||
}
|
||||
@@ -120,7 +120,7 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe
|
||||
/**
|
||||
* Returns the innermost shell executable running in the terminal
|
||||
*/
|
||||
public getShellName(): Promise<string> {
|
||||
getShellName(): Promise<string> {
|
||||
if (this._isDisposed) {
|
||||
return Promise.resolve('');
|
||||
}
|
||||
@@ -141,7 +141,7 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe
|
||||
return this._currentRequest;
|
||||
}
|
||||
|
||||
public getShellType(executable: string): TerminalShellType {
|
||||
getShellType(executable: string): TerminalShellType {
|
||||
switch (executable.toLowerCase()) {
|
||||
case 'cmd.exe':
|
||||
return WindowsShellType.CommandPrompt;
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Event, Emitter } from 'vs/base/common/event';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { assertNever } from 'vs/base/common/types';
|
||||
|
||||
// ------ API types
|
||||
|
||||
@@ -24,11 +25,21 @@ export interface ColorContribution {
|
||||
readonly deprecationMessage: string | undefined;
|
||||
}
|
||||
|
||||
|
||||
export interface ColorFunction {
|
||||
(theme: IColorTheme): Color | undefined;
|
||||
export const enum ColorTransformType {
|
||||
Darken,
|
||||
Lighten,
|
||||
Transparent,
|
||||
OneOf,
|
||||
LessProminent,
|
||||
}
|
||||
|
||||
export type ColorTransform =
|
||||
| { op: ColorTransformType.Darken; value: ColorValue; factor: number }
|
||||
| { op: ColorTransformType.Lighten; value: ColorValue; factor: number }
|
||||
| { op: ColorTransformType.Transparent; value: ColorValue; factor: number }
|
||||
| { op: ColorTransformType.OneOf; values: readonly ColorValue[] }
|
||||
| { op: ColorTransformType.LessProminent; value: ColorValue; background: ColorValue; factor: number; transparency: number };
|
||||
|
||||
export interface ColorDefaults {
|
||||
light: ColorValue | null;
|
||||
dark: ColorValue | null;
|
||||
@@ -38,7 +49,7 @@ export interface ColorDefaults {
|
||||
/**
|
||||
* A Color Value is either a color literal, a reference to an other color or a derived color
|
||||
*/
|
||||
export type ColorValue = Color | string | ColorIdentifier | ColorFunction;
|
||||
export type ColorValue = Color | string | ColorIdentifier | ColorTransform;
|
||||
|
||||
// color registry
|
||||
export const Extensions = {
|
||||
@@ -344,8 +355,8 @@ export const editorActiveLinkForeground = registerColor('editorLink.activeForegr
|
||||
/**
|
||||
* Inline hints
|
||||
*/
|
||||
export const editorInlineHintForeground = registerColor('editorInlineHint.foreground', { dark: editorWidgetBackground, light: editorWidgetForeground, hc: editorWidgetBackground }, nls.localize('editorInlineHintForeground', 'Foreground color of inline hints'));
|
||||
export const editorInlineHintBackground = registerColor('editorInlineHint.background', { dark: editorWidgetForeground, light: editorWidgetBackground, hc: editorWidgetForeground }, nls.localize('editorInlineHintBackground', 'Background color of inline hints'));
|
||||
export const editorInlayHintForeground = registerColor('editorInlayHint.foreground', { dark: editorWidgetBackground, light: editorWidgetForeground, hc: editorWidgetBackground }, nls.localize('editorInlayHintForeground', 'Foreground color of inline hints'));
|
||||
export const editorInlayHintBackground = registerColor('editorInlayHint.background', { dark: editorWidgetForeground, light: editorWidgetBackground, hc: editorWidgetForeground }, nls.localize('editorInlayHintBackground', 'Background color of inline hints'));
|
||||
|
||||
/**
|
||||
* Editor lighbulb icon colors
|
||||
@@ -383,7 +394,8 @@ export const listInactiveFocusOutline = registerColor('list.inactiveFocusOutline
|
||||
export const listHoverBackground = registerColor('list.hoverBackground', { dark: '#2A2D2E', light: '#F0F0F0', hc: null }, nls.localize('listHoverBackground', "List/Tree background when hovering over items using the mouse."));
|
||||
export const listHoverForeground = registerColor('list.hoverForeground', { dark: null, light: null, hc: null }, nls.localize('listHoverForeground', "List/Tree foreground when hovering over items using the mouse."));
|
||||
export const listDropBackground = registerColor('list.dropBackground', { dark: '#062F4A', light: '#D6EBFF', hc: null }, nls.localize('listDropBackground', "List/Tree drag and drop background when moving items around using the mouse."));
|
||||
export const listHighlightForeground = registerColor('list.highlightForeground', { dark: '#0097fb', light: '#0066BF', hc: focusBorder }, nls.localize('highlight', 'List/Tree foreground color of the match highlights when searching inside the list/tree.'));
|
||||
export const listHighlightForeground = registerColor('list.highlightForeground', { dark: '#18A3FF', light: '#0066BF', hc: focusBorder }, nls.localize('highlight', 'List/Tree foreground color of the match highlights when searching inside the list/tree.'));
|
||||
export const listFocusHighlightForeground = registerColor('list.focusHighlightForeground', { dark: listHighlightForeground, light: listHighlightForeground, hc: listHighlightForeground }, nls.localize('listFocusHighlightForeground', 'List/Tree foreground color of the match highlights on actively focused items when searching inside the list/tree.'));
|
||||
export const listInvalidItemForeground = registerColor('list.invalidItemForeground', { dark: '#B89500', light: '#B89500', hc: '#B89500' }, nls.localize('invalidItemForeground', 'List/Tree foreground color for invalid items, for example an unresolved root in explorer.'));
|
||||
export const listErrorForeground = registerColor('list.errorForeground', { dark: '#F88070', light: '#B01011', hc: null }, nls.localize('listErrorForeground', 'Foreground color of list items containing errors.'));
|
||||
export const listWarningForeground = registerColor('list.warningForeground', { dark: '#CCA700', light: '#855F00', hc: null }, nls.localize('listWarningForeground', 'Foreground color of list items containing warnings.'));
|
||||
@@ -400,7 +412,8 @@ export const listDeemphasizedForeground = registerColor('list.deemphasizedForegr
|
||||
* Quick pick widget (dependent on List and tree colors)
|
||||
*/
|
||||
export const _deprecatedQuickInputListFocusBackground = registerColor('quickInput.list.focusBackground', { dark: null, light: null, hc: null }, '', undefined, nls.localize('quickInput.list.focusBackground deprecation', "Please use quickInputList.focusBackground instead"));
|
||||
export const quickInputListFocusBackground = registerColor('quickInputList.focusBackground', { dark: oneOf(_deprecatedQuickInputListFocusBackground, listFocusBackground, '#062F4A'), light: oneOf(_deprecatedQuickInputListFocusBackground, listFocusBackground, '#D6EBFF'), hc: null }, nls.localize('quickInput.listFocusBackground', "Quick picker background color for the focused item."));
|
||||
export const quickInputListFocusForeground = registerColor('quickInputList.focusForeground', { dark: listActiveSelectionForeground, light: listActiveSelectionForeground, hc: listActiveSelectionForeground }, nls.localize('quickInput.listFocusForeground', "Quick picker foreground color for the focused item."));
|
||||
export const quickInputListFocusBackground = registerColor('quickInputList.focusBackground', { dark: oneOf(_deprecatedQuickInputListFocusBackground, listActiveSelectionBackground, '#062F4A'), light: oneOf(_deprecatedQuickInputListFocusBackground, listActiveSelectionBackground, '#D6EBFF'), hc: null }, nls.localize('quickInput.listFocusBackground', "Quick picker background color for the focused item."));
|
||||
|
||||
/**
|
||||
* Menu colors
|
||||
@@ -493,63 +506,63 @@ export const chartsPurple = registerColor('charts.purple', { dark: '#B180D7', li
|
||||
|
||||
// ----- color functions
|
||||
|
||||
export function darken(colorValue: ColorValue, factor: number): ColorFunction {
|
||||
return (theme) => {
|
||||
let color = resolveColorValue(colorValue, theme);
|
||||
if (color) {
|
||||
return color.darken(factor);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
export function executeTransform(transform: ColorTransform, theme: IColorTheme) {
|
||||
switch (transform.op) {
|
||||
case ColorTransformType.Darken:
|
||||
return resolveColorValue(transform.value, theme)?.darken(transform.factor);
|
||||
|
||||
export function lighten(colorValue: ColorValue, factor: number): ColorFunction {
|
||||
return (theme) => {
|
||||
let color = resolveColorValue(colorValue, theme);
|
||||
if (color) {
|
||||
return color.lighten(factor);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
case ColorTransformType.Lighten:
|
||||
return resolveColorValue(transform.value, theme)?.lighten(transform.factor);
|
||||
|
||||
export function transparent(colorValue: ColorValue, factor: number): ColorFunction {
|
||||
return (theme) => {
|
||||
let color = resolveColorValue(colorValue, theme);
|
||||
if (color) {
|
||||
return color.transparent(factor);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
case ColorTransformType.Transparent:
|
||||
return resolveColorValue(transform.value, theme)?.transparent(transform.factor);
|
||||
|
||||
export function oneOf(...colorValues: ColorValue[]): ColorFunction {
|
||||
return (theme) => {
|
||||
for (let colorValue of colorValues) {
|
||||
let color = resolveColorValue(colorValue, theme);
|
||||
if (color) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
function lessProminent(colorValue: ColorValue, backgroundColorValue: ColorValue, factor: number, transparency: number): ColorFunction {
|
||||
return (theme) => {
|
||||
let from = resolveColorValue(colorValue, theme);
|
||||
if (from) {
|
||||
let backgroundColor = resolveColorValue(backgroundColorValue, theme);
|
||||
if (backgroundColor) {
|
||||
if (from.isDarkerThan(backgroundColor)) {
|
||||
return Color.getLighterColor(from, backgroundColor, factor).transparent(transparency);
|
||||
case ColorTransformType.OneOf:
|
||||
for (const candidate of transform.values) {
|
||||
const color = resolveColorValue(candidate, theme);
|
||||
if (color) {
|
||||
return color;
|
||||
}
|
||||
return Color.getDarkerColor(from, backgroundColor, factor).transparent(transparency);
|
||||
}
|
||||
return from.transparent(factor * transparency);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
return undefined;
|
||||
|
||||
case ColorTransformType.LessProminent:
|
||||
const from = resolveColorValue(transform.value, theme);
|
||||
if (!from) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const backgroundColor = resolveColorValue(transform.background, theme);
|
||||
if (!backgroundColor) {
|
||||
return from.transparent(transform.factor * transform.transparency);
|
||||
}
|
||||
|
||||
return from.isDarkerThan(backgroundColor)
|
||||
? Color.getLighterColor(from, backgroundColor, transform.factor).transparent(transform.transparency)
|
||||
: Color.getDarkerColor(from, backgroundColor, transform.factor).transparent(transform.transparency);
|
||||
default:
|
||||
throw assertNever(transform);
|
||||
}
|
||||
}
|
||||
|
||||
export function darken(colorValue: ColorValue, factor: number): ColorTransform {
|
||||
return { op: ColorTransformType.Darken, value: colorValue, factor };
|
||||
}
|
||||
|
||||
export function lighten(colorValue: ColorValue, factor: number): ColorTransform {
|
||||
return { op: ColorTransformType.Lighten, value: colorValue, factor };
|
||||
}
|
||||
|
||||
export function transparent(colorValue: ColorValue, factor: number): ColorTransform {
|
||||
return { op: ColorTransformType.Transparent, value: colorValue, factor };
|
||||
}
|
||||
|
||||
export function oneOf(...colorValues: ColorValue[]): ColorTransform {
|
||||
return { op: ColorTransformType.OneOf, values: colorValues };
|
||||
}
|
||||
|
||||
function lessProminent(colorValue: ColorValue, backgroundColorValue: ColorValue, factor: number, transparency: number): ColorTransform {
|
||||
return { op: ColorTransformType.LessProminent, value: colorValue, background: backgroundColorValue, factor, transparency };
|
||||
}
|
||||
|
||||
// ----- implementation
|
||||
@@ -567,8 +580,8 @@ export function resolveColorValue(colorValue: ColorValue | null, theme: IColorTh
|
||||
return theme.getColor(colorValue);
|
||||
} else if (colorValue instanceof Color) {
|
||||
return colorValue;
|
||||
} else if (typeof colorValue === 'function') {
|
||||
return colorValue(theme);
|
||||
} else if (typeof colorValue === 'object') {
|
||||
return executeTransform(colorValue, theme);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
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 { 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, 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, quickInputListFocusForeground, ColorTransform } 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';
|
||||
@@ -35,7 +35,7 @@ export function computeStyles(theme: IColorTheme, styleMap: IColorMapping): ICom
|
||||
}
|
||||
|
||||
export function attachStyler<T extends IColorMapping>(themeService: IThemeService, styleMap: T, widgetOrCallback: IThemable | styleFn): IDisposable {
|
||||
function applyStyles(theme: IColorTheme): void {
|
||||
function applyStyles(): void {
|
||||
const styles = computeStyles(themeService.getColorTheme(), styleMap);
|
||||
|
||||
if (typeof widgetOrCallback === 'function') {
|
||||
@@ -45,7 +45,7 @@ export function attachStyler<T extends IColorMapping>(themeService: IThemeServic
|
||||
}
|
||||
}
|
||||
|
||||
applyStyles(themeService.getColorTheme());
|
||||
applyStyles();
|
||||
|
||||
return themeService.onDidColorThemeChange(applyStyles);
|
||||
}
|
||||
@@ -130,7 +130,7 @@ export function attachSelectBoxStyler(widget: IThemable, themeService: IThemeSer
|
||||
selectBorder: style?.selectBorder || selectBorder,
|
||||
focusBorder: style?.focusBorder || focusBorder,
|
||||
listFocusBackground: style?.listFocusBackground || quickInputListFocusBackground,
|
||||
listFocusForeground: style?.listFocusForeground || listFocusForeground,
|
||||
listFocusForeground: style?.listFocusForeground || quickInputListFocusForeground,
|
||||
listFocusOutline: style?.listFocusOutline || ((theme: IColorTheme) => theme.type === ColorScheme.HIGH_CONTRAST ? activeContrastBorder : Color.transparent),
|
||||
listHoverBackground: style?.listHoverBackground || listHoverBackground,
|
||||
listHoverForeground: style?.listHoverForeground || listHoverForeground,
|
||||
@@ -254,16 +254,6 @@ export function attachKeybindingLabelStyler(widget: IThemable, themeService: ITh
|
||||
} 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?.textLinkForeground || textLinkForeground,
|
||||
} as ILinkStyleOverrides, widget);
|
||||
}
|
||||
|
||||
export interface IProgressBarStyleOverrides extends IStyleOverrides {
|
||||
progressBarBackground?: ColorIdentifier;
|
||||
}
|
||||
@@ -279,7 +269,7 @@ export function attachStylerCallback(themeService: IThemeService, colors: { [nam
|
||||
}
|
||||
|
||||
export interface IBreadcrumbsWidgetStyleOverrides extends IColorMapping {
|
||||
breadcrumbsBackground?: ColorIdentifier | ColorFunction;
|
||||
breadcrumbsBackground?: ColorIdentifier | ColorTransform;
|
||||
breadcrumbsForeground?: ColorIdentifier;
|
||||
breadcrumbsHoverForeground?: ColorIdentifier;
|
||||
breadcrumbsFocusForeground?: ColorIdentifier;
|
||||
@@ -324,7 +314,7 @@ export function attachMenuStyler(widget: IThemable, themeService: IThemeService,
|
||||
return attachStyler(themeService, { ...defaultMenuStyles, ...style }, widget);
|
||||
}
|
||||
|
||||
export interface IDialogStyleOverrides extends IButtonStyleOverrides, ILinkStyleOverrides {
|
||||
export interface IDialogStyleOverrides extends IButtonStyleOverrides {
|
||||
dialogForeground?: ColorIdentifier;
|
||||
dialogBackground?: ColorIdentifier;
|
||||
dialogShadow?: ColorIdentifier;
|
||||
|
||||
@@ -67,6 +67,10 @@ export namespace ThemeIcon {
|
||||
return ti1.id === ti2.id && ti1.color?.id === ti2.color?.id;
|
||||
}
|
||||
|
||||
export function asThemeIcon(codicon: Codicon): ThemeIcon {
|
||||
return { id: codicon.id };
|
||||
}
|
||||
|
||||
export const asClassNameArray: (icon: ThemeIcon) => string[] = CSSIcon.asClassNameArray;
|
||||
export const asClassName: (icon: ThemeIcon) => string = CSSIcon.asClassName;
|
||||
export const asCSSSelector: (icon: ThemeIcon) => string = CSSIcon.asCSSSelector;
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { BrowserWindow, nativeTheme } from 'electron';
|
||||
import { isWindows, isMacintosh } from 'vs/base/common/platform';
|
||||
import { ipcMain, nativeTheme } from 'electron';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { IStateMainService } from 'vs/platform/state/electron-main/state';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IPartsSplash } from 'vs/platform/windows/common/windows';
|
||||
|
||||
const DEFAULT_BG_LIGHT = '#FFFFFF';
|
||||
const DEFAULT_BG_DARK = '#1E1E1E';
|
||||
@@ -14,45 +15,38 @@ const DEFAULT_BG_HC_BLACK = '#000000';
|
||||
|
||||
const THEME_STORAGE_KEY = 'theme';
|
||||
const THEME_BG_STORAGE_KEY = 'themeBackground';
|
||||
const THEME_WINDOW_SPLASH = 'windowSplash';
|
||||
|
||||
export const IThemeMainService = createDecorator<IThemeMainService>('themeMainService');
|
||||
|
||||
export interface IThemeMainService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
getBackgroundColor(): string;
|
||||
|
||||
saveWindowSplash(windowId: number | undefined, splash: IPartsSplash): void;
|
||||
getWindowSplash(): IPartsSplash | undefined;
|
||||
}
|
||||
|
||||
export class ThemeMainService implements IThemeMainService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(@IStateService private stateService: IStateService) {
|
||||
ipcMain.on('vscode:changeColorTheme', (e: Event, windowId: number, broadcast: string) => {
|
||||
// Theme changes
|
||||
if (typeof broadcast === 'string') {
|
||||
this.storeBackgroundColor(JSON.parse(broadcast));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private storeBackgroundColor(data: { baseTheme: string, background: string }): void {
|
||||
this.stateService.setItem(THEME_STORAGE_KEY, data.baseTheme);
|
||||
this.stateService.setItem(THEME_BG_STORAGE_KEY, data.background);
|
||||
}
|
||||
constructor(@IStateMainService private stateMainService: IStateMainService) { }
|
||||
|
||||
getBackgroundColor(): string {
|
||||
if ((isWindows || isMacintosh) && nativeTheme.shouldUseInvertedColorScheme) {
|
||||
return DEFAULT_BG_HC_BLACK;
|
||||
}
|
||||
|
||||
let background = this.stateService.getItem<string | null>(THEME_BG_STORAGE_KEY, null);
|
||||
let background = this.stateMainService.getItem<string | null>(THEME_BG_STORAGE_KEY, null);
|
||||
if (!background) {
|
||||
let baseTheme: string;
|
||||
if ((isWindows || isMacintosh) && nativeTheme.shouldUseInvertedColorScheme) {
|
||||
baseTheme = 'hc-black';
|
||||
} else {
|
||||
baseTheme = this.stateService.getItem<string>(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0];
|
||||
baseTheme = this.stateMainService.getItem<string>(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0];
|
||||
}
|
||||
|
||||
background = (baseTheme === 'hc-black') ? DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? DEFAULT_BG_LIGHT : DEFAULT_BG_DARK);
|
||||
@@ -64,4 +58,32 @@ export class ThemeMainService implements IThemeMainService {
|
||||
|
||||
return background;
|
||||
}
|
||||
|
||||
saveWindowSplash(windowId: number | undefined, splash: IPartsSplash): void {
|
||||
|
||||
// Update in storage
|
||||
this.stateMainService.setItems([
|
||||
{ key: THEME_STORAGE_KEY, data: splash.baseTheme },
|
||||
{ key: THEME_BG_STORAGE_KEY, data: splash.colorInfo.background },
|
||||
{ key: THEME_WINDOW_SPLASH, data: splash }
|
||||
]);
|
||||
|
||||
// Update in opened windows
|
||||
if (typeof windowId === 'number') {
|
||||
this.updateBackgroundColor(windowId, splash);
|
||||
}
|
||||
}
|
||||
|
||||
private updateBackgroundColor(windowId: number, splash: IPartsSplash): void {
|
||||
for (const window of BrowserWindow.getAllWindows()) {
|
||||
if (window.id === windowId) {
|
||||
window.setBackgroundColor(splash.colorInfo.background);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getWindowSplash(): IPartsSplash | undefined {
|
||||
return this.stateMainService.getItem<IPartsSplash>(THEME_WINDOW_SPLASH);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,7 +709,7 @@ export class UndoRedoService implements IUndoRedoService {
|
||||
|
||||
private _onError(err: Error, element: StackElement): void {
|
||||
onUnexpectedError(err);
|
||||
// An error occured while undoing or redoing => drop the undo/redo stack for all affected resources
|
||||
// An error occurred while undoing or redoing => drop the undo/redo stack for all affected resources
|
||||
for (const strResource of element.strResources) {
|
||||
this.removeElements(strResource);
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ export abstract class AbstractUpdateService implements IUpdateService {
|
||||
|
||||
this.logService.trace('update#quitAndInstall(): before lifecycle quit()');
|
||||
|
||||
this.lifecycleMainService.quit(true /* from update */).then(vetod => {
|
||||
this.lifecycleMainService.quit(true /* will restart */).then(vetod => {
|
||||
this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`);
|
||||
if (vetod) {
|
||||
return;
|
||||
|
||||
@@ -106,7 +106,7 @@ abstract class AbstractUpdateService implements IUpdateService {
|
||||
|
||||
this.logService.trace('update#quitAndInstall(): before lifecycle quit()');
|
||||
|
||||
this.lifecycleMainService.quit(true /* from update */).then(vetod => {
|
||||
this.lifecycleMainService.quit(true /* will restart */).then(vetod => {
|
||||
this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`);
|
||||
if (vetod) {
|
||||
return;
|
||||
|
||||
@@ -54,7 +54,7 @@ export class Win32UpdateService extends AbstractUpdateService {
|
||||
@memoize
|
||||
get cachePath(): Promise<string> {
|
||||
const result = path.join(tmpdir(), `vscode-update-${this.productService.target}-${process.arch}`);
|
||||
return fs.promises.mkdir(result, { recursive: true }).then(() => result);
|
||||
return pfs.Promises.mkdir(result, { recursive: true }).then(() => result);
|
||||
}
|
||||
|
||||
constructor(
|
||||
@@ -145,7 +145,7 @@ export class Win32UpdateService extends AbstractUpdateService {
|
||||
return this.requestService.request({ url }, CancellationToken.None)
|
||||
.then(context => this.fileService.writeFile(URI.file(downloadPath), context.stream))
|
||||
.then(hash ? () => checksum(downloadPath, update.hash) : () => undefined)
|
||||
.then(() => fs.promises.rename(downloadPath, updatePackagePath))
|
||||
.then(() => pfs.Promises.rename(downloadPath, updatePackagePath))
|
||||
.then(() => updatePackagePath);
|
||||
});
|
||||
}).then(packagePath => {
|
||||
@@ -195,7 +195,7 @@ export class Win32UpdateService extends AbstractUpdateService {
|
||||
|
||||
const promises = versions.filter(filter).map(async one => {
|
||||
try {
|
||||
await fs.promises.unlink(path.join(cachePath, one));
|
||||
await pfs.Promises.unlink(path.join(cachePath, one));
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@@ -233,6 +233,31 @@ export interface IOSConfiguration {
|
||||
readonly hostname: string;
|
||||
}
|
||||
|
||||
export interface IPartsSplash {
|
||||
baseTheme: string;
|
||||
colorInfo: {
|
||||
background: string;
|
||||
foreground: string | undefined;
|
||||
editorBackground: string | undefined;
|
||||
titleBarBackground: string | undefined;
|
||||
activityBarBackground: string | undefined;
|
||||
sideBarBackground: string | undefined;
|
||||
statusBarBackground: string | undefined;
|
||||
statusBarNoFolderBackground: string | undefined;
|
||||
windowBorder: string | undefined;
|
||||
}
|
||||
layoutInfo: {
|
||||
sideBarSide: string;
|
||||
editorPartMinWidth: number;
|
||||
titleBarHeight: number;
|
||||
activityBarWidth: number;
|
||||
sideBarWidth: number;
|
||||
statusBarHeight: number;
|
||||
windowBorder: boolean;
|
||||
windowBorderRadius: string | undefined;
|
||||
} | undefined
|
||||
}
|
||||
|
||||
export interface INativeWindowConfiguration extends IWindowConfiguration, NativeParsedArgs, ISandboxConfiguration {
|
||||
mainPid: number;
|
||||
|
||||
@@ -245,7 +270,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native
|
||||
tmpDir: string;
|
||||
userDataDir: string;
|
||||
|
||||
partsSplashPath: string;
|
||||
partsSplash?: IPartsSplash;
|
||||
|
||||
workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier;
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ 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 {
|
||||
@@ -153,7 +152,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IDialogMainService private readonly dialogMainService: IDialogMainService,
|
||||
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
|
||||
@INativeHostMainService private readonly nativeHostMainService: INativeHostMainService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IProtocolMainService private readonly protocolMainService: IProtocolMainService
|
||||
) {
|
||||
@@ -441,7 +439,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
});
|
||||
|
||||
// Block all SVG requests from unsupported origins
|
||||
const supportedSvgSchemes = new Set([Schemas.file, Schemas.vscodeFileResource, Schemas.vscodeRemoteResource, 'devtools']); // TODO: handle webview origin
|
||||
const supportedSvgSchemes = new Set([Schemas.file, Schemas.vscodeFileResource, Schemas.vscodeRemoteResource, 'devtools']); // TODO@mjbvz: handle webview origin
|
||||
|
||||
// But allow them if the are made from inside an webview
|
||||
const isSafeFrame = (requestFrame: WebFrameMain | undefined): boolean => {
|
||||
@@ -453,13 +451,16 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
return false;
|
||||
};
|
||||
|
||||
const isRequestFromSafeContext = (details: Electron.OnBeforeRequestListenerDetails | Electron.OnHeadersReceivedListenerDetails): boolean => {
|
||||
return details.resourceType === 'xhr' || isSafeFrame(details.frame);
|
||||
};
|
||||
|
||||
this._win.webContents.session.webRequest.onBeforeRequest((details, callback) => {
|
||||
const uri = URI.parse(details.url);
|
||||
if (uri.path.endsWith('.svg')) {
|
||||
const isSafeResourceUrl = supportedSvgSchemes.has(uri.scheme) || uri.path.includes(Schemas.vscodeRemoteResource);
|
||||
if (!isSafeResourceUrl) {
|
||||
const isSafeContext = isSafeFrame(details.frame);
|
||||
return callback({ cancel: !isSafeContext });
|
||||
return callback({ cancel: !isRequestFromSafeContext(details) });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,8 +486,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
// remote extension schemes have the following format
|
||||
// http://127.0.0.1:<port>/vscode-remote-resource?path=
|
||||
if (!uri.path.includes(Schemas.vscodeRemoteResource) && contentTypes.some(contentType => contentType.toLowerCase().includes('image/svg'))) {
|
||||
const isSafeContext = isSafeFrame(details.frame);
|
||||
return callback({ cancel: !isSafeContext });
|
||||
return callback({ cancel: !isRequestFromSafeContext(details) });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,22 +510,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
this._lastFocusTime = Date.now();
|
||||
});
|
||||
|
||||
if (isMacintosh) {
|
||||
this._register(this.nativeHostMainService.onDidChangeDisplay(() => {
|
||||
if (!this._win) {
|
||||
return; // disposed
|
||||
}
|
||||
|
||||
// Simple fullscreen doesn't resize automatically when the resolution changes so as a workaround
|
||||
// we need to detect when display metrics change or displays are added/removed and toggle the
|
||||
// fullscreen manually.
|
||||
if (!this.useNativeFullScreen() && this.isFullScreen) {
|
||||
this.setFullScreen(false);
|
||||
this.setFullScreen(true);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Window (Un)Maximize
|
||||
this._win.on('maximize', (e: Event) => {
|
||||
if (this.currentConfig) {
|
||||
@@ -574,7 +558,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
|
||||
switch (type) {
|
||||
case WindowError.CRASHED:
|
||||
this.logService.error(`CodeWindow: renderer process crashed (detail: ${typeof details === 'string' ? details : details?.reason})`);
|
||||
this.logService.error(`CodeWindow: renderer process crashed (detail: ${typeof details === 'string' ? details : details?.reason}, code: ${typeof details === 'string' ? '<unknown>' : details?.exitCode ?? '<unknown>'})`);
|
||||
break;
|
||||
case WindowError.UNRESPONSIVE:
|
||||
this.logService.error('CodeWindow: detected unresponsive');
|
||||
@@ -668,8 +652,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
|
||||
private destroyWindow(): void {
|
||||
this._onDidDestroy.fire(); // 'close' event will not be fired on destroy(), so signal crash via explicit event
|
||||
this._win.destroy(); // make sure to destroy the window as it has crashed
|
||||
this._onDidDestroy.fire(); // 'close' event will not be fired on destroy(), so signal crash via explicit event
|
||||
this._win.destroy(); // make sure to destroy the window as it has crashed
|
||||
}
|
||||
|
||||
private onDidDeleteUntitledWorkspace(workspace: IWorkspaceIdentifier): void {
|
||||
@@ -806,6 +790,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
// Update window related properties
|
||||
configuration.fullscreen = this.isFullScreen;
|
||||
configuration.maximized = this._win.isMaximized();
|
||||
configuration.partsSplash = this.themeMainService.getWindowSplash();
|
||||
|
||||
// Update with latest perf marks
|
||||
mark('code/willOpenNewWindow');
|
||||
|
||||
@@ -8,7 +8,7 @@ import { IWorkspaceIdentifier, IResolvedWorkspace, isWorkspaceIdentifier, isSing
|
||||
import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
|
||||
import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
|
||||
export function findWindowOnFile(windows: ICodeWindow[], fileUri: URI, localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null): ICodeWindow | undefined {
|
||||
export function findWindowOnFile(windows: ICodeWindow[], fileUri: URI, localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | undefined): ICodeWindow | undefined {
|
||||
|
||||
// First check for windows with workspaces that have a parent folder of the provided path opened
|
||||
for (const window of windows) {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
|
||||
import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup';
|
||||
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { IStateMainService } from 'vs/platform/state/electron-main/state';
|
||||
import { CodeWindow } from 'vs/platform/windows/electron-main/window';
|
||||
import { app, BrowserWindow, MessageBoxOptions, nativeTheme, WebContents } from 'electron';
|
||||
import { ILifecycleMainService, UnloadReason } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
@@ -139,13 +139,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
||||
private readonly _onDidChangeWindowsCount = this._register(new Emitter<IWindowsCountChangedEvent>());
|
||||
readonly onDidChangeWindowsCount = this._onDidChangeWindowsCount.event;
|
||||
|
||||
private readonly windowsStateHandler = this._register(new WindowsStateHandler(this, this.stateService, this.lifecycleMainService, this.logService, this.configurationService));
|
||||
private readonly windowsStateHandler = this._register(new WindowsStateHandler(this, this.stateMainService, this.lifecycleMainService, this.logService, this.configurationService));
|
||||
|
||||
constructor(
|
||||
private readonly machineId: string,
|
||||
private readonly initialUserEnv: IProcessEnvironment,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IStateService private readonly stateService: IStateService,
|
||||
@IStateMainService private readonly stateMainService: IStateMainService,
|
||||
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
|
||||
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
|
||||
@IBackupMainService private readonly backupMainService: IBackupMainService,
|
||||
@@ -409,7 +409,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
||||
let windowToUseForFiles: ICodeWindow | undefined = undefined;
|
||||
if (fileToCheck?.fileUri && !openFilesInNewWindow) {
|
||||
if (openConfig.context === OpenContext.DESKTOP || openConfig.context === OpenContext.CLI || openConfig.context === OpenContext.DOCK) {
|
||||
windowToUseForFiles = findWindowOnFile(windows, fileToCheck.fileUri, workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesManagementMainService.resolveLocalWorkspaceSync(workspace.configPath) : null);
|
||||
windowToUseForFiles = findWindowOnFile(windows, fileToCheck.fileUri, workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesManagementMainService.resolveLocalWorkspaceSync(workspace.configPath) : undefined);
|
||||
}
|
||||
|
||||
if (!windowToUseForFiles) {
|
||||
@@ -1169,7 +1169,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
||||
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
|
||||
|
||||
@@ -11,7 +11,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { IStateMainService } from 'vs/platform/state/electron-main/state';
|
||||
import { INativeWindowConfiguration, IWindowSettings } from 'vs/platform/windows/common/windows';
|
||||
import { defaultWindowState, ICodeWindow, IWindowsMainService, IWindowState as IWindowUIState, WindowMode } from 'vs/platform/windows/electron-main/windows';
|
||||
import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
@@ -53,7 +53,7 @@ export class WindowsStateHandler extends Disposable {
|
||||
private static readonly windowsStateStorageKey = 'windowsState';
|
||||
|
||||
get state() { return this._state; }
|
||||
private readonly _state = restoreWindowsState(this.stateService.getItem<ISerializedWindowsState>(WindowsStateHandler.windowsStateStorageKey));
|
||||
private readonly _state = restoreWindowsState(this.stateMainService.getItem<ISerializedWindowsState>(WindowsStateHandler.windowsStateStorageKey));
|
||||
|
||||
private lastClosedState: IWindowState | undefined = undefined;
|
||||
|
||||
@@ -61,7 +61,7 @@ export class WindowsStateHandler extends Disposable {
|
||||
|
||||
constructor(
|
||||
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
|
||||
@IStateService private readonly stateService: IStateService,
|
||||
@IStateMainService private readonly stateMainService: IStateMainService,
|
||||
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
@@ -177,7 +177,7 @@ export class WindowsStateHandler extends Disposable {
|
||||
|
||||
// Persist
|
||||
const state = getWindowsStateStoreData(currentWindowsState);
|
||||
this.stateService.setItem(WindowsStateHandler.windowsStateStorageKey, state);
|
||||
this.stateMainService.setItem(WindowsStateHandler.windowsStateStorageKey, state);
|
||||
|
||||
if (this.shuttingDown) {
|
||||
this.logService.trace('[WindowsStateHandler] onBeforeShutdown', state);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user