mirror of
https://github.com/Microsoft/vscode
synced 2024-09-19 10:40:41 +00:00
Merge remote-tracking branch 'origin/main' into rebornix/nb-list-focus
This commit is contained in:
commit
216bff5771
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -4,12 +4,18 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
import type * as markdownIt from 'markdown-it';
|
||||
|
||||
import 'katex/dist/katex.min.css';
|
||||
|
||||
declare const extendMarkdownIt: undefined | (
|
||||
(f: (md: markdownIt.MarkdownIt) => void) => void
|
||||
);
|
||||
|
||||
const styleHref = (document.currentScript as any).src.replace('notebook-out/katex.js', '') + 'node_modules/katex/dist/katex.min.css';
|
||||
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = styleHref;
|
||||
|
||||
document.head.append(link);
|
||||
|
||||
(function () {
|
||||
const katex = require('@iktakahiro/markdown-it-katex');
|
||||
if (typeof extendMarkdownIt !== 'undefined') {
|
||||
|
|
|
@ -39,11 +39,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-it": "^0.0.0",
|
||||
"css-loader": "^5.0.2",
|
||||
"markdown-it": "^12.0.4",
|
||||
"null-loader": "^4.0.1",
|
||||
"style-loader": "^2.0.0",
|
||||
"url-loader": "^4.1.1"
|
||||
"markdown-it": "^12.0.4"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -14,19 +14,6 @@ module.exports = {
|
|||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.(woff2)$/i,
|
||||
use: ['url-loader?limit=100000']
|
||||
},
|
||||
{
|
||||
test: /\.(woff|eot|ttf|otf)$/i,
|
||||
use: ['null-loader']
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -9,121 +9,26 @@
|
|||
dependencies:
|
||||
katex "^0.12.0"
|
||||
|
||||
"@types/json-schema@^7.0.6":
|
||||
version "7.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
|
||||
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
|
||||
|
||||
"@types/markdown-it@^0.0.0":
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.0.tgz#8f6acaa5e3245e275f684e95deb3e518d1c6ab16"
|
||||
integrity sha1-j2rKpeMkXidfaE6V3rPlGNHGqxY=
|
||||
|
||||
ajv-keywords@^3.5.2:
|
||||
version "3.5.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
|
||||
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
|
||||
|
||||
ajv@^6.12.5:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.1"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
argparse@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
|
||||
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||
|
||||
big.js@^5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
||||
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
|
||||
|
||||
camelcase@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
|
||||
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
|
||||
|
||||
colorette@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
|
||||
integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
|
||||
|
||||
commander@^2.19.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
css-loader@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.0.2.tgz#24f758dae349bad0a440c50d7e2067742e0899cb"
|
||||
integrity sha512-gbkBigdcHbmNvZ1Cg6aV6qh6k9N6XOr8YWzISLQGrwk2mgOH8LLrizhkxbDhQtaLtktyKHD4970S0xwz5btfTA==
|
||||
dependencies:
|
||||
camelcase "^6.2.0"
|
||||
cssesc "^3.0.0"
|
||||
icss-utils "^5.1.0"
|
||||
loader-utils "^2.0.0"
|
||||
postcss "^8.2.4"
|
||||
postcss-modules-extract-imports "^3.0.0"
|
||||
postcss-modules-local-by-default "^4.0.0"
|
||||
postcss-modules-scope "^3.0.0"
|
||||
postcss-modules-values "^4.0.0"
|
||||
postcss-value-parser "^4.1.0"
|
||||
schema-utils "^3.0.0"
|
||||
semver "^7.3.4"
|
||||
|
||||
cssesc@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
|
||||
|
||||
emojis-list@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
|
||||
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
|
||||
|
||||
entities@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
|
||||
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
|
||||
|
||||
fast-deep-equal@^3.1.1:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
||||
|
||||
icss-utils@^5.0.0, icss-utils@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
|
||||
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
|
||||
|
||||
indexes-of@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
|
||||
integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
|
||||
|
||||
json-schema-traverse@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||
|
||||
json5@^2.1.2:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
|
||||
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
katex@^0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/katex/-/katex-0.12.0.tgz#2fb1c665dbd2b043edcf8a1f5c555f46beaa0cb9"
|
||||
|
@ -138,22 +43,6 @@ linkify-it@^3.0.1:
|
|||
dependencies:
|
||||
uc.micro "^1.0.1"
|
||||
|
||||
loader-utils@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
|
||||
integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
|
||||
dependencies:
|
||||
big.js "^5.2.2"
|
||||
emojis-list "^3.0.0"
|
||||
json5 "^2.1.2"
|
||||
|
||||
lru-cache@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
markdown-it-emoji@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/markdown-it-emoji/-/markdown-it-emoji-2.0.0.tgz#3164ad4c009efd946e98274f7562ad611089a231"
|
||||
|
@ -175,154 +64,7 @@ mdurl@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
|
||||
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
|
||||
|
||||
mime-db@1.45.0:
|
||||
version "1.45.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea"
|
||||
integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==
|
||||
|
||||
mime-types@^2.1.27:
|
||||
version "2.1.28"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd"
|
||||
integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==
|
||||
dependencies:
|
||||
mime-db "1.45.0"
|
||||
|
||||
minimist@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
|
||||
nanoid@^3.1.20:
|
||||
version "3.1.20"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788"
|
||||
integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==
|
||||
|
||||
null-loader@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/null-loader/-/null-loader-4.0.1.tgz#8e63bd3a2dd3c64236a4679428632edd0a6dbc6a"
|
||||
integrity sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==
|
||||
dependencies:
|
||||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
|
||||
postcss-modules-extract-imports@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
|
||||
integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==
|
||||
|
||||
postcss-modules-local-by-default@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c"
|
||||
integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==
|
||||
dependencies:
|
||||
icss-utils "^5.0.0"
|
||||
postcss-selector-parser "^6.0.2"
|
||||
postcss-value-parser "^4.1.0"
|
||||
|
||||
postcss-modules-scope@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06"
|
||||
integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==
|
||||
dependencies:
|
||||
postcss-selector-parser "^6.0.4"
|
||||
|
||||
postcss-modules-values@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
|
||||
integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
|
||||
dependencies:
|
||||
icss-utils "^5.0.0"
|
||||
|
||||
postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
|
||||
version "6.0.4"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3"
|
||||
integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==
|
||||
dependencies:
|
||||
cssesc "^3.0.0"
|
||||
indexes-of "^1.0.1"
|
||||
uniq "^1.0.1"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
postcss-value-parser@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
|
||||
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
|
||||
|
||||
postcss@^8.2.4:
|
||||
version "8.2.6"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.6.tgz#5d69a974543b45f87e464bc4c3e392a97d6be9fe"
|
||||
integrity sha512-xpB8qYxgPuly166AGlpRjUdEYtmOWx2iCwGmrv4vqZL9YPVviDVPZPRXxnXr6xPZOdxQ9lp3ZBFCRgWJ7LE3Sg==
|
||||
dependencies:
|
||||
colorette "^1.2.1"
|
||||
nanoid "^3.1.20"
|
||||
source-map "^0.6.1"
|
||||
|
||||
punycode@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
schema-utils@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef"
|
||||
integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.6"
|
||||
ajv "^6.12.5"
|
||||
ajv-keywords "^3.5.2"
|
||||
|
||||
semver@^7.3.4:
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
|
||||
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
style-loader@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c"
|
||||
integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==
|
||||
dependencies:
|
||||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
|
||||
uc.micro@^1.0.1, uc.micro@^1.0.5:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
|
||||
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
|
||||
|
||||
uniq@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
|
||||
integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
|
||||
|
||||
uri-js@^4.2.2:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
|
||||
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
url-loader@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2"
|
||||
integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==
|
||||
dependencies:
|
||||
loader-utils "^2.0.0"
|
||||
mime-types "^2.1.27"
|
||||
schema-utils "^3.0.0"
|
||||
|
||||
util-deprecate@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
|
|
@ -683,6 +683,7 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
|
|||
if (typeof options.filterOnType !== 'undefined') {
|
||||
this._filterOnType = !!options.filterOnType;
|
||||
this.filterOnTypeDomNode.checked = this._filterOnType;
|
||||
this.updateFilterOnTypeTitleAndIcon();
|
||||
}
|
||||
|
||||
if (typeof options.automaticKeyboardNavigation !== 'undefined') {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { homedir } from 'os';
|
||||
import { constants, existsSync, statSync, unlinkSync, chmodSync, truncateSync, readFileSync } from 'fs';
|
||||
import { existsSync, statSync, unlinkSync, chmodSync, truncateSync, readFileSync } from 'fs';
|
||||
import { spawn, ChildProcess, SpawnOptions } from 'child_process';
|
||||
import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
|
@ -84,8 +84,8 @@ export async function main(argv: string[]): Promise<any> {
|
|||
let restoreMode = false;
|
||||
if (!!args['file-chmod']) {
|
||||
targetMode = statSync(target).mode;
|
||||
if (!(targetMode & constants.S_IWUSR)) {
|
||||
chmodSync(target, targetMode | constants.S_IWUSR);
|
||||
if (!(targetMode & 0o200 /* File mode indicating writable by owner */)) {
|
||||
chmodSync(target, targetMode | 0o200);
|
||||
restoreMode = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -741,6 +741,19 @@ export class ModelSemanticColoring extends Disposable {
|
|||
this._fetchDocumentSemanticTokens.schedule();
|
||||
}
|
||||
}));
|
||||
this._register(this._model.onDidChangeLanguage(() => {
|
||||
// clear any outstanding state
|
||||
if (this._currentDocumentResponse) {
|
||||
this._currentDocumentResponse.dispose();
|
||||
this._currentDocumentResponse = null;
|
||||
}
|
||||
if (this._currentDocumentRequestCancellationTokenSource) {
|
||||
this._currentDocumentRequestCancellationTokenSource.cancel();
|
||||
this._currentDocumentRequestCancellationTokenSource = null;
|
||||
}
|
||||
this._setDocumentSemanticTokens(null, null, null, []);
|
||||
this._fetchDocumentSemanticTokens.schedule(0);
|
||||
}));
|
||||
const bindDocumentChangeListeners = () => {
|
||||
dispose(this._documentProvidersChangeListeners);
|
||||
this._documentProvidersChangeListeners = [];
|
||||
|
|
|
@ -346,38 +346,48 @@ export class RenderLineOutput {
|
|||
export function renderViewLine(input: RenderLineInput, sb: IStringBuilder): RenderLineOutput {
|
||||
if (input.lineContent.length === 0) {
|
||||
|
||||
let containsForeignElements = ForeignElementType.None;
|
||||
|
||||
let content: string = '<span><span></span></span>';
|
||||
|
||||
if (input.lineDecorations.length > 0) {
|
||||
// This line is empty, but it contains inline decorations
|
||||
const beforeClassNames: string[] = [];
|
||||
const afterClassNames: string[] = [];
|
||||
for (let i = 0, len = input.lineDecorations.length; i < len; i++) {
|
||||
const lineDecoration = input.lineDecorations[i];
|
||||
if (lineDecoration.type === InlineDecorationType.Before) {
|
||||
beforeClassNames.push(input.lineDecorations[i].className);
|
||||
containsForeignElements |= ForeignElementType.Before;
|
||||
}
|
||||
if (lineDecoration.type === InlineDecorationType.After) {
|
||||
afterClassNames.push(input.lineDecorations[i].className);
|
||||
containsForeignElements |= ForeignElementType.After;
|
||||
sb.appendASCIIString(`<span>`);
|
||||
|
||||
let beforeCount = 0;
|
||||
let afterCount = 0;
|
||||
let containsForeignElements = ForeignElementType.None;
|
||||
for (const lineDecoration of input.lineDecorations) {
|
||||
if (lineDecoration.type === InlineDecorationType.Before || lineDecoration.type === InlineDecorationType.After) {
|
||||
sb.appendASCIIString(`<span class="`);
|
||||
sb.appendASCIIString(lineDecoration.className);
|
||||
sb.appendASCIIString(`"></span>`);
|
||||
|
||||
if (lineDecoration.type === InlineDecorationType.Before) {
|
||||
containsForeignElements |= ForeignElementType.Before;
|
||||
beforeCount++;
|
||||
}
|
||||
if (lineDecoration.type === InlineDecorationType.After) {
|
||||
containsForeignElements |= ForeignElementType.After;
|
||||
afterCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (containsForeignElements !== ForeignElementType.None) {
|
||||
const beforeSpan = (beforeClassNames.length > 0 ? `<span class="${beforeClassNames.join(' ')}"></span>` : ``);
|
||||
const afterSpan = (afterClassNames.length > 0 ? `<span class="${afterClassNames.join(' ')}"></span>` : ``);
|
||||
content = `<span>${beforeSpan}${afterSpan}</span>`;
|
||||
}
|
||||
sb.appendASCIIString(`</span>`);
|
||||
|
||||
const characterMapping = new CharacterMapping(1, beforeCount + afterCount);
|
||||
characterMapping.setPartData(0, beforeCount, 0, 0);
|
||||
|
||||
return new RenderLineOutput(
|
||||
characterMapping,
|
||||
false,
|
||||
containsForeignElements
|
||||
);
|
||||
}
|
||||
|
||||
sb.appendASCIIString(content);
|
||||
// completely empty line
|
||||
sb.appendASCIIString('<span><span></span></span>');
|
||||
return new RenderLineOutput(
|
||||
new CharacterMapping(0, 0),
|
||||
false,
|
||||
containsForeignElements
|
||||
ForeignElementType.None
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -320,7 +320,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget {
|
|||
keyboardNavigationLabelProvider: this._instantiationService.createInstance(StringRepresentationProvider),
|
||||
identityProvider: new IdentityProvider(),
|
||||
openOnSingleClick: true,
|
||||
openOnFocus: true,
|
||||
selectionNavigation: true,
|
||||
overrideStyles: {
|
||||
listBackground: peekView.peekViewResultsBackground
|
||||
}
|
||||
|
|
|
@ -1643,8 +1643,8 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
0,
|
||||
createViewLineTokens([createPart(0, 3)]),
|
||||
[
|
||||
new LineDecoration(1, 2, 'before', InlineDecorationType.Before),
|
||||
new LineDecoration(0, 1, 'after', InlineDecorationType.After),
|
||||
new LineDecoration(1, 1, 'before', InlineDecorationType.Before),
|
||||
new LineDecoration(1, 1, 'after', InlineDecorationType.After),
|
||||
],
|
||||
2,
|
||||
0,
|
||||
|
@ -1668,6 +1668,47 @@ suite('viewLineRenderer.renderLine 2', () => {
|
|||
assert.deepStrictEqual(actual.html, expected);
|
||||
});
|
||||
|
||||
test('issue #118759: enable multiple text editor decorations in empty lines', () => {
|
||||
|
||||
let actual = renderViewLine(new RenderLineInput(
|
||||
true,
|
||||
true,
|
||||
'',
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
0,
|
||||
createViewLineTokens([createPart(0, 3)]),
|
||||
[
|
||||
new LineDecoration(1, 1, 'after1', InlineDecorationType.After),
|
||||
new LineDecoration(1, 1, 'after2', InlineDecorationType.After),
|
||||
new LineDecoration(1, 1, 'before1', InlineDecorationType.Before),
|
||||
new LineDecoration(1, 1, 'before2', InlineDecorationType.Before),
|
||||
],
|
||||
2,
|
||||
0,
|
||||
10,
|
||||
10,
|
||||
10,
|
||||
10000,
|
||||
'none',
|
||||
false,
|
||||
false,
|
||||
null
|
||||
));
|
||||
|
||||
let expected = [
|
||||
'<span>',
|
||||
'<span class="before1"></span>',
|
||||
'<span class="before2"></span>',
|
||||
'<span class="after1"></span>',
|
||||
'<span class="after2"></span>',
|
||||
'</span>'
|
||||
].join('');
|
||||
|
||||
assert.deepStrictEqual(actual.html, expected);
|
||||
});
|
||||
|
||||
test('issue #38935: GitLens end-of-line blame no longer rendering', () => {
|
||||
|
||||
let actual = renderViewLine(new RenderLineInput(
|
||||
|
|
|
@ -41,6 +41,7 @@ export interface NativeParsedArgs {
|
|||
'builtin-extensions-dir'?: string;
|
||||
extensionDevelopmentPath?: string[]; // // undefined or array of 1 or more local paths or URIs
|
||||
extensionTestsPath?: string; // either a local path or a URI
|
||||
extensionDevelopmentKind?: string[];
|
||||
'inspect-extensions'?: string;
|
||||
'inspect-brk-extensions'?: string;
|
||||
debugId?: string;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import { createDecorator, refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import { ExtensionKind } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
export const IEnvironmentService = createDecorator<IEnvironmentService>('environmentService');
|
||||
export const INativeEnvironmentService = refineServiceDecorator<IEnvironmentService, INativeEnvironmentService>(IEnvironmentService);
|
||||
|
@ -62,6 +63,7 @@ export interface IEnvironmentService {
|
|||
isExtensionDevelopment: boolean;
|
||||
disableExtensions: boolean | string[];
|
||||
extensionDevelopmentLocationURI?: URI[];
|
||||
extensionDevelopmentKind?: ExtensionKind[];
|
||||
extensionTestsLocationURI?: URI;
|
||||
|
||||
// --- logging
|
||||
|
|
|
@ -83,6 +83,7 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
|
|||
|
||||
'locate-extension': { type: 'string[]' },
|
||||
'extensionDevelopmentPath': { type: 'string[]' },
|
||||
'extensionDevelopmentKind': { type: 'string[]' },
|
||||
'extensionTestsPath': { type: 'string' },
|
||||
'debugId': { type: 'string' },
|
||||
'debugRenderer': { type: 'boolean' },
|
||||
|
|
|
@ -14,6 +14,7 @@ import { memoize } from 'vs/base/common/decorators';
|
|||
import { toLocalISOString } from 'vs/base/common/date';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ExtensionKind } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
export class NativeEnvironmentService implements INativeEnvironmentService {
|
||||
|
||||
|
@ -151,6 +152,11 @@ export class NativeEnvironmentService implements INativeEnvironmentService {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
@memoize
|
||||
get extensionDevelopmentKind(): ExtensionKind[] | undefined {
|
||||
return this._args.extensionDevelopmentKind?.map(k => k === 'ui' || k === 'workspace' || k === 'web' ? k : 'workspace');
|
||||
}
|
||||
|
||||
@memoize
|
||||
get extensionTestsLocationURI(): URI | undefined {
|
||||
const s = this._args.extensionTestsPath;
|
||||
|
|
|
@ -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 } 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 } 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';
|
||||
|
@ -362,12 +362,12 @@ export class FileService extends Disposable implements IFileService {
|
|||
|
||||
// write file: unbuffered (only if data to write is a buffer, or the provider has no buffered write capability)
|
||||
if (!hasOpenReadWriteCloseCapability(provider) || (hasReadWriteCapability(provider) && bufferOrReadableOrStreamOrBufferedStream instanceof VSBuffer)) {
|
||||
await this.doWriteUnbuffered(provider, resource, bufferOrReadableOrStreamOrBufferedStream);
|
||||
await this.doWriteUnbuffered(provider, resource, options, bufferOrReadableOrStreamOrBufferedStream);
|
||||
}
|
||||
|
||||
// write file: buffered
|
||||
else {
|
||||
await this.doWriteBuffered(provider, resource, bufferOrReadableOrStreamOrBufferedStream instanceof VSBuffer ? bufferToReadable(bufferOrReadableOrStreamOrBufferedStream) : bufferOrReadableOrStreamOrBufferedStream);
|
||||
await this.doWriteBuffered(provider, resource, options, bufferOrReadableOrStreamOrBufferedStream instanceof VSBuffer ? bufferToReadable(bufferOrReadableOrStreamOrBufferedStream) : bufferOrReadableOrStreamOrBufferedStream);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new FileOperationError(localize('err.write', "Unable to write file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options);
|
||||
|
@ -377,6 +377,14 @@ export class FileService extends Disposable implements IFileService {
|
|||
}
|
||||
|
||||
private async validateWriteFile(provider: IFileSystemProvider, resource: URI, options?: IWriteFileOptions): Promise<IStat | undefined> {
|
||||
|
||||
// Validate unlock support
|
||||
const unlock = !!options?.unlock;
|
||||
if (unlock && !(provider.capabilities & FileSystemProviderCapabilities.FileWriteUnlock)) {
|
||||
throw new Error(localize('writeFailedUnlockUnsupported', "Unable to unlock file '{0}' because provider does not support it.", this.resourceForError(resource)));
|
||||
}
|
||||
|
||||
// Validate via file stat meta data
|
||||
let stat: IStat | undefined = undefined;
|
||||
try {
|
||||
stat = await provider.stat(resource);
|
||||
|
@ -384,7 +392,7 @@ export class FileService extends Disposable implements IFileService {
|
|||
return undefined; // file might not exist
|
||||
}
|
||||
|
||||
// file cannot be directory
|
||||
// File cannot be directory
|
||||
if ((stat.type & FileType.Directory) !== 0) {
|
||||
throw new FileOperationError(localize('fileIsDirectoryWriteError', "Unable to write file '{0}' that is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options);
|
||||
}
|
||||
|
@ -880,7 +888,7 @@ export class FileService extends Disposable implements IFileService {
|
|||
}
|
||||
}
|
||||
|
||||
async canDelete(resource: URI, options?: { useTrash?: boolean; recursive?: boolean; }): Promise<Error | true> {
|
||||
async canDelete(resource: URI, options?: Partial<FileDeleteOptions>): Promise<Error | true> {
|
||||
try {
|
||||
await this.doValidateDelete(resource, options);
|
||||
} catch (error) {
|
||||
|
@ -890,7 +898,7 @@ export class FileService extends Disposable implements IFileService {
|
|||
return true;
|
||||
}
|
||||
|
||||
private async doValidateDelete(resource: URI, options?: { useTrash?: boolean; recursive?: boolean; }): Promise<IFileSystemProvider> {
|
||||
private async doValidateDelete(resource: URI, options?: Partial<FileDeleteOptions>): Promise<IFileSystemProvider> {
|
||||
const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource), resource);
|
||||
|
||||
// Validate trash support
|
||||
|
@ -917,7 +925,7 @@ export class FileService extends Disposable implements IFileService {
|
|||
return provider;
|
||||
}
|
||||
|
||||
async del(resource: URI, options?: { useTrash?: boolean; recursive?: boolean; }): Promise<void> {
|
||||
async del(resource: URI, options?: Partial<FileDeleteOptions>): Promise<void> {
|
||||
const provider = await this.doValidateDelete(resource, options);
|
||||
|
||||
const useTrash = !!options?.useTrash;
|
||||
|
@ -1005,11 +1013,11 @@ export class FileService extends Disposable implements IFileService {
|
|||
|
||||
private readonly writeQueue = this._register(new ResourceQueue());
|
||||
|
||||
private async doWriteBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, readableOrStreamOrBufferedStream: VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream): Promise<void> {
|
||||
private async doWriteBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, options: IWriteFileOptions | undefined, readableOrStreamOrBufferedStream: VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream): Promise<void> {
|
||||
return this.writeQueue.queueFor(resource, this.getExtUri(provider).providerExtUri).queue(async () => {
|
||||
|
||||
// open handle
|
||||
const handle = await provider.open(resource, { create: true });
|
||||
const handle = await provider.open(resource, { create: true, unlock: options?.unlock ?? false });
|
||||
|
||||
// write into handle until all bytes from buffer have been written
|
||||
try {
|
||||
|
@ -1104,11 +1112,11 @@ export class FileService extends Disposable implements IFileService {
|
|||
}
|
||||
}
|
||||
|
||||
private async doWriteUnbuffered(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, bufferOrReadableOrStreamOrBufferedStream: VSBuffer | VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream): Promise<void> {
|
||||
return this.writeQueue.queueFor(resource, this.getExtUri(provider).providerExtUri).queue(() => this.doWriteUnbufferedQueued(provider, resource, bufferOrReadableOrStreamOrBufferedStream));
|
||||
private async doWriteUnbuffered(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, options: IWriteFileOptions | undefined, bufferOrReadableOrStreamOrBufferedStream: VSBuffer | VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream): Promise<void> {
|
||||
return this.writeQueue.queueFor(resource, this.getExtUri(provider).providerExtUri).queue(() => this.doWriteUnbufferedQueued(provider, resource, options, bufferOrReadableOrStreamOrBufferedStream));
|
||||
}
|
||||
|
||||
private async doWriteUnbufferedQueued(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, bufferOrReadableOrStreamOrBufferedStream: VSBuffer | VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream): Promise<void> {
|
||||
private async doWriteUnbufferedQueued(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, options: IWriteFileOptions | undefined, bufferOrReadableOrStreamOrBufferedStream: VSBuffer | VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream): Promise<void> {
|
||||
let buffer: VSBuffer;
|
||||
if (bufferOrReadableOrStreamOrBufferedStream instanceof VSBuffer) {
|
||||
buffer = bufferOrReadableOrStreamOrBufferedStream;
|
||||
|
@ -1121,7 +1129,7 @@ export class FileService extends Disposable implements IFileService {
|
|||
}
|
||||
|
||||
// Write through the provider
|
||||
await provider.writeFile(resource, buffer.buffer, { create: true, overwrite: true });
|
||||
await provider.writeFile(resource, buffer.buffer, { create: true, overwrite: true, unlock: options?.unlock ?? false });
|
||||
}
|
||||
|
||||
private async doPipeBuffered(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise<void> {
|
||||
|
@ -1136,7 +1144,7 @@ export class FileService extends Disposable implements IFileService {
|
|||
|
||||
// Open handles
|
||||
sourceHandle = await sourceProvider.open(source, { create: false });
|
||||
targetHandle = await targetProvider.open(target, { create: true });
|
||||
targetHandle = await targetProvider.open(target, { create: true, unlock: false });
|
||||
|
||||
const buffer = VSBuffer.alloc(this.BUFFER_SIZE);
|
||||
|
||||
|
@ -1175,7 +1183,7 @@ export class FileService extends Disposable implements IFileService {
|
|||
}
|
||||
|
||||
private async doPipeUnbufferedQueued(sourceProvider: IFileSystemProviderWithFileReadWriteCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability, target: URI): Promise<void> {
|
||||
return targetProvider.writeFile(target, await sourceProvider.readFile(source), { create: true, overwrite: true });
|
||||
return targetProvider.writeFile(target, await sourceProvider.readFile(source), { create: true, overwrite: true, unlock: false });
|
||||
}
|
||||
|
||||
private async doPipeUnbufferedToBuffered(sourceProvider: IFileSystemProviderWithFileReadWriteCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise<void> {
|
||||
|
@ -1185,7 +1193,7 @@ export class FileService extends Disposable implements IFileService {
|
|||
private async doPipeUnbufferedToBufferedQueued(sourceProvider: IFileSystemProviderWithFileReadWriteCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise<void> {
|
||||
|
||||
// Open handle
|
||||
const targetHandle = await targetProvider.open(target, { create: true });
|
||||
const targetHandle = await targetProvider.open(target, { create: true, unlock: false });
|
||||
|
||||
// Read entire buffer from source and write buffered
|
||||
try {
|
||||
|
@ -1204,7 +1212,7 @@ export class FileService extends Disposable implements IFileService {
|
|||
const buffer = await streamToBuffer(this.readFileBuffered(sourceProvider, source, CancellationToken.None));
|
||||
|
||||
// Write buffer into target at once
|
||||
await this.doWriteUnbuffered(targetProvider, target, buffer);
|
||||
await this.doWriteUnbuffered(targetProvider, target, undefined, buffer);
|
||||
}
|
||||
|
||||
protected throwIfFileSystemIsReadonly<T extends IFileSystemProvider>(provider: T, resource: URI): T {
|
||||
|
|
|
@ -17,6 +17,8 @@ import { ReadableStreamEvents } from 'vs/base/common/stream';
|
|||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
|
||||
//#region file service & providers
|
||||
|
||||
export const IFileService = createDecorator<IFileService>('fileService');
|
||||
|
||||
export interface IFileService {
|
||||
|
@ -170,13 +172,13 @@ export interface IFileService {
|
|||
* move the file to trash. The optional recursive parameter allows to delete
|
||||
* non-empty folders recursively.
|
||||
*/
|
||||
del(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise<void>;
|
||||
del(resource: URI, options?: Partial<FileDeleteOptions>): Promise<void>;
|
||||
|
||||
/**
|
||||
* Find out if a delete operation is possible given the arguments. No changes on disk will
|
||||
* be performed. Returns an Error if the operation cannot be done.
|
||||
*/
|
||||
canDelete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise<Error | true>;
|
||||
canDelete(resource: URI, options?: Partial<FileDeleteOptions>): Promise<Error | true>;
|
||||
|
||||
/**
|
||||
* Allows to start a watcher that reports file/folder change events on the provided resource.
|
||||
|
@ -192,9 +194,24 @@ export interface IFileService {
|
|||
}
|
||||
|
||||
export interface FileOverwriteOptions {
|
||||
|
||||
/**
|
||||
* Set to `true` to overwrite a file if it exists. Will
|
||||
* throw an error otherwise if the file does exist.
|
||||
*/
|
||||
overwrite: boolean;
|
||||
}
|
||||
|
||||
export interface FileUnlockOptions {
|
||||
|
||||
/**
|
||||
* Set to `true` to try to remove any write locks the file might
|
||||
* have. A file that is write locked will throw an error for any
|
||||
* attempt to write to unless `unlock: true` is provided.
|
||||
*/
|
||||
unlock: boolean;
|
||||
}
|
||||
|
||||
export interface FileReadStreamOptions {
|
||||
|
||||
/**
|
||||
|
@ -218,28 +235,86 @@ export interface FileReadStreamOptions {
|
|||
};
|
||||
}
|
||||
|
||||
export interface FileWriteOptions {
|
||||
overwrite: boolean;
|
||||
export interface FileWriteOptions extends FileOverwriteOptions, FileUnlockOptions {
|
||||
|
||||
/**
|
||||
* Set to `true` to create a file when it does not exist. Will
|
||||
* throw an error otherwise if the file does not exist.
|
||||
*/
|
||||
create: boolean;
|
||||
}
|
||||
|
||||
export interface FileOpenOptions {
|
||||
create: boolean;
|
||||
export type FileOpenOptions = FileOpenForReadOptions | FileOpenForWriteOptions;
|
||||
|
||||
export function isFileOpenForWriteOptions(options: FileOpenOptions): options is FileOpenForWriteOptions {
|
||||
return options.create === true;
|
||||
}
|
||||
|
||||
export interface FileOpenForReadOptions {
|
||||
|
||||
/**
|
||||
* A hint that the file should be opened for reading only.
|
||||
*/
|
||||
create: false;
|
||||
}
|
||||
|
||||
export interface FileOpenForWriteOptions extends FileUnlockOptions {
|
||||
|
||||
/**
|
||||
* A hint that the file should be opened for reading and writing.
|
||||
*/
|
||||
create: true;
|
||||
}
|
||||
|
||||
export interface FileDeleteOptions {
|
||||
|
||||
/**
|
||||
* Set to `true` to recursively delete any children of the file. This
|
||||
* only applies to folders and can lead to an error unless provided
|
||||
* if the folder is not empty.
|
||||
*/
|
||||
recursive: boolean;
|
||||
|
||||
/**
|
||||
* Set to `true` to attempt to move the file to trash
|
||||
* instead of deleting it permanently from disk. This
|
||||
* option maybe not be supported on all providers.
|
||||
*/
|
||||
useTrash: boolean;
|
||||
}
|
||||
|
||||
export enum FileType {
|
||||
|
||||
/**
|
||||
* File is unknown (neither file, directory nor symbolic link).
|
||||
*/
|
||||
Unknown = 0,
|
||||
|
||||
/**
|
||||
* File is a normal file.
|
||||
*/
|
||||
File = 1,
|
||||
|
||||
/**
|
||||
* File is a directory.
|
||||
*/
|
||||
Directory = 2,
|
||||
|
||||
/**
|
||||
* File is a symbolic link.
|
||||
*
|
||||
* Note: even when the file is a symbolic link, you can test for
|
||||
* `FileType.File` and `FileType.Directory` to know the type of
|
||||
* the target the link points to.
|
||||
*/
|
||||
SymbolicLink = 64
|
||||
}
|
||||
|
||||
export interface IStat {
|
||||
|
||||
/**
|
||||
* The file type.
|
||||
*/
|
||||
type: FileType;
|
||||
|
||||
/**
|
||||
|
@ -252,25 +327,67 @@ export interface IStat {
|
|||
*/
|
||||
ctime: number;
|
||||
|
||||
/**
|
||||
* The size of the file in bytes.
|
||||
*/
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface IWatchOptions {
|
||||
|
||||
/**
|
||||
* Set to `true` to watch for changes recursively in a folder
|
||||
* and all of its children.
|
||||
*/
|
||||
recursive: boolean;
|
||||
|
||||
/**
|
||||
* A set of paths to exclude from watching.
|
||||
*/
|
||||
excludes: string[];
|
||||
}
|
||||
|
||||
export const enum FileSystemProviderCapabilities {
|
||||
|
||||
/**
|
||||
* Provider supports unbuffered read/write.
|
||||
*/
|
||||
FileReadWrite = 1 << 1,
|
||||
|
||||
/**
|
||||
* Provider supports open/read/write/close low level file operations.
|
||||
*/
|
||||
FileOpenReadWriteClose = 1 << 2,
|
||||
|
||||
/**
|
||||
* Provider supports stream based reading.
|
||||
*/
|
||||
FileReadStream = 1 << 4,
|
||||
|
||||
/**
|
||||
* Provider supports copy operation.
|
||||
*/
|
||||
FileFolderCopy = 1 << 3,
|
||||
|
||||
/**
|
||||
* Provider is path case sensitive.
|
||||
*/
|
||||
PathCaseSensitive = 1 << 10,
|
||||
|
||||
/**
|
||||
* All files of the provider are readonly.
|
||||
*/
|
||||
Readonly = 1 << 11,
|
||||
|
||||
Trash = 1 << 12
|
||||
/**
|
||||
* Provider supports to delete via trash.
|
||||
*/
|
||||
Trash = 1 << 12,
|
||||
|
||||
/**
|
||||
* Provider support to unlock files for writing.
|
||||
*/
|
||||
FileWriteUnlock = 1 << 13
|
||||
}
|
||||
|
||||
export interface IFileSystemProvider {
|
||||
|
@ -345,6 +462,7 @@ export enum FileSystemProviderErrorCode {
|
|||
FileIsADirectory = 'EntryIsADirectory',
|
||||
FileExceedsMemoryLimit = 'EntryExceedsMemoryLimit',
|
||||
FileTooLarge = 'EntryTooLarge',
|
||||
FileWriteLocked = 'EntryWriteLocked',
|
||||
NoPermissions = 'NoPermissions',
|
||||
Unavailable = 'Unavailable',
|
||||
Unknown = 'Unknown'
|
||||
|
@ -404,6 +522,7 @@ export function toFileSystemProviderErrorCode(error: Error | undefined | null):
|
|||
case FileSystemProviderErrorCode.FileNotFound: return FileSystemProviderErrorCode.FileNotFound;
|
||||
case FileSystemProviderErrorCode.FileExceedsMemoryLimit: return FileSystemProviderErrorCode.FileExceedsMemoryLimit;
|
||||
case FileSystemProviderErrorCode.FileTooLarge: return FileSystemProviderErrorCode.FileTooLarge;
|
||||
case FileSystemProviderErrorCode.FileWriteLocked: return FileSystemProviderErrorCode.FileWriteLocked;
|
||||
case FileSystemProviderErrorCode.NoPermissions: return FileSystemProviderErrorCode.NoPermissions;
|
||||
case FileSystemProviderErrorCode.Unavailable: return FileSystemProviderErrorCode.Unavailable;
|
||||
}
|
||||
|
@ -426,6 +545,8 @@ export function toFileOperationResult(error: Error): FileOperationResult {
|
|||
return FileOperationResult.FILE_IS_DIRECTORY;
|
||||
case FileSystemProviderErrorCode.FileNotADirectory:
|
||||
return FileOperationResult.FILE_NOT_DIRECTORY;
|
||||
case FileSystemProviderErrorCode.FileWriteLocked:
|
||||
return FileOperationResult.FILE_WRITE_LOCKED;
|
||||
case FileSystemProviderErrorCode.NoPermissions:
|
||||
return FileOperationResult.FILE_PERMISSION_DENIED;
|
||||
case FileSystemProviderErrorCode.FileExists:
|
||||
|
@ -479,9 +600,9 @@ export class FileOperationEvent {
|
|||
* Possible changes that can occur to a file.
|
||||
*/
|
||||
export const enum FileChangeType {
|
||||
UPDATED = 0,
|
||||
ADDED = 1,
|
||||
DELETED = 2
|
||||
UPDATED,
|
||||
ADDED,
|
||||
DELETED
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -744,12 +865,7 @@ interface IBaseStat {
|
|||
etag?: string;
|
||||
}
|
||||
|
||||
export interface IBaseStatWithMetadata extends IBaseStat {
|
||||
mtime: number;
|
||||
ctime: number;
|
||||
etag: string;
|
||||
size: number;
|
||||
}
|
||||
export interface IBaseStatWithMetadata extends Required<IBaseStat> { }
|
||||
|
||||
/**
|
||||
* A file resource with meta information.
|
||||
|
@ -767,7 +883,10 @@ export interface IFileStat extends IBaseStat {
|
|||
isDirectory: boolean;
|
||||
|
||||
/**
|
||||
* The resource is a symbolic link.
|
||||
* The resource is a symbolic link. Note: even when the
|
||||
* file is a symbolic link, you can test for `FileType.File`
|
||||
* and `FileType.Directory` to know the type of the target
|
||||
* the link points to.
|
||||
*/
|
||||
isSymbolicLink: boolean;
|
||||
|
||||
|
@ -854,6 +973,11 @@ export interface IWriteFileOptions {
|
|||
* The etag of the file. This can be used to prevent dirty writes.
|
||||
*/
|
||||
readonly etag?: string;
|
||||
|
||||
/**
|
||||
* Whether to attempt to unlock a file before writing.
|
||||
*/
|
||||
readonly unlock?: boolean;
|
||||
}
|
||||
|
||||
export interface IResolveFileOptions {
|
||||
|
@ -905,7 +1029,7 @@ export const enum FileOperationResult {
|
|||
FILE_NOT_MODIFIED_SINCE,
|
||||
FILE_MODIFIED_SINCE,
|
||||
FILE_MOVE_CONFLICT,
|
||||
FILE_READ_ONLY,
|
||||
FILE_WRITE_LOCKED,
|
||||
FILE_PERMISSION_DENIED,
|
||||
FILE_TOO_LARGE,
|
||||
FILE_INVALID_PATH,
|
||||
|
@ -914,6 +1038,10 @@ export const enum FileOperationResult {
|
|||
FILE_OTHER_ERROR
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Settings
|
||||
|
||||
export const AutoSaveConfiguration = {
|
||||
OFF: 'off',
|
||||
AFTER_DELAY: 'afterDelay',
|
||||
|
@ -948,6 +1076,10 @@ export interface IFilesConfiguration {
|
|||
};
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Utilities
|
||||
|
||||
export enum FileKind {
|
||||
FILE,
|
||||
FOLDER,
|
||||
|
@ -1042,3 +1174,5 @@ export function getPlatformLimits(arch: Arch): IArchLimits {
|
|||
maxHeapSize: arch === Arch.IA32 ? 700 * ByteSize.MB : 2 * 700 * ByteSize.MB, // https://github.com/v8/v8/blob/5918a23a3d571b9625e5cce246bdd5b46ff7cd8b/src/heap/heap.cc#L149
|
||||
};
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { open, close, read, write, fdatasync, Stats, promises } from 'fs';
|
||||
import { promisify } from 'util';
|
||||
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 } from 'vs/platform/files/common/files';
|
||||
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';
|
||||
|
@ -64,7 +64,8 @@ export class DiskFileSystemProvider extends Disposable implements
|
|||
FileSystemProviderCapabilities.FileReadWrite |
|
||||
FileSystemProviderCapabilities.FileOpenReadWriteClose |
|
||||
FileSystemProviderCapabilities.FileReadStream |
|
||||
FileSystemProviderCapabilities.FileFolderCopy;
|
||||
FileSystemProviderCapabilities.FileFolderCopy |
|
||||
FileSystemProviderCapabilities.FileWriteUnlock;
|
||||
|
||||
if (isLinux) {
|
||||
this._capabilities |= FileSystemProviderCapabilities.PathCaseSensitive;
|
||||
|
@ -188,12 +189,12 @@ export class DiskFileSystemProvider extends Disposable implements
|
|||
}
|
||||
|
||||
// Open
|
||||
handle = await this.open(resource, { create: true });
|
||||
handle = await this.open(resource, { create: true, unlock: opts.unlock });
|
||||
|
||||
// Write content at once
|
||||
await this.write(handle, 0, content, 0, content.byteLength);
|
||||
} catch (error) {
|
||||
throw this.toFileSystemProviderError(error);
|
||||
throw await this.toFileSystemProviderWriteError(resource, error);
|
||||
} finally {
|
||||
if (typeof handle === 'number') {
|
||||
await this.close(handle);
|
||||
|
@ -203,15 +204,28 @@ export class DiskFileSystemProvider extends Disposable implements
|
|||
|
||||
private readonly mapHandleToPos: Map<number, number> = new Map();
|
||||
|
||||
private readonly writeHandles: Set<number> = new Set();
|
||||
private readonly writeHandles = new Map<number, URI>();
|
||||
private canFlush: boolean = true;
|
||||
|
||||
async open(resource: URI, opts: FileOpenOptions): Promise<number> {
|
||||
try {
|
||||
const filePath = this.toFilePath(resource);
|
||||
|
||||
// Determine wether to unlock the file (write only)
|
||||
if (isFileOpenForWriteOptions(opts) && opts.unlock) {
|
||||
try {
|
||||
const { stat } = await SymlinkSupport.stat(filePath);
|
||||
if (!(stat.mode & 0o200 /* File mode indicating writable by owner */)) {
|
||||
await promises.chmod(filePath, stat.mode | 0o200);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.trace(error); // ignore any errors here and try to just write
|
||||
}
|
||||
}
|
||||
|
||||
// Determine file flags for opening (read vs write)
|
||||
let flags: string | undefined = undefined;
|
||||
if (opts.create) {
|
||||
if (isFileOpenForWriteOptions(opts)) {
|
||||
if (isWindows) {
|
||||
try {
|
||||
// On Windows and if the file exists, we use a different strategy of saving the file
|
||||
|
@ -252,13 +266,17 @@ export class DiskFileSystemProvider extends Disposable implements
|
|||
this.mapHandleToPos.set(handle, 0);
|
||||
|
||||
// remember that this handle was used for writing
|
||||
if (opts.create) {
|
||||
this.writeHandles.add(handle);
|
||||
if (isFileOpenForWriteOptions(opts)) {
|
||||
this.writeHandles.set(handle, resource);
|
||||
}
|
||||
|
||||
return handle;
|
||||
} catch (error) {
|
||||
throw this.toFileSystemProviderError(error);
|
||||
if (isFileOpenForWriteOptions(opts)) {
|
||||
throw await this.toFileSystemProviderWriteError(resource, error);
|
||||
} else {
|
||||
throw this.toFileSystemProviderError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -388,7 +406,7 @@ export class DiskFileSystemProvider extends Disposable implements
|
|||
|
||||
return bytesWritten;
|
||||
} catch (error) {
|
||||
throw this.toFileSystemProviderError(error);
|
||||
throw await this.toFileSystemProviderWriteError(this.writeHandles.get(fd), error);
|
||||
} finally {
|
||||
this.updatePos(fd, normalizedPos, bytesWritten);
|
||||
}
|
||||
|
@ -690,6 +708,26 @@ export class DiskFileSystemProvider extends Disposable implements
|
|||
return createFileSystemProviderError(error, code);
|
||||
}
|
||||
|
||||
private async toFileSystemProviderWriteError(resource: URI | undefined, error: NodeJS.ErrnoException): Promise<FileSystemProviderError> {
|
||||
let fileSystemProviderWriteError = this.toFileSystemProviderError(error);
|
||||
|
||||
// If the write error signals permission issues, we try
|
||||
// to read the file's mode to see if the file is write
|
||||
// locked.
|
||||
if (resource && fileSystemProviderWriteError.code === FileSystemProviderErrorCode.NoPermissions) {
|
||||
try {
|
||||
const { stat } = await SymlinkSupport.stat(this.toFilePath(resource));
|
||||
if (!(stat.mode & 0o200 /* File mode indicating writable by owner */)) {
|
||||
fileSystemProviderWriteError = createFileSystemProviderError(error, FileSystemProviderErrorCode.FileWriteLocked);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.trace(error); // ignore - return original error
|
||||
}
|
||||
}
|
||||
|
||||
return fileSystemProviderWriteError;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
dispose(): void {
|
||||
|
|
|
@ -66,6 +66,7 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider {
|
|||
FileSystemProviderCapabilities.FileOpenReadWriteClose |
|
||||
FileSystemProviderCapabilities.FileReadStream |
|
||||
FileSystemProviderCapabilities.Trash |
|
||||
FileSystemProviderCapabilities.FileWriteUnlock |
|
||||
FileSystemProviderCapabilities.FileFolderCopy;
|
||||
|
||||
if (isLinux) {
|
||||
|
@ -1899,6 +1900,63 @@ flakySuite('Disk File Service', function () {
|
|||
assert.strictEqual(readFileSync(resource.fsPath).toString(), content);
|
||||
});
|
||||
|
||||
test('writeFile - locked files and unlocking', async () => {
|
||||
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.FileWriteUnlock);
|
||||
|
||||
return testLockedFiles(false);
|
||||
});
|
||||
|
||||
test('writeFile (stream) - locked files and unlocking', async () => {
|
||||
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose | FileSystemProviderCapabilities.FileWriteUnlock);
|
||||
|
||||
return testLockedFiles(false);
|
||||
});
|
||||
|
||||
test('writeFile - locked files and unlocking throws error when missing capability', async () => {
|
||||
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite);
|
||||
|
||||
return testLockedFiles(true);
|
||||
});
|
||||
|
||||
test('writeFile (stream) - locked files and unlocking throws error when missing capability', async () => {
|
||||
setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose);
|
||||
|
||||
return testLockedFiles(true);
|
||||
});
|
||||
|
||||
async function testLockedFiles(expectError: boolean) {
|
||||
const lockedFile = URI.file(join(testDir, 'my-locked-file'));
|
||||
|
||||
await service.writeFile(lockedFile, VSBuffer.fromString('Locked File'));
|
||||
|
||||
const stats = await promises.stat(lockedFile.fsPath);
|
||||
await promises.chmod(lockedFile.fsPath, stats.mode & ~0o200);
|
||||
|
||||
let error;
|
||||
const newContent = 'Updates to locked file';
|
||||
try {
|
||||
await service.writeFile(lockedFile, VSBuffer.fromString(newContent));
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
assert.ok(error);
|
||||
error = undefined;
|
||||
|
||||
if (expectError) {
|
||||
try {
|
||||
await service.writeFile(lockedFile, VSBuffer.fromString(newContent), { unlock: true });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
assert.ok(error);
|
||||
} else {
|
||||
await service.writeFile(lockedFile, VSBuffer.fromString(newContent), { unlock: true });
|
||||
assert.strictEqual(readFileSync(lockedFile.fsPath).toString(), newContent);
|
||||
}
|
||||
}
|
||||
|
||||
test('writeFile (error when folder is encountered)', async () => {
|
||||
const resource = URI.file(testDir);
|
||||
|
||||
|
@ -2296,7 +2354,7 @@ flakySuite('Disk File Service', function () {
|
|||
const resource = URI.file(join(testDir, 'lorem.txt'));
|
||||
|
||||
const buffer = VSBuffer.alloc(1024);
|
||||
const fdWrite = await fileProvider.open(resource, { create: true });
|
||||
const fdWrite = await fileProvider.open(resource, { create: true, unlock: false });
|
||||
const fdRead = await fileProvider.open(resource, { create: false });
|
||||
|
||||
let posInFileWrite = 0;
|
||||
|
|
|
@ -578,10 +578,6 @@ export interface IOpenResourceOptions {
|
|||
payload: any;
|
||||
}
|
||||
|
||||
export interface IResourceResultsNavigationOptions {
|
||||
openOnFocus: boolean;
|
||||
}
|
||||
|
||||
export interface IOpenEvent<T> {
|
||||
editorOptions: IEditorOptions;
|
||||
sideBySide: boolean;
|
||||
|
@ -591,7 +587,6 @@ export interface IOpenEvent<T> {
|
|||
|
||||
export interface IResourceNavigatorOptions {
|
||||
readonly configurationService?: IConfigurationService;
|
||||
readonly openOnFocus?: boolean;
|
||||
readonly openOnSingleClick?: boolean;
|
||||
}
|
||||
|
||||
|
@ -612,7 +607,6 @@ export function getSelectionKeyboardEvent(typeArg = 'keydown', preserveFocus?: b
|
|||
|
||||
abstract class ResourceNavigator<T> extends Disposable {
|
||||
|
||||
private readonly openOnFocus: boolean;
|
||||
private openOnSingleClick: boolean;
|
||||
|
||||
private readonly _onDidOpen = this._register(new Emitter<IOpenEvent<T | undefined>>());
|
||||
|
@ -624,16 +618,10 @@ abstract class ResourceNavigator<T> extends Disposable {
|
|||
) {
|
||||
super();
|
||||
|
||||
this.openOnFocus = options?.openOnFocus ?? false;
|
||||
|
||||
this._register(Event.filter(this.widget.onDidChangeSelection, e => e.browserEvent instanceof KeyboardEvent)(e => this.onSelectionFromKeyboard(e)));
|
||||
this._register(this.widget.onPointer((e: { browserEvent: MouseEvent, element: T | undefined }) => this.onPointer(e.element, e.browserEvent)));
|
||||
this._register(this.widget.onMouseDblClick((e: { browserEvent: MouseEvent, element: T | undefined }) => this.onMouseDblClick(e.element, e.browserEvent)));
|
||||
|
||||
if (this.openOnFocus) {
|
||||
this._register(Event.filter(this.widget.onDidChangeFocus, e => e.browserEvent instanceof KeyboardEvent)(e => this.onFocusFromKeyboard(e)));
|
||||
}
|
||||
|
||||
if (typeof options?.openOnSingleClick !== 'boolean' && options?.configurationService) {
|
||||
this.openOnSingleClick = options?.configurationService!.getValue(openModeSettingKey) !== 'doubleClick';
|
||||
this._register(options?.configurationService.onDidChangeConfiguration(() => {
|
||||
|
@ -644,18 +632,6 @@ abstract class ResourceNavigator<T> extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
private onFocusFromKeyboard(event: ITreeEvent<any>): void {
|
||||
const focus = this.widget.getFocus();
|
||||
this.widget.setSelection(focus, event.browserEvent);
|
||||
|
||||
const selectionKeyboardEvent = event.browserEvent as SelectionKeyboardEvent;
|
||||
const preserveFocus = typeof selectionKeyboardEvent.preserveFocus === 'boolean' ? selectionKeyboardEvent.preserveFocus! : true;
|
||||
const pinned = typeof selectionKeyboardEvent.pinned === 'boolean' ? selectionKeyboardEvent.pinned! : !preserveFocus;
|
||||
const sideBySide = false;
|
||||
|
||||
this._open(this.getSelectedElement(), preserveFocus, pinned, sideBySide, event.browserEvent);
|
||||
}
|
||||
|
||||
private onSelectionFromKeyboard(event: ITreeEvent<any>): void {
|
||||
if (event.elements.length !== 1) {
|
||||
return;
|
||||
|
|
|
@ -100,7 +100,7 @@ export interface ICommonNativeHostService {
|
|||
moveItemToTrash(fullPath: string, deleteOnFail?: boolean): Promise<boolean>;
|
||||
|
||||
isAdmin(): Promise<boolean>;
|
||||
writeElevated(source: URI, target: URI, options?: { overwriteReadonly?: boolean }): Promise<void>;
|
||||
writeElevated(source: URI, target: URI, options?: { unlock?: boolean }): Promise<void>;
|
||||
|
||||
getOSProperties(): Promise<IOSProperties>;
|
||||
getOSStatistics(): Promise<IOSStatistics>;
|
||||
|
|
|
@ -390,12 +390,12 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
|
|||
return isAdmin;
|
||||
}
|
||||
|
||||
async writeElevated(windowId: number | undefined, source: URI, target: URI, options?: { overwriteReadonly?: boolean }): Promise<void> {
|
||||
async writeElevated(windowId: number | undefined, source: URI, target: URI, options?: { unlock?: boolean }): Promise<void> {
|
||||
const sudoPrompt = await import('sudo-prompt');
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const sudoCommand: string[] = [`"${this.cliPath}"`];
|
||||
if (options?.overwriteReadonly) {
|
||||
if (options?.unlock) {
|
||||
sudoCommand.push('--file-chmod');
|
||||
}
|
||||
|
||||
|
|
|
@ -923,7 +923,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
|||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
private doResolvePathRemote(path: string, remoteAuthority: string, forceOpenWorkspaceAsFile?: boolean): IPathToOpen | undefined {
|
||||
const first = path.charCodeAt(0);
|
||||
|
||||
|
|
|
@ -122,10 +122,6 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
|
|||
}
|
||||
}
|
||||
|
||||
async $onExtensionHostExit(code: number): Promise<void> {
|
||||
this._extensionService._onExtensionHostExit(code);
|
||||
}
|
||||
|
||||
async $setPerformanceMarks(marks: PerformanceMark[]): Promise<void> {
|
||||
if (this._extensionHostKind === ExtensionHostKind.LocalProcess) {
|
||||
this._timerService.setPerformanceMarks('localExtHost', marks);
|
||||
|
|
|
@ -938,7 +938,6 @@ export interface MainThreadExtensionServiceShape extends IDisposable {
|
|||
$onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void;
|
||||
$onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): Promise<void>;
|
||||
$onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void;
|
||||
$onExtensionHostExit(code: number): Promise<void>;
|
||||
$setPerformanceMarks(marks: performance.PerformanceMark[]): Promise<void>;
|
||||
}
|
||||
|
||||
|
@ -1228,6 +1227,8 @@ export type IResolveAuthorityResult = IResolveAuthorityErrorResult | IResolveAut
|
|||
export interface ExtHostExtensionServiceShape {
|
||||
$resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise<IResolveAuthorityResult>;
|
||||
$startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise<void>;
|
||||
$extensionTestsExecute(): Promise<number>;
|
||||
$extensionTestsExit(code: number): Promise<void>;
|
||||
$activateByEvent(activationEvent: string, activationKind: ActivationKind): Promise<void>;
|
||||
$activate(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<boolean>;
|
||||
$setRemoteEnvironment(env: { [key: string]: string | null; }): Promise<void>;
|
||||
|
|
|
@ -547,7 +547,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|||
);
|
||||
}
|
||||
|
||||
private _handleExtensionTests(): Promise<void> {
|
||||
public $extensionTestsExecute(): Promise<number> {
|
||||
return this._doHandleExtensionTests().then(undefined, error => {
|
||||
console.error(error); // ensure any error message makes it onto the console
|
||||
|
||||
|
@ -555,76 +555,51 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|||
});
|
||||
}
|
||||
|
||||
private async _doHandleExtensionTests(): Promise<void> {
|
||||
private async _doHandleExtensionTests(): Promise<number> {
|
||||
const { extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment;
|
||||
if (!(extensionDevelopmentLocationURI && extensionTestsLocationURI && extensionTestsLocationURI.scheme === Schemas.file)) {
|
||||
return Promise.resolve(undefined);
|
||||
if (!extensionDevelopmentLocationURI || !extensionTestsLocationURI || extensionTestsLocationURI.scheme !== Schemas.file) {
|
||||
throw new Error(nls.localize('extensionTestError1', "Cannot load test runner."));
|
||||
}
|
||||
|
||||
const extensionTestsPath = originalFSPath(extensionTestsLocationURI);
|
||||
|
||||
// Require the test runner via node require from the provided path
|
||||
let testRunner: ITestRunner | INewTestRunner | undefined;
|
||||
let requireError: Error | undefined;
|
||||
try {
|
||||
testRunner = await this._loadCommonJSModule(null, URI.file(extensionTestsPath), new ExtensionActivationTimesBuilder(false));
|
||||
} catch (error) {
|
||||
requireError = error;
|
||||
const testRunner: ITestRunner | INewTestRunner | undefined = await this._loadCommonJSModule(null, URI.file(extensionTestsPath), new ExtensionActivationTimesBuilder(false));
|
||||
|
||||
if (!testRunner || typeof testRunner.run !== 'function') {
|
||||
throw new Error(nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsPath));
|
||||
}
|
||||
|
||||
// Execute the runner if it follows the old `run` spec
|
||||
if (testRunner && typeof testRunner.run === 'function') {
|
||||
return new Promise<void>((c, e) => {
|
||||
const oldTestRunnerCallback = (error: Error, failures: number | undefined) => {
|
||||
if (error) {
|
||||
e(error.toString());
|
||||
} else {
|
||||
c(undefined);
|
||||
}
|
||||
|
||||
// after tests have run, we shutdown the host
|
||||
this._testRunnerExit(error || (typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */);
|
||||
};
|
||||
|
||||
const runResult = testRunner!.run(extensionTestsPath, oldTestRunnerCallback);
|
||||
|
||||
// Using the new API `run(): Promise<void>`
|
||||
if (runResult && runResult.then) {
|
||||
runResult
|
||||
.then(() => {
|
||||
c();
|
||||
this._testRunnerExit(0);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
e(err.toString());
|
||||
this._testRunnerExit(1);
|
||||
});
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
const oldTestRunnerCallback = (error: Error, failures: number | undefined) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve((typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Otherwise make sure to shutdown anyway even in case of an error
|
||||
else {
|
||||
this._testRunnerExit(1 /* ERROR */);
|
||||
}
|
||||
const runResult = testRunner.run(extensionTestsPath, oldTestRunnerCallback);
|
||||
|
||||
return Promise.reject(new Error(requireError ? requireError.toString() : nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsPath)));
|
||||
// Using the new API `run(): Promise<void>`
|
||||
if (runResult && runResult.then) {
|
||||
runResult
|
||||
.then(() => {
|
||||
resolve(0);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
reject(err.toString());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _testRunnerExit(code: number): void {
|
||||
public async $extensionTestsExit(code: number): Promise<void> {
|
||||
this._logService.info(`extension host terminating: test runner requested exit with code ${code}`);
|
||||
this._logService.info(`exiting with code ${code}`);
|
||||
this._logService.flush();
|
||||
|
||||
// wait at most 5000ms for the renderer to confirm our exit request and for the renderer socket to drain
|
||||
// (this is to ensure all outstanding messages reach the renderer)
|
||||
const exitPromise = this._mainThreadExtensionsProxy.$onExtensionHostExit(code);
|
||||
const drainPromise = this._extHostContext.drain();
|
||||
Promise.race([Promise.all([exitPromise, drainPromise]), timeout(5000)]).then(() => {
|
||||
this._logService.info(`exiting with code ${code}`);
|
||||
this._logService.flush();
|
||||
|
||||
this._hostUtils.exit(code);
|
||||
});
|
||||
this._hostUtils.exit(code);
|
||||
}
|
||||
|
||||
private _startExtensionHost(): Promise<void> {
|
||||
|
@ -636,7 +611,6 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
|
|||
return this._readyToStartExtensionHost.wait()
|
||||
.then(() => this._readyToRunExtensions.open())
|
||||
.then(() => this._handleEagerExtensions())
|
||||
.then(() => this._handleExtensionTests())
|
||||
.then(() => {
|
||||
this._logService.info(`eager extensions activated`);
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@ import { Schemas } from 'vs/base/common/network';
|
|||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
|
||||
import { realpathSync } from 'vs/base/node/extpath';
|
||||
|
||||
class NodeModuleRequireInterceptor extends RequireInterceptor {
|
||||
|
||||
|
@ -36,7 +37,7 @@ class NodeModuleRequireInterceptor extends RequireInterceptor {
|
|||
}
|
||||
return that._factories.get(request)!.load(
|
||||
request,
|
||||
URI.file(parent.filename),
|
||||
URI.file(realpathSync(parent.filename)),
|
||||
request => original.apply(this, [request, parent, isMain])
|
||||
);
|
||||
};
|
||||
|
|
|
@ -40,10 +40,7 @@ async function updateFocus(widget: WorkbenchListWidget, updateFocusFn: (widget:
|
|||
|
||||
const newFocus = widget.getFocus();
|
||||
|
||||
if (
|
||||
(selection.length !== 1 || !equals(focus, selection) || equals(focus, newFocus))
|
||||
&& !(focus.length === 0 && selection.length === 0)
|
||||
) {
|
||||
if (selection.length > 1 || !equals(focus, selection) || equals(focus, newFocus)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la
|
|||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups, UNPIN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, UNPIN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
@ -216,7 +216,7 @@ export class JoinAllGroupsAction extends Action {
|
|||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
mergeAllGroups(this.editorGroupService);
|
||||
this.editorGroupService.mergeAllGroups();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -279,17 +279,6 @@ function registerEditorGroupsLayoutCommand(): void {
|
|||
});
|
||||
}
|
||||
|
||||
export function mergeAllGroups(editorGroupService: IEditorGroupsService): void {
|
||||
const target = editorGroupService.activeGroup;
|
||||
for (const group of editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) {
|
||||
if (group === target) {
|
||||
continue; // keep target
|
||||
}
|
||||
|
||||
editorGroupService.mergeGroup(group, target);
|
||||
}
|
||||
}
|
||||
|
||||
function registerDiffEditorCommands(): void {
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: GOTO_NEXT_CHANGE,
|
||||
|
|
|
@ -764,6 +764,18 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
|
|||
return targetView;
|
||||
}
|
||||
|
||||
mergeAllGroups(target = this.activeGroup): IEditorGroupView {
|
||||
for (const group of this.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) {
|
||||
if (group === target) {
|
||||
continue; // keep target
|
||||
}
|
||||
|
||||
this.mergeGroup(group, target);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private assertGroupView(group: IEditorGroupView | GroupIdentifier): IEditorGroupView {
|
||||
let groupView: IEditorGroupView | undefined;
|
||||
if (typeof group === 'number') {
|
||||
|
|
|
@ -89,7 +89,7 @@ import { isStandalone } from 'vs/base/browser/browser';
|
|||
'workbench.editor.untitled.hint': {
|
||||
'type': 'string',
|
||||
'enum': ['text', 'button', 'hidden'],
|
||||
'default': 'button',
|
||||
'default': 'hidden',
|
||||
'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'untitledHint' }, "Controls if the untitled hint should be inline text in the editor or a floating button or hidden.")
|
||||
},
|
||||
'workbench.editor.tabCloseButton': {
|
||||
|
|
|
@ -137,7 +137,7 @@ export class BulkEditPane extends ViewPane {
|
|||
multipleSelectionSupport: false,
|
||||
keyboardNavigationLabelProvider: new BulkEditNaviLabelProvider(),
|
||||
sorter: new BulkEditSorter(),
|
||||
openOnFocus: true
|
||||
selectionNavigation: true
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -167,7 +167,7 @@ export class CommentsPanel extends ViewPane {
|
|||
this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this));
|
||||
this.tree = this._register(this.instantiationService.createInstance(CommentsList, this.treeLabels, this.treeContainer, {
|
||||
overrideStyles: { listBackground: this.getBackgroundColor() },
|
||||
openOnFocus: true,
|
||||
selectionNavigation: true,
|
||||
accessibilityProvider: {
|
||||
getAriaLabel(element: any): string {
|
||||
if (element instanceof CommentsModel) {
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { localize } from 'vs/nls';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { basename, isEqual } from 'vs/base/common/resources';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { ITextFileService, ISaveErrorHandler, ITextFileEditorModel, IResolvedTextFileEditorModel, IWriteTextFileOptions } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextFileService, ISaveErrorHandler, ITextFileEditorModel, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
|
@ -38,7 +38,7 @@ export const CONFLICT_RESOLUTION_SCHEME = 'conflictResolution';
|
|||
|
||||
const LEARN_MORE_DIRTY_WRITE_IGNORE_KEY = 'learnMoreDirtyWriteError';
|
||||
|
||||
const conflictEditorHelp = nls.localize('userGuide', "Use the actions in the editor tool bar to either undo your changes or overwrite the content of the file with your changes.");
|
||||
const conflictEditorHelp = localize('userGuide', "Use the actions in the editor tool bar to either undo your changes or overwrite the content of the file with your changes.");
|
||||
|
||||
// A handler for text file save error happening with conflict resolution actions
|
||||
export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHandler, IWorkbenchContribution {
|
||||
|
@ -123,7 +123,7 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa
|
|||
|
||||
// Otherwise show the message that will lead the user into the save conflict editor.
|
||||
else {
|
||||
message = nls.localize('staleSaveError', "Failed to save '{0}': The content of the file is newer. Please compare your version with the file contents or overwrite the content of the file with your changes.", basename(resource));
|
||||
message = localize('staleSaveError', "Failed to save '{0}': The content of the file is newer. Please compare your version with the file contents or overwrite the content of the file with your changes.", basename(resource));
|
||||
|
||||
primaryActions.push(this.instantiationService.createInstance(ResolveSaveConflictAction, model));
|
||||
primaryActions.push(this.instantiationService.createInstance(SaveModelIgnoreModifiedSinceAction, model));
|
||||
|
@ -134,19 +134,19 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa
|
|||
|
||||
// Any other save error
|
||||
else {
|
||||
const isReadonly = fileOperationError.fileOperationResult === FileOperationResult.FILE_READ_ONLY;
|
||||
const triedToMakeWriteable = isReadonly && fileOperationError.options && (fileOperationError.options as IWriteTextFileOptions).overwriteReadonly;
|
||||
const isWriteLocked = fileOperationError.fileOperationResult === FileOperationResult.FILE_WRITE_LOCKED;
|
||||
const triedToUnlock = isWriteLocked && fileOperationError.options?.unlock;
|
||||
const isPermissionDenied = fileOperationError.fileOperationResult === FileOperationResult.FILE_PERMISSION_DENIED;
|
||||
const canHandlePermissionOrReadonlyErrors = resource.scheme === Schemas.file; // https://github.com/microsoft/vscode/issues/48659
|
||||
const canSaveElevated = resource.scheme === Schemas.file; // https://github.com/microsoft/vscode/issues/48659 TODO
|
||||
|
||||
// Save Elevated
|
||||
if (canHandlePermissionOrReadonlyErrors && (isPermissionDenied || triedToMakeWriteable)) {
|
||||
primaryActions.push(this.instantiationService.createInstance(SaveModelElevatedAction, model, !!triedToMakeWriteable));
|
||||
if (canSaveElevated && (isPermissionDenied || triedToUnlock)) {
|
||||
primaryActions.push(this.instantiationService.createInstance(SaveModelElevatedAction, model, !!triedToUnlock));
|
||||
}
|
||||
|
||||
// Overwrite
|
||||
else if (canHandlePermissionOrReadonlyErrors && isReadonly) {
|
||||
primaryActions.push(this.instantiationService.createInstance(OverwriteReadonlyModelAction, model));
|
||||
// Unlock
|
||||
else if (isWriteLocked) {
|
||||
primaryActions.push(this.instantiationService.createInstance(UnlockModelAction, model));
|
||||
}
|
||||
|
||||
// Retry
|
||||
|
@ -161,16 +161,16 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa
|
|||
primaryActions.push(this.instantiationService.createInstance(DiscardModelAction, model));
|
||||
|
||||
// Message
|
||||
if (canHandlePermissionOrReadonlyErrors && isReadonly) {
|
||||
if (triedToMakeWriteable) {
|
||||
message = isWindows ? nls.localize('readonlySaveErrorAdmin', "Failed to save '{0}': File is read-only. Select 'Overwrite as Admin' to retry as administrator.", basename(resource)) : nls.localize('readonlySaveErrorSudo', "Failed to save '{0}': File is read-only. Select 'Overwrite as Sudo' to retry as superuser.", basename(resource));
|
||||
if (isWriteLocked) {
|
||||
if (triedToUnlock && canSaveElevated) {
|
||||
message = isWindows ? localize('readonlySaveErrorAdmin', "Failed to save '{0}': File is read-only. Select 'Overwrite as Admin' to retry as administrator.", basename(resource)) : localize('readonlySaveErrorSudo', "Failed to save '{0}': File is read-only. Select 'Overwrite as Sudo' to retry as superuser.", basename(resource));
|
||||
} else {
|
||||
message = nls.localize('readonlySaveError', "Failed to save '{0}': File is read-only. Select 'Overwrite' to attempt to make it writeable.", basename(resource));
|
||||
message = localize('readonlySaveError', "Failed to save '{0}': File is read-only. Select 'Overwrite' to attempt to make it writeable.", basename(resource));
|
||||
}
|
||||
} else if (canHandlePermissionOrReadonlyErrors && isPermissionDenied) {
|
||||
message = isWindows ? nls.localize('permissionDeniedSaveError', "Failed to save '{0}': Insufficient permissions. Select 'Retry as Admin' to retry as administrator.", basename(resource)) : nls.localize('permissionDeniedSaveErrorSudo', "Failed to save '{0}': Insufficient permissions. Select 'Retry as Sudo' to retry as superuser.", basename(resource));
|
||||
} else if (canSaveElevated && isPermissionDenied) {
|
||||
message = isWindows ? localize('permissionDeniedSaveError', "Failed to save '{0}': Insufficient permissions. Select 'Retry as Admin' to retry as administrator.", basename(resource)) : localize('permissionDeniedSaveErrorSudo', "Failed to save '{0}': Insufficient permissions. Select 'Retry as Sudo' to retry as superuser.", basename(resource));
|
||||
} else {
|
||||
message = nls.localize({ key: 'genericSaveError', comment: ['{0} is the resource that failed to save and {1} the error message'] }, "Failed to save '{0}': {1}", basename(resource), toErrorMessage(error, false));
|
||||
message = localize({ key: 'genericSaveError', comment: ['{0} is the resource that failed to save and {1} the error message'] }, "Failed to save '{0}': {1}", basename(resource), toErrorMessage(error, false));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,7 +203,7 @@ class ResolveConflictLearnMoreAction extends Action {
|
|||
constructor(
|
||||
@IOpenerService private readonly openerService: IOpenerService
|
||||
) {
|
||||
super('workbench.files.action.resolveConflictLearnMore', nls.localize('learnMore', "Learn More"));
|
||||
super('workbench.files.action.resolveConflictLearnMore', localize('learnMore', "Learn More"));
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
|
@ -216,7 +216,7 @@ class DoNotShowResolveConflictLearnMoreAction extends Action {
|
|||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService
|
||||
) {
|
||||
super('workbench.files.action.resolveConflictLearnMoreDoNotShowAgain', nls.localize('dontShowAgain', "Don't Show Again"));
|
||||
super('workbench.files.action.resolveConflictLearnMoreDoNotShowAgain', localize('dontShowAgain', "Don't Show Again"));
|
||||
}
|
||||
|
||||
async run(notification: IDisposable): Promise<void> {
|
||||
|
@ -236,14 +236,14 @@ class ResolveSaveConflictAction extends Action {
|
|||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IProductService private readonly productService: IProductService
|
||||
) {
|
||||
super('workbench.files.action.resolveConflict', nls.localize('compareChanges', "Compare"));
|
||||
super('workbench.files.action.resolveConflict', localize('compareChanges', "Compare"));
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (!this.model.isDisposed()) {
|
||||
const resource = this.model.resource;
|
||||
const name = basename(resource);
|
||||
const editorLabel = nls.localize('saveConflictDiffLabel', "{0} (in file) ↔ {1} (in {2}) - Resolve save conflict", name, name, this.productService.nameLong);
|
||||
const editorLabel = localize('saveConflictDiffLabel', "{0} (in file) ↔ {1} (in {2}) - Resolve save conflict", name, name, this.productService.nameLong);
|
||||
|
||||
await TextFileContentProvider.open(resource, CONFLICT_RESOLUTION_SCHEME, editorLabel, this.editorService, { pinned: true });
|
||||
|
||||
|
@ -265,16 +265,16 @@ class SaveModelElevatedAction extends Action {
|
|||
|
||||
constructor(
|
||||
private model: ITextFileEditorModel,
|
||||
private triedToMakeWriteable: boolean
|
||||
private triedToUnlock: boolean
|
||||
) {
|
||||
super('workbench.files.action.saveModelElevated', triedToMakeWriteable ? isWindows ? nls.localize('overwriteElevated', "Overwrite as Admin...") : nls.localize('overwriteElevatedSudo', "Overwrite as Sudo...") : isWindows ? nls.localize('saveElevated', "Retry as Admin...") : nls.localize('saveElevatedSudo', "Retry as Sudo..."));
|
||||
super('workbench.files.action.saveModelElevated', triedToUnlock ? isWindows ? localize('overwriteElevated', "Overwrite as Admin...") : localize('overwriteElevatedSudo', "Overwrite as Sudo...") : isWindows ? localize('saveElevated', "Retry as Admin...") : localize('saveElevatedSudo', "Retry as Sudo..."));
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (!this.model.isDisposed()) {
|
||||
await this.model.save({
|
||||
writeElevated: true,
|
||||
overwriteReadonly: this.triedToMakeWriteable,
|
||||
writeUnlock: this.triedToUnlock,
|
||||
reason: SaveReason.EXPLICIT
|
||||
});
|
||||
}
|
||||
|
@ -286,7 +286,7 @@ class RetrySaveModelAction extends Action {
|
|||
constructor(
|
||||
private model: ITextFileEditorModel
|
||||
) {
|
||||
super('workbench.files.action.saveModel', nls.localize('retry', "Retry"));
|
||||
super('workbench.files.action.saveModel', localize('retry', "Retry"));
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
|
@ -301,7 +301,7 @@ class DiscardModelAction extends Action {
|
|||
constructor(
|
||||
private model: ITextFileEditorModel
|
||||
) {
|
||||
super('workbench.files.action.discardModel', nls.localize('discard', "Discard"));
|
||||
super('workbench.files.action.discardModel', localize('discard', "Discard"));
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
|
@ -344,17 +344,17 @@ class SaveModelAsAction extends Action {
|
|||
}
|
||||
}
|
||||
|
||||
class OverwriteReadonlyModelAction extends Action {
|
||||
class UnlockModelAction extends Action {
|
||||
|
||||
constructor(
|
||||
private model: ITextFileEditorModel
|
||||
) {
|
||||
super('workbench.files.action.overwrite', nls.localize('overwrite', "Overwrite"));
|
||||
super('workbench.files.action.unlock', localize('overwrite', "Overwrite"));
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (!this.model.isDisposed()) {
|
||||
await this.model.save({ overwriteReadonly: true, reason: SaveReason.EXPLICIT });
|
||||
await this.model.save({ writeUnlock: true, reason: SaveReason.EXPLICIT });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -364,7 +364,7 @@ class SaveModelIgnoreModifiedSinceAction extends Action {
|
|||
constructor(
|
||||
private model: ITextFileEditorModel
|
||||
) {
|
||||
super('workbench.files.action.saveIgnoreModifiedSince', nls.localize('overwrite', "Overwrite"));
|
||||
super('workbench.files.action.saveIgnoreModifiedSince', localize('overwrite', "Overwrite"));
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
|
@ -379,7 +379,7 @@ class ConfigureSaveConflictAction extends Action {
|
|||
constructor(
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService
|
||||
) {
|
||||
super('workbench.files.action.configureSaveConflict', nls.localize('configure', "Configure"));
|
||||
super('workbench.files.action.configureSaveConflict', localize('configure', "Configure"));
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
|
|
|
@ -170,6 +170,12 @@ export class ExplorerService implements IExplorerService {
|
|||
return this.model.findClosest(resource);
|
||||
}
|
||||
|
||||
findClosestRoot(resource: URI): ExplorerItem | null {
|
||||
const parentRoots = this.model.roots.filter(r => this.uriIdentityService.extUri.isEqualOrParent(resource, r.resource))
|
||||
.sort((first, second) => second.resource.path.length - first.resource.path.length);
|
||||
return parentRoots.length ? parentRoots[0] : null;
|
||||
}
|
||||
|
||||
async setEditable(stat: ExplorerItem, data: IEditableData | null): Promise<void> {
|
||||
if (!this.view) {
|
||||
return;
|
||||
|
@ -221,16 +227,13 @@ export class ExplorerService implements IExplorerService {
|
|||
|
||||
// Stat needs to be resolved first and then revealed
|
||||
const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: this.sortOrder === SortOrder.Modified };
|
||||
const workspaceFolder = this.contextService.getWorkspaceFolder(resource);
|
||||
if (workspaceFolder === null) {
|
||||
return Promise.resolve(undefined);
|
||||
const root = this.findClosestRoot(resource);
|
||||
if (!root) {
|
||||
return undefined;
|
||||
}
|
||||
const rootUri = workspaceFolder.uri;
|
||||
|
||||
const root = this.roots.find(r => this.uriIdentityService.extUri.isEqual(r.resource, rootUri))!;
|
||||
|
||||
try {
|
||||
const stat = await this.fileService.resolve(rootUri, options);
|
||||
const stat = await this.fileService.resolve(root.resource, options);
|
||||
|
||||
// Convert to model
|
||||
const modelStat = ExplorerItem.create(this.fileService, stat, undefined, options.resolveTo);
|
||||
|
|
|
@ -84,6 +84,7 @@ export interface IExplorerService {
|
|||
// If undefined is passed checks if any element is currently being edited.
|
||||
isEditable(stat: ExplorerItem | undefined): boolean;
|
||||
findClosest(resource: URI): ExplorerItem | null;
|
||||
findClosestRoot(resource: URI): ExplorerItem | null;
|
||||
refresh(): Promise<void>;
|
||||
setToCopy(stats: ExplorerItem[], cut: boolean): Promise<void>;
|
||||
isCut(stat: ExplorerItem): boolean;
|
||||
|
|
|
@ -703,9 +703,7 @@ export class ExplorerView extends ViewPane {
|
|||
}
|
||||
|
||||
// Expand all stats in the parent chain.
|
||||
let item: ExplorerItem | undefined = this.explorerService.roots.filter(i => this.uriIdentityService.extUri.isEqualOrParent(resource, i.resource))
|
||||
// Take the root that is the closest to the stat #72299
|
||||
.sort((first, second) => second.resource.path.length - first.resource.path.length)[0];
|
||||
let item: ExplorerItem | null = this.explorerService.findClosestRoot(resource);
|
||||
|
||||
while (item && item.resource.toString() !== resource.toString()) {
|
||||
try {
|
||||
|
@ -719,7 +717,7 @@ export class ExplorerView extends ViewPane {
|
|||
item = child;
|
||||
break;
|
||||
}
|
||||
item = undefined;
|
||||
item = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -532,8 +532,7 @@ interface CachedParsedExpression {
|
|||
* Makes sure that visible editors are always shown in the explorer even if they are filtered out by settings.
|
||||
*/
|
||||
export class FilesFilter implements ITreeFilter<ExplorerItem, FuzzyScore> {
|
||||
private hiddenExpressionPerRoot: Map<string, CachedParsedExpression>;
|
||||
private uriVisibilityMap = new Map<URI, boolean>();
|
||||
private hiddenExpressionPerRoot = new Map<string, CachedParsedExpression>();
|
||||
private editorsAffectingFilter = new Set<IEditorInput>();
|
||||
private _onDidChange = new Emitter<void>();
|
||||
private toDispose: IDisposable[] = [];
|
||||
|
@ -545,7 +544,6 @@ export class FilesFilter implements ITreeFilter<ExplorerItem, FuzzyScore> {
|
|||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
|
||||
) {
|
||||
this.hiddenExpressionPerRoot = new Map<string, CachedParsedExpression>();
|
||||
this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => this.updateConfiguration()));
|
||||
this.toDispose.push(this.configurationService.onDidChangeConfiguration((e) => {
|
||||
if (e.affectsConfiguration('files.exclude')) {
|
||||
|
@ -555,26 +553,30 @@ export class FilesFilter implements ITreeFilter<ExplorerItem, FuzzyScore> {
|
|||
this.toDispose.push(this.editorService.onDidVisibleEditorsChange(() => {
|
||||
const editors = this.editorService.visibleEditors;
|
||||
let shouldFire = false;
|
||||
this.uriVisibilityMap.forEach((visible, uri) => {
|
||||
if (!visible) {
|
||||
editors.forEach(e => {
|
||||
if (e.resource && this.uriIdentityService.extUri.isEqualOrParent(e.resource, uri)) {
|
||||
// A filtered resource suddenly became visible since user opened an editor
|
||||
shouldFire = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.editorsAffectingFilter.forEach(e => {
|
||||
for (const e of editors) {
|
||||
if (!e.resource) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const stat = this.explorerService.findClosest(e.resource);
|
||||
if (stat && stat.isExcluded) {
|
||||
// A filtered resource suddenly became visible since user opened an editor
|
||||
shouldFire = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const e of this.editorsAffectingFilter) {
|
||||
if (!editors.includes(e)) {
|
||||
// Editor that was affecting filtering is no longer visible
|
||||
shouldFire = true;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (shouldFire) {
|
||||
this.editorsAffectingFilter.clear();
|
||||
this.uriVisibilityMap.clear();
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}));
|
||||
|
@ -603,21 +605,12 @@ export class FilesFilter implements ITreeFilter<ExplorerItem, FuzzyScore> {
|
|||
|
||||
if (shouldFire) {
|
||||
this.editorsAffectingFilter.clear();
|
||||
this.uriVisibilityMap.clear();
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}
|
||||
|
||||
filter(stat: ExplorerItem, parentVisibility: TreeVisibility): boolean {
|
||||
const cachedVisibility = this.uriVisibilityMap.get(stat.resource);
|
||||
if (typeof cachedVisibility === 'boolean') {
|
||||
return cachedVisibility;
|
||||
}
|
||||
|
||||
const isVisible = this.isVisible(stat, parentVisibility);
|
||||
this.uriVisibilityMap.set(stat.resource, isVisible);
|
||||
|
||||
return isVisible;
|
||||
return this.isVisible(stat, parentVisibility);
|
||||
}
|
||||
|
||||
private isVisible(stat: ExplorerItem, parentVisibility: TreeVisibility): boolean {
|
||||
|
@ -636,7 +629,7 @@ export class FilesFilter implements ITreeFilter<ExplorerItem, FuzzyScore> {
|
|||
stat.isExcluded = true;
|
||||
const editors = this.editorService.visibleEditors;
|
||||
const editor = editors.find(e => e.resource && this.uriIdentityService.extUri.isEqualOrParent(e.resource, stat.resource));
|
||||
if (editor) {
|
||||
if (editor && stat.root === this.explorerService.findClosestRoot(stat.resource)) {
|
||||
this.editorsAffectingFilter.add(editor);
|
||||
return true; // Show all opened files and their parents
|
||||
}
|
||||
|
|
|
@ -405,7 +405,7 @@ export class MarkersView extends ViewPane implements IMarkersView {
|
|||
overrideStyles: {
|
||||
listBackground: this.getBackgroundColor()
|
||||
},
|
||||
openOnFocus: true
|
||||
selectionNavigation: true
|
||||
},
|
||||
));
|
||||
|
||||
|
|
|
@ -32,3 +32,5 @@ export const EDITOR_BOTTOM_PADDING_WITHOUT_STATUSBAR = 12;
|
|||
export const CELL_OUTPUT_PADDING = 14;
|
||||
|
||||
export const COLLAPSED_INDICATOR_HEIGHT = 24;
|
||||
|
||||
export const MARKDOWN_PREVIEW_PADDING = 8;
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
|
||||
import { CellKind, cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
|
||||
class NotebookClipboardContribution extends Disposable implements INotebookEditorContribution {
|
||||
static id: string = 'workbench.notebook.viewportCustomMarkdown';
|
||||
private readonly _warmupViewport: RunOnceScheduler;
|
||||
|
||||
constructor(private readonly _notebookEditor: INotebookEditor) {
|
||||
super();
|
||||
|
||||
this._warmupViewport = new RunOnceScheduler(() => this._warmupViewportNow(), 200);
|
||||
|
||||
this._register(this._notebookEditor.onDidScroll(() => {
|
||||
this._warmupViewport.schedule();
|
||||
}));
|
||||
}
|
||||
|
||||
private _warmupViewportNow() {
|
||||
const visibleRanges = this._notebookEditor.getVisibleRangesPlusViewportAboveBelow();
|
||||
cellRangesToIndexes(visibleRanges).forEach(index => {
|
||||
const cell = this._notebookEditor.viewModel?.viewCells[index];
|
||||
|
||||
if (cell?.cellKind === CellKind.Markdown) {
|
||||
this._notebookEditor.createMarkdownPreview(cell);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerNotebookContribution(NotebookClipboardContribution.id, NotebookClipboardContribution);
|
|
@ -41,7 +41,7 @@ import { generateUuid } from 'vs/base/common/uuid';
|
|||
import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { DiffNestedCellViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel';
|
||||
import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView';
|
||||
import { CELL_OUTPUT_PADDING } from 'vs/workbench/contrib/notebook/browser/constants';
|
||||
import { CELL_OUTPUT_PADDING, MARKDOWN_PREVIEW_PADDING } from 'vs/workbench/contrib/notebook/browser/constants';
|
||||
import { NotebookDiffEditorEventDispatcher, NotebookDiffLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher';
|
||||
|
||||
const $ = DOM.$;
|
||||
|
@ -392,6 +392,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
|
|||
private readonly webviewOptions = {
|
||||
outputNodePadding: CELL_OUTPUT_PADDING,
|
||||
outputNodeLeftPadding: 32,
|
||||
previewNodePadding: MARKDOWN_PREVIEW_PADDING,
|
||||
leftMargin: 0,
|
||||
cellMargin: 0,
|
||||
runGutter: 0
|
||||
|
|
|
@ -67,6 +67,7 @@ import 'vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline';
|
|||
import 'vs/workbench/contrib/notebook/browser/contrib/status/editorStatus';
|
||||
import 'vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo';
|
||||
import 'vs/workbench/contrib/notebook/browser/contrib/cellOperations/cellOperations';
|
||||
import 'vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown';
|
||||
|
||||
|
||||
// Diff Editor Contribution
|
||||
|
|
|
@ -242,6 +242,7 @@ export interface ICellViewModel extends IGenericCellViewModel {
|
|||
outputIsHovered: boolean;
|
||||
getText(): string;
|
||||
getTextLength(): number;
|
||||
getHeight(lineHeight: number): number;
|
||||
metadata: NotebookCellMetadata | undefined;
|
||||
textModel: ITextModel | undefined;
|
||||
hasModel(): this is IEditableCellViewModel;
|
||||
|
@ -354,6 +355,7 @@ export interface INotebookEditor extends ICommonNotebookEditor {
|
|||
readonly onDidFocusEditorWidget: Event<void>;
|
||||
activeKernel: INotebookKernel | undefined;
|
||||
multipleKernelsAvailable: boolean;
|
||||
readonly onDidScroll: Event<void>;
|
||||
readonly onDidChangeAvailableKernels: Event<void>;
|
||||
readonly onDidChangeKernel: Event<void>;
|
||||
readonly onDidChangeActiveCell: Event<void>;
|
||||
|
@ -386,6 +388,8 @@ export interface INotebookEditor extends ICommonNotebookEditor {
|
|||
*/
|
||||
getLayoutInfo(): NotebookLayoutInfo;
|
||||
|
||||
getVisibleRangesPlusViewportAboveBelow(): ICellRange[];
|
||||
|
||||
/**
|
||||
* Fetch the output renderers for notebook outputs.
|
||||
*/
|
||||
|
@ -655,6 +659,7 @@ export interface INotebookCellList {
|
|||
getViewIndex2(modelIndex: number): number | undefined;
|
||||
getModelIndex(cell: CellViewModel): number | undefined;
|
||||
getModelIndex2(viewIndex: number): number | undefined;
|
||||
getVisibleRangesPlusViewportAboveBelow(): ICellRange[];
|
||||
focusElement(element: ICellViewModel): void;
|
||||
selectElement(element: ICellViewModel): void;
|
||||
getFocusedElements(): ICellViewModel[];
|
||||
|
|
|
@ -42,7 +42,7 @@ import { IEditorMemento } from 'vs/workbench/common/editor';
|
|||
import { Memento, MementoObject } from 'vs/workbench/common/memento';
|
||||
import { PANEL_BORDER } from 'vs/workbench/common/theme';
|
||||
import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors';
|
||||
import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_OUTPUT_PADDING, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, MARKDOWN_CELL_BOTTOM_MARGIN, MARKDOWN_CELL_TOP_MARGIN, SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants';
|
||||
import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_OUTPUT_PADDING, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, MARKDOWN_CELL_BOTTOM_MARGIN, MARKDOWN_CELL_TOP_MARGIN, MARKDOWN_PREVIEW_PADDING, SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants';
|
||||
import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_ID, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||
import { NotebookDecorationCSSRules, NotebookRefCountedStyleSheet } from 'vs/workbench/contrib/notebook/browser/notebookEditorDecorations';
|
||||
import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
|
||||
|
@ -167,6 +167,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
return this._renderedEditors.get(focused);
|
||||
}
|
||||
|
||||
private readonly _onDidScroll = this._register(new Emitter<void>());
|
||||
readonly onDidScroll: Event<void> = this._onDidScroll.event;
|
||||
private readonly _onDidChangeActiveCell = this._register(new Emitter<void>());
|
||||
readonly onDidChangeActiveCell: Event<void> = this._onDidChangeActiveCell.event;
|
||||
private _cursorNavigationMode: boolean = false;
|
||||
|
@ -281,6 +283,29 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
}
|
||||
|
||||
this._updateForNotebookConfiguration();
|
||||
|
||||
if (this._debugFlag) {
|
||||
this._domFrameLog();
|
||||
}
|
||||
}
|
||||
|
||||
private _debugFlag: boolean = false;
|
||||
private _frameId = 0;
|
||||
private _domFrameLog() {
|
||||
DOM.scheduleAtNextAnimationFrame(() => {
|
||||
this._frameId++;
|
||||
|
||||
this._domFrameLog();
|
||||
});
|
||||
}
|
||||
|
||||
private _debug(...args: any[]) {
|
||||
if (!this._debugFlag) {
|
||||
return;
|
||||
}
|
||||
|
||||
const date = new Date();
|
||||
console.log(`${date.getSeconds()}:${date.getMilliseconds().toString().padStart(3, '0')}`, `frame #${this._frameId}: `, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -486,6 +511,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
this._onDidChangeVisibleRanges.fire();
|
||||
}));
|
||||
|
||||
this._register(this._list.onDidScroll(() => {
|
||||
this._onDidScroll.fire();
|
||||
}));
|
||||
|
||||
const widgetFocusTracker = DOM.trackFocus(this.getDomNode());
|
||||
this._register(widgetFocusTracker);
|
||||
this._register(widgetFocusTracker.onDidFocus(() => this._onDidFocusEmitter.fire()));
|
||||
|
@ -659,7 +688,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
|
||||
private async _loadKernelPreloads(extensionLocation: URI, kernel: INotebookKernel) {
|
||||
if (kernel.preloads && kernel.preloads.length) {
|
||||
await this._resolveWebview();
|
||||
if (!this._webview?.isResolved()) {
|
||||
await this._resolveWebview();
|
||||
}
|
||||
|
||||
this._webview?.updateKernelPreloads([extensionLocation], kernel.preloads.map(preload => URI.revive(preload)));
|
||||
}
|
||||
}
|
||||
|
@ -734,6 +766,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
this._webview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource, {
|
||||
outputNodePadding: CELL_OUTPUT_PADDING,
|
||||
outputNodeLeftPadding: CELL_OUTPUT_PADDING,
|
||||
previewNodePadding: MARKDOWN_PREVIEW_PADDING,
|
||||
leftMargin: CODE_CELL_LEFT_MARGIN,
|
||||
cellMargin: CELL_MARGIN,
|
||||
runGutter: CELL_RUN_GUTTER,
|
||||
|
@ -756,19 +789,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
this._updateForMetadata();
|
||||
}));
|
||||
|
||||
const useRenderer = this.configurationService.getValue<string>('notebook.experimental.useMarkdownRenderer');
|
||||
|
||||
if (useRenderer) {
|
||||
await this._resolveWebview();
|
||||
|
||||
await this._webview!.initializeMarkdown(this.viewModel.viewCells
|
||||
.filter(cell => cell.cellKind === CellKind.Markdown)
|
||||
.map(cell => ({ cellId: cell.id, content: cell.getText() }))
|
||||
// TODO: look at cell position cache instead of just getting first five cells
|
||||
.slice(0, 5));
|
||||
}
|
||||
|
||||
|
||||
// restore view states, including contributions
|
||||
|
||||
{
|
||||
|
@ -849,20 +869,26 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
removedItems.forEach(output => this._webview?.removeInset(output));
|
||||
|
||||
if (updateItems.length) {
|
||||
this._debug('_list.onDidChangeContentHeight/outputs', updateItems);
|
||||
this._webview?.updateViewScrollTop(-scrollTop, false, updateItems);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._webview?.markdownPreviewMapping) {
|
||||
const updateItems: { id: string, top: number }[] = [];
|
||||
this._webview!.markdownPreviewMapping.forEach(cellId => {
|
||||
this._webview.markdownPreviewMapping.forEach((_, cellId) => {
|
||||
const cell = this.viewModel?.viewCells.find(cell => cell.id === cellId);
|
||||
if (cell) {
|
||||
const cellTop = this._list.getAbsoluteTopOfElement(cell);
|
||||
updateItems.push({ id: cellId, top: cellTop });
|
||||
}
|
||||
});
|
||||
this._webview?.updateMarkdownScrollTop(updateItems);
|
||||
|
||||
if (updateItems.length) {
|
||||
this._debug('_list.onDidChangeContentHeight/markdown', updateItems);
|
||||
this._webview?.updateMarkdownScrollTop(updateItems);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
@ -875,11 +901,25 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
}));
|
||||
this._localStore.add(this._list.onDidRemoveCellFromView(cell => {
|
||||
if (cell.cellKind === CellKind.Markdown) {
|
||||
this.removeMarkdownPreview(cell as MarkdownCellViewModel);
|
||||
const mdCell = cell as MarkdownCellViewModel;
|
||||
if (this.viewModel?.viewCells.find(cell => cell.handle === mdCell.handle)) {
|
||||
// Cell has been folded but is still in model
|
||||
this.hideMarkdownPreview(mdCell);
|
||||
} else {
|
||||
// Cell was deleted
|
||||
this.removeMarkdownPreview(mdCell);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._list.attachViewModel(this.viewModel);
|
||||
// init rendering
|
||||
const useRenderer = this.configurationService.getValue<string>('notebook.experimental.useMarkdownRenderer');
|
||||
|
||||
if (useRenderer) {
|
||||
await this._warmupWithMarkdownRenderer(this.viewModel, viewState);
|
||||
} else {
|
||||
this._list.attachViewModel(this.viewModel);
|
||||
}
|
||||
|
||||
if (this._dimension) {
|
||||
this._list.layout(this._dimension.height - SCROLLABLE_ELEMENT_PADDING_TOP, this._dimension.width);
|
||||
|
@ -893,6 +933,91 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
this.restoreListViewState(viewState);
|
||||
}
|
||||
|
||||
private async _warmupWithMarkdownRenderer(viewModel: NotebookViewModel, viewState: INotebookEditorViewState | undefined) {
|
||||
|
||||
await this._resolveWebview();
|
||||
|
||||
// make sure that the webview is not visible otherwise users will see pre-rendered markdown cells in wrong position as the list view doesn't have a correct `top` offset yet
|
||||
this._webview!.element.style.visibility = 'hidden';
|
||||
// warm up can take around 200ms to load markdown libraries, etc.
|
||||
await this._warmupViewport(viewModel, viewState);
|
||||
|
||||
// todo@rebornix @mjbvz, is this too complicated?
|
||||
|
||||
/* now the webview is ready, and requests to render markdown are fast enough
|
||||
* we can start rendering the list view
|
||||
* render
|
||||
* - markdown cell -> request to webview to (10ms, basically just latency between UI and iframe)
|
||||
* - code cell -> render in place
|
||||
*/
|
||||
this._list.layout(0, 0);
|
||||
this._list.attachViewModel(viewModel);
|
||||
|
||||
// now the list widget has a correct contentHeight/scrollHeight
|
||||
// setting scrollTop will work properly
|
||||
// after setting scroll top, the list view will update `top` of the scrollable element, e.g. `top: -584px`
|
||||
this._list.scrollTop = viewState?.scrollPosition?.top ?? 0;
|
||||
this._debug('finish initial viewport warmup and view state restore.');
|
||||
this._webview!.element.style.visibility = 'visible';
|
||||
|
||||
}
|
||||
|
||||
private async _warmupViewport(viewModel: NotebookViewModel, viewState: INotebookEditorViewState | undefined) {
|
||||
if (viewState && viewState.cellTotalHeights) {
|
||||
const totalHeightCache = viewState.cellTotalHeights;
|
||||
const scrollTop = viewState.scrollPosition?.top ?? 0;
|
||||
const scrollBottom = scrollTop + Math.max(this._dimension?.height ?? 0, 1080);
|
||||
|
||||
let offset = 0;
|
||||
let requests: [ICellViewModel, number][] = [];
|
||||
|
||||
for (let i = 0; i < viewModel.viewCells.length; i++) {
|
||||
const cell = viewModel.viewCells[i];
|
||||
|
||||
if (offset + (totalHeightCache[i] ?? 0) < scrollTop) {
|
||||
offset += (totalHeightCache ? totalHeightCache[i] : 0);
|
||||
continue;
|
||||
} else {
|
||||
if (cell.cellKind === CellKind.Markdown) {
|
||||
requests.push([cell, offset]);
|
||||
}
|
||||
}
|
||||
|
||||
offset += (totalHeightCache ? totalHeightCache[i] : 0);
|
||||
|
||||
if (offset > scrollBottom) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await this._webview!.initializeMarkdown(requests
|
||||
.map(request => ({ cellId: request[0].id, cellHandle: request[0].handle, content: request[0].getText(), offset: request[1] })));
|
||||
} else {
|
||||
const initRequests = viewModel.viewCells.filter(cell => cell.cellKind === CellKind.Markdown).slice(0, 5).map(cell => ({ cellId: cell.id, cellHandle: cell.handle, content: cell.getText(), offset: -10000 }));
|
||||
await this._webview!.initializeMarkdown(initRequests);
|
||||
|
||||
// no cached view state so we are rendering the first viewport
|
||||
// after above async call, we already get init height for markdown cells, we can update their offset
|
||||
let offset = 0;
|
||||
let offsetUpdateRequests: { id: string, top: number }[] = [];
|
||||
const scrollBottom = Math.max(this._dimension?.height ?? 0, 1080);
|
||||
for (let i = 0; i < viewModel.viewCells.length; i++) {
|
||||
const cell = viewModel.viewCells[i];
|
||||
if (cell.cellKind === CellKind.Markdown) {
|
||||
offsetUpdateRequests.push({ id: cell.id, top: offset });
|
||||
}
|
||||
|
||||
offset += cell.getHeight(this.getLayoutInfo().fontInfo.lineHeight);
|
||||
|
||||
if (offset > scrollBottom) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this._webview?.updateMarkdownScrollTop(offsetUpdateRequests);
|
||||
}
|
||||
}
|
||||
|
||||
restoreListViewState(viewState: INotebookEditorViewState | undefined): void {
|
||||
if (viewState?.scrollPosition !== undefined) {
|
||||
this._list.scrollTop = viewState!.scrollPosition.top;
|
||||
|
@ -1215,6 +1340,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
return this._list.setHiddenAreas(_ranges, true);
|
||||
}
|
||||
|
||||
getVisibleRangesPlusViewportAboveBelow(): ICellRange[] {
|
||||
return this._list?.getVisibleRangesPlusViewportAboveBelow() ?? [];
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Decorations
|
||||
|
@ -1350,6 +1479,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
return;
|
||||
}
|
||||
|
||||
if (this._list.elementHeight(cell) === height) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._pendingLayouts.delete(cell);
|
||||
|
||||
relayout(cell, height);
|
||||
|
@ -1558,6 +1691,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
throw new Error('Notebook Editor move cell, index out of range');
|
||||
}
|
||||
|
||||
// this._list.move(index, desiredIndex);
|
||||
|
||||
let r: (val: ICellViewModel | null) => void;
|
||||
DOM.scheduleAtNextAnimationFrame(() => {
|
||||
if (this._isDisposed) {
|
||||
|
@ -1680,22 +1815,26 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
}
|
||||
|
||||
async createMarkdownPreview(cell: MarkdownCellViewModel) {
|
||||
const useRenderer = this.configurationService.getValue<string>('notebook.experimental.useMarkdownRenderer');
|
||||
if (!useRenderer) {
|
||||
// TODO: handle case where custom renderer is disabled?
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._webview) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this._resolveWebview();
|
||||
if (!this._webview.isResolved()) {
|
||||
await this._resolveWebview();
|
||||
}
|
||||
|
||||
if (!this._webview) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cellTop = this._list.getAbsoluteTopOfElement(cell);
|
||||
if (this._webview.markdownPreviewMapping.has(cell.id)) {
|
||||
await this._webview.showMarkdownPreview(cell.id, cell.getText(), cellTop);
|
||||
} else {
|
||||
await this._webview.createMarkdownPreview(cell.id, cell.getText(), cellTop);
|
||||
}
|
||||
await this._webview.showMarkdownPreview(cell.id, cell.handle, cell.getText(), cellTop, cell.version);
|
||||
}
|
||||
|
||||
async unhideMarkdownPreview(cell: MarkdownCellViewModel) {
|
||||
|
@ -1703,7 +1842,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
return;
|
||||
}
|
||||
|
||||
await this._resolveWebview();
|
||||
if (!this._webview.isResolved()) {
|
||||
await this._resolveWebview();
|
||||
}
|
||||
|
||||
await this._webview?.unhideMarkdownPreview(cell.id);
|
||||
}
|
||||
|
@ -1713,7 +1854,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
return;
|
||||
}
|
||||
|
||||
await this._resolveWebview();
|
||||
if (!this._webview.isResolved()) {
|
||||
await this._resolveWebview();
|
||||
}
|
||||
|
||||
await this._webview?.hideMarkdownPreview(cell.id);
|
||||
}
|
||||
|
@ -1723,7 +1866,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
return;
|
||||
}
|
||||
|
||||
await this._resolveWebview();
|
||||
if (!this._webview.isResolved()) {
|
||||
await this._resolveWebview();
|
||||
}
|
||||
|
||||
await this._webview?.removeMarkdownPreview(cell.id);
|
||||
}
|
||||
|
@ -1733,7 +1878,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
return;
|
||||
}
|
||||
|
||||
await this._resolveWebview();
|
||||
if (!this._webview.isResolved()) {
|
||||
await this._resolveWebview();
|
||||
}
|
||||
|
||||
await this._webview?.updateMarkdownPreviewSelectionState(cell.id, isSelected);
|
||||
}
|
||||
|
@ -1744,7 +1891,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
return;
|
||||
}
|
||||
|
||||
await this._resolveWebview();
|
||||
if (!this._webview.isResolved()) {
|
||||
await this._resolveWebview();
|
||||
}
|
||||
|
||||
if (!this._webview!.insetMapping.has(output.source)) {
|
||||
const cellTop = this._list.getAbsoluteTopOfElement(cell);
|
||||
|
@ -1819,7 +1968,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
|
|||
updateMarkdownCellHeight(cellId: string, height: number, isInit: boolean) {
|
||||
const cell = this.getCellById(cellId);
|
||||
if (cell && cell instanceof MarkdownCellViewModel) {
|
||||
cell.renderedMarkdownHeight = height;
|
||||
if (height + BOTTOM_CELL_TOOLBAR_GAP !== cell.layoutInfo.totalHeight) {
|
||||
this._debug('updateMarkdownCellHeight', cell.handle, height + BOTTOM_CELL_TOOLBAR_GAP, isInit);
|
||||
cell.renderedMarkdownHeight = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -203,48 +203,7 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
|
|||
if (bottomModelIndex - topModelIndex === bottomViewIndex - topViewIndex) {
|
||||
this.visibleRanges = [{ start: topModelIndex, end: bottomModelIndex }];
|
||||
} else {
|
||||
let stack: number[] = [];
|
||||
const ranges: ICellRange[] = [];
|
||||
// there are hidden ranges
|
||||
let index = topViewIndex;
|
||||
let modelIndex = topModelIndex;
|
||||
|
||||
while (index <= bottomViewIndex) {
|
||||
const accu = this.hiddenRangesPrefixSum!.getAccumulatedValue(index);
|
||||
if (accu === modelIndex + 1) {
|
||||
// no hidden area after it
|
||||
if (stack.length) {
|
||||
if (stack[stack.length - 1] === modelIndex - 1) {
|
||||
ranges.push({ start: stack[stack.length - 1], end: modelIndex });
|
||||
} else {
|
||||
ranges.push({ start: stack[stack.length - 1], end: stack[stack.length - 1] });
|
||||
}
|
||||
}
|
||||
|
||||
stack.push(modelIndex);
|
||||
index++;
|
||||
modelIndex++;
|
||||
} else {
|
||||
// there are hidden ranges after it
|
||||
if (stack.length) {
|
||||
if (stack[stack.length - 1] === modelIndex - 1) {
|
||||
ranges.push({ start: stack[stack.length - 1], end: modelIndex });
|
||||
} else {
|
||||
ranges.push({ start: stack[stack.length - 1], end: stack[stack.length - 1] });
|
||||
}
|
||||
}
|
||||
|
||||
stack.push(modelIndex);
|
||||
index++;
|
||||
modelIndex = accu;
|
||||
}
|
||||
}
|
||||
|
||||
if (stack.length) {
|
||||
ranges.push({ start: stack[stack.length - 1], end: stack[stack.length - 1] });
|
||||
}
|
||||
|
||||
this.visibleRanges = reduceCellRanges(ranges);
|
||||
this.visibleRanges = this._getVisibleRangesFromIndex(topViewIndex, topModelIndex, bottomViewIndex, bottomModelIndex);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -533,6 +492,72 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
|
|||
}
|
||||
}
|
||||
|
||||
private _getVisibleRangesFromIndex(topViewIndex: number, topModelIndex: number, bottomViewIndex: number, bottomModelIndex: number) {
|
||||
let stack: number[] = [];
|
||||
const ranges: ICellRange[] = [];
|
||||
// there are hidden ranges
|
||||
let index = topViewIndex;
|
||||
let modelIndex = topModelIndex;
|
||||
|
||||
while (index <= bottomViewIndex) {
|
||||
const accu = this.hiddenRangesPrefixSum!.getAccumulatedValue(index);
|
||||
if (accu === modelIndex + 1) {
|
||||
// no hidden area after it
|
||||
if (stack.length) {
|
||||
if (stack[stack.length - 1] === modelIndex - 1) {
|
||||
ranges.push({ start: stack[stack.length - 1], end: modelIndex });
|
||||
} else {
|
||||
ranges.push({ start: stack[stack.length - 1], end: stack[stack.length - 1] });
|
||||
}
|
||||
}
|
||||
|
||||
stack.push(modelIndex);
|
||||
index++;
|
||||
modelIndex++;
|
||||
} else {
|
||||
// there are hidden ranges after it
|
||||
if (stack.length) {
|
||||
if (stack[stack.length - 1] === modelIndex - 1) {
|
||||
ranges.push({ start: stack[stack.length - 1], end: modelIndex });
|
||||
} else {
|
||||
ranges.push({ start: stack[stack.length - 1], end: stack[stack.length - 1] });
|
||||
}
|
||||
}
|
||||
|
||||
stack.push(modelIndex);
|
||||
index++;
|
||||
modelIndex = accu;
|
||||
}
|
||||
}
|
||||
|
||||
if (stack.length) {
|
||||
ranges.push({ start: stack[stack.length - 1], end: stack[stack.length - 1] });
|
||||
}
|
||||
|
||||
return reduceCellRanges(ranges);
|
||||
}
|
||||
|
||||
getVisibleRangesPlusViewportAboveBelow() {
|
||||
if (this.view.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const top = clamp(this.getViewScrollTop() - this.renderHeight, 0, this.scrollHeight);
|
||||
const bottom = clamp(this.getViewScrollBottom() + this.renderHeight, 0, this.scrollHeight);
|
||||
const topViewIndex = clamp(this.view.indexAt(top), 0, this.view.length - 1);
|
||||
const topElement = this.view.element(topViewIndex);
|
||||
const topModelIndex = this._viewModel!.getCellIndex(topElement);
|
||||
const bottomViewIndex = clamp(this.view.indexAt(bottom), 0, this.view.length - 1);
|
||||
const bottomElement = this.view.element(bottomViewIndex);
|
||||
const bottomModelIndex = this._viewModel!.getCellIndex(bottomElement);
|
||||
|
||||
if (bottomModelIndex - topModelIndex === bottomViewIndex - topViewIndex) {
|
||||
return [{ start: topModelIndex, end: bottomModelIndex }];
|
||||
} else {
|
||||
return this._getVisibleRangesFromIndex(topViewIndex, topModelIndex, bottomViewIndex, bottomModelIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private _getViewIndexUpperBound(cell: ICellViewModel): number {
|
||||
if (!this._viewModel) {
|
||||
return -1;
|
||||
|
|
|
@ -236,6 +236,7 @@ export interface ICustomRendererMessage extends BaseToWebviewMessage {
|
|||
export interface ICreateMarkdownMessage {
|
||||
type: 'createMarkdownPreview',
|
||||
id: string;
|
||||
handle: number;
|
||||
content: string;
|
||||
top: number;
|
||||
}
|
||||
|
@ -257,7 +258,8 @@ export interface IUnhideMarkdownMessage {
|
|||
export interface IShowMarkdownMessage {
|
||||
type: 'showMarkdownPreview',
|
||||
id: string;
|
||||
content: string;
|
||||
handle: number;
|
||||
content: string | undefined;
|
||||
top: number;
|
||||
}
|
||||
|
||||
|
@ -269,7 +271,7 @@ export interface IUpdateMarkdownPreviewSelectionState {
|
|||
|
||||
export interface IInitializeMarkdownMessage {
|
||||
type: 'initializeMarkdownPreview';
|
||||
cells: Array<{ cellId: string, content: string }>;
|
||||
cells: Array<{ cellId: string, cellHandle: number, content: string, offset: number }>;
|
||||
}
|
||||
|
||||
export type FromWebviewMessage =
|
||||
|
@ -343,7 +345,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
|
|||
element: HTMLElement;
|
||||
webview: WebviewElement | undefined = undefined;
|
||||
insetMapping: Map<IDisplayOutputViewModel, ICachedInset<T>> = new Map();
|
||||
markdownPreviewMapping: Set<string> = new Set();
|
||||
markdownPreviewMapping = new Map<string, { version: number, visible: boolean }>();
|
||||
hiddenInsetMapping: Set<IDisplayOutputViewModel> = new Set();
|
||||
reversedInsetMapping: Map<string, IDisplayOutputViewModel> = new Map();
|
||||
localResourceRootsCache: URI[] | undefined = undefined;
|
||||
|
@ -363,6 +365,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
|
|||
public options: {
|
||||
outputNodePadding: number,
|
||||
outputNodeLeftPadding: number,
|
||||
previewNodePadding: number,
|
||||
leftMargin: number,
|
||||
cellMargin: number,
|
||||
runGutter: number,
|
||||
|
@ -415,6 +418,8 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
|
|||
color: var(--vscode-foreground);
|
||||
width: calc(100% - ${this.options.cellMargin}px);
|
||||
padding-left: ${this.options.leftMargin}px;
|
||||
padding-top: ${this.options.previewNodePadding}px;
|
||||
padding-bottom: ${this.options.previewNodePadding}px;
|
||||
}
|
||||
|
||||
#container > div > div.preview.selected {
|
||||
|
@ -449,11 +454,20 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
|
|||
}
|
||||
|
||||
#container > div > div.preview h1 {
|
||||
padding-bottom: 0.3em;
|
||||
line-height: 1.2;
|
||||
font-size: 26px;
|
||||
padding-bottom: 8px;
|
||||
line-height: 31px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-color: var(--vscode-foreground);
|
||||
margin: 0;
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
|
||||
#container > div > div.preview h2 {
|
||||
font-size: 19px;
|
||||
margin: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#container > div > div.preview h1,
|
||||
|
@ -485,7 +499,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
|
|||
|
||||
/* makes all markdown cells consistent */
|
||||
#container > div > div.preview div {
|
||||
min-height: 24px;
|
||||
min-height: ${this.options.previewNodePadding * 2}px;
|
||||
}
|
||||
|
||||
#container > div > div.preview table {
|
||||
|
@ -620,7 +634,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
|
|||
<script>${preloadsScriptStr({
|
||||
outputNodePadding: this.options.outputNodePadding,
|
||||
outputNodeLeftPadding: this.options.outputNodeLeftPadding,
|
||||
previewNodePadding: 8,
|
||||
previewNodePadding: this.options.previewNodePadding,
|
||||
leftMargin: this.options.leftMargin
|
||||
})}</script>
|
||||
${markdownRenderersSrc}
|
||||
|
@ -1051,33 +1065,57 @@ var requirejs = (function() {
|
|||
});
|
||||
}
|
||||
|
||||
async createMarkdownPreview(cellId: string, content: string, cellTop: number) {
|
||||
private async createMarkdownPreview(cellId: string, cellHandle: number, content: string, cellTop: number, contentVersion: number) {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.markdownPreviewMapping.has(cellId)) {
|
||||
console.error('Trying to create markdown preview that already exists');
|
||||
return;
|
||||
}
|
||||
|
||||
const initialTop = cellTop;
|
||||
this.markdownPreviewMapping.add(cellId);
|
||||
this.markdownPreviewMapping.set(cellId, { version: contentVersion, visible: true });
|
||||
|
||||
this._sendMessageToWebview({
|
||||
type: 'createMarkdownPreview',
|
||||
id: cellId,
|
||||
handle: cellHandle,
|
||||
content: content,
|
||||
top: initialTop,
|
||||
});
|
||||
}
|
||||
|
||||
async showMarkdownPreview(cellId: string, content: string, cellTop: number) {
|
||||
async showMarkdownPreview(cellId: string, cellHandle: number, content: string, cellTop: number, contentVersion: number) {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._sendMessageToWebview({
|
||||
type: 'showMarkdownPreview',
|
||||
id: cellId,
|
||||
content: content,
|
||||
top: cellTop
|
||||
});
|
||||
if (!this.markdownPreviewMapping.has(cellId)) {
|
||||
return this.createMarkdownPreview(cellId, cellHandle, content, cellTop, contentVersion);
|
||||
}
|
||||
|
||||
const entry = this.markdownPreviewMapping.get(cellId);
|
||||
if (!entry) {
|
||||
console.error('Try to show a preview that does not exist');
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.version !== contentVersion || !entry.visible) {
|
||||
this._sendMessageToWebview({
|
||||
type: 'showMarkdownPreview',
|
||||
id: cellId,
|
||||
handle: cellHandle,
|
||||
// If the content has not changed, we still want to make sure the
|
||||
// preview is visible but don't need to send anything over
|
||||
content: entry.version === contentVersion ? undefined : content,
|
||||
top: cellTop
|
||||
});
|
||||
}
|
||||
|
||||
entry.version = contentVersion;
|
||||
entry.visible = true;
|
||||
}
|
||||
|
||||
async hideMarkdownPreview(cellId: string,) {
|
||||
|
@ -1085,10 +1123,20 @@ var requirejs = (function() {
|
|||
return;
|
||||
}
|
||||
|
||||
this._sendMessageToWebview({
|
||||
type: 'hideMarkdownPreview',
|
||||
id: cellId
|
||||
});
|
||||
const entry = this.markdownPreviewMapping.get(cellId);
|
||||
if (!entry) {
|
||||
// TODO: this currently seems expected on first load
|
||||
// console.error(`Try to hide a preview that does not exist: ${cellId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.visible) {
|
||||
this._sendMessageToWebview({
|
||||
type: 'hideMarkdownPreview',
|
||||
id: cellId
|
||||
});
|
||||
entry.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
async unhideMarkdownPreview(cellId: string,) {
|
||||
|
@ -1096,10 +1144,19 @@ var requirejs = (function() {
|
|||
return;
|
||||
}
|
||||
|
||||
this._sendMessageToWebview({
|
||||
type: 'unhideMarkdownPreview',
|
||||
id: cellId
|
||||
});
|
||||
const entry = this.markdownPreviewMapping.get(cellId);
|
||||
if (!entry) {
|
||||
console.error(`Try to unhide a preview that does not exist: ${cellId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entry.visible) {
|
||||
this._sendMessageToWebview({
|
||||
type: 'unhideMarkdownPreview',
|
||||
id: cellId
|
||||
});
|
||||
entry.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
async removeMarkdownPreview(cellId: string,) {
|
||||
|
@ -1107,6 +1164,11 @@ var requirejs = (function() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!this.markdownPreviewMapping.has(cellId)) {
|
||||
console.error(`Try to delete a preview that does not exist: ${cellId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.markdownPreviewMapping.delete(cellId);
|
||||
|
||||
this._sendMessageToWebview({
|
||||
|
@ -1120,15 +1182,9 @@ var requirejs = (function() {
|
|||
return;
|
||||
}
|
||||
|
||||
this._sendMessageToWebview({
|
||||
type: 'updateMarkdownPreviewSelectionState',
|
||||
id: cellId,
|
||||
isSelected
|
||||
});
|
||||
}
|
||||
|
||||
async updateMarkdownPreviewDecpratopms(cellId: any, isSelected: boolean) {
|
||||
if (this._disposed) {
|
||||
if (!this.markdownPreviewMapping.has(cellId)) {
|
||||
// TODO: this currently seems expected on first load
|
||||
// console.error(`Try to update selection state for preview that does not exist: ${cellId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1139,9 +1195,13 @@ var requirejs = (function() {
|
|||
});
|
||||
}
|
||||
|
||||
async initializeMarkdown(cells: Array<{ cellId: string, content: string }>) {
|
||||
async initializeMarkdown(cells: Array<{ cellId: string, cellHandle: number, content: string, offset: number }>) {
|
||||
await this._loaded;
|
||||
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: use proper handler
|
||||
const p = new Promise<void>(resolve => {
|
||||
this.webview?.onMessage(e => {
|
||||
|
@ -1152,7 +1212,7 @@ var requirejs = (function() {
|
|||
});
|
||||
|
||||
for (const cell of cells) {
|
||||
this.markdownPreviewMapping.add(cell.cellId);
|
||||
this.markdownPreviewMapping.set(cell.cellId, { version: 0, visible: false });
|
||||
}
|
||||
|
||||
this._sendMessageToWebview({
|
||||
|
|
|
@ -144,25 +144,34 @@ function webviewPreloads() {
|
|||
}
|
||||
|
||||
if (entry.target.id === id && entry.contentRect) {
|
||||
if (entry.contentRect.height !== 0) {
|
||||
const padding = output ? __outputNodePadding__ : __previewNodePadding__;
|
||||
|
||||
entry.target.style.padding = `${padding}px ${padding}px ${padding}px ${output ? __outputNodeLeftPadding__ : __leftMargin__}px`;
|
||||
postNotebookMessage<IDimensionMessage>('dimension', {
|
||||
id: id,
|
||||
data: {
|
||||
height: entry.contentRect.height + padding * 2
|
||||
},
|
||||
isOutput: output
|
||||
});
|
||||
if (output) {
|
||||
if (entry.contentRect.height !== 0) {
|
||||
entry.target.style.padding = `${__outputNodePadding__}px ${__outputNodePadding__}px ${__outputNodePadding__}px ${output ? __outputNodeLeftPadding__ : __leftMargin__}px`;
|
||||
postNotebookMessage<IDimensionMessage>('dimension', {
|
||||
id: id,
|
||||
data: {
|
||||
height: entry.contentRect.height + __outputNodePadding__ * 2
|
||||
},
|
||||
isOutput: true
|
||||
});
|
||||
} else {
|
||||
entry.target.style.padding = `0px`;
|
||||
postNotebookMessage<IDimensionMessage>('dimension', {
|
||||
id: id,
|
||||
data: {
|
||||
height: entry.contentRect.height
|
||||
},
|
||||
isOutput: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
entry.target.style.padding = `0px`;
|
||||
postNotebookMessage<IDimensionMessage>('dimension', {
|
||||
id: id,
|
||||
data: {
|
||||
height: entry.contentRect.height
|
||||
// entry.contentRect does not include padding
|
||||
height: entry.contentRect.height + __previewNodePadding__ * 2
|
||||
},
|
||||
isOutput: output
|
||||
isOutput: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -403,7 +412,12 @@ function webviewPreloads() {
|
|||
switch (event.data.type) {
|
||||
case 'initializeMarkdownPreview':
|
||||
for (const cell of event.data.cells) {
|
||||
createMarkdownPreview(cell.cellId, cell.content, -10000);
|
||||
createMarkdownPreview(cell.cellId, cell.content, cell.offset);
|
||||
|
||||
const cellContainer = document.getElementById(cell.cellId);
|
||||
if (cellContainer) {
|
||||
cellContainer.style.visibility = 'hidden';
|
||||
}
|
||||
}
|
||||
|
||||
postNotebookMessage('initializedMarkdownPreview', {});
|
||||
|
@ -414,15 +428,18 @@ function webviewPreloads() {
|
|||
case 'showMarkdownPreview':
|
||||
{
|
||||
const data = event.data;
|
||||
let cellContainer = document.getElementById(data.id);
|
||||
if (cellContainer) {
|
||||
cellContainer.style.display = 'block';
|
||||
}
|
||||
const previewNode = document.getElementById(`${data.id}_container`);
|
||||
if (previewNode) {
|
||||
previewNode.style.top = `${data.top}px`;
|
||||
}
|
||||
updateMarkdownPreview(data.id, data.content);
|
||||
const cellContainer = document.getElementById(data.id);
|
||||
if (cellContainer) {
|
||||
cellContainer.style.visibility = 'visible';
|
||||
}
|
||||
|
||||
if (typeof data.content === 'string') {
|
||||
updateMarkdownPreview(data.id, data.content);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'hideMarkdownPreview':
|
||||
|
@ -430,7 +447,7 @@ function webviewPreloads() {
|
|||
const data = event.data;
|
||||
const cellContainer = document.getElementById(data.id);
|
||||
if (cellContainer) {
|
||||
cellContainer.style.display = 'none';
|
||||
cellContainer.style.visibility = 'hidden';
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -439,7 +456,7 @@ function webviewPreloads() {
|
|||
const data = event.data;
|
||||
const cellContainer = document.getElementById(data.id);
|
||||
if (cellContainer) {
|
||||
cellContainer.style.display = '';
|
||||
cellContainer.style.visibility = 'visible';
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -574,7 +591,7 @@ function webviewPreloads() {
|
|||
case 'view-scroll-markdown':
|
||||
{
|
||||
// const date = new Date();
|
||||
// console.log('----- will scroll ---- ', date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds());
|
||||
// console.log(`${date.getSeconds()}:${date.getMilliseconds().toString().padStart(3, '0')}`, '[iframe]: view-scroll-markdown', event.data.cells);
|
||||
event.data.cells.map(cell => {
|
||||
const widget = document.getElementById(`${cell.id}_preview`)!;
|
||||
|
||||
|
@ -582,6 +599,11 @@ function webviewPreloads() {
|
|||
widget.style.top = `${cell.top}px`;
|
||||
}
|
||||
|
||||
const markdownPreview = document.getElementById(`${cell.id}`);
|
||||
|
||||
if (markdownPreview) {
|
||||
markdownPreview.style.display = 'block';
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
|
@ -698,88 +720,82 @@ function webviewPreloads() {
|
|||
});
|
||||
|
||||
function createMarkdownPreview(cellId: string, content: string, top: number) {
|
||||
let cellContainer = document.getElementById(cellId);
|
||||
if (!cellContainer) {
|
||||
const container = document.getElementById('container')!;
|
||||
const newElement = document.createElement('div');
|
||||
const container = document.getElementById('container')!;
|
||||
const cellContainer = document.createElement('div');
|
||||
|
||||
newElement.id = `${cellId}`;
|
||||
container.appendChild(newElement);
|
||||
cellContainer = newElement;
|
||||
cellContainer.id = `${cellId}`;
|
||||
container.appendChild(cellContainer);
|
||||
|
||||
const previewContainerNode = document.createElement('div');
|
||||
previewContainerNode.style.position = 'absolute';
|
||||
previewContainerNode.style.top = top + 'px';
|
||||
previewContainerNode.id = `${cellId}_preview`;
|
||||
previewContainerNode.classList.add('preview');
|
||||
const previewContainerNode = document.createElement('div');
|
||||
previewContainerNode.style.position = 'absolute';
|
||||
previewContainerNode.style.top = top + 'px';
|
||||
previewContainerNode.id = `${cellId}_preview`;
|
||||
previewContainerNode.classList.add('preview');
|
||||
|
||||
previewContainerNode.addEventListener('dblclick', () => {
|
||||
postNotebookMessage<IToggleMarkdownPreviewMessage>('toggleMarkdownPreview', { cellId });
|
||||
previewContainerNode.addEventListener('dblclick', () => {
|
||||
postNotebookMessage<IToggleMarkdownPreviewMessage>('toggleMarkdownPreview', { cellId });
|
||||
});
|
||||
|
||||
previewContainerNode.addEventListener('click', () => {
|
||||
postNotebookMessage<IFocusMarkdownPreviewMessage>('focusMarkdownPreview', { cellId });
|
||||
});
|
||||
|
||||
previewContainerNode.addEventListener('mouseenter', () => {
|
||||
postNotebookMessage<IMouseEnterMarkdownPreviewMessage>('mouseEnterMarkdownPreview', { cellId });
|
||||
});
|
||||
|
||||
previewContainerNode.addEventListener('mouseleave', () => {
|
||||
postNotebookMessage<IMouseLeaveMarkdownPreviewMessage>('mouseLeaveMarkdownPreview', { cellId });
|
||||
});
|
||||
|
||||
previewContainerNode.setAttribute('draggable', 'true');
|
||||
|
||||
previewContainerNode.addEventListener('dragstart', e => {
|
||||
if (!e.dataTransfer) {
|
||||
return;
|
||||
}
|
||||
e.dataTransfer.setData(markdownCellDragDataType, JSON.stringify({ cellId }));
|
||||
|
||||
(e.target as HTMLElement).classList.add('dragging');
|
||||
|
||||
postNotebookMessage<ICellDragStartMessage>('cell-drag-start', {
|
||||
cellId: cellId,
|
||||
position: { clientX: e.clientX, clientY: e.clientY },
|
||||
});
|
||||
});
|
||||
|
||||
previewContainerNode.addEventListener('click', () => {
|
||||
postNotebookMessage<IFocusMarkdownPreviewMessage>('focusMarkdownPreview', { cellId });
|
||||
previewContainerNode.addEventListener('drag', e => {
|
||||
postNotebookMessage<ICellDragMessage>('cell-drag', {
|
||||
cellId: cellId,
|
||||
position: { clientX: e.clientX, clientY: e.clientY },
|
||||
});
|
||||
});
|
||||
|
||||
previewContainerNode.addEventListener('mouseenter', () => {
|
||||
postNotebookMessage<IMouseEnterMarkdownPreviewMessage>('mouseEnterMarkdownPreview', { cellId });
|
||||
});
|
||||
previewContainerNode.addEventListener('dragend', e => {
|
||||
(e.target as HTMLElement).classList.remove('dragging');
|
||||
});
|
||||
|
||||
previewContainerNode.addEventListener('mouseleave', () => {
|
||||
postNotebookMessage<IMouseLeaveMarkdownPreviewMessage>('mouseLeaveMarkdownPreview', { cellId });
|
||||
});
|
||||
cellContainer.appendChild(previewContainerNode);
|
||||
|
||||
previewContainerNode.setAttribute('draggable', 'true');
|
||||
const previewNode = document.createElement('div');
|
||||
previewContainerNode.appendChild(previewNode);
|
||||
|
||||
previewContainerNode.addEventListener('dragstart', e => {
|
||||
if (!e.dataTransfer) {
|
||||
return;
|
||||
}
|
||||
e.dataTransfer.setData(markdownCellDragDataType, JSON.stringify({ cellId }));
|
||||
// TODO: handle namespace
|
||||
onDidCreateMarkdown.fire([undefined /* data.apiNamespace */, {
|
||||
element: previewNode,
|
||||
content: content
|
||||
}]);
|
||||
|
||||
(e.target as HTMLElement).classList.add('dragging');
|
||||
resizeObserve(previewContainerNode, `${cellId}_preview`, false);
|
||||
|
||||
postNotebookMessage<ICellDragStartMessage>('cell-drag-start', {
|
||||
cellId: cellId,
|
||||
position: { clientX: e.clientX, clientY: e.clientY },
|
||||
});
|
||||
});
|
||||
|
||||
previewContainerNode.addEventListener('drag', e => {
|
||||
postNotebookMessage<ICellDragMessage>('cell-drag', {
|
||||
cellId: cellId,
|
||||
position: { clientX: e.clientX, clientY: e.clientY },
|
||||
});
|
||||
});
|
||||
|
||||
previewContainerNode.addEventListener('dragend', e => {
|
||||
(e.target as HTMLElement).classList.remove('dragging');
|
||||
});
|
||||
|
||||
cellContainer.appendChild(previewContainerNode);
|
||||
|
||||
const previewNode = document.createElement('div');
|
||||
previewContainerNode.appendChild(previewNode);
|
||||
|
||||
// TODO: handle namespace
|
||||
onDidCreateMarkdown.fire([undefined /* data.apiNamespace */, {
|
||||
element: previewNode,
|
||||
content: content
|
||||
}]);
|
||||
|
||||
resizeObserve(previewContainerNode, `${cellId}_preview`, false);
|
||||
|
||||
postNotebookMessage<IDimensionMessage>('dimension', {
|
||||
id: `${cellId}_preview`,
|
||||
init: true,
|
||||
data: {
|
||||
height: previewContainerNode.clientHeight,
|
||||
},
|
||||
isOutput: false
|
||||
});
|
||||
} else {
|
||||
updateMarkdownPreview(cellId, content);
|
||||
}
|
||||
postNotebookMessage<IDimensionMessage>('dimension', {
|
||||
id: `${cellId}_preview`,
|
||||
init: true,
|
||||
data: {
|
||||
height: previewContainerNode.clientHeight,
|
||||
},
|
||||
isOutput: false
|
||||
});
|
||||
}
|
||||
|
||||
function postNotebookMessage<T extends FromWebviewMessage>(
|
||||
|
@ -795,21 +811,23 @@ function webviewPreloads() {
|
|||
|
||||
function updateMarkdownPreview(cellId: string, content: string) {
|
||||
const previewContainerNode = document.getElementById(`${cellId}_preview`);
|
||||
if (previewContainerNode) {
|
||||
// TODO: handle namespace
|
||||
onDidCreateMarkdown.fire([undefined /* data.apiNamespace */, {
|
||||
element: previewContainerNode,
|
||||
content: content
|
||||
}]);
|
||||
|
||||
postNotebookMessage<IDimensionMessage>('dimension', {
|
||||
id: `${cellId}_preview`,
|
||||
data: {
|
||||
height: previewContainerNode.clientHeight,
|
||||
},
|
||||
isOutput: false
|
||||
});
|
||||
if (!previewContainerNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: handle namespace
|
||||
onDidCreateMarkdown.fire([undefined /* data.apiNamespace */, {
|
||||
element: previewContainerNode,
|
||||
content: content
|
||||
}]);
|
||||
|
||||
postNotebookMessage<IDimensionMessage>('dimension', {
|
||||
id: `${cellId}_preview`,
|
||||
data: {
|
||||
height: previewContainerNode.clientHeight,
|
||||
},
|
||||
isOutput: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie
|
|||
private _html: HTMLElement | null = null;
|
||||
private _layoutInfo: MarkdownCellLayoutInfo;
|
||||
|
||||
private _version = 0;
|
||||
|
||||
get layoutInfo() {
|
||||
return this._layoutInfo;
|
||||
}
|
||||
|
@ -81,6 +83,10 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie
|
|||
this._onDidChangeState.fire({ cellIsHoveredChanged: true });
|
||||
}
|
||||
|
||||
public get version(): number {
|
||||
return this._version;
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly viewType: string,
|
||||
readonly model: NotebookCellTextModel,
|
||||
|
@ -158,7 +164,8 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie
|
|||
|
||||
restoreEditorViewState(editorViewStates: editorCommon.ICodeEditorViewState | null, totalHeight?: number) {
|
||||
super.restoreEditorViewState(editorViewStates);
|
||||
if (totalHeight !== undefined) {
|
||||
// we might already warmup the viewport so the cell has a total height computed
|
||||
if (totalHeight !== undefined && this._layoutInfo.totalHeight === 0) {
|
||||
this._layoutInfo = {
|
||||
fontInfo: this._layoutInfo.fontInfo,
|
||||
editorWidth: this._layoutInfo.editorWidth,
|
||||
|
@ -212,9 +219,14 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie
|
|||
if (!this.textModel) {
|
||||
const ref = await this.model.resolveTextModelRef();
|
||||
this.textModel = ref.object.textEditorModel;
|
||||
this._version = this.textModel.getVersionId();
|
||||
|
||||
this._register(ref);
|
||||
this._register(this.textModel.onDidChangeContent(() => {
|
||||
this._html = null;
|
||||
if (this.textModel) {
|
||||
this._version = this.textModel.getVersionId();
|
||||
}
|
||||
this._onDidChangeState.fire({ contentChanged: true });
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -76,8 +76,13 @@ export class TestNotebookEditor implements INotebookEditor {
|
|||
getCellsFromViewRange(startIndex: number, endIndex: number): ICellViewModel[] {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getFocus(): ICellRange | undefined {
|
||||
return undefined;
|
||||
}
|
||||
getVisibleRangesPlusViewportAboveBelow(): ICellRange[] {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getSelection(): ICellRange | undefined {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getSelections(): ICellRange[] {
|
||||
|
@ -141,6 +146,7 @@ export class TestNotebookEditor implements INotebookEditor {
|
|||
}
|
||||
|
||||
multipleKernelsAvailable: boolean = false;
|
||||
onDidScroll: Event<void> = new Emitter<void>().event;
|
||||
onDidChangeAvailableKernels: Event<void> = new Emitter<void>().event;
|
||||
onDidChangeActiveCell: Event<void> = new Emitter<void>().event;
|
||||
onDidChangeVisibleRanges: Event<void> = new Emitter<void>().event;
|
||||
|
|
|
@ -24,6 +24,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
|
|||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { truncate } from 'vs/base/common/strings';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
|
||||
export class RemoteStatusIndicator extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
|
@ -53,7 +54,8 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
|
|||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
|
||||
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@IHostService private readonly hostService: IHostService
|
||||
@IHostService private readonly hostService: IHostService,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService
|
||||
) {
|
||||
super();
|
||||
|
||||
|
@ -200,10 +202,11 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
|
|||
const remoteIndicator = this.environmentService.options?.windowIndicator;
|
||||
if (remoteIndicator) {
|
||||
this.renderRemoteStatusIndicator(truncate(remoteIndicator.label, RemoteStatusIndicator.REMOTE_STATUS_LABEL_MAX_LENGTH), remoteIndicator.tooltip, remoteIndicator.command);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remote Authority: show connection state
|
||||
else if (this.remoteAuthority) {
|
||||
if (this.remoteAuthority) {
|
||||
const hostLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, this.remoteAuthority) || this.remoteAuthority;
|
||||
switch (this.connectionState) {
|
||||
case 'initializing':
|
||||
|
@ -218,13 +221,18 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
|
|||
default:
|
||||
this.renderRemoteStatusIndicator(`$(remote) ${truncate(hostLabel, RemoteStatusIndicator.REMOTE_STATUS_LABEL_MAX_LENGTH)}`, nls.localize('host.tooltip', "Editing on {0}", hostLabel));
|
||||
}
|
||||
return;
|
||||
}
|
||||
// workspace with label
|
||||
const workspaceLabel = this.getWorkspaceLabel();
|
||||
if (workspaceLabel) {
|
||||
this.renderRemoteStatusIndicator(`$(remote) ${truncate(workspaceLabel, RemoteStatusIndicator.REMOTE_STATUS_LABEL_MAX_LENGTH)}`, nls.localize('workspace.tooltip', "Editing on {0}", workspaceLabel));
|
||||
return;
|
||||
}
|
||||
|
||||
// Remote Extensions Installed: offer the indicator to show actions
|
||||
else if (this.remoteMenu.getActions().length > 0) {
|
||||
if (this.remoteMenu.getActions().length > 0) {
|
||||
this.renderRemoteStatusIndicator(`$(remote)`, nls.localize('noHost.tooltip', "Open a Remote Window"));
|
||||
}
|
||||
|
||||
// No Remote Extensions: hide status indicator
|
||||
else {
|
||||
dispose(this.remoteStatusEntry);
|
||||
|
@ -232,6 +240,15 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr
|
|||
}
|
||||
}
|
||||
|
||||
private getWorkspaceLabel() {
|
||||
const workspace = this.workspaceContextService.getWorkspace();
|
||||
const loc = workspace.configuration || workspace.folders.length === 1 ? workspace.folders[0].uri : undefined;
|
||||
if (loc) {
|
||||
return this.labelService.getHostLabel(loc.scheme, loc.authority);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private renderRemoteStatusIndicator(text: string, tooltip?: string, command?: string, showProgress?: boolean): void {
|
||||
const name = nls.localize('remoteHost', "Remote Host");
|
||||
if (typeof command !== 'string' && this.remoteMenu.getActions().length > 0) {
|
||||
|
|
|
@ -184,10 +184,43 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
|
|||
}
|
||||
}
|
||||
},
|
||||
markdownDescription: localize('remote.portsAttributes', "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Labeled Port\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```"),
|
||||
defaultSnippets: [{ body: { '${1:3000}': { label: '${2:My Port}', onAutoForward: 'openPreview' }, 'others': { onAutoForward: 'notify' } } }],
|
||||
markdownDescription: localize('remote.portsAttributes', "Set properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Labeled Port\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```"),
|
||||
defaultSnippets: [{ body: { '${1:3000}': { label: '${2:My Port}', onAutoForward: 'openPreview' } } }],
|
||||
errorMessage: localize('remote.portsAttributes.patternError', "Must be a port number, range of port numbers, or regular expression."),
|
||||
additionalProperties: false
|
||||
},
|
||||
'remote.portsAttributes.defaults': {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'onAutoForward': {
|
||||
type: 'string',
|
||||
enum: ['notify', 'openBrowser', 'openPreview', 'silent', 'ignore'],
|
||||
enumDescriptions: [
|
||||
localize('remote.portsAttributes.notify', "Shows a notification when a port is automatically forwarded."),
|
||||
localize('remote.portsAttributes.openBrowser', "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser."),
|
||||
localize('remote.portsAttributes.openPreview', "Opens a preview in the same window when the port is automatically forwarded."),
|
||||
localize('remote.portsAttributes.silent', "Shows no notification and takes no action when this port is automatically forwarded."),
|
||||
localize('remote.portsAttributes.ignore', "This port will not be automatically forwarded.")
|
||||
],
|
||||
description: localize('remote.portsAttributes.onForward', "Defines the action that occurs when the port is discovered for automatic forwarding"),
|
||||
default: 'notify'
|
||||
},
|
||||
'elevateIfNeeded': {
|
||||
type: 'boolean',
|
||||
description: localize('remote.portsAttributes.elevateIfNeeded', "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port."),
|
||||
default: false
|
||||
},
|
||||
'label': {
|
||||
type: 'string',
|
||||
description: localize('remote.portsAttributes.label', "Label that will be shown in the UI for this port."),
|
||||
default: localize('remote.portsAttributes.labelDefault', "Labeled Port")
|
||||
}
|
||||
},
|
||||
default: {
|
||||
'onAutoForward': 'notify'
|
||||
},
|
||||
markdownDescription: localize('remote.portsAttributes.defaults', "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"notify\"\n}\n```"),
|
||||
additionalProperties: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -715,7 +715,7 @@ export class SearchView extends ViewPane {
|
|||
accessibilityProvider: this.treeAccessibilityProvider,
|
||||
dnd: this.instantiationService.createInstance(SearchDND),
|
||||
multipleSelectionSupport: false,
|
||||
openOnFocus: true,
|
||||
selectionNavigation: true,
|
||||
overrideStyles: {
|
||||
listBackground: this.getBackgroundColor()
|
||||
}
|
||||
|
|
|
@ -76,6 +76,12 @@
|
|||
outline-offset: 2px !important;
|
||||
}
|
||||
|
||||
.workspace-trust-editor.settings-editor .workspace-trust-section-title {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
/** Features List */
|
||||
.workspace-trust-editor.settings-editor > .workspace-trust-features {
|
||||
padding: 14px;
|
||||
|
@ -83,12 +89,21 @@
|
|||
user-select: text;
|
||||
}
|
||||
|
||||
.workspace-trust-editor.settings-editor > .workspace-trust-features .workspace-trust-extensions-list {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.workspace-trust-editor.settings-editor > .workspace-trust-features .workspace-trust-extension-list-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
|
||||
.workspace-trust-editor.settings-editor > .workspace-trust-features .workspace-trust-extension-list-description {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.workspace-trust-editor.settings-editor > .workspace-trust-features .workspace-trust-extension-list-entry {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
|
@ -110,6 +125,10 @@
|
|||
}
|
||||
|
||||
/** Settings */
|
||||
.workspace-trust-editor.settings-editor .workspace-trust-settings .workspace-trust-section-title {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.workspace-trust-editor.settings-editor > .settings-body > .settings-tree-container .shadow.top {
|
||||
left: initial;
|
||||
margin-left: initial;
|
||||
|
|
|
@ -5,18 +5,17 @@
|
|||
|
||||
import { $, append, clearNode, Dimension, EventHelper } from 'vs/base/browser/dom';
|
||||
import { ButtonBar } from 'vs/base/browser/ui/button/button';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { parseLinkedText } from 'vs/base/common/linkedText';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { isArray } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { DefaultIconPath } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionWorkspaceTrustRequirement } from 'vs/platform/extensions/common/extensions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Link } from 'vs/platform/opener/browser/link';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
|
@ -27,9 +26,11 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
|
|||
import { WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust';
|
||||
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
|
||||
import { EditorOptions, IEditorOpenContext } from 'vs/workbench/common/editor';
|
||||
import { ExtensionsGridView, getExtensions } from 'vs/workbench/contrib/extensions/browser/extensionsViewer';
|
||||
import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { getInstalledExtensions, IExtensionStatus } from 'vs/workbench/contrib/extensions/common/extensionsUtils';
|
||||
import { trustedForegroundColor, untrustedForegroundColor } from 'vs/workbench/contrib/workspace/browser/workspaceTrustColors';
|
||||
import { IWorkspaceTrustSettingChangeEvent, WorkspaceTrustSettingArrayRenderer, WorkspaceTrustTree, WorkspaceTrustTreeModel } from 'vs/workbench/contrib/workspace/browser/workspaceTrustTree';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { WorkspaceTrustEditorInput } from 'vs/workbench/services/workspaces/browser/workspaceTrustEditorInput';
|
||||
import { WorkspaceTrustEditorModel } from 'vs/workbench/services/workspaces/common/workspaceTrust';
|
||||
|
||||
|
@ -54,7 +55,6 @@ export class WorkspaceTrustEditor extends EditorPane {
|
|||
private extensionsContainer!: HTMLElement;
|
||||
private onDemandExtensionsContainer!: HTMLElement;
|
||||
private onStartExtensionsContainer!: HTMLElement;
|
||||
private extensionsRequiringTrust: IExtensionDescription[] = [];
|
||||
|
||||
// Settings Section
|
||||
private configurationContainer!: HTMLElement;
|
||||
|
@ -68,7 +68,7 @@ export class WorkspaceTrustEditor extends EditorPane {
|
|||
@IThemeService themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) { super(WorkspaceTrustEditor.ID, telemetryService, themeService, storageService); }
|
||||
|
||||
|
@ -95,17 +95,23 @@ export class WorkspaceTrustEditor extends EditorPane {
|
|||
return;
|
||||
}
|
||||
|
||||
this._register(model.dataModel.onDidChangeTrustState(() => {
|
||||
this.render(model);
|
||||
}));
|
||||
|
||||
this.extensionsRequiringTrust = (await this.extensionService.getExtensions()).filter(ext => ext.requiresWorkspaceTrust);
|
||||
this.registerListeners(model);
|
||||
|
||||
this.render(model);
|
||||
|
||||
this.workspaceTrustEditorModel = model;
|
||||
}
|
||||
|
||||
private registerListeners(model: WorkspaceTrustEditorModel): void {
|
||||
this._register(model.dataModel.onDidChangeTrustState(() => {
|
||||
this.render(model);
|
||||
}));
|
||||
|
||||
this._register(this.extensionWorkbenchService.onChange(() => {
|
||||
this.render(model);
|
||||
}));
|
||||
}
|
||||
|
||||
private getHeaderContainerClass(trustState: WorkspaceTrustState): string {
|
||||
switch (trustState) {
|
||||
case WorkspaceTrustState.Trusted:
|
||||
|
@ -149,7 +155,7 @@ export class WorkspaceTrustEditor extends EditorPane {
|
|||
}
|
||||
|
||||
private rerenderDisposables: DisposableStore = this._register(new DisposableStore());
|
||||
private render(model: WorkspaceTrustEditorModel): void {
|
||||
private async render(model: WorkspaceTrustEditorModel) {
|
||||
this.rerenderDisposables.clear();
|
||||
|
||||
// Header Section
|
||||
|
@ -202,50 +208,56 @@ export class WorkspaceTrustEditor extends EditorPane {
|
|||
createButton(localize('doNotTrustButton', "Don't Trust"), model.currentWorkspaceTrustState !== WorkspaceTrustState.Untrusted, () => setTrustState(WorkspaceTrustState.Untrusted));
|
||||
|
||||
// Features List
|
||||
const installedExtensions = await this.instantiationService.invokeFunction(getInstalledExtensions);
|
||||
const onDemandExtensions = await this.getExtensionsByTrustRequirement(installedExtensions, 'onDemand');
|
||||
const onStartExtensions = await this.getExtensionsByTrustRequirement(installedExtensions, 'onStart');
|
||||
|
||||
this.renderExtensionList(
|
||||
localize('onStartExtensions', "Disabled Extensions"),
|
||||
localize('onStartExtensionsDescription', "The following extensions require the workspace to be trusted. They will be disabled while the workspace is not trusted."),
|
||||
this.onStartExtensionsContainer,
|
||||
this.extensionsRequiringTrust.filter(ext => ext.requiresWorkspaceTrust === 'onStart'));
|
||||
onStartExtensions);
|
||||
this.renderExtensionList(
|
||||
localize('onDemandExtensions', "Limited Extensions"),
|
||||
localize('onDemandExtensionsDescription', "The following extensions can function partially in a non-trusted workspace. Some functionality will be turned off while the workspace is not trusted."),
|
||||
this.onDemandExtensionsContainer,
|
||||
this.extensionsRequiringTrust.filter(ext => ext.requiresWorkspaceTrust === 'onDemand'));
|
||||
onDemandExtensions);
|
||||
|
||||
// Configuration Tree
|
||||
this.workspaceTrustSettingsTreeModel.update(model.dataModel.getTrustStateInfo());
|
||||
this.trustSettingsTree.setChildren(null, Iterable.map(this.workspaceTrustSettingsTreeModel.settings, s => { return { element: s }; }));
|
||||
}
|
||||
|
||||
private renderExtensionList(title: string, parent: HTMLElement, extensions: IExtensionDescription[]) {
|
||||
private async getExtensionsByTrustRequirement(extensions: IExtensionStatus[], trustRequirement: ExtensionWorkspaceTrustRequirement): Promise<IExtension[]> {
|
||||
const filtered = extensions.filter(ext => ext.local.manifest.requiresWorkspaceTrust === trustRequirement);
|
||||
const ids = filtered.map(ext => ext.identifier.id);
|
||||
|
||||
return getExtensions(ids, this.extensionWorkbenchService);
|
||||
}
|
||||
|
||||
private renderExtensionList(title: string, description: string, parent: HTMLElement, extensions: IExtension[]) {
|
||||
clearNode(parent);
|
||||
|
||||
if (!extensions.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const titleElement = append(parent, $('.workspace-trust-extension-list-title'));
|
||||
titleElement.innerText = title;
|
||||
|
||||
const listContainer = append(parent, $('.workspace-trust-extension-list'));
|
||||
extensions.forEach(ext => {
|
||||
const extensionEntry = append(listContainer, $('.workspace-trust-extension-list-entry'));
|
||||
this.renderExtension(extensionEntry, ext);
|
||||
});
|
||||
}
|
||||
const descriptionElement = append(parent, $('.workspace-trust-extension-list-description'));
|
||||
descriptionElement.innerText = description;
|
||||
|
||||
private renderExtension(parent: HTMLElement, extension: IExtensionDescription) {
|
||||
const iconContainer = append(parent, $('.workspace-trust-extension-icon'));
|
||||
const icon = append(iconContainer, $<HTMLImageElement>('img.icon'));
|
||||
const textContainer = append(parent, $('.workspace-trust-extension-text'));
|
||||
const nameContainer = append(textContainer, $('.workspace-trust-extension-name'));
|
||||
const content = $('div', { class: 'subcontent' });
|
||||
const scrollableContent = new DomScrollableElement(content, { useShadows: false });
|
||||
append(parent, scrollableContent.getDomNode());
|
||||
|
||||
const extensionDescription = append(textContainer, $('.workspace-trust-extension-description'));
|
||||
const extensionsGridView = this.instantiationService.createInstance(ExtensionsGridView, content);
|
||||
extensionsGridView.setExtensions(extensions);
|
||||
scrollableContent.scanDomNode();
|
||||
|
||||
nameContainer.innerText = extension.displayName || extension.name;
|
||||
icon.src = extension.icon
|
||||
? FileAccess.asBrowserUri(joinPath(extension.extensionLocation, extension.icon)).toString(true)
|
||||
: DefaultIconPath;
|
||||
extensionDescription.innerText = extension.description || '';
|
||||
this.rerenderDisposables.add(scrollableContent);
|
||||
this.rerenderDisposables.add(extensionsGridView);
|
||||
this.rerenderDisposables.add(toDisposable(arrays.insert(this.layoutParticipants, { layout: () => scrollableContent.scanDomNode() })));
|
||||
}
|
||||
|
||||
private createHeaderElement(parent: HTMLElement): void {
|
||||
|
@ -262,6 +274,9 @@ export class WorkspaceTrustEditor extends EditorPane {
|
|||
private createConfigurationElement(parent: HTMLElement): void {
|
||||
this.configurationContainer = append(parent, $('.workspace-trust-settings.settings-body'));
|
||||
|
||||
const titleContainer = append(this.configurationContainer, $('.workspace-trust-section-title'));
|
||||
titleContainer.innerText = localize('configurationSectionTitle', "Configure All Workspaces");
|
||||
|
||||
const workspaceTrustTreeContainer = append(this.configurationContainer, $('.workspace-trust-settings-tree-container.settings-tree-container'));
|
||||
const renderer = this.instantiationService.createInstance(WorkspaceTrustSettingArrayRenderer,);
|
||||
|
||||
|
@ -276,9 +291,12 @@ export class WorkspaceTrustEditor extends EditorPane {
|
|||
|
||||
private createAffectedFeaturesElement(parent: HTMLElement): void {
|
||||
this.affectedFeaturesContainer = append(parent, $('.workspace-trust-features'));
|
||||
const titleContainer = append(this.affectedFeaturesContainer, $('.workspace-trust-section-title'));
|
||||
titleContainer.innerText = localize('affectedFeaturesTitle', "Features Affected By Workspace Trust");
|
||||
|
||||
this.extensionsContainer = append(this.affectedFeaturesContainer, $('.workspace-trust-extensions'));
|
||||
this.onDemandExtensionsContainer = append(this.extensionsContainer, $('.workspace-trust-extensions-on-demand'));
|
||||
this.onStartExtensionsContainer = append(this.extensionsContainer, $('.workspace-trust-extensions-on-start'));
|
||||
this.onDemandExtensionsContainer = append(this.extensionsContainer, $('.workspace-trust-extensions-list'));
|
||||
this.onStartExtensionsContainer = append(this.extensionsContainer, $('.workspace-trust-extensions-list'));
|
||||
}
|
||||
|
||||
private onDidChangeSetting(change: IWorkspaceTrustSettingChangeEvent) {
|
||||
|
@ -295,6 +313,7 @@ export class WorkspaceTrustEditor extends EditorPane {
|
|||
}
|
||||
}
|
||||
|
||||
private layoutParticipants: { layout: () => void; }[] = [];
|
||||
layout(dimension: Dimension): void {
|
||||
if (!this.isVisible()) {
|
||||
return;
|
||||
|
@ -304,5 +323,9 @@ export class WorkspaceTrustEditor extends EditorPane {
|
|||
this.configurationContainer.style.height = `${listHeight}`;
|
||||
|
||||
this.trustSettingsTree.layout(listHeight, dimension.width);
|
||||
|
||||
this.layoutParticipants.forEach(participant => {
|
||||
participant.layout();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,346 +5,50 @@
|
|||
|
||||
import * as fs from 'fs';
|
||||
import { gracefulify } from 'graceful-fs';
|
||||
import { zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows';
|
||||
import { mark } from 'vs/base/common/performance';
|
||||
import { Workbench } from 'vs/workbench/browser/workbench';
|
||||
import { NativeWindow } from 'vs/workbench/electron-sandbox/window';
|
||||
import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser';
|
||||
import { domContentLoaded } from 'vs/base/browser/dom';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
|
||||
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
|
||||
import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceInitializationPayload, reviveIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { ILoggerService, ILogService } from 'vs/platform/log/common/log';
|
||||
import { NativeStorageService } from 'vs/platform/storage/electron-sandbox/storageService';
|
||||
import { INativeWorkbenchConfiguration } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver';
|
||||
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
|
||||
import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentServiceImpl';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider';
|
||||
import { RemoteFileSystemProvider } from 'vs/workbench/services/remote/common/remoteAgentFileSystemChannel';
|
||||
import { ConfigurationCache } from 'vs/workbench/services/configuration/electron-sandbox/configurationCache';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { NativeLogService } from 'vs/workbench/services/log/electron-sandbox/logService';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService';
|
||||
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
|
||||
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
|
||||
import { KeyboardLayoutService } from 'vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout';
|
||||
import { IKeyboardLayoutService } from 'vs/platform/keyboardLayout/common/keyboardLayout';
|
||||
import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
||||
import { LoggerChannelClient } from 'vs/platform/log/common/logIpc';
|
||||
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { productService, SharedDesktopMain } from 'vs/workbench/electron-sandbox/shared.desktop.main';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver';
|
||||
|
||||
class DesktopMain extends Disposable {
|
||||
class DesktopMain extends SharedDesktopMain {
|
||||
|
||||
private readonly productService: IProductService = { _serviceBrand: undefined, ...product };
|
||||
private readonly environmentService = new NativeWorkbenchEnvironmentService(this.configuration, this.productService);
|
||||
|
||||
constructor(private configuration: INativeWorkbenchConfiguration) {
|
||||
super();
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
private init(): void {
|
||||
constructor(configuration: INativeWorkbenchConfiguration) {
|
||||
super(configuration, new NativeWorkbenchEnvironmentService(configuration, productService));
|
||||
|
||||
// Enable gracefulFs
|
||||
gracefulify(fs);
|
||||
|
||||
// Massage configuration file URIs
|
||||
this.reviveUris();
|
||||
|
||||
// Browser config
|
||||
const zoomLevel = this.configuration.zoomLevel || 0;
|
||||
setZoomFactor(zoomLevelToZoomFactor(zoomLevel));
|
||||
setZoomLevel(zoomLevel, true /* isTrusted */);
|
||||
setFullscreen(!!this.configuration.fullscreen);
|
||||
}
|
||||
|
||||
private reviveUris() {
|
||||
|
||||
// Workspace
|
||||
const workspace = reviveIdentifier(this.configuration.workspace);
|
||||
if (isWorkspaceIdentifier(workspace) || isSingleFolderWorkspaceIdentifier(workspace)) {
|
||||
this.configuration.workspace = workspace;
|
||||
}
|
||||
|
||||
// Files
|
||||
const filesToWait = this.configuration.filesToWait;
|
||||
const filesToWaitPaths = filesToWait?.paths;
|
||||
[filesToWaitPaths, this.configuration.filesToOpenOrCreate, this.configuration.filesToDiff].forEach(paths => {
|
||||
if (Array.isArray(paths)) {
|
||||
paths.forEach(path => {
|
||||
if (path.fileUri) {
|
||||
path.fileUri = URI.revive(path.fileUri);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (filesToWait) {
|
||||
filesToWait.waitMarkerFileUri = URI.revive(filesToWait.waitMarkerFileUri);
|
||||
}
|
||||
}
|
||||
|
||||
async open(): Promise<void> {
|
||||
const services = await this.initServices();
|
||||
|
||||
await domContentLoaded();
|
||||
mark('code/willStartWorkbench');
|
||||
|
||||
// Create Workbench
|
||||
const workbench = new Workbench(document.body, services.serviceCollection, services.logService);
|
||||
|
||||
// Listeners
|
||||
this.registerListeners(workbench, services.storageService);
|
||||
|
||||
// Startup
|
||||
const instantiationService = workbench.startup();
|
||||
|
||||
// Window
|
||||
this._register(instantiationService.createInstance(NativeWindow));
|
||||
protected joinOpen(instantiationService: IInstantiationService): void {
|
||||
|
||||
// Driver
|
||||
if (this.configuration.driver) {
|
||||
instantiationService.invokeFunction(async accessor => this._register(await registerWindowDriver(accessor, this.configuration.windowId)));
|
||||
}
|
||||
|
||||
// Logging
|
||||
services.logService.trace('workbench configuration', JSON.stringify(this.configuration));
|
||||
}
|
||||
|
||||
private registerListeners(workbench: Workbench, storageService: NativeStorageService): void {
|
||||
|
||||
// Workbench Lifecycle
|
||||
this._register(workbench.onWillShutdown(event => event.join(storageService.close(), 'join.closeStorage')));
|
||||
this._register(workbench.onShutdown(() => this.dispose()));
|
||||
protected createLogService(loggerService: LoggerChannelClient, mainProcessService: IMainProcessService): ILogService {
|
||||
return this._register(new NativeLogService(`renderer${this.configuration.windowId}`, loggerService, mainProcessService, this.environmentService));
|
||||
}
|
||||
|
||||
private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: NativeStorageService }> {
|
||||
const serviceCollection = new ServiceCollection();
|
||||
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
//
|
||||
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
||||
// from `workbench.common.main.ts` if the service is shared between
|
||||
// desktop and web or `workbench.sandbox.main.ts` if the service
|
||||
// is desktop only.
|
||||
//
|
||||
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
||||
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
||||
//
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
|
||||
// Main Process
|
||||
const mainProcessService = this._register(new ElectronIPCMainProcessService(this.configuration.windowId));
|
||||
serviceCollection.set(IMainProcessService, mainProcessService);
|
||||
|
||||
// Environment
|
||||
serviceCollection.set(INativeWorkbenchEnvironmentService, this.environmentService);
|
||||
|
||||
// Product
|
||||
serviceCollection.set(IProductService, this.productService);
|
||||
|
||||
// Logger
|
||||
const loggerService = new LoggerChannelClient(mainProcessService.getChannel('logger'));
|
||||
serviceCollection.set(ILoggerService, loggerService);
|
||||
|
||||
// Log
|
||||
const logService = this._register(new NativeLogService(`renderer${this.configuration.windowId}`, loggerService, mainProcessService, this.environmentService));
|
||||
serviceCollection.set(ILogService, logService);
|
||||
|
||||
// Remote
|
||||
const remoteAuthorityResolverService = new RemoteAuthorityResolverService();
|
||||
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
|
||||
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
//
|
||||
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
||||
// from `workbench.common.main.ts` if the service is shared between
|
||||
// desktop and web or `workbench.sandbox.main.ts` if the service
|
||||
// is desktop only.
|
||||
//
|
||||
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
||||
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
||||
//
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
|
||||
// Sign
|
||||
const signService = ProxyChannel.toService<ISignService>(mainProcessService.getChannel('sign'));
|
||||
serviceCollection.set(ISignService, signService);
|
||||
|
||||
// Remote Agent
|
||||
const remoteAgentService = this._register(new RemoteAgentService(this.environmentService, this.productService, remoteAuthorityResolverService, signService, logService));
|
||||
serviceCollection.set(IRemoteAgentService, remoteAgentService);
|
||||
|
||||
// Native Host
|
||||
const nativeHostService = new NativeHostService(this.configuration.windowId, mainProcessService) as INativeHostService;
|
||||
serviceCollection.set(INativeHostService, nativeHostService);
|
||||
|
||||
// Files
|
||||
const fileService = this._register(new FileService(logService));
|
||||
serviceCollection.set(IFileService, fileService);
|
||||
protected registerFileSystemProviders(fileService: IFileService, logService: ILogService, nativeHostService: INativeHostService): void {
|
||||
|
||||
// Local Files
|
||||
const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService, nativeHostService));
|
||||
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
|
||||
|
||||
// User Data Provider
|
||||
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, logService));
|
||||
|
||||
// Uri Identity
|
||||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
serviceCollection.set(IUriIdentityService, uriIdentityService);
|
||||
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
//
|
||||
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
||||
// from `workbench.common.main.ts` if the service is shared between
|
||||
// desktop and web or `workbench.sandbox.main.ts` if the service
|
||||
// is desktop only.
|
||||
//
|
||||
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
||||
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
||||
//
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
|
||||
const connection = remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
const remoteFileSystemProvider = this._register(new RemoteFileSystemProvider(remoteAgentService));
|
||||
fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider);
|
||||
}
|
||||
|
||||
const payload = this.resolveWorkspaceInitializationPayload();
|
||||
|
||||
const services = await Promise.all([
|
||||
this.createWorkspaceService(payload, fileService, remoteAgentService, uriIdentityService, logService).then(service => {
|
||||
|
||||
// Workspace
|
||||
serviceCollection.set(IWorkspaceContextService, service);
|
||||
|
||||
// Configuration
|
||||
serviceCollection.set(IWorkbenchConfigurationService, service);
|
||||
|
||||
return service;
|
||||
}),
|
||||
|
||||
this.createStorageService(payload, mainProcessService).then(service => {
|
||||
|
||||
// Storage
|
||||
serviceCollection.set(IStorageService, service);
|
||||
|
||||
return service;
|
||||
}),
|
||||
|
||||
this.createKeyboardLayoutService(mainProcessService).then(service => {
|
||||
|
||||
// KeyboardLayout
|
||||
serviceCollection.set(IKeyboardLayoutService, service);
|
||||
|
||||
return service;
|
||||
})
|
||||
]);
|
||||
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
//
|
||||
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
||||
// from `workbench.common.main.ts` if the service is shared between
|
||||
// desktop and web or `workbench.sandbox.main.ts` if the service
|
||||
// is desktop only.
|
||||
//
|
||||
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
||||
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
||||
//
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
|
||||
return { serviceCollection, logService, storageService: services[1] };
|
||||
}
|
||||
|
||||
private resolveWorkspaceInitializationPayload(): IWorkspaceInitializationPayload {
|
||||
let workspaceInitializationPayload: IWorkspaceInitializationPayload | undefined = this.configuration.workspace;
|
||||
|
||||
// Fallback to empty workspace if we have no payload yet.
|
||||
if (!workspaceInitializationPayload) {
|
||||
let id: string;
|
||||
if (this.configuration.backupPath) {
|
||||
id = basename(this.configuration.backupPath); // we know the backupPath must be a unique path so we leverage its name as workspace ID
|
||||
} else if (this.environmentService.isExtensionDevelopment) {
|
||||
id = 'ext-dev'; // extension development window never stores backups and is a singleton
|
||||
} else {
|
||||
throw new Error('Unexpected window configuration without backupPath');
|
||||
}
|
||||
|
||||
workspaceInitializationPayload = { id };
|
||||
}
|
||||
|
||||
return workspaceInitializationPayload;
|
||||
}
|
||||
|
||||
private async createWorkspaceService(payload: IWorkspaceInitializationPayload, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise<WorkspaceService> {
|
||||
const workspaceService = new WorkspaceService({ remoteAuthority: this.environmentService.remoteAuthority, configurationCache: new ConfigurationCache(URI.file(this.environmentService.userDataPath), fileService) }, this.environmentService, fileService, remoteAgentService, uriIdentityService, logService);
|
||||
|
||||
try {
|
||||
await workspaceService.initialize(payload);
|
||||
|
||||
return workspaceService;
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
|
||||
return workspaceService;
|
||||
}
|
||||
}
|
||||
|
||||
private async createStorageService(payload: IWorkspaceInitializationPayload, mainProcessService: IMainProcessService): Promise<NativeStorageService> {
|
||||
const storageService = new NativeStorageService(payload, mainProcessService, this.environmentService);
|
||||
|
||||
try {
|
||||
await storageService.initialize();
|
||||
|
||||
return storageService;
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
|
||||
return storageService;
|
||||
}
|
||||
}
|
||||
|
||||
private async createKeyboardLayoutService(mainProcessService: IMainProcessService): Promise<KeyboardLayoutService> {
|
||||
const keyboardLayoutService = new KeyboardLayoutService(mainProcessService);
|
||||
|
||||
try {
|
||||
await keyboardLayoutService.initialize();
|
||||
|
||||
return keyboardLayoutService;
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
|
||||
return keyboardLayoutService;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,322 +3,34 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows';
|
||||
import { mark } from 'vs/base/common/performance';
|
||||
import { Workbench } from 'vs/workbench/browser/workbench';
|
||||
import { NativeWindow } from 'vs/workbench/electron-sandbox/window';
|
||||
import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser';
|
||||
import { domContentLoaded } from 'vs/base/browser/dom';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceInitializationPayload, reviveIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { ILoggerService, ILogService } from 'vs/platform/log/common/log';
|
||||
import { NativeStorageService } from 'vs/platform/storage/electron-sandbox/storageService';
|
||||
import { INativeWorkbenchConfiguration } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
|
||||
import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentServiceImpl';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { RemoteFileSystemProvider } from 'vs/workbench/services/remote/common/remoteAgentFileSystemChannel';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService';
|
||||
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
|
||||
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
|
||||
import { KeyboardLayoutService } from 'vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout';
|
||||
import { IKeyboardLayoutService } from 'vs/platform/keyboardLayout/common/keyboardLayout';
|
||||
import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
||||
import { SimpleConfigurationService, simpleFileSystemProvider, SimpleNativeWorkbenchEnvironmentService, SimpleWorkspaceService, SimpleLogService } from 'vs/workbench/electron-sandbox/sandbox.simpleservices';
|
||||
import { simpleFileSystemProvider, SimpleNativeWorkbenchEnvironmentService, SimpleLogService, simpleWorkspace } from 'vs/workbench/electron-sandbox/sandbox.simpleservices';
|
||||
import { LoggerChannelClient } from 'vs/platform/log/common/logIpc';
|
||||
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { SharedDesktopMain } from 'vs/workbench/electron-sandbox/shared.desktop.main';
|
||||
|
||||
class DesktopMain extends Disposable {
|
||||
class DesktopMain extends SharedDesktopMain {
|
||||
|
||||
private readonly productService: IProductService = { _serviceBrand: undefined, ...product };
|
||||
private readonly environmentService = new SimpleNativeWorkbenchEnvironmentService(this.configuration);
|
||||
|
||||
constructor(private configuration: INativeWorkbenchConfiguration) {
|
||||
super();
|
||||
|
||||
this.init();
|
||||
constructor(configuration: INativeWorkbenchConfiguration) {
|
||||
super({ ...configuration, workspace: simpleWorkspace }, new SimpleNativeWorkbenchEnvironmentService(configuration));
|
||||
}
|
||||
|
||||
private init(): void {
|
||||
|
||||
// Massage configuration file URIs
|
||||
this.reviveUris();
|
||||
|
||||
// Browser config
|
||||
const zoomLevel = this.configuration.zoomLevel || 0;
|
||||
setZoomFactor(zoomLevelToZoomFactor(zoomLevel));
|
||||
setZoomLevel(zoomLevel, true /* isTrusted */);
|
||||
setFullscreen(!!this.configuration.fullscreen);
|
||||
protected createLogService(loggerService: LoggerChannelClient, mainProcessService: IMainProcessService): ILogService {
|
||||
return new SimpleLogService(); // we can only use the real logger, once `IEnvironmentService#logFile` has a proper file:// based value (https://github.com/microsoft/vscode/issues/116829)
|
||||
}
|
||||
|
||||
private reviveUris() {
|
||||
|
||||
// Workspace
|
||||
const workspace = reviveIdentifier(this.configuration.workspace);
|
||||
if (isWorkspaceIdentifier(workspace) || isSingleFolderWorkspaceIdentifier(workspace)) {
|
||||
this.configuration.workspace = workspace;
|
||||
}
|
||||
|
||||
// Files
|
||||
const filesToWait = this.configuration.filesToWait;
|
||||
const filesToWaitPaths = filesToWait?.paths;
|
||||
[filesToWaitPaths, this.configuration.filesToOpenOrCreate, this.configuration.filesToDiff].forEach(paths => {
|
||||
if (Array.isArray(paths)) {
|
||||
paths.forEach(path => {
|
||||
if (path.fileUri) {
|
||||
path.fileUri = URI.revive(path.fileUri);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (filesToWait) {
|
||||
filesToWait.waitMarkerFileUri = URI.revive(filesToWait.waitMarkerFileUri);
|
||||
}
|
||||
}
|
||||
|
||||
async open(): Promise<void> {
|
||||
const services = await this.initServices();
|
||||
|
||||
await domContentLoaded();
|
||||
mark('code/willStartWorkbench');
|
||||
|
||||
// Create Workbench
|
||||
const workbench = new Workbench(document.body, services.serviceCollection, services.logService);
|
||||
|
||||
// Listeners
|
||||
this.registerListeners(workbench, services.storageService);
|
||||
|
||||
// Startup
|
||||
const instantiationService = workbench.startup();
|
||||
|
||||
// Window
|
||||
this._register(instantiationService.createInstance(NativeWindow));
|
||||
|
||||
// Logging
|
||||
services.logService.trace('workbench configuration', JSON.stringify(this.configuration));
|
||||
}
|
||||
|
||||
private registerListeners(workbench: Workbench, storageService: NativeStorageService): void {
|
||||
|
||||
// Workbench Lifecycle
|
||||
this._register(workbench.onWillShutdown(event => event.join(storageService.close(), 'join.closeStorage')));
|
||||
this._register(workbench.onShutdown(() => this.dispose()));
|
||||
}
|
||||
|
||||
private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: NativeStorageService }> {
|
||||
const serviceCollection = new ServiceCollection();
|
||||
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
//
|
||||
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
||||
// from `workbench.common.main.ts` if the service is shared between
|
||||
// desktop and web or `workbench.sandbox.main.ts` if the service
|
||||
// is desktop only.
|
||||
//
|
||||
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
||||
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
||||
//
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
|
||||
// Main Process
|
||||
const mainProcessService = this._register(new ElectronIPCMainProcessService(this.configuration.windowId));
|
||||
serviceCollection.set(IMainProcessService, mainProcessService);
|
||||
|
||||
// Environment
|
||||
serviceCollection.set(INativeWorkbenchEnvironmentService, this.environmentService);
|
||||
|
||||
// Product
|
||||
serviceCollection.set(IProductService, this.productService);
|
||||
|
||||
// Logger
|
||||
const loggerService = new LoggerChannelClient(mainProcessService.getChannel('logger'));
|
||||
serviceCollection.set(ILoggerService, loggerService);
|
||||
|
||||
// Log (we can only use the real logger, once `IEnvironmentService#logFile` has a proper file:// based value (https://github.com/microsoft/vscode/issues/116829))
|
||||
const logService = new SimpleLogService();
|
||||
serviceCollection.set(ILogService, logService);
|
||||
|
||||
// Remote
|
||||
const remoteAuthorityResolverService = new RemoteAuthorityResolverService();
|
||||
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
|
||||
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
//
|
||||
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
||||
// from `workbench.common.main.ts` if the service is shared between
|
||||
// desktop and web or `workbench.sandbox.main.ts` if the service
|
||||
// is desktop only.
|
||||
//
|
||||
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
||||
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
||||
//
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
|
||||
// Sign
|
||||
const signService = ProxyChannel.toService<ISignService>(mainProcessService.getChannel('sign'));
|
||||
serviceCollection.set(ISignService, signService);
|
||||
|
||||
// Remote Agent
|
||||
const remoteAgentService = this._register(new RemoteAgentService(this.environmentService, this.productService, remoteAuthorityResolverService, signService, logService));
|
||||
serviceCollection.set(IRemoteAgentService, remoteAgentService);
|
||||
|
||||
// Native Host
|
||||
const nativeHostService = new NativeHostService(this.configuration.windowId, mainProcessService) as INativeHostService;
|
||||
serviceCollection.set(INativeHostService, nativeHostService);
|
||||
|
||||
// Files
|
||||
const fileService = this._register(new FileService(logService));
|
||||
serviceCollection.set(IFileService, fileService);
|
||||
protected registerFileSystemProviders(fileService: IFileService, logService: ILogService, nativeHostService: INativeHostService): void {
|
||||
|
||||
// Local Files
|
||||
fileService.registerProvider(Schemas.file, simpleFileSystemProvider);
|
||||
|
||||
// User Data Provider
|
||||
fileService.registerProvider(Schemas.userData, new FileUserDataProvider(Schemas.file, simpleFileSystemProvider, Schemas.userData, logService));
|
||||
|
||||
// Uri Identity
|
||||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
serviceCollection.set(IUriIdentityService, uriIdentityService);
|
||||
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
//
|
||||
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
||||
// from `workbench.common.main.ts` if the service is shared between
|
||||
// desktop and web or `workbench.sandbox.main.ts` if the service
|
||||
// is desktop only.
|
||||
//
|
||||
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
||||
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
||||
//
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
|
||||
const connection = remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
const remoteFileSystemProvider = this._register(new RemoteFileSystemProvider(remoteAgentService));
|
||||
fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider);
|
||||
}
|
||||
|
||||
const payload = this.resolveWorkspaceInitializationPayload();
|
||||
|
||||
const services = await Promise.all([
|
||||
this.createWorkspaceService().then(service => {
|
||||
|
||||
// Workspace
|
||||
serviceCollection.set(IWorkspaceContextService, service);
|
||||
|
||||
// Configuration
|
||||
serviceCollection.set(IWorkbenchConfigurationService, new SimpleConfigurationService());
|
||||
|
||||
return service;
|
||||
}),
|
||||
|
||||
this.createStorageService(payload, mainProcessService).then(service => {
|
||||
|
||||
// Storage
|
||||
serviceCollection.set(IStorageService, service);
|
||||
|
||||
return service;
|
||||
}),
|
||||
|
||||
this.createKeyboardLayoutService(mainProcessService).then(service => {
|
||||
|
||||
// KeyboardLayout
|
||||
serviceCollection.set(IKeyboardLayoutService, service);
|
||||
|
||||
return service;
|
||||
})
|
||||
]);
|
||||
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
//
|
||||
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
||||
// from `workbench.common.main.ts` if the service is shared between
|
||||
// desktop and web or `workbench.sandbox.main.ts` if the service
|
||||
// is desktop only.
|
||||
//
|
||||
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
||||
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
||||
//
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
|
||||
return { serviceCollection, logService, storageService: services[1] };
|
||||
}
|
||||
|
||||
private resolveWorkspaceInitializationPayload(): IWorkspaceInitializationPayload {
|
||||
let workspaceInitializationPayload: IWorkspaceInitializationPayload | undefined = this.configuration.workspace;
|
||||
|
||||
// Fallback to empty workspace if we have no payload yet.
|
||||
if (!workspaceInitializationPayload) {
|
||||
let id: string;
|
||||
if (this.configuration.backupPath) {
|
||||
id = basename(this.configuration.backupPath); // we know the backupPath must be a unique path so we leverage its name as workspace ID
|
||||
} else if (this.environmentService.isExtensionDevelopment) {
|
||||
id = 'ext-dev'; // extension development window never stores backups and is a singleton
|
||||
} else {
|
||||
throw new Error('Unexpected window configuration without backupPath');
|
||||
}
|
||||
|
||||
workspaceInitializationPayload = { id };
|
||||
}
|
||||
|
||||
return workspaceInitializationPayload;
|
||||
}
|
||||
|
||||
private async createWorkspaceService(): Promise<IWorkspaceContextService> {
|
||||
return new SimpleWorkspaceService();
|
||||
}
|
||||
|
||||
private async createStorageService(payload: IWorkspaceInitializationPayload, mainProcessService: IMainProcessService): Promise<NativeStorageService> {
|
||||
const storageService = new NativeStorageService(payload, mainProcessService, this.environmentService);
|
||||
|
||||
try {
|
||||
await storageService.initialize();
|
||||
|
||||
return storageService;
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
|
||||
return storageService;
|
||||
}
|
||||
}
|
||||
|
||||
private async createKeyboardLayoutService(mainProcessService: IMainProcessService): Promise<KeyboardLayoutService> {
|
||||
const keyboardLayoutService = new KeyboardLayoutService(mainProcessService);
|
||||
|
||||
try {
|
||||
await keyboardLayoutService.initialize();
|
||||
|
||||
return keyboardLayoutService;
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
|
||||
return keyboardLayoutService;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,20 +10,18 @@ import { URI } from 'vs/base/common/uri';
|
|||
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { SimpleConfigurationService as BaseSimpleConfigurationService } from 'vs/editor/standalone/browser/simpleServices';
|
||||
import { ExtensionKind } from 'vs/platform/extensions/common/extensions';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { IWebviewService, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService';
|
||||
import { ITunnelProvider, ITunnelService, RemoteTunnel, TunnelProviderFeatures } from 'vs/platform/remote/common/tunnel';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { ITaskProvider, ITaskService, ITaskSummary, ProblemMatcherRunOptions, Task, TaskFilter, TaskTerminateResponse, WorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { LinkedMap } from 'vs/base/common/map';
|
||||
import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder, WorkbenchState, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { CustomTask, ContributedTask, InMemoryTask, TaskRunSource, ConfiguringTask, TaskIdentifier, TaskSorter } from 'vs/workbench/contrib/tasks/common/tasks';
|
||||
import { TaskSystemInfo } from 'vs/workbench/contrib/tasks/common/taskSystem';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
|
@ -35,12 +33,12 @@ import type { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.a
|
|||
import { Schemas } from 'vs/base/common/network';
|
||||
import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminalInstanceService';
|
||||
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { ConsoleLogger, LogService } from 'vs/platform/log/common/log';
|
||||
|
||||
|
||||
//#region Environment
|
||||
|
||||
const userDataDir = URI.file('/sandbox-user-data-dir');
|
||||
|
||||
export class SimpleNativeWorkbenchEnvironmentService implements INativeWorkbenchEnvironmentService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
@ -49,7 +47,7 @@ export class SimpleNativeWorkbenchEnvironmentService implements INativeWorkbench
|
|||
readonly configuration: INativeWorkbenchConfiguration
|
||||
) { }
|
||||
|
||||
get userRoamingDataHome(): URI { return URI.file('/sandbox-user-data-dir').with({ scheme: Schemas.userData }); }
|
||||
get userRoamingDataHome(): URI { return userDataDir.with({ scheme: Schemas.userData }); }
|
||||
get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); }
|
||||
get argvResource(): URI { return joinPath(this.userRoamingDataHome, 'argv.json'); }
|
||||
get snippetsHome(): URI { return joinPath(this.userRoamingDataHome, 'snippets'); }
|
||||
|
@ -61,6 +59,7 @@ export class SimpleNativeWorkbenchEnvironmentService implements INativeWorkbench
|
|||
get serviceMachineIdResource(): URI { return joinPath(this.userRoamingDataHome, 'machineid'); }
|
||||
get userDataSyncLogResource(): URI { return joinPath(this.userRoamingDataHome, 'syncLog'); }
|
||||
get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, 'syncHome'); }
|
||||
get userDataPath(): string { return userDataDir.fsPath; }
|
||||
get tmpDir(): URI { return joinPath(this.userRoamingDataHome, 'tmp'); }
|
||||
get logsPath(): string { return joinPath(this.userRoamingDataHome, 'logs').path; }
|
||||
|
||||
|
@ -83,6 +82,7 @@ export class SimpleNativeWorkbenchEnvironmentService implements INativeWorkbench
|
|||
isExtensionDevelopment: boolean = false;
|
||||
disableExtensions: boolean | string[] = [];
|
||||
extensionDevelopmentLocationURI?: URI[] | undefined;
|
||||
extensionDevelopmentKind?: ExtensionKind[] | undefined;
|
||||
extensionTestsLocationURI?: URI | undefined;
|
||||
logLevel?: string | undefined;
|
||||
|
||||
|
@ -92,7 +92,6 @@ export class SimpleNativeWorkbenchEnvironmentService implements INativeWorkbench
|
|||
appRoot: string = undefined!;
|
||||
userHome: URI = undefined!;
|
||||
appSettingsHome: URI = undefined!;
|
||||
userDataPath: string = undefined!;
|
||||
machineSettingsResource: URI = undefined!;
|
||||
|
||||
log?: string | undefined;
|
||||
|
@ -123,49 +122,7 @@ export class SimpleNativeWorkbenchEnvironmentService implements INativeWorkbench
|
|||
|
||||
//#region Workspace
|
||||
|
||||
export const workspaceResource = URI.file(isWindows ? '\\simpleWorkspace' : '/simpleWorkspace');
|
||||
|
||||
export class SimpleWorkspaceService implements IWorkspaceContextService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
readonly onDidChangeWorkspaceName = Event.None;
|
||||
readonly onDidChangeWorkspaceFolders = Event.None;
|
||||
readonly onDidChangeWorkbenchState = Event.None;
|
||||
|
||||
private readonly workspace: IWorkspace;
|
||||
|
||||
constructor() {
|
||||
this.workspace = { id: '4064f6ec-cb38-4ad0-af64-ee6467e63c82', folders: [new WorkspaceFolder({ uri: workspaceResource, name: '', index: 0 })] };
|
||||
}
|
||||
|
||||
async getCompleteWorkspace(): Promise<IWorkspace> { return this.getWorkspace(); }
|
||||
|
||||
getWorkspace(): IWorkspace { return this.workspace; }
|
||||
|
||||
getWorkbenchState(): WorkbenchState {
|
||||
if (this.workspace) {
|
||||
if (this.workspace.configuration) {
|
||||
return WorkbenchState.WORKSPACE;
|
||||
}
|
||||
return WorkbenchState.FOLDER;
|
||||
}
|
||||
return WorkbenchState.EMPTY;
|
||||
}
|
||||
|
||||
getWorkspaceFolder(resource: URI): IWorkspaceFolder | null { return resource && resource.scheme === workspaceResource.scheme ? this.workspace.folders[0] : null; }
|
||||
isInsideWorkspace(resource: URI): boolean { return resource && resource.scheme === workspaceResource.scheme; }
|
||||
isCurrentWorkspace(workspaceIdOrFolder: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI): boolean { return true; }
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region Configuration
|
||||
|
||||
export class SimpleConfigurationService extends BaseSimpleConfigurationService implements IWorkbenchConfigurationService {
|
||||
async whenRemoteConfigurationLoaded() { }
|
||||
}
|
||||
export const simpleWorkspace: ISingleFolderWorkspaceIdentifier = { id: '4064f6ec-cb38-4ad0-af64-ee6467e63c82', uri: URI.file(isWindows ? '\\simpleWorkspace' : '/simpleWorkspace') };
|
||||
|
||||
//#endregion
|
||||
|
||||
|
@ -189,25 +146,27 @@ class SimpleFileSystemProvider extends InMemoryFileSystemProvider { }
|
|||
|
||||
export const simpleFileSystemProvider = new SimpleFileSystemProvider();
|
||||
|
||||
function createFile(parent: string, name: string, content: string = ''): void {
|
||||
simpleFileSystemProvider.writeFile(joinPath(workspaceResource, parent, name), VSBuffer.fromString(content).buffer, { create: true, overwrite: true });
|
||||
simpleFileSystemProvider.mkdir(userDataDir);
|
||||
|
||||
function createWorkspaceFile(parent: string, name: string, content: string = ''): void {
|
||||
simpleFileSystemProvider.writeFile(joinPath(simpleWorkspace.uri, parent, name), VSBuffer.fromString(content).buffer, { create: true, overwrite: true, unlock: false });
|
||||
}
|
||||
|
||||
function createFolder(name: string): void {
|
||||
simpleFileSystemProvider.mkdir(joinPath(workspaceResource, name));
|
||||
function createWorkspaceFolder(name: string): void {
|
||||
simpleFileSystemProvider.mkdir(joinPath(simpleWorkspace.uri, name));
|
||||
}
|
||||
|
||||
createFolder('');
|
||||
createFolder('src');
|
||||
createFolder('test');
|
||||
createWorkspaceFolder('');
|
||||
createWorkspaceFolder('src');
|
||||
createWorkspaceFolder('test');
|
||||
|
||||
createFile('', '.gitignore', `out
|
||||
createWorkspaceFile('', '.gitignore', `out
|
||||
node_modules
|
||||
.vscode-test/
|
||||
*.vsix
|
||||
`);
|
||||
|
||||
createFile('', '.vscodeignore', `.vscode/**
|
||||
createWorkspaceFile('', '.vscodeignore', `.vscode/**
|
||||
.vscode-test/**
|
||||
out/test/**
|
||||
src/**
|
||||
|
@ -218,14 +177,14 @@ vsc-extension-quickstart.md
|
|||
**/*.map
|
||||
**/*.ts`);
|
||||
|
||||
createFile('', 'CHANGELOG.md', `# Change Log
|
||||
createWorkspaceFile('', 'CHANGELOG.md', `# Change Log
|
||||
All notable changes to the "test-ts" extension will be documented in this file.
|
||||
|
||||
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
||||
|
||||
## [Unreleased]
|
||||
- Initial release`);
|
||||
createFile('', 'package.json', `{
|
||||
createWorkspaceFile('', 'package.json', `{
|
||||
"name": "test-ts",
|
||||
"displayName": "test-ts",
|
||||
"description": "",
|
||||
|
@ -265,7 +224,7 @@ createFile('', 'package.json', `{
|
|||
}
|
||||
`);
|
||||
|
||||
createFile('', 'tsconfig.json', `{
|
||||
createWorkspaceFile('', 'tsconfig.json', `{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
|
@ -288,7 +247,7 @@ createFile('', 'tsconfig.json', `{
|
|||
}
|
||||
`);
|
||||
|
||||
createFile('', 'tslint.json', `{
|
||||
createWorkspaceFile('', 'tslint.json', `{
|
||||
"rules": {
|
||||
"no-string-throw": true,
|
||||
"no-unused-expression": true,
|
||||
|
@ -305,7 +264,7 @@ createFile('', 'tslint.json', `{
|
|||
}
|
||||
`);
|
||||
|
||||
createFile('src', 'extension.ts', `// The module 'vscode' contains the VS Code extensibility API
|
||||
createWorkspaceFile('src', 'extension.ts', `// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
|
@ -334,7 +293,7 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
export function deactivate() {}
|
||||
`);
|
||||
|
||||
createFile('test', 'extension.test.ts', `//
|
||||
createWorkspaceFile('test', 'extension.test.ts', `//
|
||||
// Note: This example test is leveraging the Mocha test framework.
|
||||
// Please refer to their documentation on https://mochajs.org/ for help.
|
||||
//
|
||||
|
@ -357,7 +316,7 @@ suite("Extension Tests", function () {
|
|||
});
|
||||
});`);
|
||||
|
||||
createFile('test', 'index.ts', `//
|
||||
createWorkspaceFile('test', 'index.ts', `//
|
||||
// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
|
||||
//
|
||||
// This file is providing the test runner to use when running extension tests.
|
||||
|
@ -409,17 +368,6 @@ registerSingleton(IWebviewService, SimpleWebviewService);
|
|||
//#endregion
|
||||
|
||||
|
||||
//#region Textfiles
|
||||
|
||||
class SimpleTextFileService extends AbstractTextFileService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
}
|
||||
|
||||
registerSingleton(ITextFileService, SimpleTextFileService);
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region Tunnel
|
||||
|
||||
class SimpleTunnelService implements ITunnelService {
|
||||
|
@ -496,3 +444,5 @@ registerSingleton(ITaskService, SimpleTaskService);
|
|||
class SimpleTerminalInstanceService extends TerminalInstanceService { }
|
||||
|
||||
registerSingleton(ITerminalInstanceService, SimpleTerminalInstanceService);
|
||||
|
||||
//#endregion
|
||||
|
|
342
src/vs/workbench/electron-sandbox/shared.desktop.main.ts
Normal file
342
src/vs/workbench/electron-sandbox/shared.desktop.main.ts
Normal file
|
@ -0,0 +1,342 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows';
|
||||
import { mark } from 'vs/base/common/performance';
|
||||
import { Workbench } from 'vs/workbench/browser/workbench';
|
||||
import { NativeWindow } from 'vs/workbench/electron-sandbox/window';
|
||||
import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser';
|
||||
import { domContentLoaded } from 'vs/base/browser/dom';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
|
||||
import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceInitializationPayload, reviveIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { ILoggerService, ILogService } from 'vs/platform/log/common/log';
|
||||
import { NativeStorageService } from 'vs/platform/storage/electron-sandbox/storageService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
|
||||
import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentServiceImpl';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { RemoteFileSystemProvider } from 'vs/workbench/services/remote/common/remoteAgentFileSystemChannel';
|
||||
import { ConfigurationCache } from 'vs/workbench/services/configuration/electron-sandbox/configurationCache';
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService';
|
||||
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
|
||||
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
|
||||
import { KeyboardLayoutService } from 'vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout';
|
||||
import { IKeyboardLayoutService } from 'vs/platform/keyboardLayout/common/keyboardLayout';
|
||||
import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
||||
import { LoggerChannelClient } from 'vs/platform/log/common/logIpc';
|
||||
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const productService = { _serviceBrand: undefined, ...product };
|
||||
|
||||
export abstract class SharedDesktopMain extends Disposable {
|
||||
|
||||
protected readonly productService: IProductService = productService;
|
||||
|
||||
constructor(
|
||||
protected readonly configuration: INativeWorkbenchConfiguration,
|
||||
protected readonly environmentService: INativeWorkbenchEnvironmentService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
private init(): void {
|
||||
|
||||
// Massage configuration file URIs
|
||||
this.reviveUris();
|
||||
|
||||
// Browser config
|
||||
const zoomLevel = this.configuration.zoomLevel || 0;
|
||||
setZoomFactor(zoomLevelToZoomFactor(zoomLevel));
|
||||
setZoomLevel(zoomLevel, true /* isTrusted */);
|
||||
setFullscreen(!!this.configuration.fullscreen);
|
||||
}
|
||||
|
||||
private reviveUris() {
|
||||
|
||||
// Workspace
|
||||
const workspace = reviveIdentifier(this.configuration.workspace);
|
||||
if (isWorkspaceIdentifier(workspace) || isSingleFolderWorkspaceIdentifier(workspace)) {
|
||||
this.configuration.workspace = workspace;
|
||||
}
|
||||
|
||||
// Files
|
||||
const filesToWait = this.configuration.filesToWait;
|
||||
const filesToWaitPaths = filesToWait?.paths;
|
||||
[filesToWaitPaths, this.configuration.filesToOpenOrCreate, this.configuration.filesToDiff].forEach(paths => {
|
||||
if (Array.isArray(paths)) {
|
||||
paths.forEach(path => {
|
||||
if (path.fileUri) {
|
||||
path.fileUri = URI.revive(path.fileUri);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (filesToWait) {
|
||||
filesToWait.waitMarkerFileUri = URI.revive(filesToWait.waitMarkerFileUri);
|
||||
}
|
||||
}
|
||||
|
||||
async open(): Promise<void> {
|
||||
const services = await this.initServices();
|
||||
|
||||
await domContentLoaded();
|
||||
mark('code/willStartWorkbench');
|
||||
|
||||
// Create Workbench
|
||||
const workbench = new Workbench(document.body, services.serviceCollection, services.logService);
|
||||
|
||||
// Listeners
|
||||
this.registerListeners(workbench, services.storageService);
|
||||
|
||||
// Startup
|
||||
const instantiationService = workbench.startup();
|
||||
|
||||
// Window
|
||||
this._register(instantiationService.createInstance(NativeWindow));
|
||||
|
||||
// Logging
|
||||
services.logService.trace('workbench configuration', JSON.stringify(this.configuration));
|
||||
|
||||
// Allow subclass to participate
|
||||
this.joinOpen(instantiationService);
|
||||
}
|
||||
|
||||
private registerListeners(workbench: Workbench, storageService: NativeStorageService): void {
|
||||
|
||||
// Workbench Lifecycle
|
||||
this._register(workbench.onWillShutdown(event => event.join(storageService.close(), 'join.closeStorage')));
|
||||
this._register(workbench.onShutdown(() => this.dispose()));
|
||||
}
|
||||
|
||||
protected abstract createLogService(loggerService: ILoggerService, mainProcessService: IMainProcessService): ILogService;
|
||||
protected abstract registerFileSystemProviders(fileService: IFileService, logService: ILogService, nativeHostService: INativeHostService): void;
|
||||
protected joinOpen(instantiationService: IInstantiationService): void { }
|
||||
|
||||
private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: NativeStorageService }> {
|
||||
const serviceCollection = new ServiceCollection();
|
||||
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
//
|
||||
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
||||
// from `workbench.common.main.ts` if the service is shared between
|
||||
// desktop and web or `workbench.sandbox.main.ts` if the service
|
||||
// is desktop only.
|
||||
//
|
||||
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
||||
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
||||
//
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
|
||||
// Main Process
|
||||
const mainProcessService = this._register(new ElectronIPCMainProcessService(this.configuration.windowId));
|
||||
serviceCollection.set(IMainProcessService, mainProcessService);
|
||||
|
||||
// Environment
|
||||
serviceCollection.set(INativeWorkbenchEnvironmentService, this.environmentService);
|
||||
|
||||
// Product
|
||||
serviceCollection.set(IProductService, this.productService);
|
||||
|
||||
// Logger
|
||||
const loggerService = new LoggerChannelClient(mainProcessService.getChannel('logger'));
|
||||
serviceCollection.set(ILoggerService, loggerService);
|
||||
|
||||
// Log
|
||||
const logService = this.createLogService(loggerService, mainProcessService);
|
||||
serviceCollection.set(ILogService, logService);
|
||||
|
||||
// Remote
|
||||
const remoteAuthorityResolverService = new RemoteAuthorityResolverService();
|
||||
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
|
||||
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
//
|
||||
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
||||
// from `workbench.common.main.ts` if the service is shared between
|
||||
// desktop and web or `workbench.sandbox.main.ts` if the service
|
||||
// is desktop only.
|
||||
//
|
||||
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
||||
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
||||
//
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
|
||||
// Sign
|
||||
const signService = ProxyChannel.toService<ISignService>(mainProcessService.getChannel('sign'));
|
||||
serviceCollection.set(ISignService, signService);
|
||||
|
||||
// Remote Agent
|
||||
const remoteAgentService = this._register(new RemoteAgentService(this.environmentService, this.productService, remoteAuthorityResolverService, signService, logService));
|
||||
serviceCollection.set(IRemoteAgentService, remoteAgentService);
|
||||
|
||||
// Native Host
|
||||
const nativeHostService = new NativeHostService(this.configuration.windowId, mainProcessService) as INativeHostService;
|
||||
serviceCollection.set(INativeHostService, nativeHostService);
|
||||
|
||||
// Files
|
||||
const fileService = this._register(new FileService(logService));
|
||||
serviceCollection.set(IFileService, fileService);
|
||||
|
||||
this.registerFileSystemProviders(fileService, logService, nativeHostService);
|
||||
|
||||
// Uri Identity
|
||||
const uriIdentityService = new UriIdentityService(fileService);
|
||||
serviceCollection.set(IUriIdentityService, uriIdentityService);
|
||||
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
//
|
||||
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
||||
// from `workbench.common.main.ts` if the service is shared between
|
||||
// desktop and web or `workbench.sandbox.main.ts` if the service
|
||||
// is desktop only.
|
||||
//
|
||||
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
||||
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
||||
//
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
|
||||
const connection = remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
const remoteFileSystemProvider = this._register(new RemoteFileSystemProvider(remoteAgentService));
|
||||
fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider);
|
||||
}
|
||||
|
||||
const payload = this.resolveWorkspaceInitializationPayload();
|
||||
|
||||
const services = await Promise.all([
|
||||
this.createWorkspaceService(payload, fileService, remoteAgentService, uriIdentityService, logService).then(service => {
|
||||
|
||||
// Workspace
|
||||
serviceCollection.set(IWorkspaceContextService, service);
|
||||
|
||||
// Configuration
|
||||
serviceCollection.set(IWorkbenchConfigurationService, service);
|
||||
|
||||
return service;
|
||||
}),
|
||||
|
||||
this.createStorageService(payload, mainProcessService).then(service => {
|
||||
|
||||
// Storage
|
||||
serviceCollection.set(IStorageService, service);
|
||||
|
||||
return service;
|
||||
}),
|
||||
|
||||
this.createKeyboardLayoutService(mainProcessService).then(service => {
|
||||
|
||||
// KeyboardLayout
|
||||
serviceCollection.set(IKeyboardLayoutService, service);
|
||||
|
||||
return service;
|
||||
})
|
||||
]);
|
||||
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
//
|
||||
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
||||
// from `workbench.common.main.ts` if the service is shared between
|
||||
// desktop and web or `workbench.sandbox.main.ts` if the service
|
||||
// is desktop only.
|
||||
//
|
||||
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
||||
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
||||
//
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
|
||||
return { serviceCollection, logService, storageService: services[1] };
|
||||
}
|
||||
|
||||
private resolveWorkspaceInitializationPayload(): IWorkspaceInitializationPayload {
|
||||
let workspaceInitializationPayload: IWorkspaceInitializationPayload | undefined = this.configuration.workspace;
|
||||
|
||||
// Fallback to empty workspace if we have no payload yet.
|
||||
if (!workspaceInitializationPayload) {
|
||||
let id: string;
|
||||
if (this.configuration.backupPath) {
|
||||
id = basename(this.configuration.backupPath); // we know the backupPath must be a unique path so we leverage its name as workspace ID
|
||||
} else if (this.environmentService.isExtensionDevelopment) {
|
||||
id = 'ext-dev'; // extension development window never stores backups and is a singleton
|
||||
} else {
|
||||
throw new Error('Unexpected window configuration without backupPath');
|
||||
}
|
||||
|
||||
workspaceInitializationPayload = { id };
|
||||
}
|
||||
|
||||
return workspaceInitializationPayload;
|
||||
}
|
||||
|
||||
private async createWorkspaceService(payload: IWorkspaceInitializationPayload, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise<WorkspaceService> {
|
||||
const workspaceService = new WorkspaceService({ remoteAuthority: this.environmentService.remoteAuthority, configurationCache: new ConfigurationCache(URI.file(this.environmentService.userDataPath), fileService) }, this.environmentService, fileService, remoteAgentService, uriIdentityService, logService);
|
||||
|
||||
try {
|
||||
await workspaceService.initialize(payload);
|
||||
|
||||
return workspaceService;
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
|
||||
return workspaceService;
|
||||
}
|
||||
}
|
||||
|
||||
private async createStorageService(payload: IWorkspaceInitializationPayload, mainProcessService: IMainProcessService): Promise<NativeStorageService> {
|
||||
const storageService = new NativeStorageService(payload, mainProcessService, this.environmentService);
|
||||
|
||||
try {
|
||||
await storageService.initialize();
|
||||
|
||||
return storageService;
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
|
||||
return storageService;
|
||||
}
|
||||
}
|
||||
|
||||
private async createKeyboardLayoutService(mainProcessService: IMainProcessService): Promise<KeyboardLayoutService> {
|
||||
const keyboardLayoutService = new KeyboardLayoutService(mainProcessService);
|
||||
|
||||
try {
|
||||
await keyboardLayoutService.initialize();
|
||||
|
||||
return keyboardLayoutService;
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
|
||||
return keyboardLayoutService;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -314,6 +314,11 @@ export interface IEditorGroupsService {
|
|||
*/
|
||||
mergeGroup(group: IEditorGroup | GroupIdentifier, target: IEditorGroup | GroupIdentifier, options?: IMergeGroupOptions): IEditorGroup;
|
||||
|
||||
/**
|
||||
* Merge all editor groups into the active one.
|
||||
*/
|
||||
mergeAllGroups(): IEditorGroup;
|
||||
|
||||
/**
|
||||
* Copy a group to a new group in the editor area.
|
||||
*
|
||||
|
|
|
@ -315,6 +315,34 @@ suite('EditorGroupsService', () => {
|
|||
part.dispose();
|
||||
});
|
||||
|
||||
test('merge all groups', async () => {
|
||||
const [part] = createPart();
|
||||
|
||||
const rootGroup = part.groups[0];
|
||||
|
||||
const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID);
|
||||
const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID);
|
||||
const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID);
|
||||
|
||||
await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true }));
|
||||
|
||||
const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT);
|
||||
await rightGroup.openEditor(input2, EditorOptions.create({ pinned: true }));
|
||||
|
||||
const downGroup = part.copyGroup(rootGroup, rightGroup, GroupDirection.DOWN);
|
||||
await downGroup.openEditor(input3, EditorOptions.create({ pinned: true }));
|
||||
|
||||
part.activateGroup(rootGroup);
|
||||
|
||||
assert.strictEqual(rootGroup.count, 1);
|
||||
|
||||
const result = part.mergeAllGroups();
|
||||
assert.strictEqual(result.id, rootGroup.id);
|
||||
assert.strictEqual(rootGroup.count, 3);
|
||||
|
||||
part.dispose();
|
||||
});
|
||||
|
||||
test('whenRestored', async () => {
|
||||
const [part] = createPart();
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import { memoize } from 'vs/base/common/decorators';
|
|||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { parseLineAndColumnAware } from 'vs/base/common/extpath';
|
||||
import { LogLevelToString } from 'vs/platform/log/common/log';
|
||||
import { ExtensionKind } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
class BrowserWorkbenchConfiguration implements IWindowConfiguration {
|
||||
|
||||
|
@ -86,6 +87,7 @@ interface IExtensionHostDebugEnvironment {
|
|||
debugRenderer: boolean;
|
||||
isExtensionDevelopment: boolean;
|
||||
extensionDevelopmentLocationURI?: URI[];
|
||||
extensionDevelopmentKind?: ExtensionKind[];
|
||||
extensionTestsLocationURI?: URI;
|
||||
extensionEnabledProposedApi?: string[];
|
||||
}
|
||||
|
@ -193,6 +195,14 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
|
|||
return this._extensionHostDebugEnvironment.extensionDevelopmentLocationURI;
|
||||
}
|
||||
|
||||
get extensionDevelopmentLocationKind(): ExtensionKind[] | undefined {
|
||||
if (!this._extensionHostDebugEnvironment) {
|
||||
this._extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment();
|
||||
}
|
||||
|
||||
return this._extensionHostDebugEnvironment.extensionDevelopmentKind;
|
||||
}
|
||||
|
||||
get extensionTestsLocationURI(): URI | undefined {
|
||||
if (!this._extensionHostDebugEnvironment) {
|
||||
this._extensionHostDebugEnvironment = this.resolveExtensionHostDebugEnvironment();
|
||||
|
@ -272,7 +282,8 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
|
|||
},
|
||||
debugRenderer: false,
|
||||
isExtensionDevelopment: false,
|
||||
extensionDevelopmentLocationURI: undefined
|
||||
extensionDevelopmentLocationURI: undefined,
|
||||
extensionDevelopmentKind: undefined
|
||||
};
|
||||
|
||||
// Fill in selected extra environmental properties
|
||||
|
@ -283,6 +294,9 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
|
|||
extensionHostDebugEnvironment.extensionDevelopmentLocationURI = [URI.parse(value)];
|
||||
extensionHostDebugEnvironment.isExtensionDevelopment = true;
|
||||
break;
|
||||
case 'extensionDevelopmentKind':
|
||||
extensionHostDebugEnvironment.extensionDevelopmentKind = [<ExtensionKind>value];
|
||||
break;
|
||||
case 'extensionTestsPath':
|
||||
extensionHostDebugEnvironment.extensionTestsLocationURI = URI.parse(value);
|
||||
break;
|
||||
|
|
|
@ -50,7 +50,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
|||
) {
|
||||
super(
|
||||
new ExtensionRunningLocationClassifier(
|
||||
manifest => this._getExtensionKind(manifest),
|
||||
(extension) => this._getExtensionKind(extension),
|
||||
(extensionKinds, isInstalledLocally, isInstalledRemotely) => ExtensionService.pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely)
|
||||
),
|
||||
instantiationService,
|
||||
|
|
|
@ -21,7 +21,7 @@ import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensi
|
|||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
|
||||
import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
|
||||
import { ExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
|
||||
import { ExtensionIdentifier, IExtensionDescription, ExtensionType, ITranslatedScannedExtension, IExtension, ExtensionKind, IExtensionContributions, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionIdentifier, IExtensionDescription, ExtensionType, ITranslatedScannedExtension, IExtension, ExtensionKind, IExtensionContributions } from 'vs/platform/extensions/common/extensions';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
@ -173,8 +173,12 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
|||
}));
|
||||
}
|
||||
|
||||
protected _getExtensionKind(manifest: IExtensionManifest): ExtensionKind[] {
|
||||
return this._extensionKindController.getExtensionKind(manifest);
|
||||
protected _getExtensionKind(extensionDescription: IExtensionDescription): ExtensionKind[] {
|
||||
if (extensionDescription.isUnderDevelopment && this._environmentService.extensionDevelopmentKind) {
|
||||
return this._environmentService.extensionDevelopmentKind;
|
||||
}
|
||||
|
||||
return this._extensionKindController.getExtensionKind(extensionDescription);
|
||||
}
|
||||
|
||||
protected _getExtensionHostManager(kind: ExtensionHostKind): ExtensionHostManager | null {
|
||||
|
@ -211,16 +215,16 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
|||
for (let i = 0, len = _toAdd.length; i < len; i++) {
|
||||
const extension = _toAdd[i];
|
||||
|
||||
if (!this._canAddExtension(extension)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const extensionDescription = await this._scanSingleExtension(extension);
|
||||
if (!extensionDescription) {
|
||||
// could not scan extension...
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.canAddExtension(extensionDescription)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
toAdd.push(extensionDescription);
|
||||
}
|
||||
|
||||
|
@ -310,24 +314,20 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
|||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
public canAddExtension(extensionDescription: IExtensionDescription): boolean {
|
||||
return this._canAddExtension(toExtension(extensionDescription));
|
||||
}
|
||||
|
||||
private _canAddExtension(extension: IExtension): boolean {
|
||||
const extensionDescription = this._registry.getExtensionDescription(extension.identifier.id);
|
||||
if (extensionDescription) {
|
||||
public canAddExtension(extension: IExtensionDescription): boolean {
|
||||
const existing = this._registry.getExtensionDescription(extension.identifier);
|
||||
if (existing) {
|
||||
// this extension is already running (most likely at a different version)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if extension is renamed
|
||||
if (extension.identifier.uuid && this._registry.getAllExtensionDescriptions().some(e => e.uuid === extension.identifier.uuid)) {
|
||||
if (extension.uuid && this._registry.getAllExtensionDescriptions().some(e => e.uuid === extension.uuid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const extensionKind = this._getExtensionKind(extension.manifest);
|
||||
const isRemote = extension.location.scheme === Schemas.vscodeRemote;
|
||||
const extensionKind = this._getExtensionKind(extension);
|
||||
const isRemote = extension.extensionLocation.scheme === Schemas.vscodeRemote;
|
||||
const runningLocation = this._runningLocationClassifier.pickRunningLocation(extensionKind, !isRemote, isRemote);
|
||||
if (runningLocation === ExtensionRunningLocation.None) {
|
||||
return false;
|
||||
|
@ -420,6 +420,43 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
|||
await this._scanAndHandleExtensions();
|
||||
this._releaseBarrier();
|
||||
perf.mark('code/didLoadExtensions');
|
||||
await this._handleExtensionTests();
|
||||
}
|
||||
|
||||
private async _handleExtensionTests(): Promise<void> {
|
||||
if (!this._environmentService.extensionDevelopmentLocationURI || !this._environmentService.extensionTestsLocationURI) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Use `this._registry` and `this._runningLocation` to better determine which extension host should launch the test runner
|
||||
let extensionHostManager: ExtensionHostManager | null = null;
|
||||
if (this._environmentService.extensionTestsLocationURI.scheme === Schemas.vscodeRemote) {
|
||||
extensionHostManager = this._getExtensionHostManager(ExtensionHostKind.Remote);
|
||||
}
|
||||
if (!extensionHostManager) {
|
||||
// When a debugger attaches to the extension host, it will surface all console.log messages from the extension host,
|
||||
// but not necessarily from the window. So it would be best if any errors get printed to the console of the extension host.
|
||||
// That is why here we use the local process extension host even for non-file URIs
|
||||
extensionHostManager = this._getExtensionHostManager(ExtensionHostKind.LocalProcess);
|
||||
}
|
||||
|
||||
if (!extensionHostManager) {
|
||||
const msg = nls.localize('extensionTestError', "No extension host found that can launch the test runner at {0}.", this._environmentService.extensionTestsLocationURI.toString());
|
||||
console.error(msg);
|
||||
this._notificationService.error(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
let exitCode: number;
|
||||
try {
|
||||
exitCode = await extensionHostManager.extensionTestsExecute();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
exitCode = 1 /* ERROR */;
|
||||
}
|
||||
|
||||
await extensionHostManager.extensionTestsSendExit(exitCode);
|
||||
this._onExtensionHostExit(exitCode);
|
||||
}
|
||||
|
||||
private _releaseBarrier(): void {
|
||||
|
@ -779,7 +816,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
|
|||
|
||||
export class ExtensionRunningLocationClassifier {
|
||||
constructor(
|
||||
public readonly getExtensionKind: (manifest: IExtensionManifest) => ExtensionKind[],
|
||||
public readonly getExtensionKind: (extensionDescription: IExtensionDescription) => ExtensionKind[],
|
||||
public readonly pickRunningLocation: (extensionKinds: ExtensionKind[], isInstalledLocally: boolean, isInstalledRemotely: boolean) => ExtensionRunningLocation,
|
||||
) {
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
|
|||
import { IExtensionHost, ExtensionHostKind, ActivationKind } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
|
||||
import { CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
|
||||
// Enable to see detailed message communication between window and extension host
|
||||
const LOG_EXTENSION_HOST_COMMUNICATION = false;
|
||||
|
@ -292,6 +293,28 @@ export class ExtensionHostManager extends Disposable {
|
|||
return proxy.$startExtensionHost(enabledExtensionIds);
|
||||
}
|
||||
|
||||
public async extensionTestsExecute(): Promise<number> {
|
||||
const proxy = await this._getProxy();
|
||||
if (!proxy) {
|
||||
throw new Error('Could not obtain Extension Host Proxy');
|
||||
}
|
||||
return proxy.$extensionTestsExecute();
|
||||
}
|
||||
|
||||
public async extensionTestsSendExit(exitCode: number): Promise<void> {
|
||||
const proxy = await this._getProxy();
|
||||
if (!proxy) {
|
||||
return;
|
||||
}
|
||||
// This method does not wait for the actual RPC to be confirmed
|
||||
// It waits for the socket to drain (i.e. the message has been sent)
|
||||
// It also times out after 5s in case drain takes too long
|
||||
proxy.$extensionTestsExit(exitCode);
|
||||
if (this._rpcProtocol) {
|
||||
await Promise.race([this._rpcProtocol.drain(), timeout(5000)]);
|
||||
}
|
||||
}
|
||||
|
||||
public async deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void> {
|
||||
const proxy = await this._getProxy();
|
||||
if (!proxy) {
|
||||
|
|
|
@ -253,7 +253,6 @@ export interface IExtensionService {
|
|||
_onWillActivateExtension(extensionId: ExtensionIdentifier): void;
|
||||
_onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void;
|
||||
_onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void;
|
||||
_onExtensionHostExit(code: number): void;
|
||||
}
|
||||
|
||||
export interface ProfileSession {
|
||||
|
|
|
@ -70,7 +70,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
|||
) {
|
||||
super(
|
||||
new ExtensionRunningLocationClassifier(
|
||||
manifest => this._getExtensionKind(manifest),
|
||||
(extension) => this._getExtensionKind(extension),
|
||||
(extensionKinds, isInstalledLocally, isInstalledRemotely) => this._pickRunningLocation(extensionKinds, isInstalledLocally, isInstalledRemotely)
|
||||
),
|
||||
instantiationService,
|
||||
|
@ -85,7 +85,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
|||
configurationService,
|
||||
);
|
||||
|
||||
this._enableLocalWebWorker = this._configurationService.getValue<boolean>(webWorkerExtHostConfig);
|
||||
this._enableLocalWebWorker = this._isLocalWebWorkerEnabled();
|
||||
this._remoteInitData = new Map<string, IRemoteExtensionHostInitData>();
|
||||
this._extensionScanner = instantiationService.createInstance(CachedExtensionScanner);
|
||||
|
||||
|
@ -103,6 +103,16 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
|||
});
|
||||
}
|
||||
|
||||
private _isLocalWebWorkerEnabled() {
|
||||
if (this._configurationService.getValue<boolean>(webWorkerExtHostConfig)) {
|
||||
return true;
|
||||
}
|
||||
if (this._environmentService.isExtensionDevelopment && this._environmentService.extensionDevelopmentKind?.some(k => k === 'web')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected _scanSingleExtension(extension: IExtension): Promise<IExtensionDescription | null> {
|
||||
if (extension.location.scheme === Schemas.vscodeRemote) {
|
||||
return this._remoteAgentService.scanSingleExtension(extension.location, extension.type === ExtensionType.System);
|
||||
|
|
|
@ -209,6 +209,10 @@ export class LocalProcessExtensionHost implements IExtensionHost {
|
|||
opts.execArgv.unshift('--prof');
|
||||
}
|
||||
|
||||
if (this._environmentService.args['max-memory']) {
|
||||
opts.execArgv.unshift(`--max-old-space-size=${this._environmentService.args['max-memory']}`);
|
||||
}
|
||||
|
||||
// On linux crash reporter needs to be started on child node processes explicitly
|
||||
if (platform.isLinux) {
|
||||
const crashReporterStartOptions: CrashReporterStartOptions = {
|
||||
|
|
|
@ -79,6 +79,7 @@ export class RemoteFileSystemProvider extends Disposable implements
|
|||
| FileSystemProviderCapabilities.FileOpenReadWriteClose
|
||||
| FileSystemProviderCapabilities.FileReadStream
|
||||
| FileSystemProviderCapabilities.FileFolderCopy
|
||||
| FileSystemProviderCapabilities.FileWriteUnlock
|
||||
);
|
||||
|
||||
if (isCaseSensitive) {
|
||||
|
|
|
@ -154,10 +154,10 @@ interface PortAttributes extends Attributes {
|
|||
|
||||
export class PortsAttributes extends Disposable {
|
||||
private static SETTING = 'remote.portsAttributes';
|
||||
private static DEFAULTS = 'remote.portsAttributes.defaults';
|
||||
private static RANGE = /^(\d+)\-(\d+)$/;
|
||||
private static OTHERS = 'others';
|
||||
private portsAttributes: PortAttributes[] = [];
|
||||
private otherPortAttributes: Attributes | undefined;
|
||||
private defaultPortAttributes: Attributes | undefined;
|
||||
private _onDidChangeAttributes = new Emitter<void>();
|
||||
public readonly onDidChangeAttributes = this._onDidChangeAttributes.event;
|
||||
|
||||
|
@ -241,12 +241,6 @@ export class PortsAttributes extends Disposable {
|
|||
let key: number | { start: number, end: number } | RegExp | undefined = undefined;
|
||||
if (Number(attributesKey)) {
|
||||
key = Number(attributesKey);
|
||||
} else if (attributesKey === PortsAttributes.OTHERS) {
|
||||
this.otherPortAttributes = {
|
||||
elevateIfNeeded: setting.elevateIfPrivileged,
|
||||
onAutoForward: setting.onAutoForward,
|
||||
label: setting.label
|
||||
};
|
||||
} else if (isString(attributesKey)) {
|
||||
if (PortsAttributes.RANGE.test(attributesKey)) {
|
||||
const match = (<string>attributesKey).match(PortsAttributes.RANGE);
|
||||
|
@ -269,6 +263,15 @@ export class PortsAttributes extends Disposable {
|
|||
});
|
||||
}
|
||||
|
||||
const defaults = <any>this.configurationService.getValue(PortsAttributes.DEFAULTS);
|
||||
if (defaults) {
|
||||
this.defaultPortAttributes = {
|
||||
elevateIfNeeded: defaults.elevateIfNeeded,
|
||||
label: defaults.label,
|
||||
onAutoForward: defaults.onAutoForward
|
||||
};
|
||||
}
|
||||
|
||||
return this.sortAttributes(attributes);
|
||||
}
|
||||
|
||||
|
@ -289,7 +292,7 @@ export class PortsAttributes extends Disposable {
|
|||
}
|
||||
|
||||
private getOtherAttributes() {
|
||||
return this.otherPortAttributes;
|
||||
return this.defaultPortAttributes;
|
||||
}
|
||||
|
||||
static providedActionToAction(providedAction: ProvidedOnAutoForward | undefined) {
|
||||
|
@ -324,6 +327,7 @@ export class TunnelModel extends Disposable {
|
|||
public onEnvironmentTunnelsSet: Event<void> = this._onEnvironmentTunnelsSet.event;
|
||||
private _environmentTunnelsSet: boolean = false;
|
||||
private configPortsAttributes: PortsAttributes;
|
||||
private restoreListener: IDisposable | undefined;
|
||||
|
||||
private portAttributesProviders: PortAttributesProvider[] = [];
|
||||
|
||||
|
@ -426,18 +430,19 @@ export class TunnelModel extends Disposable {
|
|||
await this.forward({ host: tunnel.remoteHost, port: tunnel.remotePort }, tunnel.localPort, tunnel.name, undefined, undefined, tunnel.privacy === TunnelPrivacy.Public);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// It's possible that at restore time the value hasn't synced.
|
||||
const key = await this.getStorageKey();
|
||||
const listener = this.storageService.onDidChangeValue(async (e) => {
|
||||
if (e.key === key) {
|
||||
listener.dispose();
|
||||
this.tunnelRestoreValue = Promise.resolve(this.storageService.get(await this.getStorageKey(), StorageScope.GLOBAL));
|
||||
await this.restoreForwarded();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.restoreListener) {
|
||||
// It's possible that at restore time the value hasn't synced.
|
||||
const key = await this.getStorageKey();
|
||||
this.restoreListener = this._register(this.storageService.onDidChangeValue(async (e) => {
|
||||
if (e.key === key) {
|
||||
this.tunnelRestoreValue = Promise.resolve(this.storageService.get(await this.getStorageKey(), StorageScope.GLOBAL));
|
||||
await this.restoreForwarded();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private async storeForwarded() {
|
||||
|
|
|
@ -798,10 +798,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
|||
return this.saveSequentializer.setPending(versionId, (async () => {
|
||||
try {
|
||||
const stat = await this.textFileService.write(lastResolvedFileStat.resource, resolvedTextFileEditorModel.createSnapshot(), {
|
||||
overwriteReadonly: options.overwriteReadonly,
|
||||
mtime: lastResolvedFileStat.mtime,
|
||||
encoding: this.getEncoding(),
|
||||
etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource, resolvedTextFileEditorModel.getMode())) ? ETAG_DISABLED : lastResolvedFileStat.etag,
|
||||
unlock: options.writeUnlock,
|
||||
writeElevated: options.writeElevated
|
||||
});
|
||||
|
||||
|
|
|
@ -130,11 +130,6 @@ export interface IWriteTextFileOptions extends IWriteFileOptions {
|
|||
*/
|
||||
encoding?: string;
|
||||
|
||||
/**
|
||||
* Whether to overwrite a file even if it is readonly.
|
||||
*/
|
||||
overwriteReadonly?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to write to the file as elevated (admin) user. When setting this option a prompt will
|
||||
* ask the user to authenticate as super user.
|
||||
|
@ -367,9 +362,9 @@ export interface ITextFileEditorModelManager {
|
|||
export interface ITextFileSaveOptions extends ISaveOptions {
|
||||
|
||||
/**
|
||||
* Makes the file writable if it is readonly.
|
||||
* Save the file with an attempt to unlock it.
|
||||
*/
|
||||
overwriteReadonly?: boolean;
|
||||
writeUnlock?: boolean;
|
||||
|
||||
/**
|
||||
* Save the file with elevated privileges.
|
||||
|
|
|
@ -3,14 +3,12 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { promises } from 'fs';
|
||||
import { localize } from 'vs/nls';
|
||||
import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService';
|
||||
import { ITextFileService, ITextFileStreamContent, ITextFileContent, IReadTextFileOptions, IWriteTextFileOptions, TextFileEditorModelState, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IFileStatWithMetadata, FileOperationError, FileOperationResult, IFileService, ByteSize, getPlatformLimits, Arch } from 'vs/platform/files/common/files';
|
||||
import { IFileStatWithMetadata, IFileService, ByteSize, getPlatformLimits, Arch } from 'vs/platform/files/common/files';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
|
@ -123,46 +121,13 @@ export class NativeTextFileService extends AbstractTextFileService {
|
|||
|
||||
async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata> {
|
||||
|
||||
// check for overwriteReadonly property (only supported for local file://)
|
||||
try {
|
||||
if (options?.overwriteReadonly && resource.scheme === Schemas.file) {
|
||||
const fileStat = await promises.stat(resource.fsPath);
|
||||
|
||||
// try to change mode to writeable
|
||||
await promises.chmod(resource.fsPath, fileStat.mode | 0o200 /* File mode indicating writable by owner (fs.constants.S_IWUSR) */);
|
||||
}
|
||||
} catch (error) {
|
||||
// ignore and simply retry the operation
|
||||
}
|
||||
|
||||
// check for writeElevated property (only supported for local file://)
|
||||
// check for `writeElevated property` to write elevated
|
||||
// (file:// only: https://github.com/microsoft/vscode/issues/48659)
|
||||
if (options?.writeElevated && resource.scheme === Schemas.file) {
|
||||
return this.writeElevated(resource, value, options);
|
||||
}
|
||||
|
||||
try {
|
||||
return await super.write(resource, value, options);
|
||||
} catch (error) {
|
||||
|
||||
// In case of permission denied, we need to check for readonly
|
||||
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_PERMISSION_DENIED) {
|
||||
let isReadonly = false;
|
||||
try {
|
||||
const fileStat = await promises.stat(resource.fsPath);
|
||||
if (!(fileStat.mode & 0o200 /* File mode indicating writable by owner (fs.constants.S_IWUSR) */)) {
|
||||
isReadonly = true;
|
||||
}
|
||||
} catch (error) {
|
||||
// ignore - rethrow original error
|
||||
}
|
||||
|
||||
if (isReadonly) {
|
||||
throw new FileOperationError(localize('fileReadOnlyError', "File is Read Only"), FileOperationResult.FILE_READ_ONLY, options);
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
return super.write(resource, value, options);
|
||||
}
|
||||
|
||||
private async writeElevated(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata> {
|
|
@ -57,7 +57,7 @@ if (isWeb) {
|
|||
await fileProvider.writeFile(
|
||||
URI.file(join(testDir, fileName)),
|
||||
files[fileName],
|
||||
{ create: true, overwrite: false }
|
||||
{ create: true, overwrite: false, unlock: false }
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -641,6 +641,7 @@ export class TestEditorGroupsService implements IEditorGroupsService {
|
|||
removeGroup(_group: number | IEditorGroup): void { }
|
||||
moveGroup(_group: number | IEditorGroup, _location: number | IEditorGroup, _direction: GroupDirection): IEditorGroup { throw new Error('not implemented'); }
|
||||
mergeGroup(_group: number | IEditorGroup, _target: number | IEditorGroup, _options?: IMergeGroupOptions): IEditorGroup { throw new Error('not implemented'); }
|
||||
mergeAllGroups(): IEditorGroup { throw new Error('not implemented'); }
|
||||
copyGroup(_group: number | IEditorGroup, _location: number | IEditorGroup, _direction: GroupDirection): IEditorGroup { throw new Error('not implemented'); }
|
||||
centerLayout(active: boolean): void { }
|
||||
isLayoutCentered(): boolean { return false; }
|
||||
|
|
|
@ -7,7 +7,7 @@ import { workbenchInstantiationService as browserWorkbenchInstantiationService,
|
|||
import { Event } from 'vs/base/common/event';
|
||||
import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services';
|
||||
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
|
||||
import { NativeTextFileService, } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService';
|
||||
import { NativeTextFileService, } from 'vs/workbench/services/textfile/electron-sandbox/nativeTextFileService';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { FileOperationError, IFileService } from 'vs/platform/files/common/files';
|
||||
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
|
||||
|
@ -201,7 +201,7 @@ export class TestNativeHostService implements INativeHostService {
|
|||
async showItemInFolder(path: string): Promise<void> { }
|
||||
async setRepresentedFilename(path: string): Promise<void> { }
|
||||
async isAdmin(): Promise<boolean> { return false; }
|
||||
async writeElevated(source: URI, target: URI, options?: { overwriteReadonly?: boolean | undefined; }): Promise<void> { }
|
||||
async writeElevated(source: URI, target: URI): Promise<void> { }
|
||||
async getOSProperties(): Promise<IOSProperties> { return Object.create(null); }
|
||||
async getOSStatistics(): Promise<IOSStatistics> { return Object.create(null); }
|
||||
async getOSVirtualMachineHint(): Promise<number> { return 0; }
|
||||
|
|
|
@ -58,7 +58,6 @@ import 'vs/workbench/electron-browser/desktop.main';
|
|||
|
||||
|
||||
import 'vs/workbench/services/search/electron-browser/searchService';
|
||||
import 'vs/workbench/services/textfile/electron-browser/nativeTextFileService';
|
||||
import 'vs/workbench/services/extensions/electron-browser/extensionService';
|
||||
import 'vs/workbench/services/remote/electron-browser/tunnelServiceImpl';
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import 'vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution';
|
|||
|
||||
//#region --- workbench services
|
||||
|
||||
import 'vs/workbench/services/textfile/electron-sandbox/nativeTextFileService';
|
||||
import 'vs/workbench/services/dialogs/electron-sandbox/fileDialogService';
|
||||
import 'vs/workbench/services/workspaces/electron-sandbox/workspacesService';
|
||||
import 'vs/workbench/services/textMate/electron-sandbox/textMateService';
|
||||
|
|
Loading…
Reference in a new issue