diff options
124 files changed, 1744 insertions, 1379 deletions
@@ -1,4 +1,4 @@ disturl "https://electronjs.org/headers" -target "13.5.1" +target "13.5.2" runtime "electron" build_from_source "true" diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index f21bd633a76..ce62104f949 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -233,7 +233,7 @@ steps: APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) APP_NAME="`ls $APP_ROOT | head -n 1`" VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin" \ - yarn smoketest-no-compile --build "$APP_ROOT/$APP_NAME" --remote --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests + yarn smoketest-no-compile --build "$APP_ROOT/$APP_NAME" --remote --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests-remote timeoutInMinutes: 5 displayName: Run smoke tests (Remote) condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 5c742c2503c..a1bcb6a8a90 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -220,7 +220,7 @@ steps: set -e APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --build "$APP_PATH" --remote --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader" --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests + yarn smoketest-no-compile --build "$APP_PATH" --remote --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader" --screenshots $(Build.SourcesDirectory)/.build/logs/smoke-tests-remote timeoutInMinutes: 5 displayName: Run smoke tests (Remote) condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index d365c165f8b..ccd863c615b 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -216,7 +216,7 @@ steps: $ErrorActionPreference = "Stop" $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --build "$AppRoot" --remote } + exec { yarn smoketest-no-compile --build "$AppRoot" --remote --screenshots $(Build.SourcesDirectory)\.build\logs\smoke-tests-remote } displayName: Run smoke tests (Remote) timeoutInMinutes: 5 condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) diff --git a/build/lib/eslint/vscode-dts-region-comments.js b/build/lib/eslint/vscode-dts-region-comments.js index 7d37a20fb6a..2dc9487314e 100644 --- a/build/lib/eslint/vscode-dts-region-comments.js +++ b/build/lib/eslint/vscode-dts-region-comments.js @@ -7,7 +7,7 @@ module.exports = new class ApiEventNaming { constructor() { this.meta = { messages: { - comment: 'region comments should start with the GH issue link, e.g #region https://github.com/microsoft/vscode/issues/<number>', + comment: 'region comments should start with a camel case identifier, `:`, then either a GH issue link or owner, e.g #region myProposalName: https://github.com/microsoft/vscode/issues/<number>', } }; } @@ -15,14 +15,14 @@ module.exports = new class ApiEventNaming { const sourceCode = context.getSourceCode(); return { ['Program']: (_node) => { - for (let comment of sourceCode.getAllComments()) { + for (const comment of sourceCode.getAllComments()) { if (comment.type !== 'Line') { continue; } - if (!comment.value.match(/^\s*#region /)) { + if (!/^\s*#region /.test(comment.value)) { continue; } - if (!comment.value.match(/https:\/\/github.com\/microsoft\/vscode\/issues\/\d+/i)) { + if (!/^\s*#region ([a-z]+): (@[a-z]+|https:\/\/github.com\/microsoft\/vscode\/issues\/\d+)/i.test(comment.value)) { context.report({ node: comment, messageId: 'comment', diff --git a/build/lib/eslint/vscode-dts-region-comments.ts b/build/lib/eslint/vscode-dts-region-comments.ts index 175fb9040ab..63139a50e3b 100644 --- a/build/lib/eslint/vscode-dts-region-comments.ts +++ b/build/lib/eslint/vscode-dts-region-comments.ts @@ -9,7 +9,7 @@ export = new class ApiEventNaming implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { messages: { - comment: 'region comments should start with the GH issue link, e.g #region https://github.com/microsoft/vscode/issues/<number>', + comment: 'region comments should start with a camel case identifier, `:`, then either a GH issue link or owner, e.g #region myProposalName: https://github.com/microsoft/vscode/issues/<number>', } }; @@ -17,18 +17,16 @@ export = new class ApiEventNaming implements eslint.Rule.RuleModule { const sourceCode = context.getSourceCode(); - return { ['Program']: (_node: any) => { - - for (let comment of sourceCode.getAllComments()) { + for (const comment of sourceCode.getAllComments()) { if (comment.type !== 'Line') { continue; } - if (!comment.value.match(/^\s*#region /)) { + if (!/^\s*#region /.test(comment.value)) { continue; } - if (!comment.value.match(/https:\/\/github.com\/microsoft\/vscode\/issues\/\d+/i)) { + if (!/^\s*#region ([a-z]+): (@[a-z]+|https:\/\/github.com\/microsoft\/vscode\/issues\/\d+)/i.test(comment.value)) { context.report({ node: <any>comment, messageId: 'comment', diff --git a/build/monaco/package.json b/build/monaco/package.json index b987a610d72..2e30e04628e 100644 --- a/build/monaco/package.json +++ b/build/monaco/package.json @@ -1,7 +1,7 @@ { "name": "monaco-editor-core", "private": true, - "version": "0.29.2", + "version": "0.30.0", "description": "A browser based code editor", "author": "Microsoft Corporation", "license": "MIT", diff --git a/extensions/git/src/askpass.sh b/extensions/git/src/askpass.sh index d19b62affa3..c85c64ad2fd 100755 --- a/extensions/git/src/askpass.sh +++ b/extensions/git/src/askpass.sh @@ -1,5 +1,5 @@ #!/bin/sh VSCODE_GIT_ASKPASS_PIPE=`mktemp` -ELECTRON_RUN_AS_NODE="1" VSCODE_GIT_ASKPASS_PIPE="$VSCODE_GIT_ASKPASS_PIPE" "$VSCODE_GIT_ASKPASS_NODE" "$VSCODE_GIT_ASKPASS_MAIN" $* +ELECTRON_RUN_AS_NODE="1" VSCODE_GIT_ASKPASS_PIPE="$VSCODE_GIT_ASKPASS_PIPE" "$VSCODE_GIT_ASKPASS_NODE" "$VSCODE_GIT_ASKPASS_EXTRA_ARGS" "$VSCODE_GIT_ASKPASS_MAIN" $* cat $VSCODE_GIT_ASKPASS_PIPE rm $VSCODE_GIT_ASKPASS_PIPE diff --git a/extensions/git/src/askpass.ts b/extensions/git/src/askpass.ts index 7fc29a371ad..e6c21efa9cb 100644 --- a/extensions/git/src/askpass.ts +++ b/extensions/git/src/askpass.ts @@ -83,6 +83,7 @@ export class Askpass implements IIPCHandler { ...this.ipc.getEnv(), GIT_ASKPASS: path.join(__dirname, 'askpass.sh'), VSCODE_GIT_ASKPASS_NODE: process.execPath, + VSCODE_GIT_ASKPASS_EXTRA_ARGS: (process.versions['electron'] && process.versions['microsoft-build']) ? '--ms-enable-electron-run-as-node' : '', VSCODE_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js') }; } diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 9a2f9197378..a57e5f73fac 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -300,7 +300,7 @@ export class Model implements IRemoteSourceProviderRegistry, IPushErrorHandlerRe const repository = new Repository(this.git.open(repositoryRoot, dotGit), this, this, this.globalState, this.outputChannel); this.open(repository); - await repository.status(); + repository.status(); // do not await this, we want SCM to know about the repo asap } catch (ex) { // noop this.outputChannel.appendLine(`Opening repository for path='${repoPath}' failed; ex=${ex}`); diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index 2586457e2c2..fe25756c0ea 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -29,10 +29,6 @@ export const activate: ActivationFunction<void> = (ctx) => { const style = document.createElement('style'); style.textContent = ` - #preview { - font-size: 1.1em; - } - .emptyMarkdownCell::before { content: "${document.documentElement.style.getPropertyValue('--notebook-cell-markup-empty-content')}"; font-style: italic; @@ -165,7 +161,6 @@ export const activate: ActivationFunction<void> = (ctx) => { pre code { font-family: var(--vscode-editor-font-family); - font-size: var(--vscode-editor-font-size); line-height: 1.357em; white-space: pre-wrap; diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index ff5f00cb949..499f168afff 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -120,6 +120,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { private imageInfo: { readonly id: string, readonly width: number, readonly height: number; }[] = []; private readonly _fileWatchersBySrc = new Map</* src: */ string, vscode.FileSystemWatcher>(); + private readonly _onScrollEmitter = this._register(new vscode.EventEmitter<LastScrollLocation>()); public readonly onScroll = this._onScrollEmitter.event; @@ -262,13 +263,13 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { * The first call immediately refreshes the preview, * calls happening shortly thereafter are debounced. */ - public refresh() { + public refresh(forceUpdate: boolean = false) { // Schedule update if none is pending if (!this.throttleTimer) { if (this.firstUpdate) { this.updatePreview(true); } else { - this.throttleTimer = setTimeout(() => this.updatePreview(true), this.delay); + this.throttleTimer = setTimeout(() => this.updatePreview(forceUpdate), this.delay); } } @@ -333,7 +334,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { return; } - const shouldReloadPage = !this.currentVersion || this.currentVersion.resource.toString() !== pendingVersion.resource.toString(); + const shouldReloadPage = forceUpdate || !this.currentVersion || this.currentVersion.resource.toString() !== pendingVersion.resource.toString(); this.currentVersion = pendingVersion; const content = await (shouldReloadPage @@ -429,7 +430,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { if (uri && uri.scheme === 'file' && !this._fileWatchersBySrc.has(src)) { const watcher = vscode.workspace.createFileSystemWatcher(uri.fsPath); watcher.onDidChange(() => { - this.refresh(); + this.refresh(true); }); this._fileWatchersBySrc.set(src, watcher); } diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts index 4e6be890442..4709ef62db0 100644 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ b/extensions/microsoft-authentication/src/AADHelper.ts @@ -240,6 +240,7 @@ export class AzureActiveDirectoryService { } if (added.length || removed.length) { + Logger.info(`Sending change event with ${added.length} added and ${removed.length} removed`); onDidChangeSessions.fire({ added: added, removed: removed, changed: [] }); } } @@ -380,7 +381,7 @@ export class AzureActiveDirectoryService { throw codeRes.err; } token = await this.exchangeCodeForToken(codeRes.code, codeVerifier, scope); - this.setToken(token, scope); + await this.setToken(token, scope); Logger.info(`Login successful for scopes: ${scope}`); res.writeHead(302, { Location: '/' }); const session = await this.convertToSession(token); @@ -491,7 +492,7 @@ export class AzureActiveDirectoryService { } const token = await this.exchangeCodeForToken(code, verifier, scope); - this.setToken(token, scope); + await this.setToken(token, scope); const session = await this.convertToSession(token); resolve(session); @@ -509,6 +510,7 @@ export class AzureActiveDirectoryService { } private async setToken(token: IToken, scope: string): Promise<void> { + Logger.info(`Setting token for scopes: ${scope}`); const existingTokenIndex = this._tokens.findIndex(t => t.sessionId === token.sessionId); if (existingTokenIndex > -1) { this._tokens.splice(existingTokenIndex, 1, token); @@ -522,6 +524,7 @@ export class AzureActiveDirectoryService { this._refreshTimeouts.set(token.sessionId, setTimeout(async () => { try { const refreshedToken = await this.refreshToken(token.refreshToken, scope, token.sessionId); + Logger.info('Triggering change session event...'); onDidChangeSessions.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] }); } catch (e) { if (e.message === REFRESH_NETWORK_FAILURE) { @@ -537,7 +540,7 @@ export class AzureActiveDirectoryService { }, 1000 * (token.expiresIn - 30))); } - this.storeTokenData(); + await this.storeTokenData(); } private getTokenFromResponse(json: ITokenResponse, scope: string, existingId?: string): IToken { @@ -649,7 +652,7 @@ export class AzureActiveDirectoryService { if (result.ok) { const json = await result.json(); const token = this.getTokenFromResponse(json, scope, sessionId); - this.setToken(token, scope); + await this.setToken(token, scope); Logger.info(`Token refresh success for scopes: ${token.scope}`); return token; } else { diff --git a/extensions/vscode-test-resolver/src/extension.ts b/extensions/vscode-test-resolver/src/extension.ts index 63efe6d8bf9..9e54bd40dbe 100644 --- a/extensions/vscode-test-resolver/src/extension.ts +++ b/extensions/vscode-test-resolver/src/extension.ts @@ -83,6 +83,10 @@ export function activate(context: vscode.ExtensionContext) { const commandArgs = ['--port=0', '--disable-telemetry']; const env = getNewEnv(); const remoteDataDir = process.env['TESTRESOLVER_DATA_FOLDER'] || path.join(os.homedir(), serverDataFolderName || `${dataFolderName}-testresolver`); + const logsDir = process.env['TESTRESOLVER_LOGS_FOLDER']; + if (logsDir) { + commandArgs.push('--logsPath', logsDir); + } env['VSCODE_AGENT_FOLDER'] = remoteDataDir; outputChannel.appendLine(`Using data folder at ${remoteDataDir}`); diff --git a/package.json b/package.json index e6a02348d76..368265d0742 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.63.0", - "distro": "f4a21145f5ec390f681f48c7140bf4f60d2987ac", + "distro": "cc8976e5470edb06e4b3abd29841ffe7bf5042d2", "author": { "name": "Microsoft Corporation" }, @@ -59,7 +59,7 @@ }, "dependencies": { "@microsoft/applicationinsights-web": "^2.6.4", - "@parcel/watcher": "2.0.0", + "@parcel/watcher": "2.0.1", "@vscode/sqlite3": "4.0.12", "@vscode/vscode-languagedetection": "1.0.21", "applicationinsights": "1.0.8", @@ -84,12 +84,12 @@ "vscode-regexpp": "^3.1.0", "vscode-ripgrep": "^1.12.1", "vscode-textmate": "5.4.1", - "xterm": "4.15.0-beta.10", - "xterm-addon-search": "0.9.0-beta.5", - "xterm-addon-serialize": "0.7.0-beta.2", - "xterm-addon-unicode11": "0.3.0", - "xterm-addon-webgl": "0.12.0-beta.15", - "xterm-headless": "4.15.0-beta.10", + "xterm": "4.16.0-beta.2", + "xterm-addon-search": "0.9.0-beta.6", + "xterm-addon-serialize": "0.7.0-beta.3", + "xterm-addon-unicode11": "0.4.0-beta.1", + "xterm-addon-webgl": "0.12.0-beta.16", + "xterm-headless": "4.16.0-beta.2", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/package.json b/remote/package.json index 1851794ddd6..501827c3f79 100644 --- a/remote/package.json +++ b/remote/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@microsoft/applicationinsights-web": "^2.6.4", - "@parcel/watcher": "2.0.0", + "@parcel/watcher": "2.0.1", "@vscode/vscode-languagedetection": "1.0.21", "applicationinsights": "1.0.8", "cookie": "^0.4.0", @@ -24,12 +24,12 @@ "vscode-regexpp": "^3.1.0", "vscode-ripgrep": "^1.12.1", "vscode-textmate": "5.4.1", - "xterm": "4.15.0-beta.10", - "xterm-addon-search": "0.9.0-beta.5", - "xterm-addon-serialize": "0.7.0-beta.2", - "xterm-addon-unicode11": "0.3.0", - "xterm-addon-webgl": "0.12.0-beta.15", - "xterm-headless": "4.15.0-beta.10", + "xterm": "4.16.0-beta.2", + "xterm-addon-search": "0.9.0-beta.6", + "xterm-addon-serialize": "0.7.0-beta.3", + "xterm-addon-unicode11": "0.4.0-beta.1", + "xterm-addon-webgl": "0.12.0-beta.16", + "xterm-headless": "4.16.0-beta.2", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index 307a919a301..f69e0798f01 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -10,9 +10,9 @@ "tas-client-umd": "0.1.4", "vscode-oniguruma": "1.5.1", "vscode-textmate": "5.4.1", - "xterm": "4.15.0-beta.10", - "xterm-addon-search": "0.9.0-beta.5", - "xterm-addon-unicode11": "0.3.0", - "xterm-addon-webgl": "0.12.0-beta.15" + "xterm": "4.16.0-beta.2", + "xterm-addon-search": "0.9.0-beta.6", + "xterm-addon-unicode11": "0.4.0-beta.1", + "xterm-addon-webgl": "0.12.0-beta.16" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 384abe27126..4bf3abe60b5 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -113,22 +113,22 @@ vscode-textmate@5.4.1: resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.4.1.tgz#09d566724fc76b60b3ad9791eebf1f0b50f29e5a" integrity sha512-4CvPHmfuZQaXrcCpathdh6jo7myuR+MU8BvscgQADuponpbqfmu2rwTOtCXhGwwEgStvJF8V4s9FwMKRVLNmKQ== -xterm-addon-search@0.9.0-beta.5: - version "0.9.0-beta.5" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.5.tgz#e0e60a203d1c9d6c8af933648a46865dba299302" - integrity sha512-ylfqim0ISBvuuX83LQwgu/06p5GC545QsAo9SssXw03TPpIrcd0zwaVMEnhOftSIzM9EKRRsyx3GbBjgUdiF5w== - -xterm-addon-unicode11@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0.tgz#e4435c3c91a5294a7eb8b79c380acbb28a659463" - integrity sha512-x5fHDZT2j9tlTlHnzPHt++9uKZ2kJ/lYQOj3L6xJA22xoJsS8UQRw/5YIFg2FUHqEAbV77Z1fZij/9NycMSH/A== - -xterm-addon-webgl@0.12.0-beta.15: - version "0.12.0-beta.15" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.15.tgz#9ae82127f2a39b3cb7f5ae45a6af223810c933d4" - integrity sha512-LWZ3iLspQOCc26OoT8qa+SuyuIcn2cAMRbBkinOuQCk4aW5kjovIrGovj9yVAcXNvOBnPm3sUqmnwGlN579kDA== - -xterm@4.15.0-beta.10: - version "4.15.0-beta.10" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.15.0-beta.10.tgz#8cda3d7885e8345f2fc6cf9275a43f3833d29acf" - integrity sha512-valoh5ZcY/y7Pe+ffgcSAEFeuZfjzVeUUXcthdxTTsrGEiU1s4QR2EOg4U5jn5wye/Nc6mSfLW3s79R6Ac186w== +xterm-addon-search@0.9.0-beta.6: + version "0.9.0-beta.6" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.6.tgz#8b016baac5580dc0ba93bb52610bc4f5776d3b17" + integrity sha512-UAEzas4O+NrF7BSGf0C9N5ngAkmbtr/hSTFvLAM/Rw7EfLUatf8aLMqAWZTggRGMwDjuqR0GXJI4+e5QrJhQfw== + +xterm-addon-unicode11@0.4.0-beta.1: + version "0.4.0-beta.1" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.1.tgz#aeefd26e87bad15d8dfd8a1e0b804fe408c9b882" + integrity sha512-pG8mpxnqpYDry0e20vuEFKhd4kKIcLLNwdNftNvfo+R/EjYRnTYnF+H8L+7eQHq6hqDH61xCEP4H4qR2CyT4pg== + +xterm-addon-webgl@0.12.0-beta.16: + version "0.12.0-beta.16" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.16.tgz#63a0f1f5be9e66286e035448e2011e3065769ad5" + integrity sha512-g6v3RegOhSsD9Zt8ArWBMNT30QyPUlIWEIvP/xLHAluUZ1S5sDjFyZDB0nJAyn9MwQozJpwb0ylYO1nznN/TzA== + +xterm@4.16.0-beta.2: + version "4.16.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.16.0-beta.2.tgz#251beef21a232143f272da74c7005bc4d832ca79" + integrity sha512-PD0agueJ7qvbn1/QhZriAQXf+ykaoPKgQN9qiIGf88VMxHs8T47MYHW/+qPsrXagTmbrENtncughTIzOzv8Q5Q== diff --git a/remote/yarn.lock b/remote/yarn.lock index 091d7fe7dad..6eb9ea16e1b 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -83,10 +83,10 @@ resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.4.tgz#40e1c0ad20743fcee1604a7df2c57faf0aa1af87" integrity sha512-Ot53G927ykMF8cQ3/zq4foZtdk+Tt1YpX7aUTHxBU7UHNdkEiBvBfZSq+rnlUmKCJ19VatwPG4mNzvcGpBj4og== -"@parcel/watcher@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.0.tgz#ebe992a4838b35c3da9a568eb95a71cb26ddf551" - integrity sha512-ByalKmRRXNNAhwZ0X1r0XeIhh1jG8zgdlvjgHk9ZV3YxiersEGNQkwew+RfqJbIL4gOJfvC2ey6lg5kaeRainw== +"@parcel/watcher@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.1.tgz#ec4bb6c43d9588a1ffd3d2abe6df5b501463c62d" + integrity sha512-XegFF4L8sFn1RzU5KKOZxXUuzgOSwd6+X2ez3Cy6MVhYMbiLZ1moceMTqDhuT3N8DNbdumK3zP1wojsIsnX40w== dependencies: node-addon-api "^3.2.1" node-gyp-build "^4.3.0" @@ -541,35 +541,35 @@ xregexp@2.0.0: resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM= -xterm-addon-search@0.9.0-beta.5: - version "0.9.0-beta.5" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.5.tgz#e0e60a203d1c9d6c8af933648a46865dba299302" - integrity sha512-ylfqim0ISBvuuX83LQwgu/06p5GC545QsAo9SssXw03TPpIrcd0zwaVMEnhOftSIzM9EKRRsyx3GbBjgUdiF5w== - -xterm-addon-serialize@0.7.0-beta.2: - version "0.7.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.7.0-beta.2.tgz#ced9f664c74ab88448e7b63850721bc272aa6806" - integrity sha512-KuSwdx2AAliUv7SvjKYUKHrB7vscbHLv8QsmwSDI3pgL1BpjyLJ8LR99iFFfuNpPW9CG4TX6adKPIJXtqiN3Vg== - -xterm-addon-unicode11@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0.tgz#e4435c3c91a5294a7eb8b79c380acbb28a659463" - integrity sha512-x5fHDZT2j9tlTlHnzPHt++9uKZ2kJ/lYQOj3L6xJA22xoJsS8UQRw/5YIFg2FUHqEAbV77Z1fZij/9NycMSH/A== - -xterm-addon-webgl@0.12.0-beta.15: - version "0.12.0-beta.15" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.15.tgz#9ae82127f2a39b3cb7f5ae45a6af223810c933d4" - integrity sha512-LWZ3iLspQOCc26OoT8qa+SuyuIcn2cAMRbBkinOuQCk4aW5kjovIrGovj9yVAcXNvOBnPm3sUqmnwGlN579kDA== - -xterm-headless@4.15.0-beta.10: - version "4.15.0-beta.10" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.15.0-beta.10.tgz#2dbcb40dfda7ecfdacc7b63889c80da965480ce7" - integrity sha512-kDAzmaeFX8hAJvbPUJc4dW4SoVBSg4onCVOPyi8QTmxZz1o7I9mX4U7DX1v3PceyfrU27A9k6zXjuTuPjxCCSQ== - -xterm@4.15.0-beta.10: - version "4.15.0-beta.10" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.15.0-beta.10.tgz#8cda3d7885e8345f2fc6cf9275a43f3833d29acf" - integrity sha512-valoh5ZcY/y7Pe+ffgcSAEFeuZfjzVeUUXcthdxTTsrGEiU1s4QR2EOg4U5jn5wye/Nc6mSfLW3s79R6Ac186w== +xterm-addon-search@0.9.0-beta.6: + version "0.9.0-beta.6" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.6.tgz#8b016baac5580dc0ba93bb52610bc4f5776d3b17" + integrity sha512-UAEzas4O+NrF7BSGf0C9N5ngAkmbtr/hSTFvLAM/Rw7EfLUatf8aLMqAWZTggRGMwDjuqR0GXJI4+e5QrJhQfw== + +xterm-addon-serialize@0.7.0-beta.3: + version "0.7.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.7.0-beta.3.tgz#a8ce52a59685041bd3b6d6a2a77a3df8bc3daf29" + integrity sha512-fgB0h8JiSN1cOMh3slenysprnGfFwbDZ/D38WA0Pdjxf3YDy4j2SwoUajlvXpkFWR7sHjVHmgpw/nHggO731KA== + +xterm-addon-unicode11@0.4.0-beta.1: + version "0.4.0-beta.1" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.1.tgz#aeefd26e87bad15d8dfd8a1e0b804fe408c9b882" + integrity sha512-pG8mpxnqpYDry0e20vuEFKhd4kKIcLLNwdNftNvfo+R/EjYRnTYnF+H8L+7eQHq6hqDH61xCEP4H4qR2CyT4pg== + +xterm-addon-webgl@0.12.0-beta.16: + version "0.12.0-beta.16" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.16.tgz#63a0f1f5be9e66286e035448e2011e3065769ad5" + integrity sha512-g6v3RegOhSsD9Zt8ArWBMNT30QyPUlIWEIvP/xLHAluUZ1S5sDjFyZDB0nJAyn9MwQozJpwb0ylYO1nznN/TzA== + +xterm-headless@4.16.0-beta.2: + version "4.16.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.16.0-beta.2.tgz#62e66a655a30c814e3a311f3542d42c87446cecd" + integrity sha512-g92HDaIZcu1TQFlrjq2CHtt7A2qAwSD6s8RwncU/7u1kaq2e7rc9O3OKfu5v3QzgaRSKuugtquMr0OTKjkmLUg== + +xterm@4.16.0-beta.2: + version "4.16.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.16.0-beta.2.tgz#251beef21a232143f272da74c7005bc4d832ca79" + integrity sha512-PD0agueJ7qvbn1/QhZriAQXf+ykaoPKgQN9qiIGf88VMxHs8T47MYHW/+qPsrXagTmbrENtncughTIzOzv8Q5Q== yauzl@^2.9.2: version "2.10.0" diff --git a/resources/darwin/bin/code.sh b/resources/darwin/bin/code.sh index 6a9a9ec737c..20aee89edf9 100755 --- a/resources/darwin/bin/code.sh +++ b/resources/darwin/bin/code.sh @@ -7,5 +7,5 @@ function realpath() { python -c "import os,sys; print(os.path.realpath(sys.argv[ CONTENTS="$(dirname "$(dirname "$(dirname "$(dirname "$(realpath "$0")")")")")" ELECTRON="$CONTENTS/MacOS/Electron" CLI="$CONTENTS/Resources/app/out/cli.js" -ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" "$@" +ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --ms-enable-electron-run-as-node "$@" exit $? diff --git a/resources/linux/bin/code.sh b/resources/linux/bin/code.sh index 06973937f14..73ef78f62dc 100755 --- a/resources/linux/bin/code.sh +++ b/resources/linux/bin/code.sh @@ -50,5 +50,5 @@ fi ELECTRON="$VSCODE_PATH/@@NAME@@" CLI="$VSCODE_PATH/resources/app/out/cli.js" -ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" "$@" +ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --ms-enable-electron-run-as-node "$@" exit $? diff --git a/resources/server/test/test-remote-integration.bat b/resources/server/test/test-remote-integration.bat index 47ee286f120..c31dff424d2 100644 --- a/resources/server/test/test-remote-integration.bat +++ b/resources/server/test/test-remote-integration.bat @@ -21,8 +21,9 @@ IF "%VSCODEUSERDATADIR%" == "" ( set REMOTE_VSCODE=%AUTHORITY%%EXT_PATH% set VSCODECRASHDIR=%~dp0\..\..\..\.build\crashes -set VSCODELOGSDIR=%~dp0\..\..\..\.build\logs\remote-integration-tests +set VSCODELOGSDIR=%~dp0\..\..\..\.build\logs\integration-tests-remote set TESTRESOLVER_DATA_FOLDER=%TMP%\testresolverdatafolder-%RANDOM%-%TIME:~6,5% +set TESTRESOLVER_LOGS_FOLDER=%VSCODELOGSDIR%\server if "%VSCODE_REMOTE_SERVER_PATH%"=="" ( echo "Using remote server out of sources for integration tests" diff --git a/resources/server/test/test-remote-integration.sh b/resources/server/test/test-remote-integration.sh index 09d14d50c95..71fee7327b4 100755 --- a/resources/server/test/test-remote-integration.sh +++ b/resources/server/test/test-remote-integration.sh @@ -29,7 +29,7 @@ fi export REMOTE_VSCODE=$AUTHORITY$EXT_PATH VSCODECRASHDIR=$ROOT/.build/crashes -VSCODELOGSDIR=$ROOT/.build/logs/remote-integration-tests +VSCODELOGSDIR=$ROOT/.build/logs/integration-tests-remote # Figure out which Electron to use for running tests if [ -z "$INTEGRATION_TEST_ELECTRON_PATH" ] @@ -74,6 +74,7 @@ else fi export TESTRESOLVER_DATA_FOLDER=$TESTRESOLVER_DATA_FOLDER +export TESTRESOLVER_LOGS_FOLDER=$VSCODELOGSDIR/server # Figure out which remote server to use for running tests if [ -z "$VSCODE_REMOTE_SERVER_PATH" ] diff --git a/resources/win32/bin/code.cmd b/resources/win32/bin/code.cmd index 33c640f5dd1..c72e9e28333 100644 --- a/resources/win32/bin/code.cmd +++ b/resources/win32/bin/code.cmd @@ -2,5 +2,5 @@ setlocal set VSCODE_DEV= set ELECTRON_RUN_AS_NODE=1 -"%~dp0..\@@NAME@@.exe" "%~dp0..\resources\app\out\cli.js" %* -endlocal
\ No newline at end of file +"%~dp0..\@@NAME@@.exe" "%~dp0..\resources\app\out\cli.js" --ms-enable-electron-run-as-node %* +endlocal diff --git a/resources/win32/bin/code.sh b/resources/win32/bin/code.sh index 23fbbc9bf20..999a5b5445c 100644 --- a/resources/win32/bin/code.sh +++ b/resources/win32/bin/code.sh @@ -43,7 +43,7 @@ if [ $IN_WSL = true ]; then # use the Remote WSL extension if installed WSL_EXT_ID="ms-vscode-remote.remote-wsl" - ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --locate-extension $WSL_EXT_ID >/tmp/remote-wsl-loc.txt 2>/dev/null </dev/null + ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --ms-enable-electron-run-as-node --locate-extension $WSL_EXT_ID >/tmp/remote-wsl-loc.txt 2>/dev/null </dev/null WSL_EXT_WLOC=$(cat /tmp/remote-wsl-loc.txt) if [ -n "$WSL_EXT_WLOC" ]; then @@ -58,5 +58,5 @@ elif [ -x "$(command -v cygpath)" ]; then else CLI="$VSCODE_PATH/resources/app/out/cli.js" fi -ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" "$@" +ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --ms-enable-electron-run-as-node "$@" exit $? diff --git a/scripts/code-cli.bat b/scripts/code-cli.bat index c2eae24d4d9..2b3c9db3ca3 100644 --- a/scripts/code-cli.bat +++ b/scripts/code-cli.bat @@ -24,7 +24,7 @@ set ELECTRON_ENABLE_LOGGING=1 set ELECTRON_ENABLE_STACK_DUMPING=1 :: Launch Code -%CODE% --inspect=5874 out\cli.js %~dp0.. %* +%CODE% --inspect=5874 out\cli.js --ms-enable-electron-run-as-node %~dp0.. %* goto end :builtin diff --git a/scripts/code-cli.sh b/scripts/code-cli.sh index a792c08532e..cdf75ff785c 100755 --- a/scripts/code-cli.sh +++ b/scripts/code-cli.sh @@ -34,7 +34,7 @@ function code() { VSCODE_DEV=1 \ ELECTRON_ENABLE_LOGGING=1 \ ELECTRON_ENABLE_STACK_DUMPING=1 \ - "$CODE" --inspect=5874 "$ROOT/out/cli.js" . "$@" + "$CODE" --inspect=5874 "$ROOT/out/cli.js" --ms-enable-electron-run-as-node . "$@" } code "$@" diff --git a/scripts/code.sh b/scripts/code.sh index 9caa49b1bf7..08fc867ca12 100755 --- a/scripts/code.sh +++ b/scripts/code.sh @@ -58,7 +58,7 @@ function code-wsl() cd $ROOT export WSLENV=ELECTRON_RUN_AS_NODE/w:VSCODE_DEV/w:$WSLENV local WSL_EXT_ID="ms-vscode-remote.remote-wsl" - local WSL_EXT_WLOC=$(echo "" | VSCODE_DEV=1 ELECTRON_RUN_AS_NODE=1 "$ROOT/.build/electron/Code - OSS.exe" "out/cli.js" --locate-extension $WSL_EXT_ID) + local WSL_EXT_WLOC=$(echo "" | VSCODE_DEV=1 ELECTRON_RUN_AS_NODE=1 "$ROOT/.build/electron/Code - OSS.exe" "out/cli.js" --ms-enable-electron-run-as-node --locate-extension $WSL_EXT_ID) cd $CWD if [ -n "$WSL_EXT_WLOC" ]; then # replace \r\n with \n in WSL_EXT_WLOC diff --git a/scripts/node-electron.bat b/scripts/node-electron.bat index 4ddb95b3cae..c67e2ea607d 100644 --- a/scripts/node-electron.bat +++ b/scripts/node-electron.bat @@ -10,9 +10,9 @@ set NAMESHORT=%NAMESHORT: "=% set NAMESHORT=%NAMESHORT:"=%.exe set CODE=".build\electron\%NAMESHORT%" -%CODE% %* +%CODE% %* --ms-enable-electron-run-as-node popd endlocal -exit /b %errorlevel%
\ No newline at end of file +exit /b %errorlevel% diff --git a/scripts/node-electron.sh b/scripts/node-electron.sh index 0a822b6c383..2bf50817c94 100755 --- a/scripts/node-electron.sh +++ b/scripts/node-electron.sh @@ -26,9 +26,11 @@ export VSCODE_DEV=1 if [[ "$OSTYPE" == "darwin"* ]]; then ulimit -n 4096 ; ELECTRON_RUN_AS_NODE=1 \ "$CODE" \ - "$@" + "$@" \ + --ms-enable-electron-run-as-node else ELECTRON_RUN_AS_NODE=1 \ "$CODE" \ - "$@" + "$@" \ + --ms-enable-electron-run-as-node fi diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 5210ccc55ac..3da6d3c9e62 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -26,6 +26,7 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( compile-extension:markdown-language-features^ compile-extension:typescript-language-features^ compile-extension:vscode-custom-editor-tests^ + compile-extension:vscode-notebook-tests^ compile-extension:emmet^ compile-extension:css-language-features-server^ compile-extension:html-language-features-server^ diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 72e8fdf4d1f..2eb8ce50a73 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -32,6 +32,7 @@ else yarn gulp compile-extension:vscode-api-tests \ compile-extension:vscode-colorize-tests \ compile-extension:vscode-custom-editor-tests \ + compile-extension:vscode-notebook-tests \ compile-extension:markdown-language-features \ compile-extension:typescript-language-features \ compile-extension:emmet \ diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index 334b238ed94..aa9bcfffc0a 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -163,7 +163,6 @@ export class Sash extends Disposable { private el: HTMLElement; private layoutProvider: ISashLayoutProvider; - private hidden: boolean; private orientation!: Orientation; private size: number; private hoverDelay = globalHoverDelay; @@ -317,7 +316,6 @@ export class Sash extends Disposable { this._register(onDidChangeHoverDelay.event(delay => this.hoverDelay = delay)); - this.hidden = false; this.layoutProvider = layoutProvider; this.orthogonalStartSash = options.orthogonalStartSash; @@ -504,22 +502,6 @@ export class Sash extends Disposable { } } - show(): void { - this.hidden = false; - this.el.style.removeProperty('display'); - this.el.setAttribute('aria-hidden', 'false'); - } - - hide(): void { - this.hidden = true; - this.el.style.display = 'none'; - this.el.setAttribute('aria-hidden', 'true'); - } - - isHidden(): boolean { - return this.hidden; - } - private getOrthogonalSash(e: PointerEvent): Sash | undefined { if (!e.target || !(e.target instanceof HTMLElement)) { return undefined; diff --git a/src/vs/base/browser/ui/scrollbar/media/scrollbars.css b/src/vs/base/browser/ui/scrollbar/media/scrollbars.css index 5d7a2dc705a..d50aa58526c 100644 --- a/src/vs/base/browser/ui/scrollbar/media/scrollbars.css +++ b/src/vs/base/browser/ui/scrollbar/media/scrollbars.css @@ -36,7 +36,6 @@ left: 3px; height: 3px; width: 100%; - box-shadow: #DDD 0 6px 6px -6px inset; } .monaco-scrollable-element > .shadow.left { display: block; @@ -44,7 +43,6 @@ left: 0; height: 100%; width: 3px; - box-shadow: #DDD 6px 0 6px -6px inset; } .monaco-scrollable-element > .shadow.top-left-corner { display: block; @@ -53,59 +51,3 @@ height: 3px; width: 3px; } -.monaco-scrollable-element > .shadow.top.left { - box-shadow: #DDD 6px 6px 6px -6px inset; -} - -/* ---------- Default Style ---------- */ - -.vs .monaco-scrollable-element > .scrollbar > .slider { - background: rgba(100, 100, 100, .4); -} -.vs-dark .monaco-scrollable-element > .scrollbar > .slider { - background: rgba(121, 121, 121, .4); -} -.hc-black .monaco-scrollable-element > .scrollbar > .slider { - background: rgba(111, 195, 223, .6); -} - -.monaco-scrollable-element > .scrollbar > .slider:hover { - background: rgba(100, 100, 100, .7); -} -.hc-black .monaco-scrollable-element > .scrollbar > .slider:hover { - background: rgba(111, 195, 223, .8); -} - -.monaco-scrollable-element > .scrollbar > .slider.active { - background: rgba(0, 0, 0, .6); -} -.vs-dark .monaco-scrollable-element > .scrollbar > .slider.active { - background: rgba(191, 191, 191, .4); -} -.hc-black .monaco-scrollable-element > .scrollbar > .slider.active { - background: rgba(111, 195, 223, 1); -} - -.vs-dark .monaco-scrollable-element .shadow.top { - box-shadow: none; -} - -.vs-dark .monaco-scrollable-element .shadow.left { - box-shadow: #000 6px 0 6px -6px inset; -} - -.vs-dark .monaco-scrollable-element .shadow.top.left { - box-shadow: #000 6px 6px 6px -6px inset; -} - -.hc-black .monaco-scrollable-element .shadow.top { - box-shadow: none; -} - -.hc-black .monaco-scrollable-element .shadow.left { - box-shadow: none; -} - -.hc-black .monaco-scrollable-element .shadow.top.left { - box-shadow: none; -} diff --git a/src/vs/base/browser/ui/table/tableWidget.ts b/src/vs/base/browser/ui/table/tableWidget.ts index c6c174c0d4b..eb298a582c7 100644 --- a/src/vs/base/browser/ui/table/tableWidget.ts +++ b/src/vs/base/browser/ui/table/tableWidget.ts @@ -9,7 +9,7 @@ import { IListOptions, IListOptionsUpdate, IListStyles, List } from 'vs/base/bro import { ISplitViewDescriptor, IView, Orientation, SplitView } from 'vs/base/browser/ui/splitview/splitview'; import { ITableColumn, ITableContextMenuEvent, ITableEvent, ITableGestureEvent, ITableMouseEvent, ITableRenderer, ITableTouchEvent, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; import { ISpliceable } from 'vs/base/common/sequence'; import { IThemable } from 'vs/base/common/styler'; @@ -148,9 +148,11 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable { readonly domNode: HTMLElement; private splitview: SplitView; private list: List<TRow>; - private columnLayoutDisposable: IDisposable; - private cachedHeight: number = 0; private styleElement: HTMLStyleElement; + protected readonly disposables = new DisposableStore(); + + private cachedWidth: number = 0; + private cachedHeight: number = 0; get onDidChangeFocus(): Event<ITableEvent<TRow>> { return this.list.onDidChangeFocus; } get onDidChangeSelection(): Event<ITableEvent<TRow>> { return this.list.onDidChangeSelection; } @@ -196,21 +198,27 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable { views: headers.map(view => ({ size: view.column.weight, view })) }; - this.splitview = new SplitView(this.domNode, { + this.splitview = this.disposables.add(new SplitView(this.domNode, { orientation: Orientation.HORIZONTAL, scrollbarVisibility: ScrollbarVisibility.Hidden, getSashOrthogonalSize: () => this.cachedHeight, descriptor - }); + })); this.splitview.el.style.height = `${virtualDelegate.headerRowHeight}px`; this.splitview.el.style.lineHeight = `${virtualDelegate.headerRowHeight}px`; const renderer = new TableListRenderer(columns, renderers, i => this.splitview.getViewSize(i)); - this.list = new List(user, this.domNode, asListVirtualDelegate(virtualDelegate), [renderer], _options); + this.list = this.disposables.add(new List(user, this.domNode, asListVirtualDelegate(virtualDelegate), [renderer], _options)); + + Event.any(...headers.map(h => h.onDidLayout)) + (([index, size]) => renderer.layoutColumn(index, size), null, this.disposables); - this.columnLayoutDisposable = Event.any(...headers.map(h => h.onDidLayout)) - (([index, size]) => renderer.layoutColumn(index, size)); + this.splitview.onDidSashReset(index => { + const totalWeight = columns.reduce((r, c) => r + c.weight, 0); + const size = columns[index].weight / totalWeight * this.cachedWidth; + this.splitview.resizeView(index, size); + }, null, this.disposables); this.styleElement = createStyleSheet(this.domNode); this.style({}); @@ -248,6 +256,7 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable { height = height ?? getContentHeight(this.domNode); width = width ?? getContentWidth(this.domNode); + this.cachedWidth = width; this.cachedHeight = height; this.splitview.layout(width); @@ -337,8 +346,6 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable { } dispose(): void { - this.splitview.dispose(); - this.list.dispose(); - this.columnLayoutDisposable.dispose(); + this.disposables.dispose(); } } diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts index b9db31257f9..2e3e020856c 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -229,9 +229,9 @@ export function getCaseInsensitive(target: obj, key: string): any { export function filter(obj: obj, predicate: (key: string, value: any) => boolean): obj { const result = Object.create(null); - for (const key of Object.keys(obj)) { - if (predicate(key, obj[key])) { - result[key] = obj[key]; + for (const [key, value] of Object.entries(obj)) { + if (predicate(key, value)) { + result[key] = value; } } return result; diff --git a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts index b7025539667..3736489d8fc 100644 --- a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts +++ b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts @@ -13,8 +13,9 @@ import { INewScrollPosition, ScrollType } from 'vs/editor/common/editorCommon'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; -import { getThemeTypeSelector } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, getThemeTypeSelector } from 'vs/platform/theme/common/themeService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; export class EditorScrollbar extends ViewPart { @@ -180,3 +181,51 @@ export class EditorScrollbar extends ViewPart { this.scrollbar.renderNow(); } } + +registerThemingParticipant((theme, collector) => { + + // Scrollbars + const scrollbarShadowColor = theme.getColor(scrollbarShadow); + if (scrollbarShadowColor) { + collector.addRule(` + .monaco-scrollable-element > .shadow.top { + box-shadow: ${scrollbarShadowColor} 0 6px 6px -6px inset; + } + + .monaco-scrollable-element > .shadow.left { + box-shadow: ${scrollbarShadowColor} 6px 0 6px -6px inset; + } + + .monaco-scrollable-element > .shadow.top.left { + box-shadow: ${scrollbarShadowColor} 6px 6px 6px -6px inset; + } + `); + } + + const scrollbarSliderBackgroundColor = theme.getColor(scrollbarSliderBackground); + if (scrollbarSliderBackgroundColor) { + collector.addRule(` + .monaco-scrollable-element > .scrollbar > .slider { + background: ${scrollbarSliderBackgroundColor}; + } + `); + } + + const scrollbarSliderHoverBackgroundColor = theme.getColor(scrollbarSliderHoverBackground); + if (scrollbarSliderHoverBackgroundColor) { + collector.addRule(` + .monaco-scrollable-element > .scrollbar > .slider:hover { + background: ${scrollbarSliderHoverBackgroundColor}; + } + `); + } + + const scrollbarSliderActiveBackgroundColor = theme.getColor(scrollbarSliderActiveBackground); + if (scrollbarSliderActiveBackgroundColor) { + collector.addRule(` + .monaco-scrollable-element > .scrollbar > .slider.active { + background: ${scrollbarSliderActiveBackgroundColor}; + } + `); + } +}); diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index fae61d04453..6eda6513b9f 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -4373,7 +4373,7 @@ export const EditorOptions = { default: 0, minimum: 0, maximum: 100, - markdownDescription: nls.localize('codeLensFontSize', "Controls the font size in pixels for CodeLens. When set to `0`, the 90% of `#editor.fontSize#` is used.") + markdownDescription: nls.localize('codeLensFontSize', "Controls the font size in pixels for CodeLens. When set to `0`, 90% of `#editor.fontSize#` is used.") })), colorDecorators: register(new EditorBooleanOption( EditorOption.colorDecorators, 'colorDecorators', true, diff --git a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts index 279c419a56c..129a4a9f58f 100644 --- a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts @@ -11,7 +11,6 @@ import { Disposable, IDisposable, MutableDisposable, toDisposable } from 'vs/bas import { commonPrefixLength, commonSuffixLength } from 'vs/base/common/strings'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; @@ -43,8 +42,6 @@ export class InlineCompletionsModel extends Disposable implements GhostTextWidge this._register(commandService.onDidExecuteCommand(e => { // These commands don't trigger onDidType. const commands = new Set([ - UndoCommand.id, - RedoCommand.id, CoreEditingCommands.Tab.id, CoreEditingCommands.DeleteLeft.id, CoreEditingCommands.DeleteRight.id, diff --git a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts index cf1a2e73370..671f8e39ad3 100644 --- a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts @@ -491,7 +491,6 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { this._resizeSash = this._disposables.add(new Sash(this.domNode, this, { orientation: Orientation.HORIZONTAL })); if (!this.options.isResizeable) { - this._resizeSash.hide(); this._resizeSash.state = SashState.Disabled; } diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 1d8403c185b..14b5bbb886a 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -49,8 +49,8 @@ export function getEncodedLanguageId(languageId: string): number { * @event */ export function onLanguage(languageId: string, callback: () => void): IDisposable { - let disposable = StaticServices.modeService.get().onDidEncounterLanguage((languageId) => { - if (languageId === languageId) { + let disposable = StaticServices.modeService.get().onDidEncounterLanguage((encounteredLanguageId) => { + if (encounteredLanguageId === languageId) { // stop listening disposable.dispose(); // invoke actual listener diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 779f91ebf31..9feb2e1bcb3 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -132,6 +132,19 @@ class StandaloneTheme implements IStandaloneTheme { encodedTokensColors = baseData.encodedTokensColors; } } + // Pick up default colors from `editor.foreground` and `editor.background` if available + const editorForeground = this.themeData.colors['editor.foreground']; + const editorBackground = this.themeData.colors['editor.background']; + if (editorForeground || editorBackground) { + const rule: ITokenThemeRule = { token: '' }; + if (editorForeground) { + rule.foreground = editorForeground; + } + if (editorBackground) { + rule.background = editorBackground; + } + rules.push(rule); + } rules = rules.concat(this.themeData.rules); if (this.themeData.encodedTokensColors) { encodedTokensColors = this.themeData.encodedTokensColors; diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index a67a1dc44ea..326a4430832 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -106,4 +106,7 @@ export interface NativeParsedArgs { 'allow-insecure-localhost'?: boolean; 'log-net-log'?: string; 'vmodule'?: string; + + // MS Build command line arg + 'ms-enable-electron-run-as-node'?: boolean; } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 7260f87c2c5..821cbf257f6 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -75,6 +75,7 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = { 'inspect-extensions': { type: 'string', deprecates: 'debugPluginHost', args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") }, 'inspect-brk-extensions': { type: 'string', deprecates: 'debugBrkPluginHost', args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") }, 'disable-gpu': { type: 'boolean', cat: 't', description: localize('disableGPU', "Disable GPU hardware acceleration.") }, + 'ms-enable-electron-run-as-node': { type: 'boolean' }, 'max-memory': { type: 'string', cat: 't', description: localize('maxMemory', "Max memory size for a window (in Mbytes)."), args: 'memory' }, 'telemetry': { type: 'boolean', cat: 't', description: localize('telemetry', "Shows all telemetry events which VS code collects.") }, diff --git a/src/vs/platform/environment/node/shellEnv.ts b/src/vs/platform/environment/node/shellEnv.ts index de217e36abe..a80d1f69863 100644 --- a/src/vs/platform/environment/node/shellEnv.ts +++ b/src/vs/platform/environment/node/shellEnv.ts @@ -127,13 +127,14 @@ async function doResolveUnixShellEnv(logService: ILogService, token: Cancellatio // handle popular non-POSIX shells const name = basename(systemShellUnix); let command: string, shellArgs: Array<string>; + const extraArgs = (process.versions['electron'] && process.versions['microsoft-build']) ? '--ms-enable-electron-run-as-node' : ''; if (/^pwsh(-preview)?$/.test(name)) { // Older versions of PowerShell removes double quotes sometimes so we use "double single quotes" which is how // you escape single quotes inside of a single quoted string. - command = `& '${process.execPath}' -p '''${mark}'' + JSON.stringify(process.env) + ''${mark}'''`; + command = `& '${process.execPath}' ${extraArgs} -p '''${mark}'' + JSON.stringify(process.env) + ''${mark}'''`; shellArgs = ['-Login', '-Command']; } else { - command = `'${process.execPath}' -p '"${mark}" + JSON.stringify(process.env) + "${mark}"'`; + command = `'${process.execPath}' ${extraArgs} -p '"${mark}" + JSON.stringify(process.env) + "${mark}"'`; if (name === 'tcsh') { shellArgs = ['-ic']; diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 875f8dc075f..3c14449399f 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -484,7 +484,6 @@ export class WorkbenchTable<TRow> extends Table<TRow> { private horizontalScrolling: boolean | undefined; private _styler: IDisposable | undefined; private _useAltAsMultipleSelectionModifier: boolean; - private readonly disposables: DisposableStore; private navigator: TableResourceNavigator<TRow>; get onDidOpen(): Event<IOpenEvent<TRow | undefined>> { return this.navigator.onDidOpen; } @@ -513,7 +512,6 @@ export class WorkbenchTable<TRow> extends Table<TRow> { } ); - this.disposables = new DisposableStore(); this.disposables.add(workbenchListOptionsDisposable); this.contextKeyService = createScopedContextKeyService(contextKeyService, this); diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index da5cb11136c..37d19f0bcad 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -57,7 +57,7 @@ else { // Running out of sources if (Object.keys(product).length === 0) { Object.assign(product, { - version: '1.62.0-dev', + version: '1.63.0-dev', nameShort: 'Code - OSS Dev', nameLong: 'Code - OSS Dev', applicationName: 'code-oss', diff --git a/src/vs/platform/userDataSync/common/userDataSyncAccount.ts b/src/vs/platform/userDataSync/common/userDataSyncAccount.ts index 514e3d00e6c..1c5c6894c74 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncAccount.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncAccount.ts @@ -6,7 +6,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncLogService, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync'; export interface IUserDataSyncAccount { readonly authenticationProviderId: string; @@ -39,10 +39,12 @@ export class UserDataSyncAccountService extends Disposable implements IUserDataS private wasTokenFailed: boolean = false; constructor( - @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService + @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, ) { super(); this._register(userDataSyncStoreService.onTokenFailed(() => { + this.logService.info('Settings Sync auth token failed', this.account?.authenticationProviderId, this.wasTokenFailed); this.updateAccount(undefined); this._onTokenFailed.fire(this.wasTokenFailed); this.wasTokenFailed = true; diff --git a/src/vs/server/remoteCli.ts b/src/vs/server/remoteCli.ts index fcc82d710ae..28dfc625974 100644 --- a/src/vs/server/remoteCli.ts +++ b/src/vs/server/remoteCli.ts @@ -236,6 +236,7 @@ export function main(desc: ProductDescription, args: string[]): void { } else { const cliCwd = dirname(cliCommand); const env = { ...process.env, ELECTRON_RUN_AS_NODE: '1' }; + newCommandline.unshift('--ms-enable-electron-run-as-node'); newCommandline.unshift('resources/app/out/cli.js'); if (parsedArgs['verbose']) { console.log(`Invoking: ${cliCommand} ${newCommandline.join(' ')} in ${cliCwd}`); diff --git a/src/vs/server/remoteTerminalChannel.ts b/src/vs/server/remoteTerminalChannel.ts index 2c31f0cd99e..5c47282ff63 100644 --- a/src/vs/server/remoteTerminalChannel.ts +++ b/src/vs/server/remoteTerminalChannel.ts @@ -15,7 +15,7 @@ import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { createRandomIPCHandle } from 'vs/base/parts/ipc/node/ipc.net'; import { ILogService } from 'vs/platform/log/common/log'; import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; -import { IPtyService, IShellLaunchConfig, ITerminalProfile, ITerminalsLayoutInfo } from 'vs/platform/terminal/common/terminal'; +import { IPtyService, IShellLaunchConfig, ITerminalProfile } from 'vs/platform/terminal/common/terminal'; import { IGetTerminalLayoutInfoArgs, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { createRemoteURITransformer } from 'vs/server/remoteUriTransformer'; @@ -124,8 +124,8 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel< case '$getProfiles': return this._getProfiles.apply(this, args); case '$getEnvironment': return this._getEnvironment(); case '$getWslPath': return this._getWslPath(args[0]); - case '$getTerminalLayoutInfo': return this._getTerminalLayoutInfo(<IGetTerminalLayoutInfoArgs>args); - case '$setTerminalLayoutInfo': return this._setTerminalLayoutInfo(<ISetTerminalLayoutInfoArgs>args); + case '$getTerminalLayoutInfo': return this._ptyService.getTerminalLayoutInfo(<IGetTerminalLayoutInfoArgs>args); + case '$setTerminalLayoutInfo': return this._ptyService.setTerminalLayoutInfo(<ISetTerminalLayoutInfoArgs>args); case '$serializeTerminalState': return this._ptyService.serializeTerminalState.apply(this._ptyService, args); case '$reviveTerminalProcesses': return this._ptyService.reviveTerminalProcesses.apply(this._ptyService, args); case '$setUnicodeVersion': return this._ptyService.setUnicodeVersion.apply(this._ptyService, args); @@ -315,13 +315,6 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel< return this._ptyService.getWslPath(original); } - private _setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): void { - this._ptyService.setTerminalLayoutInfo(args); - } - - private async _getTerminalLayoutInfo(args: IGetTerminalLayoutInfoArgs): Promise<ITerminalsLayoutInfo | undefined> { - return this._ptyService.getTerminalLayoutInfo(args); - } private _reduceConnectionGraceTime(): Promise<void> { return this._ptyService.reduceConnectionGraceTime(); diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index 18dddc41b8c..96a88b5c886 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -10,7 +10,7 @@ import { IActiveCodeEditor, IViewZone } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { reviveWebviewContentOptions } from 'vs/workbench/api/browser/mainThreadWebviews'; -import { ExtHostContext, ExtHostEditorInsetsShape, IExtHostContext, IWebviewOptions, MainContext, MainThreadEditorInsetsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, ExtHostEditorInsetsShape, IExtHostContext, IWebviewContentOptions, MainContext, MainThreadEditorInsetsShape } from 'vs/workbench/api/common/extHost.protocol'; import { IWebviewService, IWebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; import { extHostNamedCustomer } from '../common/extHostCustomers'; @@ -70,7 +70,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { this._disposables.dispose(); } - async $createEditorInset(handle: number, id: string, uri: UriComponents, line: number, height: number, options: IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise<void> { + async $createEditorInset(handle: number, id: string, uri: UriComponents, line: number, height: number, options: IWebviewContentOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise<void> { let editor: IActiveCodeEditor | undefined; id = id.substr(0, id.indexOf(',')); //todo@jrieken HACK @@ -121,7 +121,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { inset.webview.html = value; } - $setOptions(handle: number, options: IWebviewOptions): void { + $setOptions(handle: number, options: IWebviewContentOptions): void { const inset = this.getInset(handle); inset.webview.contentOptions = reviveWebviewContentOptions(options); } diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 75328bdf9ae..0e36a47285a 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -431,15 +431,6 @@ export class MainThreadSCM implements MainThreadSCMShape { repository.input.visible = visible; } - $setInputBoxFocus(sourceControlHandle: number): void { - const repository = this._repositories.get(sourceControlHandle); - if (!repository) { - return; - } - - repository.input.setFocus(); - } - $showValidationMessage(sourceControlHandle: number, message: string | IMarkdownString, type: InputValidationType) { const repository = this._repositories.get(sourceControlHandle); if (!repository) { diff --git a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts index 1aad68ab99a..d355e0876c4 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts @@ -5,17 +5,17 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { MainThreadWebviews, reviveWebviewContentOptions, reviveWebviewExtension } from 'vs/workbench/api/browser/mainThreadWebviews'; import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { EditorGroupColumn, columnToEditorGroup, editorGroupToColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput'; import { WebviewIcons } from 'vs/workbench/contrib/webviewPanel/browser/webviewIconManager'; import { ICreateWebViewShowOptions, IWebviewWorkbenchService } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService'; +import { columnToEditorGroup, editorGroupToColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -151,13 +151,8 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc extensionData: extHostProtocol.WebviewExtensionDescription, handle: extHostProtocol.WebviewHandle, viewType: string, - initData: { - title: string; - webviewOptions: extHostProtocol.IWebviewOptions; - panelOptions: extHostProtocol.IWebviewPanelOptions; - serializeBuffersForPostMessage: boolean; - }, - showOptions: { viewColumn?: EditorGroupColumn, preserveFocus?: boolean; }, + initData: extHostProtocol.IWebviewInitData, + showOptions: extHostProtocol.WebviewPanelShowOptions, ): void { const mainThreadShowOptions: ICreateWebViewShowOptions = Object.create(null); if (showOptions) { @@ -192,7 +187,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc webview.setName(value); } - public $setIconPath(handle: extHostProtocol.WebviewHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void { + public $setIconPath(handle: extHostProtocol.WebviewHandle, value: extHostProtocol.IWebviewIconPath | undefined): void { const webview = this.getWebviewInput(handle); webview.iconPath = reviveWebviewIcon(value); } @@ -316,12 +311,14 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc } } -function reviveWebviewIcon( - value: { light: UriComponents, dark: UriComponents; } | undefined -): WebviewIcons | undefined { - return value - ? { light: URI.revive(value.light), dark: URI.revive(value.dark) } - : undefined; +function reviveWebviewIcon(value: extHostProtocol.IWebviewIconPath | undefined): WebviewIcons | undefined { + if (!value) { + return undefined; + } + return { + light: URI.revive(value.light), + dark: URI.revive(value.dark), + }; } function reviveWebviewOptions(panelOptions: extHostProtocol.IWebviewPanelOptions): WebviewOptions { diff --git a/src/vs/workbench/api/browser/mainThreadWebviews.ts b/src/vs/workbench/api/browser/mainThreadWebviews.ts index 0870dcc05d6..25b3f61f574 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviews.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviews.ts @@ -55,7 +55,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma webview.html = value; } - public $setOptions(handle: extHostProtocol.WebviewHandle, options: extHostProtocol.IWebviewOptions): void { + public $setOptions(handle: extHostProtocol.WebviewHandle, options: extHostProtocol.IWebviewContentOptions): void { const webview = this.getWebview(handle); webview.contentOptions = reviveWebviewContentOptions(options); } @@ -123,10 +123,13 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma } export function reviveWebviewExtension(extensionData: extHostProtocol.WebviewExtensionDescription): WebviewExtensionDescription { - return { id: extensionData.id, location: URI.revive(extensionData.location) }; + return { + id: extensionData.id, + location: URI.revive(extensionData.location), + }; } -export function reviveWebviewContentOptions(webviewOptions: extHostProtocol.IWebviewOptions): WebviewContentOptions { +export function reviveWebviewContentOptions(webviewOptions: extHostProtocol.IWebviewContentOptions): WebviewContentOptions { return { allowScripts: webviewOptions.enableScripts, allowForms: webviewOptions.enableForms, diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 3313761cc00..84e2a4eb04a 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -226,7 +226,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const authentication: typeof vscode.authentication = { getSession(providerId: string, scopes: readonly string[], options?: vscode.AuthenticationGetSessionOptions) { - if (options?.forceNewSession || options?.silent) { + if (options?.forceNewSession) { checkProposedApiEnabled(extension); } return extHostAuthentication.getSession(extension, providerId, scopes, options as any); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 2291bc2eea0..12c7f9a5bd3 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -618,11 +618,11 @@ export interface MainThreadTelemetryShape extends IDisposable { } export interface MainThreadEditorInsetsShape extends IDisposable { - $createEditorInset(handle: number, id: string, uri: UriComponents, line: number, height: number, options: IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise<void>; + $createEditorInset(handle: number, id: string, uri: UriComponents, line: number, height: number, options: IWebviewContentOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): Promise<void>; $disposeEditorInset(handle: number): void; $setHtml(handle: number, value: string): void; - $setOptions(handle: number, options: IWebviewOptions): void; + $setOptions(handle: number, options: IWebviewContentOptions): void; $postMessage(handle: number, value: any): Promise<boolean>; } @@ -681,12 +681,12 @@ export interface IWebviewPortMapping { readonly extensionHostPort: number; } -export interface IWebviewOptions { +export interface IWebviewContentOptions { readonly enableScripts?: boolean; readonly enableForms?: boolean; readonly enableCommandUris?: boolean; - readonly localResourceRoots?: ReadonlyArray<UriComponents>; - readonly portMapping?: ReadonlyArray<IWebviewPortMapping>; + readonly localResourceRoots?: readonly UriComponents[]; + readonly portMapping?: readonly IWebviewPortMapping[]; } export interface IWebviewPanelOptions { @@ -729,27 +729,34 @@ export interface WebviewMessageArrayBufferReference { export interface MainThreadWebviewsShape extends IDisposable { $setHtml(handle: WebviewHandle, value: string): void; - $setOptions(handle: WebviewHandle, options: IWebviewOptions): void; + $setOptions(handle: WebviewHandle, options: IWebviewContentOptions): void; $postMessage(handle: WebviewHandle, value: string, ...buffers: VSBuffer[]): Promise<boolean> } +export interface IWebviewIconPath { + readonly light: UriComponents; + readonly dark: UriComponents; +} + +export interface IWebviewInitData { + readonly title: string; + readonly webviewOptions: IWebviewContentOptions; + readonly panelOptions: IWebviewPanelOptions; + readonly serializeBuffersForPostMessage: boolean; +} + export interface MainThreadWebviewPanelsShape extends IDisposable { $createWebviewPanel( extension: WebviewExtensionDescription, handle: WebviewHandle, viewType: string, - initData: { - title: string; - webviewOptions: IWebviewOptions; - panelOptions: IWebviewPanelOptions; - serializeBuffersForPostMessage: boolean; - }, + initData: IWebviewInitData, showOptions: WebviewPanelShowOptions, ): void; $disposeWebview(handle: WebviewHandle): void; $reveal(handle: WebviewHandle, showOptions: WebviewPanelShowOptions): void; $setTitle(handle: WebviewHandle, value: string): void; - $setIconPath(handle: WebviewHandle, value: { light: UriComponents, dark: UriComponents; } | undefined): void; + $setIconPath(handle: WebviewHandle, value: IWebviewIconPath | undefined): void; $registerSerializer(viewType: string, options: { serializeBuffersForPostMessage: boolean }): void; $unregisterSerializer(viewType: string): void; @@ -796,7 +803,7 @@ export interface ExtHostWebviewPanelsShape { initData: { title: string; state: any; - webviewOptions: IWebviewOptions; + webviewOptions: IWebviewContentOptions; panelOptions: IWebviewPanelOptions; }, position: EditorGroupColumn, @@ -810,7 +817,7 @@ export interface ExtHostCustomEditorsShape { viewType: string, initData: { title: string; - webviewOptions: IWebviewOptions; + webviewOptions: IWebviewContentOptions; panelOptions: IWebviewPanelOptions; }, position: EditorGroupColumn, @@ -1096,7 +1103,6 @@ export interface MainThreadSCMShape extends IDisposable { $setInputBoxValue(sourceControlHandle: number, value: string): void; $setInputBoxPlaceholder(sourceControlHandle: number, placeholder: string): void; $setInputBoxVisibility(sourceControlHandle: number, visible: boolean): void; - $setInputBoxFocus(sourceControlHandle: number): void; $showValidationMessage(sourceControlHandle: number, message: string | IMarkdownString, type: InputValidationType): void; $setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void; } diff --git a/src/vs/workbench/api/common/extHostCustomEditors.ts b/src/vs/workbench/api/common/extHostCustomEditors.ts index 35eb01c390c..5f9a06d01de 100644 --- a/src/vs/workbench/api/common/extHostCustomEditors.ts +++ b/src/vs/workbench/api/common/extHostCustomEditors.ts @@ -253,7 +253,7 @@ export class ExtHostCustomEditors implements extHostProtocol.ExtHostCustomEditor viewType: string, initData: { title: string; - webviewOptions: extHostProtocol.IWebviewOptions; + webviewOptions: extHostProtocol.IWebviewContentOptions; panelOptions: extHostProtocol.IWebviewPanelOptions; }, position: EditorGroupColumn, diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index 8d7158e8778..186031d7205 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { Disposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import { IExtensionDescription, checkProposedApiEnabled } from 'vs/platform/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogger, ILoggerService } from 'vs/platform/log/common/log'; import { OutputChannelUpdateMode } from 'vs/workbench/contrib/output/common/output'; import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer'; @@ -127,7 +127,7 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { this.channels.set(channel.id, channel); channel.visible = channel.id === this.visibleChannelId; }); - return this.createExtHostOutputChannel(name, extHostOutputChannel, extension); + return this.createExtHostOutputChannel(name, extHostOutputChannel); } private async doCreateOutputChannel(name: string, extension: IExtensionDescription): Promise<ExtHostOutputChannel> { @@ -145,58 +145,42 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { return this.outputDirectoryPromise; } - private createExtHostOutputChannel(name: string, channelPromise: Promise<ExtHostOutputChannel>, extensionDescription: IExtensionDescription): vscode.OutputChannel { - const validate = (channel: ExtHostOutputChannel, checkProposedApi?: boolean) => { - if (checkProposedApi) { - checkProposedApiEnabled(extensionDescription); - } - if (channel.disposed) { + private createExtHostOutputChannel(name: string, channelPromise: Promise<ExtHostOutputChannel>): vscode.OutputChannel { + let disposed = false; + const validate = () => { + if (disposed) { throw new Error('Channel has been closed'); } }; return { get name(): string { return name; }, append(value: string): void { - channelPromise.then(channel => { - validate(channel); - channel.append(value); - }); + validate(); + channelPromise.then(channel => channel.append(value)); }, appendLine(value: string): void { - channelPromise.then(channel => { - validate(channel); - channel.appendLine(value); - }); + validate(); + channelPromise.then(channel => channel.appendLine(value)); }, clear(): void { - channelPromise.then(channel => { - validate(channel); - channel.clear(); - }); + validate(); + channelPromise.then(channel => channel.clear()); }, replace(value: string): void { - channelPromise.then(channel => { - validate(channel, true); - channel.replace(value); - }); + validate(); + channelPromise.then(channel => channel.replace(value)); }, show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { - channelPromise.then(channel => { - validate(channel); - channel.show(columnOrPreserveFocus, preserveFocus); - }); + validate(); + channelPromise.then(channel => channel.show(columnOrPreserveFocus, preserveFocus)); }, hide(): void { - channelPromise.then(channel => { - validate(channel); - channel.hide(); - }); + validate(); + channelPromise.then(channel => channel.hide()); }, dispose(): void { - channelPromise.then(channel => { - validate(channel); - channel.dispose(); - }); + disposed = true; + channelPromise.then(channel => channel.dispose()); } }; } diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index f05cc14db02..96374d8e4be 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -193,7 +193,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx // ---- QuickInput createQuickPick<T extends QuickPickItem>(extensionId: ExtensionIdentifier, enableProposedApi: boolean): QuickPick<T> { - const session: ExtHostQuickPick<T> = new ExtHostQuickPick(extensionId, enableProposedApi, () => this._sessions.delete(session._id)); + const session: ExtHostQuickPick<T> = new ExtHostQuickPick(extensionId, () => this._sessions.delete(session._id)); this._sessions.set(session._id, session); return session; } @@ -531,7 +531,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx private readonly _onDidChangeSelectionEmitter = new Emitter<T[]>(); private readonly _onDidTriggerItemButtonEmitter = new Emitter<QuickPickItemButtonEvent<T>>(); - constructor(extensionId: ExtensionIdentifier, private readonly enableProposedApi: boolean, onDispose: () => void) { + constructor(extensionId: ExtensionIdentifier, onDispose: () => void) { super(extensionId, onDispose); this._disposables.push( this._onDidChangeActiveEmitter, @@ -561,16 +561,13 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx detail: item.detail, picked: item.picked, alwaysShow: item.alwaysShow, - // Proposed API only at the moment - buttons: item.buttons && this.enableProposedApi - ? item.buttons.map<TransferQuickInputButton>((button, i) => { - return { - ...getIconPathOrClass(button), - tooltip: button.tooltip, - handle: i - }; - }) - : undefined, + buttons: item.buttons?.map<TransferQuickInputButton>((button, i) => { + return { + ...getIconPathOrClass(button), + tooltip: button.tooltip, + handle: i + }; + }), })) }); } diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 11aa457b084..26988a5fe22 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -266,16 +266,6 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { // noop } - focus(): void { - checkProposedApiEnabled(this._extension); - - if (!this._visible) { - this.visible = true; - } - - this._proxy.$setInputBoxFocus(this._sourceControlHandle); - } - showValidationMessage(message: string | vscode.MarkdownString, type: vscode.SourceControlInputBoxValidationType) { checkProposedApiEnabled(this._extension); diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index d472ed4414d..2eb67a7bf10 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -176,7 +176,7 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape { this._logService.warn(`${extensionId} created a webview without a content security policy: https://aka.ms/vscode-webview-missing-csp`); } - public createNewWebview(handle: string, options: extHostProtocol.IWebviewOptions, extension: IExtensionDescription): ExtHostWebview { + public createNewWebview(handle: string, options: extHostProtocol.IWebviewContentOptions, extension: IExtensionDescription): ExtHostWebview { const webview = new ExtHostWebview(handle, this._webviewProxy, reviveOptions(options), this.initData, this.workspace, extension, this._deprecationService); this._webviews.set(handle, webview); @@ -202,7 +202,7 @@ export function serializeWebviewOptions( extension: IExtensionDescription, workspace: IExtHostWorkspace | undefined, options: vscode.WebviewOptions, -): extHostProtocol.IWebviewOptions { +): extHostProtocol.IWebviewContentOptions { return { enableCommandUris: options.enableCommandUris, enableScripts: options.enableScripts, @@ -212,7 +212,7 @@ export function serializeWebviewOptions( }; } -export function reviveOptions(options: extHostProtocol.IWebviewOptions): vscode.WebviewOptions { +export function reviveOptions(options: extHostProtocol.IWebviewContentOptions): vscode.WebviewOptions { return { enableCommandUris: options.enableCommandUris, enableScripts: options.enableScripts, diff --git a/src/vs/workbench/api/common/extHostWebviewPanels.ts b/src/vs/workbench/api/common/extHostWebviewPanels.ts index c76c364c392..188bda392c1 100644 --- a/src/vs/workbench/api/common/extHostWebviewPanels.ts +++ b/src/vs/workbench/api/common/extHostWebviewPanels.ts @@ -281,7 +281,7 @@ export class ExtHostWebviewPanels implements extHostProtocol.ExtHostWebviewPanel initData: { title: string; state: any; - webviewOptions: extHostProtocol.IWebviewOptions; + webviewOptions: extHostProtocol.IWebviewContentOptions; panelOptions: extHostProtocol.IWebviewPanelOptions; }, position: EditorGroupColumn diff --git a/src/vs/workbench/browser/parts/editor/editorPanes.ts b/src/vs/workbench/browser/parts/editor/editorPanes.ts index 13e9fbc0e8d..86562c72954 100644 --- a/src/vs/workbench/browser/parts/editor/editorPanes.ts +++ b/src/vs/workbench/browser/parts/editor/editorPanes.ts @@ -12,7 +12,7 @@ import { IEditorPaneRegistry, IEditorPaneDescriptor } from 'vs/workbench/browser import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorProgressService, IOperation, LongRunningOperation } from 'vs/platform/progress/common/progress'; +import { IEditorProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress'; import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; import { Emitter } from 'vs/base/common/event'; import { assertIsDefined } from 'vs/base/common/types'; @@ -146,20 +146,8 @@ export class EditorPanes extends Disposable { // Editor pane const pane = this.doShowEditorPane(descriptor); - // Show progress while setting input after a certain timeout. - // If the workbench is opening be more relaxed about progress - // showing by increasing the delay a little bit to reduce flicker. - const operation = this.editorOperation.start(this.layoutService.isRestored() ? 800 : 3200); - // Apply input to pane - let changed: boolean; - let cancelled: boolean; - try { - changed = await this.doSetInput(pane, operation, editor, options, context); - cancelled = !operation.isCurrent(); - } finally { - operation.stop(); - } + const { changed, cancelled } = await this.doSetInput(pane, editor, options, context); // Focus unless cancelled if (!cancelled) { @@ -263,22 +251,36 @@ export class EditorPanes extends Disposable { this._onDidChangeSizeConstraints.fire(undefined); } - private async doSetInput(editorPane: EditorPane, operation: IOperation, editor: EditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext): Promise<boolean> { - const forceReload = options?.forceReload; - const inputMatches = editorPane.input?.matches(editor); + private async doSetInput(editorPane: EditorPane, editor: EditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext): Promise<{ changed: boolean, cancelled: boolean }> { - // If the input did not change, return early and only apply the options - // unless the options instruct us to force open it even if it is the same - if (inputMatches && !forceReload) { + // If the input did not change, return early and only + // apply the options unless the options instruct us to + // force open it even if it is the same + const inputMatches = editorPane.input?.matches(editor); + if (inputMatches && !options?.forceReload) { editorPane.setOptions(options); + + return { changed: false, cancelled: false }; } - // Otherwise set the input to the editor pane - else { + // Start a new editor input operation to report progress + // and to support cancellation. Any new operation that is + // started will cancel the previous one. + const operation = this.editorOperation.start(this.layoutService.isRestored() ? 800 : 3200); + + // Set the input to the editor pane + let cancelled = false; + try { await editorPane.setInput(editor, options, context, operation.token); + + if (!operation.isCurrent()) { + cancelled = true; + } + } finally { + operation.stop(); } - return !inputMatches; + return { changed: !inputMatches, cancelled }; } private doHideActiveEditorPane(): void { diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index 6971905465e..d7472ac31b2 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/style'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground, toolbarHoverBackground, toolbarActiveBackground, toolbarHoverOutline, listFocusHighlightForeground } from 'vs/platform/theme/common/colorRegistry'; +import { iconForeground, foreground, selectionBackground, focusBorder, listHighlightForeground, inputPlaceholderForeground, toolbarHoverBackground, toolbarActiveBackground, toolbarHoverOutline, listFocusHighlightForeground } from 'vs/platform/theme/common/colorRegistry'; import { WORKBENCH_BACKGROUND, TITLE_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; import { isWeb, isIOS, isMacintosh, isWindows } from 'vs/base/common/platform'; import { createMetaElement } from 'vs/base/browser/dom'; @@ -71,51 +71,6 @@ registerThemingParticipant((theme, collector) => { `); } - // Scrollbars - const scrollbarShadowColor = theme.getColor(scrollbarShadow); - if (scrollbarShadowColor) { - collector.addRule(` - .monaco-workbench .monaco-scrollable-element > .shadow.top { - box-shadow: ${scrollbarShadowColor} 0 6px 6px -6px inset; - } - - .monaco-workbench .monaco-scrollable-element > .shadow.left { - box-shadow: ${scrollbarShadowColor} 6px 0 6px -6px inset; - } - - .monaco-workbench .monaco-scrollable-element > .shadow.top.left { - box-shadow: ${scrollbarShadowColor} 6px 6px 6px -6px inset; - } - `); - } - - const scrollbarSliderBackgroundColor = theme.getColor(scrollbarSliderBackground); - if (scrollbarSliderBackgroundColor) { - collector.addRule(` - .monaco-workbench .monaco-scrollable-element > .scrollbar > .slider { - background: ${scrollbarSliderBackgroundColor}; - } - `); - } - - const scrollbarSliderHoverBackgroundColor = theme.getColor(scrollbarSliderHoverBackground); - if (scrollbarSliderHoverBackgroundColor) { - collector.addRule(` - .monaco-workbench .monaco-scrollable-element > .scrollbar > .slider:hover { - background: ${scrollbarSliderHoverBackgroundColor}; - } - `); - } - - const scrollbarSliderActiveBackgroundColor = theme.getColor(scrollbarSliderActiveBackground); - if (scrollbarSliderActiveBackgroundColor) { - collector.addRule(` - .monaco-workbench .monaco-scrollable-element > .scrollbar > .slider.active { - background: ${scrollbarSliderActiveBackgroundColor}; - } - `); - } - // Focus outline const focusOutline = theme.getColor(focusBorder); if (focusOutline) { diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 02eb0b0f14b..03526042cdf 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -574,13 +574,16 @@ class Launch extends AbstractLaunch implements ILaunch { } catch { // launch.json not found: create one by collecting launch configs from debugConfigProviders content = await this.getInitialConfigurationContent(this.workspace.uri, type, token); - if (content) { - created = true; // pin only if config file is created #8727 - try { - await this.textFileService.write(resource, content); - } catch (error) { - throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error.message)); - } + if (!content) { + // Cancelled + return { editor: null, created: false }; + } + + created = true; // pin only if config file is created #8727 + try { + await this.textFileService.write(resource, content); + } catch (error) { + throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error.message)); } } diff --git a/src/vs/workbench/contrib/externalUriOpener/common/contributedOpeners.ts b/src/vs/workbench/contrib/externalUriOpener/common/contributedOpeners.ts index 7c7ec29e9b9..24a9f06dcee 100644 --- a/src/vs/workbench/contrib/externalUriOpener/common/contributedOpeners.ts +++ b/src/vs/workbench/contrib/externalUriOpener/common/contributedOpeners.ts @@ -19,8 +19,6 @@ interface OpenersMemento { [id: string]: RegisteredExternalOpener; } -/** - */ export class ContributedExternalUriOpenersStore extends Disposable { private static readonly STORAGE_ID = 'externalUriOpeners'; @@ -37,8 +35,8 @@ export class ContributedExternalUriOpenersStore extends Disposable { this._memento = new Memento(ContributedExternalUriOpenersStore.STORAGE_ID, storageService); this._mementoObject = this._memento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); - for (const id of Object.keys(this._mementoObject || {})) { - this.add(id, this._mementoObject[id].extensionId, { isCurrentlyRegistered: false }); + for (const [id, value] of Object.entries(this._mementoObject || {})) { + this.add(id, value.extensionId, { isCurrentlyRegistered: false }); } this.invalidateOpenersOnExtensionsChanged(); diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 20eabfbdff4..aef0c3f87a1 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -15,7 +15,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IFileService, whenProviderRegistered } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/services/output/common/output'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; @@ -24,8 +24,9 @@ import { LogsDataCleaner } from 'vs/workbench/contrib/logs/common/logsDataCleane import { IOutputService } from 'vs/workbench/contrib/output/common/output'; import { supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { IProductService } from 'vs/platform/product/common/productService'; -import { timeout } from 'vs/base/common/async'; -import { getErrorMessage } from 'vs/base/common/errors'; +import { createCancelablePromise, timeout } from 'vs/base/common/async'; +import { canceled, getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; +import { CancellationToken } from 'vs/base/common/cancellation'; const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions); workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(SetLogLevelAction), 'Developer: Set Log Level...', CATEGORIES.Developer.value); @@ -99,24 +100,31 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { await whenProviderRegistered(file, this.fileService); const outputChannelRegistry = Registry.as<IOutputChannelRegistry>(OutputExt.OutputChannels); try { - await this.whenFileExists(file, 1); + const promise = createCancelablePromise(token => this.whenFileExists(file, 1, token)); + this._register(toDisposable(() => promise.cancel())); + await promise; outputChannelRegistry.registerChannel({ id, label, file, log: true }); } catch (error) { - this.logService.error('Error while registering log channel', file.toString(), getErrorMessage(error)); + if (!isPromiseCanceledError(error)) { + this.logService.error('Error while registering log channel', file.toString(), getErrorMessage(error)); + } } } - private async whenFileExists(file: URI, trial: number): Promise<void> { + private async whenFileExists(file: URI, trial: number, token: CancellationToken): Promise<void> { const exists = await this.fileService.exists(file); if (exists) { return; } + if (token.isCancellationRequested) { + throw canceled(); + } if (trial > 10) { throw new Error(`Timed out while waiting for file to be created`); } this.logService.debug(`[Registering Log Channel] File does not exist. Waiting for 1s to retry.`, file.toString()); - await timeout(1000); - await this.whenFileExists(file, trial + 1); + await timeout(1000, token); + await this.whenFileExists(file, trial + 1, token); } } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts b/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts index e13987153f8..18965b389d0 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts @@ -16,7 +16,7 @@ import { CATEGORIES } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { Memento } from 'vs/workbench/common/memento'; import { HAS_OPENED_NOTEBOOK } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { OpenGettingStarted } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -45,7 +45,7 @@ export class NotebookGettingStarted extends Disposable implements IWorkbenchCont hasOpenedNotebook.set(true); } - const needToShowGettingStarted = _configurationService.getValue(OpenGettingStarted) && !storedValue[hasShownGettingStartedKey]; + const needToShowGettingStarted = _configurationService.getValue(NotebookSetting.openGettingStarted) && !storedValue[hasShownGettingStartedKey]; if (!storedValue[hasOpenedNotebookKey] || needToShowGettingStarted) { const onDidOpenNotebook = () => { hasOpenedNotebook.set(true); @@ -83,7 +83,7 @@ registerAction2(class NotebookClearNotebookLayoutAction extends Action2 { id: 'workbench.notebook.layout.gettingStarted', title: localize('workbench.notebook.layout.gettingStarted.label', "Reset notebook getting started"), f1: true, - precondition: ContextKeyExpr.equals(`config.${OpenGettingStarted}`, true), + precondition: ContextKeyExpr.equals(`config.${NotebookSetting.openGettingStarted}`, true), category: CATEGORIES.Developer, }); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts index c764085038f..fe1c2706c22 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts @@ -8,7 +8,7 @@ import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/act import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { INotebookActionContext, NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { CellToolbarLocation } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; const TOGGLE_CELL_TOOLBAR_POSITION = 'notebook.toggleCellToolbarPosition'; @@ -33,9 +33,9 @@ export class ToggleCellToolbarPositionAction extends Action2 { // from toolbar const viewType = editor.textModel.viewType; const configurationService = accessor.get(IConfigurationService); - const toolbarPosition = configurationService.getValue<string | { [key: string]: string }>(CellToolbarLocation); + const toolbarPosition = configurationService.getValue<string | { [key: string]: string }>(NotebookSetting.cellToolbarLocation); const newConfig = this.togglePosition(viewType, toolbarPosition); - await configurationService.updateValue(CellToolbarLocation, newConfig); + await configurationService.updateValue(NotebookSetting.cellToolbarLocation, newConfig); } } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts index 090e1383270..350e3c7e7d1 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts @@ -9,7 +9,7 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { CellToolbarLocation, CompactView, ConsolidatedRunButton, FocusIndicator, GlobalToolbar, InsertToolbarLocation, ShowCellStatusBar, UndoRedoPerCell } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -22,34 +22,34 @@ export enum NotebookProfileType { const profiles = { [NotebookProfileType.default]: { - [FocusIndicator]: 'gutter', - [InsertToolbarLocation]: 'both', - [GlobalToolbar]: true, - [CellToolbarLocation]: { default: 'right' }, - [CompactView]: true, - [ShowCellStatusBar]: 'visible', - [ConsolidatedRunButton]: true, - [UndoRedoPerCell]: false + [NotebookSetting.focusIndicator]: 'gutter', + [NotebookSetting.insertToolbarLocation]: 'both', + [NotebookSetting.globalToolbar]: true, + [NotebookSetting.cellToolbarLocation]: { default: 'right' }, + [NotebookSetting.compactView]: true, + [NotebookSetting.showCellStatusBar]: 'visible', + [NotebookSetting.consolidatedRunButton]: true, + [NotebookSetting.undoRedoPerCell]: false }, [NotebookProfileType.jupyter]: { - [FocusIndicator]: 'gutter', - [InsertToolbarLocation]: 'notebookToolbar', - [GlobalToolbar]: true, - [CellToolbarLocation]: { default: 'left' }, - [CompactView]: true, - [ShowCellStatusBar]: 'visible', - [ConsolidatedRunButton]: false, - [UndoRedoPerCell]: true + [NotebookSetting.focusIndicator]: 'gutter', + [NotebookSetting.insertToolbarLocation]: 'notebookToolbar', + [NotebookSetting.globalToolbar]: true, + [NotebookSetting.cellToolbarLocation]: { default: 'left' }, + [NotebookSetting.compactView]: true, + [NotebookSetting.showCellStatusBar]: 'visible', + [NotebookSetting.consolidatedRunButton]: false, + [NotebookSetting.undoRedoPerCell]: true }, [NotebookProfileType.colab]: { - [FocusIndicator]: 'border', - [InsertToolbarLocation]: 'betweenCells', - [GlobalToolbar]: false, - [CellToolbarLocation]: { default: 'right' }, - [CompactView]: false, - [ShowCellStatusBar]: 'hidden', - [ConsolidatedRunButton]: true, - [UndoRedoPerCell]: false + [NotebookSetting.focusIndicator]: 'border', + [NotebookSetting.insertToolbarLocation]: 'betweenCells', + [NotebookSetting.globalToolbar]: false, + [NotebookSetting.cellToolbarLocation]: { default: 'right' }, + [NotebookSetting.compactView]: false, + [NotebookSetting.showCellStatusBar]: 'hidden', + [NotebookSetting.consolidatedRunButton]: true, + [NotebookSetting.undoRedoPerCell]: false } }; @@ -101,13 +101,13 @@ export class NotebookProfileContribution extends Disposable { return; } else { // check if settings are already modified - const focusIndicator = configService.getValue(FocusIndicator); - const insertToolbarPosition = configService.getValue(InsertToolbarLocation); - const globalToolbar = configService.getValue(GlobalToolbar); - // const cellToolbarLocation = configService.getValue(CellToolbarLocation); - const compactView = configService.getValue(CompactView); - const showCellStatusBar = configService.getValue(ShowCellStatusBar); - const consolidatedRunButton = configService.getValue(ConsolidatedRunButton); + const focusIndicator = configService.getValue(NotebookSetting.focusIndicator); + const insertToolbarPosition = configService.getValue(NotebookSetting.insertToolbarLocation); + const globalToolbar = configService.getValue(NotebookSetting.globalToolbar); + // const cellToolbarLocation = configService.getValue(NotebookSetting.cellToolbarLocation); + const compactView = configService.getValue(NotebookSetting.compactView); + const showCellStatusBar = configService.getValue(NotebookSetting.showCellStatusBar); + const consolidatedRunButton = configService.getValue(NotebookSetting.consolidatedRunButton); if (focusIndicator === 'border' && insertToolbarPosition === 'both' && globalToolbar === false diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index 83cdd5c305d..a57d2d97203 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -17,7 +17,7 @@ import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cel import { cellExecutionArgs, CellToolbarOrder, CELL_TITLE_CELL_GROUP_ID, executeNotebookCondition, getContextFromActiveEditor, getContextFromUri, INotebookActionContext, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NotebookAction, NotebookCellAction, NotebookMultiCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, parseMultiCellExecutionArgs } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_MISSING_KERNEL_EXTENSION } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { CellKind, ConsolidatedRunButton, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, NotebookSetting, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -207,7 +207,7 @@ registerAction2(class ExecuteAboveCells extends NotebookMultiCellAction { id: MenuId.NotebookCellExecute, when: ContextKeyExpr.and( executeCondition, - ContextKeyExpr.equals(`config.${ConsolidatedRunButton}`, true)) + ContextKeyExpr.equals(`config.${NotebookSetting.consolidatedRunButton}`, true)) }, { id: MenuId.NotebookCellTitle, @@ -215,7 +215,7 @@ registerAction2(class ExecuteAboveCells extends NotebookMultiCellAction { group: CELL_TITLE_CELL_GROUP_ID, when: ContextKeyExpr.and( executeCondition, - ContextKeyExpr.equals(`config.${ConsolidatedRunButton}`, false)) + ContextKeyExpr.equals(`config.${NotebookSetting.consolidatedRunButton}`, false)) } ], icon: icons.executeAboveIcon @@ -253,7 +253,7 @@ registerAction2(class ExecuteCellAndBelow extends NotebookMultiCellAction { id: MenuId.NotebookCellExecute, when: ContextKeyExpr.and( executeCondition, - ContextKeyExpr.equals(`config.${ConsolidatedRunButton}`, true)) + ContextKeyExpr.equals(`config.${NotebookSetting.consolidatedRunButton}`, true)) }, { id: MenuId.NotebookCellTitle, @@ -261,7 +261,7 @@ registerAction2(class ExecuteCellAndBelow extends NotebookMultiCellAction { group: CELL_TITLE_CELL_GROUP_ID, when: ContextKeyExpr.and( executeCondition, - ContextKeyExpr.equals(`config.${ConsolidatedRunButton}`, false)) + ContextKeyExpr.equals(`config.${NotebookSetting.consolidatedRunButton}`, false)) } ], icon: icons.executeBelowIcon diff --git a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts index c15d0bfb871..c88434cf848 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts @@ -16,7 +16,7 @@ import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cel import { INotebookActionContext, NotebookAction } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellKind, GlobalToolbarShowLabel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; const INSERT_CODE_CELL_ABOVE_COMMAND_ID = 'notebook.cell.insertCodeCellAbove'; const INSERT_CODE_CELL_BELOW_COMMAND_ID = 'notebook.cell.insertCodeCellBelow'; @@ -323,7 +323,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, { NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true), ContextKeyExpr.notEquals('config.notebook.insertToolbarLocation', 'betweenCells'), ContextKeyExpr.notEquals('config.notebook.insertToolbarLocation', 'hidden'), - ContextKeyExpr.notEquals(`config.${GlobalToolbarShowLabel}`, false) + ContextKeyExpr.notEquals(`config.${NotebookSetting.globalToolbarShowLabel}`, false) ) }); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts index c3f39c3cb4e..9e16c29d0e4 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts @@ -13,7 +13,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { OpenGettingStarted } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; @@ -23,7 +23,7 @@ registerAction2(class NotebookConfigureLayoutAction extends Action2 { id: 'workbench.notebook.layout.select', title: localize('workbench.notebook.layout.select.label', "Select between Notebook Layouts"), f1: true, - precondition: ContextKeyExpr.equals(`config.${OpenGettingStarted}`, true), + precondition: ContextKeyExpr.equals(`config.${NotebookSetting.openGettingStarted}`, true), category: NOTEBOOK_ACTIONS_CATEGORY, menu: [ { @@ -32,7 +32,7 @@ registerAction2(class NotebookConfigureLayoutAction extends Action2 { when: ContextKeyExpr.and( NOTEBOOK_IS_ACTIVE_EDITOR, ContextKeyExpr.notEquals('config.notebook.globalToolbar', true), - ContextKeyExpr.equals(`config.${OpenGettingStarted}`, true) + ContextKeyExpr.equals(`config.${NotebookSetting.openGettingStarted}`, true) ), order: 0 }, @@ -41,7 +41,7 @@ registerAction2(class NotebookConfigureLayoutAction extends Action2 { group: 'notebookLayout', when: ContextKeyExpr.and( ContextKeyExpr.equals('config.notebook.globalToolbar', true), - ContextKeyExpr.equals(`config.${OpenGettingStarted}`, true) + ContextKeyExpr.equals(`config.${NotebookSetting.openGettingStarted}`, true) ), order: 0 } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index 259fda6abfc..14c6dc4b5dd 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -8,13 +8,13 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { DiffElementViewModelBase, getFormatedMetadataJSON, OUTPUT_EDITOR_HEIGHT_MAGIC, PropertyFoldingState, SideBySideDiffElementViewModel, SingleSideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { DiffElementViewModelBase, getFormatedMetadataJSON, getFormatedOutputJSON, OUTPUT_EDITOR_HEIGHT_MAGIC, PropertyFoldingState, SideBySideDiffElementViewModel, SingleSideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { CellDiffSideBySideRenderTemplate, CellDiffSingleSideRenderTemplate, DiffSide, DIFF_CELL_MARGIN, INotebookTextDiffEditor, NOTEBOOK_DIFF_CELL_INPUT, NOTEBOOK_DIFF_CELL_PROPERTY, NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { CellEditType, CellUri, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellUri, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IMenu, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; @@ -99,6 +99,7 @@ export const fixedDiffEditorOptions: IDiffEditorConstructionOptions = { class PropertyHeader extends Disposable { protected _foldingIndicator!: HTMLElement; protected _statusSpan!: HTMLElement; + protected _description!: HTMLElement; protected _toolbar!: ToolBar; protected _menu!: IMenu; protected _propertyExpanded?: IContextKey<boolean>; @@ -109,7 +110,7 @@ class PropertyHeader extends Disposable { readonly notebookEditor: INotebookTextDiffEditor, readonly accessor: { updateInfoRendering: (renderOutput: boolean) => void; - checkIfModified: (cell: DiffElementViewModelBase) => boolean; + checkIfModified: (cell: DiffElementViewModelBase) => false | { reason: string | undefined }; getFoldingState: (cell: DiffElementViewModelBase) => PropertyFoldingState; updateFoldingState: (cell: DiffElementViewModelBase, newState: PropertyFoldingState) => void; unChangedLabel: string; @@ -132,14 +133,20 @@ class PropertyHeader extends Disposable { this._foldingIndicator.classList.add(this.accessor.prefix); this._updateFoldingIcon(); const metadataStatus = DOM.append(this.propertyHeaderContainer, DOM.$('div.property-status')); + this._statusSpan = DOM.append(metadataStatus, DOM.$('span')); + this._description = DOM.append(metadataStatus, DOM.$('span.property-description')); if (metadataChanged) { this._statusSpan.textContent = this.accessor.changedLabel; this._statusSpan.style.fontWeight = 'bold'; + if (metadataChanged.reason) { + this._description.textContent = metadataChanged.reason; + } this.propertyHeaderContainer.classList.add('modified'); } else { this._statusSpan.textContent = this.accessor.unChangedLabel; + this._description.textContent = ''; this.propertyHeaderContainer.classList.remove('modified'); } @@ -162,7 +169,7 @@ class PropertyHeader extends Disposable { const scopedContextKeyService = this.contextKeyService.createScoped(cellToolbarContainer); this._register(scopedContextKeyService); const propertyChanged = NOTEBOOK_DIFF_CELL_PROPERTY.bindTo(scopedContextKeyService); - propertyChanged.set(metadataChanged); + propertyChanged.set(!!metadataChanged); this._propertyExpanded = NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED.bindTo(scopedContextKeyService); this._menu = this.menuService.createMenu(this.accessor.menuId, scopedContextKeyService); @@ -224,6 +231,9 @@ class PropertyHeader extends Disposable { if (metadataChanged) { this._statusSpan.textContent = this.accessor.changedLabel; this._statusSpan.style.fontWeight = 'bold'; + if (metadataChanged.reason) { + this._description.textContent = metadataChanged.reason; + } this.propertyHeaderContainer.classList.add('modified'); const actions: IAction[] = []; createAndFillInActionBarActions(this._menu, undefined, actions); @@ -231,6 +241,7 @@ class PropertyHeader extends Disposable { } else { this._statusSpan.textContent = this.accessor.unChangedLabel; this._statusSpan.style.fontWeight = 'normal'; + this._description.textContent = ''; this.propertyHeaderContainer.classList.remove('modified'); this._toolbar.setActions([]); } @@ -612,16 +623,12 @@ abstract class AbstractElementRenderer extends Disposable { } } - private _getFormatedOutputJSON(outputs: IOutputDto[]) { - return JSON.stringify(outputs.map(op => ({ outputs: op.outputs })), undefined, '\t'); - } - private _buildOutputEditor() { this._outputEditorDisposeStore.clear(); if ((this.cell.type === 'modified' || this.cell.type === 'unchanged') && !this.notebookEditor.textModel!.transientOptions.transientOutputs) { - const originalOutputsSource = this._getFormatedOutputJSON(this.cell.original?.outputs || []); - const modifiedOutputsSource = this._getFormatedOutputJSON(this.cell.modified?.outputs || []); + const originalOutputsSource = getFormatedOutputJSON(this.cell.original?.outputs || []); + const modifiedOutputsSource = getFormatedOutputJSON(this.cell.modified?.outputs || []); if (originalOutputsSource !== modifiedOutputsSource) { const mode = this.modeService.create('json'); const originalModel = this.modelService.createModel(originalOutputsSource, mode, undefined, true); @@ -664,7 +671,7 @@ abstract class AbstractElementRenderer extends Disposable { })); this._outputEditorDisposeStore.add(this.cell.modified!.textModel.onDidChangeOutputs(() => { - const modifiedOutputsSource = this._getFormatedOutputJSON(this.cell.modified?.outputs || []); + const modifiedOutputsSource = getFormatedOutputJSON(this.cell.modified?.outputs || []); modifiedModel.setValue(modifiedOutputsSource); this._outputHeader.refresh(); })); @@ -684,7 +691,7 @@ abstract class AbstractElementRenderer extends Disposable { this._outputEditorDisposeStore.add(this._outputEditor); const mode = this.modeService.create('json'); - const originaloutputSource = this._getFormatedOutputJSON( + const originaloutputSource = getFormatedOutputJSON( this.notebookEditor.textModel!.transientOptions.transientOutputs ? [] : this.cell.type === 'insert' diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts index 421d414e96f..fd60f3310ed 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts @@ -12,7 +12,7 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { hash } from 'vs/base/common/hash'; import { format } from 'vs/base/common/jsonFormatter'; import { applyEdits } from 'vs/base/common/jsonEdit'; -import { ICellOutput, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellOutput, IOutputDto, IOutputItemDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { DiffNestedCellViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel'; import { URI } from 'vs/base/common/uri'; import { NotebookDiffEditorEventDispatcher, NotebookDiffViewEventType } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher'; @@ -274,8 +274,8 @@ export abstract class DiffElementViewModelBase extends Disposable { this.editorEventDispatcher.emit([{ type: NotebookDiffViewEventType.CellLayoutChanged, source: this._layoutInfo }]); } - abstract checkIfOutputsModified(): boolean; - abstract checkMetadataIfModified(): boolean; + abstract checkIfOutputsModified(): false | { reason: string | undefined; }; + abstract checkMetadataIfModified(): false | { reason: string | undefined; }; abstract isOutputEmpty(): boolean; abstract getRichOutputTotalHeight(): number; abstract getCellByUri(cellUri: URI): IGenericCellViewModel; @@ -375,11 +375,28 @@ export class SideBySideDiffElementViewModel extends DiffElementViewModelBase { } checkIfOutputsModified() { - return !this.mainDocumentTextModel.transientOptions.transientOutputs && !outputsEqual(this.original?.outputs ?? [], this.modified?.outputs ?? []); + if (this.mainDocumentTextModel.transientOptions.transientOutputs) { + return false; + } + + const ret = outputsEqual(this.original?.outputs ?? [], this.modified?.outputs ?? []); + + if (ret === OutputComparison.Unchanged) { + return false; + } + + return { + reason: ret === OutputComparison.Metadata ? 'Output metadata is changed' : undefined + }; } - checkMetadataIfModified(): boolean { - return hash(getFormatedMetadataJSON(this.mainDocumentTextModel, this.original?.metadata || {}, this.original?.language)) !== hash(getFormatedMetadataJSON(this.mainDocumentTextModel, this.modified?.metadata ?? {}, this.modified?.language)); + checkMetadataIfModified() { + const modified = hash(getFormatedMetadataJSON(this.mainDocumentTextModel, this.original?.metadata || {}, this.original?.language)) !== hash(getFormatedMetadataJSON(this.mainDocumentTextModel, this.modified?.metadata ?? {}, this.modified?.language)); + if (modified) { + return { reason: undefined }; + } else { + return false; + } } updateOutputHeight(diffSide: DiffSide, index: number, height: number) { @@ -489,11 +506,11 @@ export class SingleSideDiffElementViewModel extends DiffElementViewModelBase { } - checkIfOutputsModified(): boolean { + checkIfOutputsModified(): false | { reason: string | undefined } { return false; } - checkMetadataIfModified(): boolean { + checkMetadataIfModified(): false | { reason: string | undefined } { return false; } @@ -536,9 +553,15 @@ export class SingleSideDiffElementViewModel extends DiffElementViewModelBase { } } +const enum OutputComparison { + Unchanged = 0, + Metadata = 1, + Other = 2 +} + function outputsEqual(original: ICellOutput[], modified: ICellOutput[]) { if (original.length !== modified.length) { - return false; + return OutputComparison.Other; } const len = original.length; @@ -547,11 +570,11 @@ function outputsEqual(original: ICellOutput[], modified: ICellOutput[]) { const b = modified[i]; if (hash(a.metadata) !== hash(b.metadata)) { - return false; + return OutputComparison.Metadata; } if (a.outputs.length !== b.outputs.length) { - return false; + return OutputComparison.Other; } for (let j = 0; j < a.outputs.length; j++) { @@ -559,22 +582,22 @@ function outputsEqual(original: ICellOutput[], modified: ICellOutput[]) { const bOutputItem = b.outputs[j]; if (aOutputItem.mime !== bOutputItem.mime) { - return false; + return OutputComparison.Other; } if (aOutputItem.data.buffer.length !== bOutputItem.data.buffer.length) { - return false; + return OutputComparison.Other; } for (let k = 0; k < aOutputItem.data.buffer.length; k++) { if (aOutputItem.data.buffer[k] !== bOutputItem.data.buffer[k]) { - return false; + return OutputComparison.Other; } } } } - return true; + return OutputComparison.Unchanged; } export function getFormatedMetadataJSON(documentTextModel: NotebookTextModel, metadata: NotebookCellMetadata, language?: string) { @@ -604,3 +627,38 @@ export function getFormatedMetadataJSON(documentTextModel: NotebookTextModel, me return metadataSource; } + +export function getStreamOutputData(outputs: IOutputItemDto[]) { + if (!outputs.length) { + return null; + } + + const first = outputs[0]; + const mime = first.mime; + const sameStream = !outputs.find(op => op.mime !== mime); + + if (sameStream) { + return outputs.map(opit => opit.data.toString()).join(''); + } else { + return null; + } +} + +export function getFormatedOutputJSON(outputs: IOutputDto[]) { + if (outputs.length === 1) { + const streamOutputData = getStreamOutputData(outputs[0].outputs); + if (streamOutputData) { + return streamOutputData; + } + } + + return JSON.stringify(outputs.map(output => { + return ({ + metadata: output.metadata, + outputItems: output.outputs.map(opit => ({ + mimeType: opit.mime, + data: opit.data.toString() + })) + }); + }), undefined, '\t'); +} diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css index 45d7774cb88..5dc1777ddb0 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css @@ -129,10 +129,15 @@ .notebook-text-diff-editor .cell-diff-editor-container .output-header-container .property-status span, .notebook-text-diff-editor .cell-diff-editor-container .metadata-header-container .property-status span { - margin: 0 8px; + margin: 0 0 0 8px; line-height: 21px; } +.notebook-text-diff-editor .cell-diff-editor-container .output-header-container .property-status span.property-description, +.notebook-text-diff-editor .cell-diff-editor-container .metadata-header-container .property-status span.property-description { + font-style: italic; +} + .notebook-text-diff-editor { overflow: hidden; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index f380458fb38..399f893bed9 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -30,7 +30,7 @@ import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEd import { isCompositeNotebookEditorInput, NotebookEditorInput, NotebookEditorInputOptions } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl'; -import { CellKind, CellToolbarLocation, CellToolbarVisibility, CellUri, DisplayOrderKey, UndoRedoPerCell, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookTextDiffEditorPreview, NotebookWorkingCopyTypeIdentifier, ShowCellStatusBar, CompactView, FocusIndicator, InsertToolbarLocation, GlobalToolbar, ConsolidatedOutputButton, ShowFoldingControls, DragAndDropEnabled, NotebookCellEditorOptionsCustomizations, ConsolidatedRunButton, TextOutputLineLimit, GlobalToolbarShowLabel, IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellUri, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookWorkingCopyTypeIdentifier, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; @@ -45,7 +45,7 @@ import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/brows import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { Event } from 'vs/base/common/event'; -import { getFormatedMetadataJSON } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; +import { getFormatedMetadataJSON, getStreamOutputData } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { NotebookModelResolverServiceImpl } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { NotebookKernelService } from 'vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl'; @@ -208,7 +208,7 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri ) { super(); - const undoRedoPerCell = configurationService.getValue<boolean>(UndoRedoPerCell); + const undoRedoPerCell = configurationService.getValue<boolean>(NotebookSetting.undoRedoPerCell); this._register(undoRedoService.registerUriComparisonKeyComputer(CellUri.scheme, { getComparisonKey: (uri: URI): string => { @@ -372,22 +372,6 @@ class CellInfoContentProvider { return result; } - private _getStreamOutputData(outputs: IOutputItemDto[]) { - if (!outputs.length) { - return null; - } - - const first = outputs[0]; - const mime = first.mime; - const sameStream = !outputs.find(op => op.mime !== mime); - - if (sameStream) { - return outputs.map(opit => opit.data.toString()).join(''); - } else { - return null; - } - } - async provideOutputTextContent(resource: URI): Promise<ITextModel | null> { const existing = this._modelService.getModel(resource); if (existing) { @@ -408,7 +392,7 @@ class CellInfoContentProvider { if (cell.handle === data.handle) { if (cell.outputs.length === 1) { // single output - const streamOutputData = this._getStreamOutputData(cell.outputs[0].outputs); + const streamOutputData = getStreamOutputData(cell.outputs[0].outputs); if (streamOutputData) { result = this._modelService.createModel( streamOutputData, @@ -656,7 +640,7 @@ configurationRegistry.registerConfiguration({ title: nls.localize('notebookConfigurationTitle', "Notebook"), type: 'object', properties: { - [DisplayOrderKey]: { + [NotebookSetting.displayOrder]: { description: nls.localize('notebook.displayOrder.description', "Priority list for output mime types"), type: ['array'], items: { @@ -664,7 +648,7 @@ configurationRegistry.registerConfiguration({ }, default: [] }, - [CellToolbarLocation]: { + [NotebookSetting.cellToolbarLocation]: { description: nls.localize('notebook.cellToolbarLocation.description', "Where the cell toolbar should be shown, or whether it should be hidden."), type: 'object', additionalProperties: { @@ -677,7 +661,7 @@ configurationRegistry.registerConfiguration({ }, tags: ['notebookLayout'] }, - [ShowCellStatusBar]: { + [NotebookSetting.showCellStatusBar]: { description: nls.localize('notebook.showCellStatusbar.description', "Whether the cell status bar should be shown."), type: 'string', enum: ['hidden', 'visible', 'visibleAfterExecute'], @@ -688,39 +672,39 @@ configurationRegistry.registerConfiguration({ default: 'visible', tags: ['notebookLayout'] }, - [NotebookTextDiffEditorPreview]: { + [NotebookSetting.textDiffEditorPreview]: { description: nls.localize('notebook.diff.enablePreview.description', "Whether to use the enhanced text diff editor for notebook."), type: 'boolean', default: true, tags: ['notebookLayout'] }, - [CellToolbarVisibility]: { + [NotebookSetting.cellToolbarVisibility]: { markdownDescription: nls.localize('notebook.cellToolbarVisibility.description', "Whether the cell toolbar should appear on hover or click."), type: 'string', enum: ['hover', 'click'], default: 'click', tags: ['notebookLayout'] }, - [UndoRedoPerCell]: { + [NotebookSetting.undoRedoPerCell]: { description: nls.localize('notebook.undoRedoPerCell.description', "Whether to use separate undo/redo stack for each cell."), type: 'boolean', default: true, tags: ['notebookLayout'] }, - [CompactView]: { + [NotebookSetting.compactView]: { description: nls.localize('notebook.compactView.description', "Control whether the notebook editor should be rendered in a compact form. For example, when turned on, it will decrease the left margin width."), type: 'boolean', default: true, tags: ['notebookLayout'] }, - [FocusIndicator]: { + [NotebookSetting.focusIndicator]: { description: nls.localize('notebook.focusIndicator.description', "Controls where the focus indicator is rendered, either along the cell borders or on the left gutter"), type: 'string', enum: ['border', 'gutter'], default: 'gutter', tags: ['notebookLayout'] }, - [InsertToolbarLocation]: { + [NotebookSetting.insertToolbarLocation]: { description: nls.localize('notebook.insertToolbarPosition.description', "Control where the insert cell actions should appear."), type: 'string', enum: ['betweenCells', 'notebookToolbar', 'both', 'hidden'], @@ -733,19 +717,19 @@ configurationRegistry.registerConfiguration({ default: 'both', tags: ['notebookLayout'] }, - [GlobalToolbar]: { + [NotebookSetting.globalToolbar]: { description: nls.localize('notebook.globalToolbar.description', "Control whether to render a global toolbar inside the notebook editor."), type: 'boolean', default: true, tags: ['notebookLayout'] }, - [ConsolidatedOutputButton]: { + [NotebookSetting.consolidatedOutputButton]: { description: nls.localize('notebook.consolidatedOutputButton.description', "Control whether outputs action should be rendered in the output toolbar."), type: 'boolean', default: true, tags: ['notebookLayout'] }, - [ShowFoldingControls]: { + [NotebookSetting.showFoldingControls]: { description: nls.localize('notebook.showFoldingControls.description', "Controls when the Markdown header folding arrow is shown."), type: 'string', enum: ['always', 'mouseover'], @@ -756,30 +740,36 @@ configurationRegistry.registerConfiguration({ default: 'mouseover', tags: ['notebookLayout'] }, - [DragAndDropEnabled]: { + [NotebookSetting.dragAndDropEnabled]: { description: nls.localize('notebook.dragAndDrop.description', "Control whether the notebook editor should allow moving cells through drag and drop."), type: 'boolean', default: true, tags: ['notebookLayout'] }, - [ConsolidatedRunButton]: { + [NotebookSetting.consolidatedRunButton]: { description: nls.localize('notebook.consolidatedRunButton.description', "Control whether extra actions are shown in a dropdown next to the run button."), type: 'boolean', default: false, tags: ['notebookLayout'] }, - [GlobalToolbarShowLabel]: { + [NotebookSetting.globalToolbarShowLabel]: { description: nls.localize('notebook.globalToolbarShowLabel', "Control whether the actions on the notebook toolbar should render label or not."), type: 'boolean', default: true, tags: ['notebookLayout'] }, - [TextOutputLineLimit]: { + [NotebookSetting.textOutputLineLimit]: { description: nls.localize('notebook.textOutputLineLimit', "Control how many lines of text in a text output is rendered."), type: 'number', default: 30, tags: ['notebookLayout'] }, - [NotebookCellEditorOptionsCustomizations]: editorOptionsCustomizationSchema + [NotebookSetting.markupFontSize]: { + markdownDescription: nls.localize('notebook.markup.fontSize', "Controls the font size of rendered markup in notebooks. When set to `0`, 120% of `#editor.fontSize#` is used."), + type: 'number', + default: 0, + tags: ['notebookLayout'] + }, + [NotebookSetting.cellEditorOptionsCustomizations]: editorOptionsCustomizationSchema } }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookCellStatusBarServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookCellStatusBarServiceImpl.ts index 56cf615dd92..67d223ef756 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookCellStatusBarServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookCellStatusBarServiceImpl.ts @@ -13,17 +13,15 @@ import { INotebookCellStatusBarItemList, INotebookCellStatusBarItemProvider } fr export class NotebookCellStatusBarService extends Disposable implements INotebookCellStatusBarService { - private _onDidChangeProviders = this._register(new Emitter<void>()); + readonly _serviceBrand: undefined; + + private readonly _onDidChangeProviders = this._register(new Emitter<void>()); readonly onDidChangeProviders: Event<void> = this._onDidChangeProviders.event; - private _onDidChangeItems = this._register(new Emitter<void>()); + private readonly _onDidChangeItems = this._register(new Emitter<void>()); readonly onDidChangeItems: Event<void> = this._onDidChangeItems.event; - private _providers: INotebookCellStatusBarItemProvider[] = []; - - constructor() { - super(); - } + private readonly _providers: INotebookCellStatusBarItemProvider[] = []; registerCellStatusBarItemProvider(provider: INotebookCellStatusBarItemProvider): IDisposable { this._providers.push(provider); @@ -52,6 +50,4 @@ export class NotebookCellStatusBarService extends Disposable implements INoteboo } })); } - - readonly _serviceBrand: undefined; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index de441e639c2..33c5df88faf 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -412,7 +412,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._updateForNotebookConfiguration(); } - if (e.compactView || e.focusIndicator || e.insertToolbarPosition || e.cellToolbarLocation || e.dragAndDropEnabled || e.fontSize || e.insertToolbarAlignment) { + if (e.compactView || e.focusIndicator || e.insertToolbarPosition || e.cellToolbarLocation || e.dragAndDropEnabled || e.fontSize || e.markupFontSize || e.insertToolbarAlignment) { this._styleElement?.remove(); this._createLayoutStyles(); this._webview?.updateOptions(this.notebookOptions.computeWebviewOptions()); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index c1fd60d25a2..6f309a54847 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -30,7 +30,7 @@ import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/no import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditorInput'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, DisplayOrderKey, INotebookContributionData, INotebookExclusiveDocumentFilter, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, MimeTypeDisplayOrder, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookData, NotebookEditorPriority, NotebookRendererMatch, NotebookTextDiffEditorPreview, NOTEBOOK_DISPLAY_ORDER, RENDERER_EQUIVALENT_EXTENSIONS, RENDERER_NOT_AVAILABLE, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, NotebookSetting, INotebookContributionData, INotebookExclusiveDocumentFilter, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, MimeTypeDisplayOrder, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookData, NotebookEditorPriority, NotebookRendererMatch, NOTEBOOK_DISPLAY_ORDER, RENDERER_EQUIVALENT_EXTENSIONS, RENDERER_NOT_AVAILABLE, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { updateEditorTopPadding } from 'vs/workbench/contrib/notebook/common/notebookOptions'; @@ -161,7 +161,7 @@ export class NotebookProviderInfoStore extends Disposable { priority: notebookProviderInfo.exclusive ? RegisteredEditorPriority.exclusive : notebookProviderInfo.priority, }; const notebookEditorOptions = { - canHandleDiff: () => !!this._configurationService.getValue(NotebookTextDiffEditorPreview) && !this._accessibilityService.isScreenReaderOptimized(), + canHandleDiff: () => !!this._configurationService.getValue(NotebookSetting.textDiffEditorPreview) && !this._accessibilityService.isScreenReaderOptimized(), canSupportResource: (resource: URI) => resource.scheme === Schemas.untitled || resource.scheme === Schemas.vscodeNotebookCell || this._fileService.hasProvider(resource) }; const notebookEditorInputFactory: EditorInputFactoryFunction = ({ resource, options }) => { @@ -458,7 +458,7 @@ export class NotebookService extends Disposable implements INotebookService { const updateOrder = () => { this._displayOrder = new MimeTypeDisplayOrder( - this._configurationService.getValue<string[]>(DisplayOrderKey) || [], + this._configurationService.getValue<string[]>(NotebookSetting.displayOrder) || [], this._accessibilityService.isScreenReaderOptimized() ? ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER : NOTEBOOK_DISPLAY_ORDER, @@ -468,7 +468,7 @@ export class NotebookService extends Disposable implements INotebookService { updateOrder(); this._register(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectedKeys.indexOf(DisplayOrderKey) >= 0) { + if (e.affectedKeys.indexOf(NotebookSetting.displayOrder) >= 0) { updateOrder(); } })); @@ -626,7 +626,7 @@ export class NotebookService extends Disposable implements INotebookService { } saveMimeDisplayOrder(target: ConfigurationTarget) { - this._configurationService.updateValue(DisplayOrderKey, this._displayOrder.toArray(), target); + this._configurationService.updateValue(NotebookSetting.displayOrder, this._displayOrder.toArray(), target); } getRenderers(): INotebookRendererInfo[] { diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts index ea32085e655..57f448967a6 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts @@ -6,9 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; -import { dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; @@ -17,10 +15,10 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { handleANSIOutput } from 'vs/workbench/contrib/debug/browser/debugANSIHandling'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { ICellOutputViewModel, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookDelegateForOutput, IOutputTransformContribution as IOutputRendererContribution } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; import { OutputRendererRegistry } from 'vs/workbench/contrib/notebook/browser/view/output/rendererRegistry'; import { truncatedArrayOfString } from 'vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper'; -import { IOutputItemDto, TextOutputLineLimit } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookDelegateForOutput, IOutputTransformContribution as IOutputRendererContribution } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; +import { IOutputItemDto, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; class JavaScriptRendererContrib extends Disposable implements IOutputRendererContribution { @@ -76,7 +74,7 @@ class StreamRendererContrib extends Disposable implements IOutputRendererContrib const text = getStringValue(item); const contentNode = DOM.$('span.output-stream'); - const lineLimit = this.configurationService.getValue<number>(TextOutputLineLimit) ?? 30; + const lineLimit = this.configurationService.getValue<number>(NotebookSetting.textOutputLineLimit) ?? 30; truncatedArrayOfString(notebookUri, output.cellViewModel, Math.max(lineLimit, 6), contentNode, [text], disposables, linkDetector, this.openerService, this.themeService); container.appendChild(contentNode); @@ -180,7 +178,7 @@ class PlainTextRendererContrib extends Disposable implements IOutputRendererCont const str = getStringValue(item); const contentNode = DOM.$('.output-plaintext'); - const lineLimit = this.configurationService.getValue<number>(TextOutputLineLimit) ?? 30; + const lineLimit = this.configurationService.getValue<number>(NotebookSetting.textOutputLineLimit) ?? 30; truncatedArrayOfString(notebookUri, output.cellViewModel, Math.max(lineLimit, 6), contentNode, [str], disposables, linkDetector, this.openerService, this.themeService); container.appendChild(contentNode); @@ -213,34 +211,6 @@ class HTMLRendererContrib extends Disposable implements IOutputRendererContribut } } -class MdRendererContrib extends Disposable implements IOutputRendererContribution { - getType() { - return RenderOutputType.Mainframe; - } - - getMimetypes() { - return [Mimes.markdown, Mimes.latex]; - } - - constructor( - public notebookEditor: INotebookDelegateForOutput, - @IInstantiationService private readonly instantiationService: IInstantiationService, - ) { - super(); - } - - render(output: ICellOutputViewModel, item: IOutputItemDto, container: HTMLElement, notebookUri: URI): IRenderOutput { - const disposable = new DisposableStore(); - const str = getStringValue(item); - const mdOutput = document.createElement('div'); - const mdRenderer = this.instantiationService.createInstance(MarkdownRenderer, { baseUrl: dirname(notebookUri) }); - mdOutput.appendChild(mdRenderer.render({ value: str, isTrusted: true, supportThemeIcons: true }, undefined, { gfm: true }).element); - container.appendChild(mdOutput); - disposable.add(mdRenderer); - return { type: RenderOutputType.Mainframe, disposable }; - } -} - class ImgRendererContrib extends Disposable implements IOutputRendererContribution { getType() { return RenderOutputType.Mainframe; @@ -276,7 +246,6 @@ class ImgRendererContrib extends Disposable implements IOutputRendererContributi OutputRendererRegistry.registerOutputTransform(JavaScriptRendererContrib); OutputRendererRegistry.registerOutputTransform(HTMLRendererContrib); -OutputRendererRegistry.registerOutputTransform(MdRendererContrib); OutputRendererRegistry.registerOutputTransform(ImgRendererContrib); OutputRendererRegistry.registerOutputTransform(PlainTextRendererContrib); OutputRendererRegistry.registerOutputTransform(JSErrorRendererContrib); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index d7fc86b2e6c..c50030657da 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -51,14 +51,6 @@ export interface ICachedInset<K extends ICommonCellInfo> { cachedCreation: ICreationRequestMessage; } -function html(strings: TemplateStringsArray, ...values: any[]): string { - let str = ''; - strings.forEach((string, i) => { - str += string + (values[i] || ''); - }); - return str; -} - export interface INotebookWebviewMessage { message: unknown; } @@ -89,6 +81,19 @@ export interface INotebookDelegateForWebview { triggerScroll(event: IMouseWheelEvent): void; } +interface BacklayerWebviewOptions { + readonly outputNodePadding: number; + readonly outputNodeLeftPadding: number; + readonly previewNodePadding: number; + readonly markdownLeftMargin: number; + readonly leftMargin: number; + readonly rightMargin: number; + readonly runGutter: number; + readonly dragAndDropEnabled: boolean; + readonly fontSize: number; + readonly markupFontSize: number; +} + export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable { element: HTMLElement; webview: IWebviewElement | undefined = undefined; @@ -100,7 +105,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable { private readonly _onMessage = this._register(new Emitter<INotebookWebviewMessage>()); private readonly _preloadsCache = new Set<string>(); public readonly onMessage: Event<INotebookWebviewMessage> = this._onMessage.event; - private _initalized?: Promise<void>; + private _initialized?: Promise<void>; private _disposed = false; private _currentKernel?: INotebookKernel; @@ -110,17 +115,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable { public readonly notebookEditor: INotebookDelegateForWebview, public readonly id: string, public readonly documentUri: URI, - private options: { - outputNodePadding: number, - outputNodeLeftPadding: number, - previewNodePadding: number, - markdownLeftMargin: number, - leftMargin: number, - rightMargin: number, - runGutter: number, - dragAndDropEnabled: boolean, - fontSize: number - }, + private options: BacklayerWebviewOptions, private readonly rendererMessaging: IScopedRendererMessaging | undefined, @IWebviewService readonly webviewService: IWebviewService, @IOpenerService readonly openerService: IOpenerService, @@ -177,17 +172,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable { })); } - updateOptions(options: { - outputNodePadding: number, - outputNodeLeftPadding: number, - previewNodePadding: number, - markdownLeftMargin: number, - leftMargin: number, - rightMargin: number, - runGutter: number, - dragAndDropEnabled: boolean, - fontSize: number - }) { + updateOptions(options: BacklayerWebviewOptions) { this.options = options; this._updateStyles(); this._updateOptions(); @@ -215,10 +200,11 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable { 'notebook-output-width': `calc(100% - ${this.options.leftMargin + this.options.rightMargin + this.options.runGutter}px)`, 'notebook-output-node-padding': `${this.options.outputNodePadding}px`, 'notebook-run-gutter': `${this.options.runGutter}px`, - 'notebook-preivew-node-padding': `${this.options.previewNodePadding}px`, + 'notebook-preview-node-padding': `${this.options.previewNodePadding}px`, 'notebook-markdown-left-margin': `${this.options.markdownLeftMargin}px`, 'notebook-output-node-left-padding': `${this.options.outputNodeLeftPadding}px`, 'notebook-markdown-min-height': `${this.options.previewNodePadding * 2}px`, + 'notebook-markup-font-size': typeof this.options.markupFontSize === 'number' && this.options.markupFontSize > 0 ? `${this.options.markupFontSize}px` : `calc(${this.options.fontSize}px * 1.2)`, 'notebook-cell-output-font-size': `${this.options.fontSize}px`, 'notebook-cell-markup-empty-content': nls.localize('notebook.emptyMarkdownPlaceholder', "Empty markdown cell, double click or press enter to edit."), 'notebook-cell-renderer-not-found-error': nls.localize({ @@ -238,7 +224,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable { this.nonce); const enableCsp = this.configurationService.getValue('notebook.experimental.enableCsp'); - return html` + return /* html */` <html lang="en"> <head> <meta charset="UTF-8"> @@ -279,15 +265,17 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable { /* markdown */ #container > div.preview { width: 100%; - padding-right: var(--notebook-preivew-node-padding); + padding-right: var(--notebook-preview-node-padding); padding-left: var(--notebook-markdown-left-margin); - padding-top: var(--notebook-preivew-node-padding); - padding-bottom: var(--notebook-preivew-node-padding); + padding-top: var(--notebook-preview-node-padding); + padding-bottom: var(--notebook-preview-node-padding); box-sizing: border-box; white-space: nowrap; overflow: hidden; white-space: initial; + + font-size: var(--notebook-markup-font-size); color: var(--theme-ui-foreground); } @@ -434,7 +422,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable { let coreDependencies = ''; let resolveFunc: () => void; - this._initalized = new Promise<void>((resolve, reject) => { + this._initialized = new Promise<void>((resolve) => { resolveFunc = resolve; }); @@ -483,7 +471,7 @@ var requirejs = (function() { }); } - await this._initalized; + await this._initialized; } private _initialize(content: string) { @@ -835,7 +823,7 @@ var requirejs = (function() { allowScripts: true, localResourceRoots: this.localResourceRootsCache, }, undefined); - // console.log(this.localResourceRootsCache); + webview.html = content; return webview; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index edc8222bd42..52430aabb78 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -10,7 +10,7 @@ interface BaseToWebviewMessage { readonly __vscode_notebook_message: true; } -export interface WebviewIntialized extends BaseToWebviewMessage { +export interface WebviewInitialized extends BaseToWebviewMessage { readonly type: 'initialized'; } @@ -162,22 +162,22 @@ export interface IOutputRequestMetadata { /** * Additional attributes of a cell metadata. */ - readonly custom?: { [key: string]: unknown; }; + readonly custom?: { readonly [key: string]: unknown; }; } export interface IOutputRequestDto { /** * { mime_type: value } */ - readonly data: { [key: string]: unknown; }; + readonly data: { readonly [key: string]: unknown; }; readonly metadata?: IOutputRequestMetadata; readonly outputId: string; } export type ICreationContent = - | { type: RenderOutputType.Html; htmlContent: string; } - | { type: RenderOutputType.Extension; outputId: string; valueBytes: Uint8Array; metadata: unknown; mimeType: string; }; + | { readonly type: RenderOutputType.Html; readonly htmlContent: string; } + | { readonly type: RenderOutputType.Extension; readonly outputId: string; readonly valueBytes: Uint8Array; readonly metadata: unknown; readonly mimeType: string; }; export interface ICreationRequestMessage { readonly type: 'html'; @@ -187,7 +187,7 @@ export interface ICreationRequestMessage { cellTop: number; outputOffset: number; readonly left: number; - readonly requiredPreloads: ReadonlyArray<IControllerPreload>; + readonly requiredPreloads: readonly IControllerPreload[]; readonly initiallyHidden?: boolean; readonly rendererId?: string | undefined; } @@ -200,10 +200,15 @@ export interface IContentWidgetTopRequest { readonly forceDisplay: boolean; } +export interface IMarkupCellScrollTops { + readonly id: string; + readonly top: number; +} + export interface IViewScrollTopRequestMessage { readonly type: 'view-scroll'; - readonly widgets: IContentWidgetTopRequest[]; - readonly markupCells: { id: string; top: number; }[]; + readonly widgets: readonly IContentWidgetTopRequest[]; + readonly markupCells: readonly IMarkupCellScrollTops[]; } export interface IScrollRequestMessage { @@ -259,14 +264,14 @@ export interface IControllerPreload { export interface IUpdateControllerPreloadsMessage { readonly type: 'preload'; - readonly resources: IControllerPreload[]; + readonly resources: readonly IControllerPreload[]; } export interface IUpdateDecorationsMessage { readonly type: 'decorations'; readonly cellId: string; - readonly addedClassNames: string[]; - readonly removedClassNames: string[]; + readonly addedClassNames: readonly string[]; + readonly removedClassNames: readonly string[]; } export interface ICustomKernelMessage extends BaseToWebviewMessage { @@ -324,13 +329,13 @@ export interface IMarkupCellInitialization { export interface IInitializeMarkupCells { readonly type: 'initializeMarkup'; - readonly cells: ReadonlyArray<IMarkupCellInitialization>; + readonly cells: readonly IMarkupCellInitialization[]; } export interface INotebookStylesMessage { readonly type: 'notebookStyles'; readonly styles: { - [key: string]: string; + readonly [key: string]: string; }; } @@ -354,7 +359,7 @@ export interface ITokenizedStylesChangedMessage { readonly css: string; } -export type FromWebviewMessage = WebviewIntialized | +export type FromWebviewMessage = WebviewInitialized | IDimensionMessage | IMouseEnterMessage | IMouseLeaveMessage | diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index c3bc9163d90..4459b8f032a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -664,8 +664,8 @@ async function webviewPreloads(ctx: PreloadContext) { } // Re-add new properties - for (const variable of Object.keys(event.data.styles)) { - documentStyle.setProperty(`--${variable}`, event.data.styles[variable]); + for (const [name, value] of Object.entries(event.data.styles)) { + documentStyle.setProperty(`--${name}`, value); } break; case 'notebookOptions': @@ -1007,7 +1007,7 @@ async function webviewPreloads(ctx: PreloadContext) { } } - public updateMarkupScrolls(markupCells: { id: string; top: number; }[]) { + public updateMarkupScrolls(markupCells: readonly webviewMessages.IMarkupCellScrollTops[]) { for (const { id, top } of markupCells) { const cell = this._markupCells.get(id); if (cell) { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection.ts index ba3c117629b..2bc631d67e3 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection.ts @@ -24,11 +24,9 @@ function rangesEqual(a: ICellRange[], b: ICellRange[]) { // Handle first, then we migrate to ICellRange competely // Challenge is List View talks about `element`, which needs extra work to convert to ICellRange as we support Folding and Cell Move export class NotebookCellSelectionCollection extends Disposable { + private readonly _onDidChangeSelection = this._register(new Emitter<string>()); get onDidChangeSelection(): Event<string> { return this._onDidChangeSelection.event; } - constructor() { - super(); - } private _primary: ICellRange | null = null; diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts index 8e1f4b086e7..2a32670df98 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts @@ -23,7 +23,7 @@ import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controll import { INotebookEditorDelegate, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebooKernelActionViewItem } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem'; import { ActionViewWithLabel } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView'; -import { GlobalToolbarShowLabel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions'; @@ -114,7 +114,7 @@ export class NotebookEditorToolbar extends Disposable { this._register(this._notebookGlobalActionsMenu); this._useGlobalToolbar = this.notebookOptions.getLayoutConfiguration().globalToolbar; - this._renderLabel = this.configurationService.getValue<boolean>(GlobalToolbarShowLabel); + this._renderLabel = this.configurationService.getValue<boolean>(NotebookSetting.globalToolbarShowLabel); const context = { ui: true, @@ -184,8 +184,8 @@ export class NotebookEditorToolbar extends Disposable { })); this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(GlobalToolbarShowLabel)) { - this._renderLabel = this.configurationService.getValue<boolean>(GlobalToolbarShowLabel); + if (e.affectsConfiguration(NotebookSetting.globalToolbarShowLabel)) { + this._renderLabel = this.configurationService.getValue<boolean>(NotebookSetting.globalToolbarShowLabel); const oldElement = this._notebookLeftToolbar.getElement(); oldElement.parentElement?.removeChild(oldElement); this._notebookLeftToolbar.dispose(); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index badebb06286..c495315f175 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -579,7 +579,6 @@ const _mimeTypeInfo = new Map<string, MimeTypeInfo>([ ['image/svg+xml', { supportedByCore: true }], ['application/json', { alwaysSecure: true, supportedByCore: true }], [Mimes.latex, { alwaysSecure: true, supportedByCore: true }], - [Mimes.markdown, { alwaysSecure: true, supportedByCore: true }], [Mimes.text, { alwaysSecure: true, supportedByCore: true }], ['text/html', { supportedByCore: true }], ['text/x-javascript', { alwaysSecure: true, supportedByCore: true }], // secure because rendered as text, not executed @@ -902,26 +901,30 @@ export interface INotebookCellStatusBarItemList { dispose?(): void; } -export const DisplayOrderKey = 'notebook.displayOrder'; -export const CellToolbarLocation = 'notebook.cellToolbarLocation'; -export const CellToolbarVisibility = 'notebook.cellToolbarVisibility'; export type ShowCellStatusBarType = 'hidden' | 'visible' | 'visibleAfterExecute'; -export const ShowCellStatusBar = 'notebook.showCellStatusBar'; -export const NotebookTextDiffEditorPreview = 'notebook.diff.enablePreview'; -export const ExperimentalInsertToolbarAlignment = 'notebook.experimental.insertToolbarAlignment'; -export const CompactView = 'notebook.compactView'; -export const FocusIndicator = 'notebook.cellFocusIndicator'; -export const InsertToolbarLocation = 'notebook.insertToolbarLocation'; -export const GlobalToolbar = 'notebook.globalToolbar'; -export const UndoRedoPerCell = 'notebook.undoRedoPerCell'; -export const ConsolidatedOutputButton = 'notebook.consolidatedOutputButton'; -export const ShowFoldingControls = 'notebook.showFoldingControls'; -export const DragAndDropEnabled = 'notebook.dragAndDropEnabled'; -export const NotebookCellEditorOptionsCustomizations = 'notebook.editorOptionsCustomizations'; -export const ConsolidatedRunButton = 'notebook.consolidatedRunButton'; -export const OpenGettingStarted = 'notebook.experimental.openGettingStarted'; -export const TextOutputLineLimit = 'notebook.output.textLineLimit'; -export const GlobalToolbarShowLabel = 'notebook.globalToolbarShowLabel'; + +export const NotebookSetting = { + displayOrder: 'notebook.displayOrder', + cellToolbarLocation: 'notebook.cellToolbarLocation', + cellToolbarVisibility: 'notebook.cellToolbarVisibility', + showCellStatusBar: 'notebook.showCellStatusBar', + textDiffEditorPreview: 'notebook.diff.enablePreview', + experimentalInsertToolbarAlignment: 'notebook.experimental.insertToolbarAlignment', + compactView: 'notebook.compactView', + focusIndicator: 'notebook.cellFocusIndicator', + insertToolbarLocation: 'notebook.insertToolbarLocation', + globalToolbar: 'notebook.globalToolbar', + undoRedoPerCell: 'notebook.undoRedoPerCell', + consolidatedOutputButton: 'notebook.consolidatedOutputButton', + showFoldingControls: 'notebook.showFoldingControls', + dragAndDropEnabled: 'notebook.dragAndDropEnabled', + cellEditorOptionsCustomizations: 'notebook.editorOptionsCustomizations', + consolidatedRunButton: 'notebook.consolidatedRunButton', + openGettingStarted: 'notebook.experimental.openGettingStarted', + textOutputLineLimit: 'notebook.output.textLineLimit', + globalToolbarShowLabel: 'notebook.globalToolbarShowLabel', + markupFontSize: 'notebook.markup.fontSize', +} as const; export const enum CellStatusbarAlignment { Left = 1, diff --git a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts index 387fabb158e..040cedd0bd6 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts @@ -6,7 +6,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { CellToolbarLocation, CellToolbarVisibility, CompactView, ConsolidatedOutputButton, ConsolidatedRunButton, DragAndDropEnabled, ExperimentalInsertToolbarAlignment, FocusIndicator, GlobalToolbar, InsertToolbarLocation, NotebookCellEditorOptionsCustomizations, NotebookCellInternalMetadata, ShowCellStatusBar, ShowCellStatusBarType, ShowFoldingControls } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookCellInternalMetadata, NotebookSetting, ShowCellStatusBarType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; const SCROLLABLE_ELEMENT_PADDING_TOP = 18; @@ -59,30 +59,32 @@ export interface NotebookLayoutConfiguration { showFoldingControls: 'always' | 'mouseover'; dragAndDropEnabled: boolean; fontSize: number; + markupFontSize: number; focusIndicatorLeftMargin: number; editorOptionsCustomizations: any | undefined; } export interface NotebookOptionsChangeEvent { - cellStatusBarVisibility?: boolean; - cellToolbarLocation?: boolean; - cellToolbarInteraction?: boolean; - editorTopPadding?: boolean; - compactView?: boolean; - focusIndicator?: boolean; - insertToolbarPosition?: boolean; - insertToolbarAlignment?: boolean; - globalToolbar?: boolean; - showFoldingControls?: boolean; - consolidatedOutputButton?: boolean; - consolidatedRunButton?: boolean; - dragAndDropEnabled?: boolean; - fontSize?: boolean; - editorOptionsCustomizations?: boolean; - cellBreakpointMargin?: boolean; + readonly cellStatusBarVisibility?: boolean; + readonly cellToolbarLocation?: boolean; + readonly cellToolbarInteraction?: boolean; + readonly editorTopPadding?: boolean; + readonly compactView?: boolean; + readonly focusIndicator?: boolean; + readonly insertToolbarPosition?: boolean; + readonly insertToolbarAlignment?: boolean; + readonly globalToolbar?: boolean; + readonly showFoldingControls?: boolean; + readonly consolidatedOutputButton?: boolean; + readonly consolidatedRunButton?: boolean; + readonly dragAndDropEnabled?: boolean; + readonly fontSize?: boolean; + readonly markupFontSize?: boolean; + readonly editorOptionsCustomizations?: boolean; + readonly cellBreakpointMargin?: boolean; } -const defaultConfigConstants = { +const defaultConfigConstants = Object.freeze({ codeCellLeftMargin: 28, cellRunGutter: 32, markdownCellTopMargin: 8, @@ -90,9 +92,9 @@ const defaultConfigConstants = { markdownCellLeftMargin: 0, markdownCellGutter: 32, focusIndicatorLeftMargin: 4 -}; +}); -const compactConfigConstants = { +const compactConfigConstants = Object.freeze({ codeCellLeftMargin: 8, cellRunGutter: 36, markdownCellTopMargin: 6, @@ -100,7 +102,7 @@ const compactConfigConstants = { markdownCellLeftMargin: 8, markdownCellGutter: 36, focusIndicatorLeftMargin: 4 -}; +}); export class NotebookOptions extends Disposable { private _layoutConfiguration: NotebookLayoutConfiguration; @@ -109,21 +111,22 @@ export class NotebookOptions extends Disposable { constructor(private readonly configurationService: IConfigurationService, private readonly overrides?: { cellToolbarInteraction: string, globalToolbar: boolean }) { super(); - const showCellStatusBar = this.configurationService.getValue<ShowCellStatusBarType>(ShowCellStatusBar); - const globalToolbar = overrides?.globalToolbar ?? this.configurationService.getValue<boolean | undefined>(GlobalToolbar) ?? true; - const consolidatedOutputButton = this.configurationService.getValue<boolean | undefined>(ConsolidatedOutputButton) ?? true; - const consolidatedRunButton = this.configurationService.getValue<boolean | undefined>(ConsolidatedRunButton) ?? false; - const dragAndDropEnabled = this.configurationService.getValue<boolean | undefined>(DragAndDropEnabled) ?? true; - const cellToolbarLocation = this.configurationService.getValue<string | { [key: string]: string; }>(CellToolbarLocation) ?? { 'default': 'right' }; - const cellToolbarInteraction = overrides?.cellToolbarInteraction ?? this.configurationService.getValue<string>(CellToolbarVisibility); - const compactView = this.configurationService.getValue<boolean | undefined>(CompactView) ?? true; + const showCellStatusBar = this.configurationService.getValue<ShowCellStatusBarType>(NotebookSetting.showCellStatusBar); + const globalToolbar = overrides?.globalToolbar ?? this.configurationService.getValue<boolean | undefined>(NotebookSetting.globalToolbar) ?? true; + const consolidatedOutputButton = this.configurationService.getValue<boolean | undefined>(NotebookSetting.consolidatedOutputButton) ?? true; + const consolidatedRunButton = this.configurationService.getValue<boolean | undefined>(NotebookSetting.consolidatedRunButton) ?? false; + const dragAndDropEnabled = this.configurationService.getValue<boolean | undefined>(NotebookSetting.dragAndDropEnabled) ?? true; + const cellToolbarLocation = this.configurationService.getValue<string | { [key: string]: string; }>(NotebookSetting.cellToolbarLocation) ?? { 'default': 'right' }; + const cellToolbarInteraction = overrides?.cellToolbarInteraction ?? this.configurationService.getValue<string>(NotebookSetting.cellToolbarVisibility); + const compactView = this.configurationService.getValue<boolean | undefined>(NotebookSetting.compactView) ?? true; const focusIndicator = this._computeFocusIndicatorOption(); const insertToolbarPosition = this._computeInsertToolbarPositionOption(); const insertToolbarAlignment = this._computeInsertToolbarAlignmentOption(); const showFoldingControls = this._computeShowFoldingControlsOption(); // const { bottomToolbarGap, bottomToolbarHeight } = this._computeBottomToolbarDimensions(compactView, insertToolbarPosition, insertToolbarAlignment); const fontSize = this.configurationService.getValue<number>('editor.fontSize'); - const editorOptionsCustomizations = this.configurationService.getValue(NotebookCellEditorOptionsCustomizations); + const markupFontSize = this.configurationService.getValue<number>(NotebookSetting.markupFontSize); + const editorOptionsCustomizations = this.configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations); this._layoutConfiguration = { ...(compactView ? compactConfigConstants : defaultConfigConstants), @@ -153,6 +156,7 @@ export class NotebookOptions extends Disposable { insertToolbarAlignment, showFoldingControls, fontSize, + markupFontSize, editorOptionsCustomizations, }; @@ -169,20 +173,21 @@ export class NotebookOptions extends Disposable { } private _updateConfiguration(e: IConfigurationChangeEvent) { - const cellStatusBarVisibility = e.affectsConfiguration(ShowCellStatusBar); - const cellToolbarLocation = e.affectsConfiguration(CellToolbarLocation); - const cellToolbarInteraction = e.affectsConfiguration(CellToolbarVisibility); - const compactView = e.affectsConfiguration(CompactView); - const focusIndicator = e.affectsConfiguration(FocusIndicator); - const insertToolbarPosition = e.affectsConfiguration(InsertToolbarLocation); - const insertToolbarAlignment = e.affectsConfiguration(ExperimentalInsertToolbarAlignment); - const globalToolbar = e.affectsConfiguration(GlobalToolbar); - const consolidatedOutputButton = e.affectsConfiguration(ConsolidatedOutputButton); - const consolidatedRunButton = e.affectsConfiguration(ConsolidatedRunButton); - const showFoldingControls = e.affectsConfiguration(ShowFoldingControls); - const dragAndDropEnabled = e.affectsConfiguration(DragAndDropEnabled); + const cellStatusBarVisibility = e.affectsConfiguration(NotebookSetting.showCellStatusBar); + const cellToolbarLocation = e.affectsConfiguration(NotebookSetting.cellToolbarLocation); + const cellToolbarInteraction = e.affectsConfiguration(NotebookSetting.cellToolbarVisibility); + const compactView = e.affectsConfiguration(NotebookSetting.compactView); + const focusIndicator = e.affectsConfiguration(NotebookSetting.focusIndicator); + const insertToolbarPosition = e.affectsConfiguration(NotebookSetting.insertToolbarLocation); + const insertToolbarAlignment = e.affectsConfiguration(NotebookSetting.experimentalInsertToolbarAlignment); + const globalToolbar = e.affectsConfiguration(NotebookSetting.globalToolbar); + const consolidatedOutputButton = e.affectsConfiguration(NotebookSetting.consolidatedOutputButton); + const consolidatedRunButton = e.affectsConfiguration(NotebookSetting.consolidatedRunButton); + const showFoldingControls = e.affectsConfiguration(NotebookSetting.showFoldingControls); + const dragAndDropEnabled = e.affectsConfiguration(NotebookSetting.dragAndDropEnabled); const fontSize = e.affectsConfiguration('editor.fontSize'); - const editorOptionsCustomizations = e.affectsConfiguration(NotebookCellEditorOptionsCustomizations); + const markupFontSize = e.affectsConfiguration(NotebookSetting.markupFontSize); + const editorOptionsCustomizations = e.affectsConfiguration(NotebookSetting.cellEditorOptionsCustomizations); if ( !cellStatusBarVisibility @@ -198,6 +203,7 @@ export class NotebookOptions extends Disposable { && !showFoldingControls && !dragAndDropEnabled && !fontSize + && !markupFontSize && !editorOptionsCustomizations) { return; } @@ -205,15 +211,15 @@ export class NotebookOptions extends Disposable { let configuration = Object.assign({}, this._layoutConfiguration); if (cellStatusBarVisibility) { - configuration.showCellStatusBar = this.configurationService.getValue<ShowCellStatusBarType>(ShowCellStatusBar); + configuration.showCellStatusBar = this.configurationService.getValue<ShowCellStatusBarType>(NotebookSetting.showCellStatusBar); } if (cellToolbarLocation) { - configuration.cellToolbarLocation = this.configurationService.getValue<string | { [key: string]: string; }>(CellToolbarLocation) ?? { 'default': 'right' }; + configuration.cellToolbarLocation = this.configurationService.getValue<string | { [key: string]: string; }>(NotebookSetting.cellToolbarLocation) ?? { 'default': 'right' }; } if (cellToolbarInteraction && !this.overrides?.cellToolbarInteraction) { - configuration.cellToolbarInteraction = this.configurationService.getValue<string>(CellToolbarVisibility); + configuration.cellToolbarInteraction = this.configurationService.getValue<string>(NotebookSetting.cellToolbarVisibility); } if (focusIndicator) { @@ -221,7 +227,7 @@ export class NotebookOptions extends Disposable { } if (compactView) { - const compactViewValue = this.configurationService.getValue<boolean | undefined>(CompactView) ?? true; + const compactViewValue = this.configurationService.getValue<boolean | undefined>(NotebookSetting.compactView) ?? true; configuration = Object.assign(configuration, { ...(compactViewValue ? compactConfigConstants : defaultConfigConstants), }); @@ -237,15 +243,15 @@ export class NotebookOptions extends Disposable { } if (globalToolbar && this.overrides?.globalToolbar === undefined) { - configuration.globalToolbar = this.configurationService.getValue<boolean>(GlobalToolbar) ?? true; + configuration.globalToolbar = this.configurationService.getValue<boolean>(NotebookSetting.globalToolbar) ?? true; } if (consolidatedOutputButton) { - configuration.consolidatedOutputButton = this.configurationService.getValue<boolean>(ConsolidatedOutputButton) ?? true; + configuration.consolidatedOutputButton = this.configurationService.getValue<boolean>(NotebookSetting.consolidatedOutputButton) ?? true; } if (consolidatedRunButton) { - configuration.consolidatedRunButton = this.configurationService.getValue<boolean>(ConsolidatedRunButton) ?? true; + configuration.consolidatedRunButton = this.configurationService.getValue<boolean>(NotebookSetting.consolidatedRunButton) ?? true; } if (showFoldingControls) { @@ -253,15 +259,19 @@ export class NotebookOptions extends Disposable { } if (dragAndDropEnabled) { - configuration.dragAndDropEnabled = this.configurationService.getValue<boolean>(DragAndDropEnabled) ?? true; + configuration.dragAndDropEnabled = this.configurationService.getValue<boolean>(NotebookSetting.dragAndDropEnabled) ?? true; } if (fontSize) { configuration.fontSize = this.configurationService.getValue<number>('editor.fontSize'); } + if (markupFontSize) { + configuration.markupFontSize = this.configurationService.getValue<number>(NotebookSetting.markupFontSize); + } + if (editorOptionsCustomizations) { - configuration.editorOptionsCustomizations = this.configurationService.getValue(NotebookCellEditorOptionsCustomizations); + configuration.editorOptionsCustomizations = this.configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations); } this._layoutConfiguration = Object.freeze(configuration); @@ -281,24 +291,25 @@ export class NotebookOptions extends Disposable { consolidatedRunButton, dragAndDropEnabled, fontSize, + markupFontSize, editorOptionsCustomizations }); } private _computeInsertToolbarPositionOption() { - return this.configurationService.getValue<'betweenCells' | 'notebookToolbar' | 'both' | 'hidden'>(InsertToolbarLocation) ?? 'both'; + return this.configurationService.getValue<'betweenCells' | 'notebookToolbar' | 'both' | 'hidden'>(NotebookSetting.insertToolbarLocation) ?? 'both'; } private _computeInsertToolbarAlignmentOption() { - return this.configurationService.getValue<'left' | 'center'>(ExperimentalInsertToolbarAlignment) ?? 'center'; + return this.configurationService.getValue<'left' | 'center'>(NotebookSetting.experimentalInsertToolbarAlignment) ?? 'center'; } private _computeShowFoldingControlsOption() { - return this.configurationService.getValue<'always' | 'mouseover'>(ShowFoldingControls) ?? 'mouseover'; + return this.configurationService.getValue<'always' | 'mouseover'>(NotebookSetting.showFoldingControls) ?? 'mouseover'; } private _computeFocusIndicatorOption() { - return this.configurationService.getValue<'border' | 'gutter'>(FocusIndicator) ?? 'gutter'; + return this.configurationService.getValue<'border' | 'gutter'>(NotebookSetting.focusIndicator) ?? 'gutter'; } getLayoutConfiguration(): NotebookLayoutConfiguration { @@ -456,7 +467,8 @@ export class NotebookOptions extends Disposable { rightMargin: this._layoutConfiguration.cellRightMargin, runGutter: this._layoutConfiguration.cellRunGutter, dragAndDropEnabled: this._layoutConfiguration.dragAndDropEnabled, - fontSize: this._layoutConfiguration.fontSize + fontSize: this._layoutConfiguration.fontSize, + markupFontSize: this._layoutConfiguration.markupFontSize, }; } @@ -470,7 +482,8 @@ export class NotebookOptions extends Disposable { rightMargin: 0, runGutter: 0, dragAndDropEnabled: false, - fontSize: this._layoutConfiguration.fontSize + fontSize: this._layoutConfiguration.fontSize, + markupFontSize: this._layoutConfiguration.markupFontSize, }; } diff --git a/src/vs/workbench/contrib/notebook/test/notebookDiff.test.ts b/src/vs/workbench/contrib/notebook/test/notebookDiff.test.ts index 4881945cbf4..cab9ddc108d 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookDiff.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookDiff.test.ts @@ -374,7 +374,7 @@ suite('NotebookCommon', () => { assert.strictEqual(diffViewModels.viewModels[0].type, 'unchanged'); assert.strictEqual(diffViewModels.viewModels[0].checkIfOutputsModified(), false); assert.strictEqual(diffViewModels.viewModels[1].type, 'modified'); - assert.strictEqual(diffViewModels.viewModels[1].checkIfOutputsModified(), true); + assert.deepStrictEqual(diffViewModels.viewModels[1].checkIfOutputsModified(), { reason: undefined }); }); }); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 44b34029af1..f12bf278726 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -122,7 +122,7 @@ export class SettingsEditor2 extends EditorPane { return false; } return type === SettingValueType.Enum || - type === SettingValueType.StringOrEnumArray || + type === SettingValueType.Array || type === SettingValueType.BooleanObject || type === SettingValueType.Object || type === SettingValueType.Complex || diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index c4676688baf..d5b6f5b6156 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -1101,8 +1101,7 @@ export class SettingArrayRenderer extends AbstractSettingRenderer implements ITr common.toDispose.add( listWidget.onDidChangeList(e => { const newList = this.computeNewList(template, e); - this.onDidChangeList(template, newList); - if (newList !== null && template.onChange) { + if (template.onChange) { template.onChange(newList); } }) @@ -1111,8 +1110,8 @@ export class SettingArrayRenderer extends AbstractSettingRenderer implements ITr return template; } - private onDidChangeList(template: ISettingListItemTemplate, newList: string[] | undefined | null): void { - if (!template.context || newList === null) { + private onDidChangeList(template: ISettingListItemTemplate, newList: unknown[] | undefined): void { + if (!template.context || !newList) { return; } @@ -1179,7 +1178,7 @@ export class SettingArrayRenderer extends AbstractSettingRenderer implements ITr super.renderSettingElement(element, index, templateData); } - protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingListItemTemplate, onChange: (value: string[] | undefined) => void): void { + protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingListItemTemplate, onChange: (value: string[] | number[] | undefined) => void): void { const value = getListDisplayValue(dataElement); const keySuggester = dataElement.setting.enum ? createArraySuggester(dataElement) : undefined; template.listWidget.setValue(value, { @@ -1193,8 +1192,17 @@ export class SettingArrayRenderer extends AbstractSettingRenderer implements ITr })); template.onChange = (v) => { - onChange(v); - renderArrayValidations(dataElement, template, v, false); + if (!renderArrayValidations(dataElement, template, v, false)) { + let arrToSave; + const itemType = dataElement.setting.arrayItemType; + if (v && (itemType === 'number' || itemType === 'integer')) { + arrToSave = v.map(a => +a); + } else { + arrToSave = v; + } + this.onDidChangeList(template, arrToSave); + onChange(arrToSave); + } }; renderArrayValidations(dataElement, template, value.map(v => v.value.data.toString()), true); @@ -2000,12 +2008,15 @@ function renderValidations(dataElement: SettingsTreeSettingElement, template: IS return false; } +/** + * Validate and render any error message for arrays. Returns true if the value is invalid. + */ function renderArrayValidations( dataElement: SettingsTreeSettingElement, template: ISettingListItemTemplate | ISettingObjectItemTemplate, value: string[] | Record<string, unknown> | undefined, calledOnStartup: boolean -) { +): boolean { template.containerElement.classList.add('invalid-input'); if (dataElement.setting.validator) { const errMsg = dataElement.setting.validator(value); @@ -2015,12 +2026,13 @@ function renderArrayValidations( const validationError = localize('validationError', "Validation Error."); template.containerElement.setAttribute('aria-label', [dataElement.setting.key, validationError, errMsg].join(' ')); if (!calledOnStartup) { ariaAlert(validationError + ' ' + errMsg); } - return; + return true; } else { template.containerElement.setAttribute('aria-label', dataElement.setting.key); template.containerElement.classList.remove('invalid-input'); } } + return false; } function cleanRenderedMarkdown(element: Node): void { @@ -2154,7 +2166,7 @@ class SettingsTreeDelegate extends CachedListVirtualDelegate<SettingsTreeGroupCh return SETTINGS_ENUM_TEMPLATE_ID; } - if (element.valueType === SettingValueType.StringOrEnumArray) { + if (element.valueType === SettingValueType.Array) { return SETTINGS_ARRAY_TEMPLATE_ID; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 3fae861e143..19a507ea512 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -241,8 +241,9 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { this.valueType = SettingValueType.Number; } else if (this.setting.type === 'boolean') { this.valueType = SettingValueType.Boolean; - } else if (this.setting.type === 'array' && (this.setting.arrayItemType === 'string' || this.setting.arrayItemType === 'enum')) { - this.valueType = SettingValueType.StringOrEnumArray; + } else if (this.setting.type === 'array' && this.setting.arrayItemType && + ['string', 'enum', 'number', 'integer'].includes(this.setting.arrayItemType)) { + this.valueType = SettingValueType.Array; } else if (isArray(this.setting.type) && this.setting.type.includes(SettingValueType.Null) && this.setting.type.length === 2) { if (this.setting.type.includes(SettingValueType.Integer)) { this.valueType = SettingValueType.NullableInteger; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index 056788ec1f9..057203bbeaa 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -824,7 +824,7 @@ export class ListSettingWidget extends AbstractListSettingWidget<IListDataItem> deleteActionTooltip: localize('removeItem', "Remove Item"), editActionTooltip: localize('editItem', "Edit Item"), addButtonLabel: localize('addItem', "Add Item"), - inputPlaceholder: localize('itemInputPlaceholder', "String Item..."), + inputPlaceholder: localize('itemInputPlaceholder', "Item..."), siblingInputPlaceholder: localize('listSiblingInputPlaceholder', "Sibling..."), }; } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts index f157881dc8c..78ac23a1588 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts @@ -11,7 +11,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { SCMMenus } from 'vs/workbench/contrib/scm/browser/menus'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { debounce } from 'vs/base/common/decorators'; -import { ILogService } from 'vs/platform/log/common/log'; function getProviderStorageKey(provider: ISCMProvider): string { return `${provider.contextValue}:${provider.label}${provider.rootUri ? `:${provider.rootUri.toString()}` : ''}`; @@ -100,8 +99,7 @@ export class SCMViewService implements ISCMViewService { constructor( @ISCMService private readonly scmService: ISCMService, @IInstantiationService instantiationService: IInstantiationService, - @IStorageService private readonly storageService: IStorageService, - @ILogService private readonly logService: ILogService + @IStorageService private readonly storageService: IStorageService ) { this.menus = instantiationService.createInstance(SCMMenus); @@ -123,8 +121,6 @@ export class SCMViewService implements ISCMViewService { } private onDidAddRepository(repository: ISCMRepository): void { - this.logService.trace('SCMViewService#onDidAddRepository', getProviderStorageKey(repository.provider)); - if (!this.didFinishLoading) { this.eventuallyFinishLoading(); } @@ -135,8 +131,6 @@ export class SCMViewService implements ISCMViewService { const index = this.previousState.all.indexOf(getProviderStorageKey(repository.provider)); if (index === -1) { // saw a repo we did not expect - this.logService.trace('SCMViewService#onDidAddRepository', 'This is a new repository, so we stop the heuristics'); - const added: ISCMRepository[] = []; for (const repo of this.scmService.repositories) { // all should be visible if (!this._visibleRepositoriesSet.has(repo)) { @@ -179,8 +173,6 @@ export class SCMViewService implements ISCMViewService { } private onDidRemoveRepository(repository: ISCMRepository): void { - this.logService.trace('SCMViewService#onDidRemoveRepository', getProviderStorageKey(repository.provider)); - if (!this.didFinishLoading) { this.eventuallyFinishLoading(); } @@ -257,7 +249,6 @@ export class SCMViewService implements ISCMViewService { @debounce(2000) private eventuallyFinishLoading(): void { - this.logService.trace('SCMViewService#eventuallyFinishLoading'); this.finishLoading(); } @@ -266,7 +257,6 @@ export class SCMViewService implements ISCMViewService { return; } - this.logService.trace('SCMViewService#finishLoading'); this.didFinishLoading = true; this.previousState = undefined; } diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index cf005b14e5a..bd605002ca4 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -40,24 +40,57 @@ .monaco-workbench .editor-instance .terminal-wrapper, .monaco-workbench .pane-body.integrated-terminal .terminal-wrapper { display: none; - margin: 0 10px; height: 100%; - padding-bottom: 2px; box-sizing: border-box; } +.monaco-workbench .editor-instance .xterm, +.monaco-workbench .pane-body.integrated-terminal .xterm { + /* All terminals have at least 10px left/right edge padding and 2 padding on the bottom (so underscores on last line are visible */ + padding: 0 10px 2px; + /* Bottom align the terminal withing the split pane */ + position: absolute; + bottom: 0; + left: 0; + right: 0; +} + +.monaco-workbench .editor-instance .terminal-wrapper.fixed-dims .xterm, +.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper.fixed-dims .xterm { + position: static; +} + +.monaco-workbench .editor-instance .xterm-viewport, +.monaco-workbench .pane-body.integrated-terminal .xterm-viewport { + z-index: 30; +} + +.monaco-workbench .editor-instance .xterm-screen, +.monaco-workbench .pane-body.integrated-terminal .xterm-screen { + z-index: 31; +} + +.xterm .xterm-screen { + cursor: text; +} + +/* Apply cursor styles to xterm-screen as well due to how .xterm-viewport/.xterm are positioned */ +.xterm.enable-mouse-events .xterm-screen { cursor: default; } +.xterm.xterm-cursor-pointer .xterm-screen { cursor: pointer; } +.xterm.column-select.focus .xterm-screen { cursor: crosshair; } + .monaco-workbench .editor-instance .terminal-wrapper.active, .monaco-workbench .pane-body.integrated-terminal .terminal-wrapper.active { display: block; } -.monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:first-child .terminal-wrapper, -.monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:first-child .terminal-wrapper { - margin-left: 20px; +.monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:first-child .xterm, +.monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:first-child .xterm { + padding-left: 20px; } -.monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .terminal-wrapper, -.monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .terminal-wrapper { - margin-right: 20px; +.monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm, +.monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm { + padding-right: 20px; } .monaco-workbench .editor-instance .xterm a:not(.xterm-invalid-link), @@ -69,22 +102,24 @@ .monaco-workbench .editor-instance .terminal-wrapper > div, .monaco-workbench .pane-body.integrated-terminal .terminal-wrapper > div { height: 100%; - /* Align the viewport and canvases to the bottom of the panel */ - display: flex; - align-items: flex-end; } .monaco-workbench .editor-instance .xterm-viewport, .monaco-workbench .pane-body.integrated-terminal .xterm-viewport { box-sizing: border-box; - margin-right: -10px; +} + +.monaco-workbench .editor-instance .terminal-wrapper.fixed-dims, +.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper.fixed-dims { + /* The viewport should be positioned against this so it does't conflict with a fixed dimensions terminal horizontal scroll bar*/ + position: relative; +} + +.monaco-workbench .editor-instance .terminal-wrapper:not(.fixed-dims) .xterm-viewport, +.monaco-workbench .pane-body.integrated-terminal .terminal-wrapper:not(.fixed-dims) .xterm-viewport { /* Override xterm.js' width as we want to size the viewport to fill the panel so the scrollbar is on the right edge */ width: auto !important; } -.monaco-workbench .editor-instance .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm-viewport, -.monaco-workbench .pane-body.integrated-terminal .terminal-group .monaco-split-view2.horizontal .split-view-view:last-child .xterm-viewport { - margin-right: -20px; -} .monaco-workbench .pane-body.integrated-terminal { font-variant-ligatures: none; diff --git a/src/vs/workbench/contrib/terminal/browser/media/widgets.css b/src/vs/workbench/contrib/terminal/browser/media/widgets.css index 51a9ab8eff2..3ed2086bebe 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/widgets.css +++ b/src/vs/workbench/contrib/terminal/browser/media/widgets.css @@ -26,12 +26,12 @@ .monaco-workbench .terminal-env-var-info { position: absolute; - right: 2px; + right: 10px; /* room for scroll bar */ top: 0; width: 28px; height: 28px; text-align: center; - z-index: 10; + z-index: 32; opacity: 0.5; } @@ -40,11 +40,6 @@ opacity: 1; } -.monaco-workbench .pane-body.integrated-terminal .monaco-split-view2.horizontal .split-view-view:last-child .terminal-env-var-info { - /* Adjust for reduced margin in splits */ - right: -8px; -} - .monaco-workbench .terminal-env-var-info.codicon { line-height: 28px; } diff --git a/src/vs/workbench/contrib/terminal/browser/media/xterm.css b/src/vs/workbench/contrib/terminal/browser/media/xterm.css index 018a797e85b..3a8f3534809 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/xterm.css +++ b/src/vs/workbench/contrib/terminal/browser/media/xterm.css @@ -40,9 +40,9 @@ */ .xterm { - font-feature-settings: "liga" 0; position: relative; user-select: none; + -ms-user-select: none; -webkit-user-select: none; } diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts index d32a431a437..6ddd5af7baf 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts @@ -337,7 +337,7 @@ class RemoteTerminalBackend extends Disposable implements ITerminalBackend { return this._remoteTerminalChannel?.getWslPath(original) || original; } - setTerminalLayoutInfo(layout: ITerminalsLayoutInfoById): Promise<void> { + async setTerminalLayoutInfo(layout?: ITerminalsLayoutInfoById): Promise<void> { if (!this._remoteTerminalChannel) { throw new Error(`Cannot call setActiveInstanceId when there is no remote`); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index b9a3fccbf94..7f2d006ee3f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, TerminalIcon, TitleEventSource, TerminalShellType, IExtensionTerminalProfile, TerminalLocation, ProcessPropertyType, ProcessCapability, IProcessPropertyMap } from 'vs/platform/terminal/common/terminal'; -import { ICommandTracker, INavigationMode, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalFont, ITerminalBackend, ITerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ICommandTracker, INavigationMode, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalFont, ITerminalBackend, ITerminalProcessExtHostProxy, IRegisterContributedProfileArgs } from 'vs/workbench/contrib/terminal/common/terminal'; import type { Terminal as XTermTerminal } from 'xterm'; import type { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; import type { Unicode11Addon as XTermUnicode11Addon } from 'xterm-addon-unicode11'; @@ -19,6 +19,7 @@ import { IEditableData } from 'vs/workbench/common/views'; import { DeserializedTerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorSerializer'; import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; +import { IKeyMods } from 'vs/platform/quickinput/common/quickInput'; export const ITerminalService = createDecorator<ITerminalService>('terminalService'); export const ITerminalEditorService = createDecorator<ITerminalEditorService>('terminalEditorService'); @@ -73,6 +74,11 @@ export const enum Direction { Down = 3 } +export interface IQuickPickTerminalObject { + config: IRegisterContributedProfileArgs | ITerminalProfile | { profile: IExtensionTerminalProfile, options: { icon?: string, color?: string } } | undefined, + keyMods: IKeyMods | undefined +} + export interface ITerminalGroup { activeInstance: ITerminalInstance | undefined; terminalInstances: ITerminalInstance[]; @@ -387,6 +393,8 @@ export interface ITerminalInstance { readonly rows: number; readonly maxCols: number; readonly maxRows: number; + readonly fixedCols?: number; + readonly fixedRows?: number; readonly icon?: TerminalIcon; readonly color?: string; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 3be66b94032..08ffd2a1e3a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -29,7 +29,7 @@ import { TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ITerminalInstanceService, ITerminalInstance, ITerminalExternalLinkProvider, IRequestAddInstanceToGroupEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; -import type { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm'; +import type { Terminal as XTermTerminal, ITerminalAddon } from 'xterm'; import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/xterm/navigationModeAddon'; import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; import { EnvironmentVariableInfoWidget } from 'vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget'; @@ -128,6 +128,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _fixedRows: number | undefined; private _cwd: string | undefined = undefined; private _initialCwd: string | undefined = undefined; + private _layoutSettingsChanged: boolean = true; private _dimensionsOverride: ITerminalDimensionsOverride | undefined; private _titleReadyPromise: Promise<string>; private _titleReadyComplete: ((title: string) => any) | undefined; @@ -364,6 +365,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (this._fixedCols) { await this._addScrollbar(); } + }).catch((err) => { + // Ignore exceptions if the terminal is already disposed + if (!this._isDisposed) { + throw err; + } }); this.addDisposable(this._configurationService.onDidChangeConfiguration(async e => { @@ -381,6 +387,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { 'editor.fontFamily' ]; if (layoutSettings.some(id => e.affectsConfiguration(id))) { + this._layoutSettingsChanged = true; await this._resize(); } if (e.affectsConfiguration(TerminalSettingId.UnicodeVersion)) { @@ -441,9 +448,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } - const computedStyle = window.getComputedStyle(this._wrapperElement!); - const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10); - const height = parseInt(computedStyle.getPropertyValue('height').replace('px', ''), 10); + const computedStyle = window.getComputedStyle(this._container); + const width = parseInt(computedStyle.width); + const height = parseInt(computedStyle.height); + this._evaluateColsAndRows(width, height); } @@ -514,10 +522,15 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return undefined; } - if (!this._wrapperElement) { + if (!this._wrapperElement || !this.xterm?.raw.element) { return undefined; } - TerminalInstance._lastKnownCanvasDimensions = new dom.Dimension(Math.min(Constants.MaxCanvasWidth, width), height + (this._hasScrollBar && !this._horizontalScrollbar ? -scrollbarHeight - 2 : 0)/* bottom padding */); + const computedStyle = window.getComputedStyle(this.xterm.raw.element); + const horizontalPadding = parseInt(computedStyle.paddingLeft) + parseInt(computedStyle.paddingRight); + const verticalPadding = parseInt(computedStyle.paddingTop) + parseInt(computedStyle.paddingBottom); + TerminalInstance._lastKnownCanvasDimensions = new dom.Dimension( + Math.min(Constants.MaxCanvasWidth, width - horizontalPadding), + height + (this._hasScrollBar && !this._horizontalScrollbar ? -scrollbarHeight : 0) - 2/* bottom padding */ - verticalPadding); return TerminalInstance._lastKnownCanvasDimensions; } @@ -543,8 +556,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { */ protected async _createXterm(): Promise<XtermTerminal> { const Terminal = await this._getXtermConstructor(); + if (this._isDisposed) { + throw new Error('Terminal disposed of during xterm.js creation'); + } - // TODO: Move cols/rows over to XtermTerminal const xterm = this._instantiationService.createInstance(XtermTerminal, Terminal, this._configHelper, this._cols, this._rows); this.xterm = xterm; const lineDataEventAddon = new LineDataEventAddon(); @@ -600,7 +615,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { lineDataEventAddon.setOperatingSystem(this._processManager.os); } if (this._processManager.os === OperatingSystem.Windows) { - xterm.raw.setOption('windowsMode', processTraits.requiresWindowsMode || false); + xterm.raw.options.windowsMode = processTraits.requiresWindowsMode || false; } this._linkManager = this._instantiationService.createInstance(TerminalLinkManager, xterm.raw, this._processManager!); this._areLinksReady = true; @@ -763,13 +778,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { xterm.raw.focus(); })); - this._register(dom.addDisposableListener(xterm.raw.element, 'wheel', (e) => { - if (this._hasScrollBar && e.shiftKey) { - e.stopImmediatePropagation(); - e.preventDefault(); - } - })); - // xterm.js currently drops selection on keyup as we need to handle this case. this._register(dom.addDisposableListener(xterm.raw.element, 'keyup', () => { // Wait until keyup has propagated through the DOM before evaluating @@ -1080,7 +1088,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); this._processManager.onEnvironmentVariableInfoChanged(e => this._onEnvironmentVariableInfoChanged(e)); this._processManager.onPtyDisconnect(() => { - this._safeSetOption('disableStdin', true); + if (this.xterm) { + this.xterm.raw.options.disableStdin = true; + } this.statusList.add({ id: TerminalStatus.Disconnected, severity: Severity.Error, @@ -1089,7 +1099,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); }); this._processManager.onPtyReconnect(() => { - this._safeSetOption('disableStdin', false); + if (this.xterm) { + this.xterm.raw.options.disableStdin = false; + } this.statusList.remove(TerminalStatus.Disconnected); }); } @@ -1223,7 +1235,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { xterm.raw.write(formatMessageForTerminal(this._shellLaunchConfig.waitOnExit)); } // Disable all input if the terminal is exiting and listen for next keypress - xterm.raw.setOption('disableStdin', true); + xterm.raw.options.disableStdin = true; if (xterm.raw.textarea) { this._attachPressAnyKeyToCloseListener(xterm.raw); } @@ -1305,7 +1317,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Clean up waitOnExit state if (this._isExiting && this._shellLaunchConfig.waitOnExit) { - this.xterm.raw.setOption('disableStdin', false); + this.xterm.raw.options.disableStdin = false; this._isExiting = false; } } @@ -1389,7 +1401,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._navigationModeAddon?.dispose(); this._navigationModeAddon = undefined; } - this.xterm!.raw.setOption('screenReaderMode', isEnabled); + this.xterm!.raw.options.screenReaderMode = isEnabled; } private _setCommandsToSkipShell(commands: string[]): void { @@ -1399,16 +1411,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }).concat(commands); } - private _safeSetOption(key: string, value: any): void { - if (!this.xterm) { - return; - } - - if (this.xterm.raw.getOption(key) !== value) { - this.xterm.raw.setOption(key, value); - } - } - layout(dimension: dom.Dimension): void { this._lastLayoutDimensions = dimension; if (this.disableLayout) { @@ -1421,6 +1423,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } + // Evaluate columns and rows, exclude the wrapper element's margin const terminalWidth = this._evaluateColsAndRows(dimension.width, dimension.height); if (!terminalWidth) { return; @@ -1444,21 +1447,23 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (this.xterm) { // Only apply these settings when the terminal is visible so that // the characters are measured correctly. - if (this._isVisible) { - const font = this.xterm ? this.xterm.getFont() : this._configHelper.getFont(); + if (this._isVisible && this._layoutSettingsChanged) { + const font = this.xterm.getFont(); const config = this._configHelper.config; - this._safeSetOption('letterSpacing', font.letterSpacing); - this._safeSetOption('lineHeight', font.lineHeight); - this._safeSetOption('fontSize', font.fontSize); - this._safeSetOption('fontFamily', font.fontFamily); - this._safeSetOption('fontWeight', config.fontWeight); - this._safeSetOption('fontWeightBold', config.fontWeightBold); + this.xterm.raw.options.letterSpacing = font.letterSpacing; + this.xterm.raw.options.lineHeight = font.lineHeight; + this.xterm.raw.options.fontSize = font.fontSize; + this.xterm.raw.options.fontFamily = font.fontFamily; + this.xterm.raw.options.fontWeight = config.fontWeight; + this.xterm.raw.options.fontWeightBold = config.fontWeightBold; // Any of the above setting changes could have changed the dimensions of the // terminal, re-evaluate now. this._initDimensions(); cols = this.cols; rows = this.rows; + + this._layoutSettingsChanged = false; } if (isNaN(cols) || isNaN(rows)) { @@ -1599,6 +1604,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } this._fixedCols = this._parseFixedDimension(cols); + this._labelComputer?.refreshLabel(); this._terminalHasFixedWidth.set(!!this._fixedCols); const rows = await this._quickInputService.input({ title: nls.localize('setTerminalDimensionsRow', "Set Fixed Dimensions: Row"), @@ -1609,7 +1615,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } this._fixedRows = this._parseFixedDimension(rows); - this._addScrollbar(); + this._labelComputer?.refreshLabel(); + await this._refreshScrollbar(); this._resize(); this.focus(); } @@ -1636,37 +1643,37 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._hasScrollBar = false; this._initDimensions(); await this._resize(); - this._horizontalScrollbar?.setScrollDimensions({ scrollWidth: 0 }); } else { - let maxCols = 0; - if (!this.xterm.raw.buffer.active.getLine(0)) { - return; - } - const lineWidth = this.xterm.raw.buffer.active.getLine(0)!.length; - for (let i = this.xterm.raw.buffer.active.length - 1; i >= this.xterm.raw.buffer.active.viewportY; i--) { - const lineInfo = this._getWrappedLineCount(i, this.xterm.raw.buffer.active); - maxCols = Math.max(maxCols, ((lineInfo.lineCount * lineWidth) - lineInfo.endSpaces) || 0); - i = lineInfo.currentIndex; + // Fixed columns should be at least xterm.js' regular column count + const proposedCols = Math.max(this.maxCols, Math.min(this.xterm.getLongestViewportWrappedLineLength(), Constants.MaxSupportedCols)); + // Don't switch to fixed dimensions if the content already fits as it makes the scroll + // bar look bad being off the edge + if (proposedCols > this.xterm.raw.cols) { + this._fixedCols = proposedCols; } - maxCols = Math.min(maxCols, Constants.MaxSupportedCols); - this._fixedCols = maxCols; - await this._addScrollbar(); } + await this._refreshScrollbar(); + this._labelComputer?.refreshLabel(); this.focus(); } + private _refreshScrollbar(): Promise<void> { + if (this._fixedCols || this._fixedRows) { + return this._addScrollbar(); + } + return this._removeScrollbar(); + } + private async _addScrollbar(): Promise<void> { const charWidth = (this.xterm ? this.xterm.getFont() : this._configHelper.getFont()).charWidth; if (!this.xterm?.raw.element || !this._wrapperElement || !this._container || !charWidth || !this._fixedCols) { return; } - if (this._fixedCols < this.xterm.raw.buffer.active.getLine(0)!.length) { - // no scrollbar needed - return; - } + this._wrapperElement.classList.add('fixed-dims'); this._hasScrollBar = true; this._initDimensions(); - this._fixedRows = this.rows; + // Always remove a row to make room for the scroll bar + this._fixedRows = this._rows - 1; await this._resize(); this._terminalHasFixedWidth.set(true); if (!this._horizontalScrollbar) { @@ -1679,39 +1686,31 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { })); this._container.appendChild(this._horizontalScrollbar.getDomNode()); } - this._horizontalScrollbar.setScrollDimensions( - { - width: this.xterm.raw.element.clientWidth, - scrollWidth: this._fixedCols * charWidth - }); - this._horizontalScrollbar!.getDomNode().style.paddingBottom = '16px'; + this._horizontalScrollbar.setScrollDimensions({ + width: this.xterm.raw.element.clientWidth, + scrollWidth: this._fixedCols * charWidth + 40 // Padding + scroll bar + }); + this._horizontalScrollbar.getDomNode().style.paddingBottom = '16px'; // work around for https://github.com/xtermjs/xterm.js/issues/3482 - for (let i = this.xterm.raw.buffer.active.viewportY; i < this.xterm.raw.buffer.active.length; i++) { - let line = this.xterm.raw.buffer.active.getLine(i); - (line as any)._line.isWrapped = false; + if (isWindows) { + for (let i = this.xterm.raw.buffer.active.viewportY; i < this.xterm.raw.buffer.active.length; i++) { + let line = this.xterm.raw.buffer.active.getLine(i); + (line as any)._line.isWrapped = false; + } } } - private _getWrappedLineCount(index: number, buffer: IBuffer): { lineCount: number, currentIndex: number, endSpaces: number } { - let line = buffer.getLine(index); - if (!line) { - throw new Error('Could not get line'); - } - let currentIndex = index; - let endSpaces = -1; - for (let i = line?.length || 0; i > 0; i--) { - if (line && !line?.getCell(i)?.getChars()) { - endSpaces++; - } else { - break; - } - } - while (line?.isWrapped && currentIndex > 0) { - currentIndex--; - line = buffer.getLine(currentIndex); + private async _removeScrollbar(): Promise<void> { + if (!this._container || !this._wrapperElement || !this._horizontalScrollbar) { + return; } - return { lineCount: index - currentIndex + 1, currentIndex, endSpaces }; + this._horizontalScrollbar.getDomNode().remove(); + this._horizontalScrollbar.dispose(); + this._horizontalScrollbar = undefined; + this._wrapperElement.remove(); + this._wrapperElement.classList.remove('fixed-dims'); + this._container.appendChild(this._wrapperElement); } private _setResolvedShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig): void { @@ -1782,8 +1781,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { async toggleEscapeSequenceLogging(): Promise<void> { const xterm = await this._xtermReadyPromise; - const isDebug = xterm.raw.getOption('logLevel') === 'debug'; - xterm.raw.setOption('logLevel', isDebug ? 'info' : 'debug'); + xterm.raw.options.logLevel = xterm.raw.options.logLevel === 'debug' ? 'info' : 'debug'; } async getInitialCwd(): Promise<string> { @@ -2065,6 +2063,7 @@ export interface ITerminalLabelTemplateProperties { process?: string | null | undefined; sequence?: string | null | undefined; task?: string | null | undefined; + fixedDimensions?: string | null | undefined; separator?: string | ISeparator | null | undefined; } @@ -2083,11 +2082,12 @@ export class TerminalLabelComputer extends Disposable { readonly onDidChangeLabel = this._onDidChangeLabel.event; constructor( private readonly _configHelper: TerminalConfigHelper, - private readonly _instance: Pick<ITerminalInstance, 'shellLaunchConfig' | 'cwd' | 'initialCwd' | 'processName' | 'sequence' | 'userHome' | 'workspaceFolder' | 'staticTitle' | 'capabilities' | 'title' | 'description'>, + private readonly _instance: Pick<ITerminalInstance, 'shellLaunchConfig' | 'cwd' | 'fixedCols' | 'fixedRows' | 'initialCwd' | 'processName' | 'sequence' | 'userHome' | 'workspaceFolder' | 'staticTitle' | 'capabilities' | 'title' | 'description'>, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService ) { super(); } + refreshLabel(): void { this._title = this.computeLabel(this._configHelper.config.tabs.title, TerminalLabelType.Title); this._description = this.computeLabel(this._configHelper.config.tabs.description, TerminalLabelType.Description); @@ -2108,6 +2108,9 @@ export class TerminalLabelComputer extends Disposable { process: this._instance.processName, sequence: this._instance.sequence, task: this._instance.shellLaunchConfig.description === 'Task' ? 'Task' : undefined, + fixedDimensions: this._instance.fixedCols + ? (this._instance.fixedRows ? `\u2194${this._instance.fixedCols} \u2195${this._instance.fixedRows}` : `\u2194${this._instance.fixedCols}`) + : (this._instance.fixedRows ? `\u2195${this._instance.fixedRows}` : ''), separator: { label: this._configHelper.config.tabs.separator } }; labelTemplate = labelTemplate.trim(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts new file mode 100644 index 00000000000..e7dac04a1c1 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts @@ -0,0 +1,240 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { iconRegistry, Codicon } from 'vs/base/common/codicons'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IQuickInputService, IKeyMods, IPickOptions, IQuickPickSeparator, IQuickInputButton, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IExtensionTerminalProfile, ITerminalProfile, ITerminalProfileObject, TerminalSettingPrefix } from 'vs/platform/terminal/common/terminal'; +import { getUriClasses, getColorClass, getColorStyleElement } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; +import { configureTerminalProfileIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons'; +import * as nls from 'vs/nls'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IQuickPickTerminalObject } from 'vs/workbench/contrib/terminal/browser/terminal'; + + +type DefaultProfileName = string; +export class TerminalProfileQuickpick { + constructor( + @ITerminalProfileService private readonly _terminalProfileService: ITerminalProfileService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IQuickInputService private readonly _quickInputService: IQuickInputService, + @IThemeService private readonly _themeService: IThemeService + ) { } + + async showAndGetResult(type: 'setDefault' | 'createInstance'): Promise<IQuickPickTerminalObject | DefaultProfileName | undefined> { + const platformKey = await this._terminalProfileService.getPlatformKey(); + const profilesKey = TerminalSettingPrefix.Profiles + platformKey; + const result = await this._createAndShow(type); + const defaultProfileKey = `${TerminalSettingPrefix.DefaultProfile}${platformKey}`; + if (!result) { + return; + } + if (type === 'setDefault') { + if ('command' in result.profile) { + return; // Should never happen + } else if ('id' in result.profile) { + // extension contributed profile + await this._configurationService.updateValue(defaultProfileKey, result.profile.title, ConfigurationTarget.USER); + return { + config: { + extensionIdentifier: result.profile.extensionIdentifier, + id: result.profile.id, + title: result.profile.title, + options: { + color: result.profile.color, + icon: result.profile.icon + } + }, + keyMods: result.keyMods + }; + } + + // Add the profile to settings if necessary + if ('isAutoDetected' in result.profile) { + const profilesConfig = await this._configurationService.getValue(profilesKey); + if (typeof profilesConfig === 'object') { + const newProfile: ITerminalProfileObject = { + path: result.profile.path + }; + if (result.profile.args) { + newProfile.args = result.profile.args; + } + (profilesConfig as { [key: string]: ITerminalProfileObject })[result.profile.profileName] = newProfile; + } + await this._configurationService.updateValue(profilesKey, profilesConfig, ConfigurationTarget.USER); + } + // Set the default profile + await this._configurationService.updateValue(defaultProfileKey, result.profileName, ConfigurationTarget.USER); + } else if (type === 'createInstance') { + if ('id' in result.profile) { + return { + config: { + extensionIdentifier: result.profile.extensionIdentifier, + id: result.profile.id, + title: result.profile.title, + options: { + icon: result.profile.icon, + color: result.profile.color, + } + }, + keyMods: result.keyMods + }; + } else { + return { config: result.profile, keyMods: result.keyMods }; + } + } + // for tests + return 'profileName' in result.profile ? result.profile.profileName : result.profile.title; + } + + private async _createAndShow(type: 'setDefault' | 'createInstance'): Promise<IProfileQuickPickItem | undefined> { + const platformKey = await this._terminalProfileService.getPlatformKey(); + const profiles = this._terminalProfileService.availableProfiles; + const profilesKey = TerminalSettingPrefix.Profiles + platformKey; + const defaultProfileName = this._terminalProfileService.getDefaultProfileName(); + let keyMods: IKeyMods | undefined; + const options: IPickOptions<IProfileQuickPickItem> = { + placeHolder: type === 'createInstance' ? nls.localize('terminal.integrated.selectProfileToCreate', "Select the terminal profile to create") : nls.localize('terminal.integrated.chooseDefaultProfile', "Select your default terminal profile"), + onDidTriggerItemButton: async (context) => { + if ('command' in context.item.profile) { + return; + } + if ('id' in context.item.profile) { + return; + } + const configProfiles: { [key: string]: any } = this._configurationService.getValue(TerminalSettingPrefix.Profiles + platformKey); + const existingProfiles = !!configProfiles ? Object.keys(configProfiles) : []; + const name = await this._quickInputService.input({ + prompt: nls.localize('enterTerminalProfileName', "Enter terminal profile name"), + value: context.item.profile.profileName, + validateInput: async input => { + if (existingProfiles.includes(input)) { + return nls.localize('terminalProfileAlreadyExists', "A terminal profile already exists with that name"); + } + return undefined; + } + }); + if (!name) { + return; + } + const newConfigValue: { [key: string]: ITerminalProfileObject } = { ...configProfiles } ?? {}; + newConfigValue[name] = { + path: context.item.profile.path, + args: context.item.profile.args + }; + await this._configurationService.updateValue(profilesKey, newConfigValue, ConfigurationTarget.USER); + }, + onKeyMods: mods => keyMods = mods + }; + + // Build quick pick items + const quickPickItems: (IProfileQuickPickItem | IQuickPickSeparator)[] = []; + const configProfiles = profiles.filter(e => !e.isAutoDetected); + const autoDetectedProfiles = profiles.filter(e => e.isAutoDetected); + + if (configProfiles.length > 0) { + quickPickItems.push({ type: 'separator', label: nls.localize('terminalProfiles', "profiles") }); + quickPickItems.push(...this._sortProfileQuickPickItems(configProfiles.map(e => this._createProfileQuickPickItem(e)), defaultProfileName!)); + } + + quickPickItems.push({ type: 'separator', label: nls.localize('ICreateContributedTerminalProfileOptions', "contributed") }); + const contributedProfiles: IProfileQuickPickItem[] = []; + for (const contributed of this._terminalProfileService.contributedProfiles) { + if (typeof contributed.icon === 'string' && contributed.icon.startsWith('$(')) { + contributed.icon = contributed.icon.substring(2, contributed.icon.length - 1); + } + const icon = contributed.icon && typeof contributed.icon === 'string' ? (iconRegistry.get(contributed.icon) || Codicon.terminal) : Codicon.terminal; + const uriClasses = getUriClasses(contributed, this._themeService.getColorTheme().type, true); + const colorClass = getColorClass(contributed); + const iconClasses = []; + if (uriClasses) { + iconClasses.push(...uriClasses); + } + if (colorClass) { + iconClasses.push(colorClass); + } + contributedProfiles.push({ + label: `$(${icon.id}) ${contributed.title}`, + profile: { + extensionIdentifier: contributed.extensionIdentifier, + title: contributed.title, + icon: contributed.icon, + id: contributed.id, + color: contributed.color + }, + profileName: contributed.title, + iconClasses + }); + } + + if (contributedProfiles.length > 0) { + quickPickItems.push(...this._sortProfileQuickPickItems(contributedProfiles, defaultProfileName!)); + } + + if (autoDetectedProfiles.length > 0) { + quickPickItems.push({ type: 'separator', label: nls.localize('terminalProfiles.detected', "detected") }); + quickPickItems.push(...this._sortProfileQuickPickItems(autoDetectedProfiles.map(e => this._createProfileQuickPickItem(e)), defaultProfileName!)); + } + const styleElement = getColorStyleElement(this._themeService.getColorTheme()); + document.body.appendChild(styleElement); + + const result = await this._quickInputService.pick(quickPickItems, options); + document.body.removeChild(styleElement); + if (!result) { + return undefined; + } + if (keyMods) { + result.keyMods = keyMods; + } + return result; + } + + private _createProfileQuickPickItem(profile: ITerminalProfile): IProfileQuickPickItem { + const buttons: IQuickInputButton[] = [{ + iconClass: ThemeIcon.asClassName(configureTerminalProfileIcon), + tooltip: nls.localize('createQuickLaunchProfile', "Configure Terminal Profile") + }]; + const icon = (profile.icon && ThemeIcon.isThemeIcon(profile.icon)) ? profile.icon : Codicon.terminal; + const label = `$(${icon.id}) ${profile.profileName}`; + const colorClass = getColorClass(profile); + const iconClasses = []; + if (colorClass) { + iconClasses.push(colorClass); + } + + if (profile.args) { + if (typeof profile.args === 'string') { + return { label, description: `${profile.path} ${profile.args}`, profile, profileName: profile.profileName, buttons, iconClasses }; + } + const argsString = profile.args.map(e => { + if (e.includes(' ')) { + return `"${e.replace('/"/g', '\\"')}"`; + } + return e; + }).join(' '); + return { label, description: `${profile.path} ${argsString}`, profile, profileName: profile.profileName, buttons, iconClasses }; + } + return { label, description: profile.path, profile, profileName: profile.profileName, buttons, iconClasses }; + } + + private _sortProfileQuickPickItems(items: IProfileQuickPickItem[], defaultProfileName: string) { + return items.sort((a, b) => { + if (b.profileName === defaultProfileName) { + return 1; + } + if (a.profileName === defaultProfileName) { + return -1; + } + return a.profileName.localeCompare(b.profileName); + }); + } +} + +export interface IProfileQuickPickItem extends IQuickPickItem { + profile: ITerminalProfile | IExtensionTerminalProfile; + profileName: string; + keyMods?: IKeyMods | undefined; +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts index 7e30984367a..00f3aa099d6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts @@ -11,12 +11,12 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { isMacintosh, isWeb, isWindows, OperatingSystem, OS } from 'vs/base/common/platform'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ITerminalProfile, IExtensionTerminalProfile, TerminalSettingPrefix, TerminalSettingId, ICreateContributedTerminalProfileOptions, ITerminalProfileObject, IShellLaunchConfig } from 'vs/platform/terminal/common/terminal'; +import { ITerminalProfile, IExtensionTerminalProfile, TerminalSettingPrefix, TerminalSettingId, ITerminalProfileObject, IShellLaunchConfig } from 'vs/platform/terminal/common/terminal'; import { registerTerminalDefaultProfileConfiguration } from 'vs/platform/terminal/common/terminalPlatformConfiguration'; import { terminalIconsEqual, terminalProfileArgsMatch } from 'vs/platform/terminal/common/terminalProfiles'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { refreshTerminalActions } from 'vs/workbench/contrib/terminal/browser/terminalActions'; -import { ITerminalProfileProvider, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IRegisterContributedProfileArgs, ITerminalProfileProvider, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { ITerminalContributionService } from 'vs/workbench/contrib/terminal/common/terminalExtensionPoints'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -61,7 +61,7 @@ export class TerminalProfileService implements ITerminalProfileService { this._extensionService.onDidChangeExtensions(() => this.refreshAvailableProfiles()); this._configurationService.onDidChangeConfiguration(async e => { - const platformKey = await this._getPlatformKey(); + const platformKey = await this.getPlatformKey(); if (e.affectsConfiguration(TerminalSettingPrefix.DefaultProfile + platformKey) || e.affectsConfiguration(TerminalSettingPrefix.Profiles + platformKey) || e.affectsConfiguration(TerminalSettingId.UseWslProfiles)) { @@ -112,7 +112,7 @@ export class TerminalProfileService implements ITerminalProfileService { } private async _updateContributedProfiles(): Promise<boolean> { - const platformKey = await this._getPlatformKey(); + const platformKey = await this.getPlatformKey(); const excludedContributedProfiles: string[] = []; const configProfiles: { [key: string]: any } = this._configurationService.getValue(TerminalSettingPrefix.Profiles + platformKey); for (const [profileName, value] of Object.entries(configProfiles)) { @@ -136,7 +136,7 @@ export class TerminalProfileService implements ITerminalProfileService { if (!primaryBackend) { return this._availableProfiles || []; } - const platform = await this._getPlatformKey(); + const platform = await this.getPlatformKey(); this._defaultProfileName = this._configurationService.getValue(`${TerminalSettingPrefix.DefaultProfile}${platform}`) ?? undefined; return primaryBackend.getProfiles(this._configurationService.getValue(`${TerminalSettingPrefix.Profiles}${platform}`), this._defaultProfileName, includeDetectedProfiles); } @@ -151,7 +151,7 @@ export class TerminalProfileService implements ITerminalProfileService { refreshTerminalActions(profiles); } - private async _getPlatformKey(): Promise<string> { + async getPlatformKey(): Promise<string> { const env = await this._remoteAgentService.getEnvironment(); if (env) { return env.os === OperatingSystem.Windows ? 'windows' : (env.os === OperatingSystem.Macintosh ? 'osx' : 'linux'); @@ -169,19 +169,19 @@ export class TerminalProfileService implements ITerminalProfileService { return toDisposable(() => this._profileProviders.delete(id)); } - async registerContributedProfile(extensionIdentifier: string, id: string, title: string, options: ICreateContributedTerminalProfileOptions): Promise<void> { - const platformKey = await this._getPlatformKey(); + async registerContributedProfile(args: IRegisterContributedProfileArgs): Promise<void> { + const platformKey = await this.getPlatformKey(); const profilesConfig = await this._configurationService.getValue(`${TerminalSettingPrefix.Profiles}${platformKey}`); if (typeof profilesConfig === 'object') { const newProfile: IExtensionTerminalProfile = { - extensionIdentifier: extensionIdentifier, - icon: options.icon, - id, - title: title, - color: options.color + extensionIdentifier: args.extensionIdentifier, + icon: args.options.icon, + id: args.id, + title: args.title, + color: args.options.color }; - (profilesConfig as { [key: string]: ITerminalProfileObject })[title] = newProfile; + (profilesConfig as { [key: string]: ITerminalProfileObject })[args.title] = newProfile; } await this._configurationService.updateValue(`${TerminalSettingPrefix.Profiles}${platformKey}`, profilesConfig, ConfigurationTarget.USER); return; @@ -191,7 +191,7 @@ export class TerminalProfileService implements ITerminalProfileService { // prevents recursion with the MainThreadTerminalService call to create terminal // and defers to the provided launch config when an executable is provided if (shellLaunchConfig && !shellLaunchConfig.extHostTerminalId && !('executable' in shellLaunchConfig)) { - const key = await this._getPlatformKey(); + const key = await this.getPlatformKey(); const defaultProfileName = this._configurationService.getValue(`${TerminalSettingPrefix.DefaultProfile}${key}`); const contributedDefaultProfile = this.contributedProfiles.find(p => p.title === defaultProfileName); return contributedDefaultProfile; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 05016499524..ef444259b8a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -10,18 +10,16 @@ import { debounce } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; -import { isMacintosh, isWeb, isWindows, OperatingSystem } from 'vs/base/common/platform'; +import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import * as nls from 'vs/nls'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { IKeyMods, IPickOptions, IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ICreateContributedTerminalProfileOptions, IExtensionTerminalProfile, IShellLaunchConfig, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalLocation, TerminalLocationString, TerminalSettingPrefix } from 'vs/platform/terminal/common/terminal'; +import { ICreateContributedTerminalProfileOptions, IShellLaunchConfig, ITerminalLaunchError, ITerminalProfile, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalLocation, TerminalLocationString } from 'vs/platform/terminal/common/terminal'; import { iconForeground } from 'vs/platform/theme/common/colorRegistry'; import { IconDefinition } from 'vs/platform/theme/common/iconRegistry'; import { ColorScheme } from 'vs/platform/theme/common/theme'; @@ -31,8 +29,7 @@ import { IEditableData, IViewsService } from 'vs/workbench/common/views'; import { ICreateTerminalOptions, IRequestAddInstanceToGroupEvent, ITerminalEditorService, ITerminalExternalLinkProvider, ITerminalFindHost, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceHost, ITerminalInstanceService, ITerminalLocationOptions, ITerminalService, ITerminalServiceNativeDelegate, TerminalConnectionState, TerminalEditorLocation } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalEditor } from 'vs/workbench/contrib/terminal/browser/terminalEditor'; -import { getColorClass, getColorStyleContent, getColorStyleElement, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; -import { configureTerminalProfileIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons'; +import { getColorStyleContent, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; import { getInstanceFromResource, getTerminalUri, parseTerminalUri } from 'vs/workbench/contrib/terminal/browser/terminalUri'; import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView'; import { IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalBackend, ITerminalProcessExtHostProxy, ITerminalProfileService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -46,6 +43,8 @@ import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/ed import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { TerminalProfileQuickpick } from 'vs/workbench/contrib/terminal/browser/terminalProfileQuickpick'; +import { IKeyMods } from 'vs/base/parts/quickinput/common/quickInput'; export class TerminalService implements ITerminalService { declare _serviceBrand: undefined; @@ -66,7 +65,7 @@ export class TerminalService implements ITerminalService { private _configHelper: TerminalConfigHelper; private _remoteTerminalsInitPromise: Promise<void> | undefined; private _localTerminalsInitPromise: Promise<void> | undefined; - private _connectionState: TerminalConnectionState; + private _connectionState: TerminalConnectionState = TerminalConnectionState.Connecting; private _nativeDelegate?: ITerminalServiceNativeDelegate; private _shutdownWindowCount?: number; @@ -144,8 +143,6 @@ export class TerminalService implements ITerminalService { @IDialogService private _dialogService: IDialogService, @IInstantiationService private _instantiationService: IInstantiationService, @IRemoteAgentService private _remoteAgentService: IRemoteAgentService, - @IQuickInputService private _quickInputService: IQuickInputService, - @IConfigurationService private _configurationService: IConfigurationService, @IViewsService private _viewsService: IViewsService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @@ -154,7 +151,6 @@ export class TerminalService implements ITerminalService { @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, @IEditorResolverService editorResolverService: IEditorResolverService, @IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService, - @IThemeService private readonly _themeService: IThemeService, @ITerminalProfileService private readonly _terminalProfileService: ITerminalProfileService, @IExtensionService private readonly _extensionService: IExtensionService, @INotificationService private readonly _notificationService: INotificationService @@ -236,29 +232,67 @@ export class TerminalService implements ITerminalService { } }); - const enableTerminalReconnection = this.configHelper.config.enablePersistentSessions; - - // Connect to the extension host if it's there, set the connection state to connected when - // it's done. This should happen even when there is no extension host. - this._connectionState = TerminalConnectionState.Connecting; + // Create async as the class depends on `this` + timeout(0).then(() => this._instantiationService.createInstance(TerminalEditorStyle, document.head)); + } + async showProfileQuickPick(type: 'setDefault' | 'createInstance', cwd?: string | URI): Promise<ITerminalInstance | undefined> { + const quickPick = this._instantiationService.createInstance(TerminalProfileQuickpick); + const result = await quickPick.showAndGetResult(type); + if (!result) { + return; + } + if (typeof result === 'string') { + return; + } + let keyMods: IKeyMods | undefined = result.keyMods; + if (type === 'createInstance') { + const activeInstance = this.getDefaultInstanceHost().activeInstance; + let instance; - const isPersistentRemote = !!this._environmentService.remoteAuthority && enableTerminalReconnection; + if (result.config && 'id' in result?.config) { + await this.createContributedTerminalProfile(result.config.extensionIdentifier, result.config.id, { + icon: result.config.options?.icon, + color: result.config.options?.color, + location: !!(keyMods?.alt && activeInstance) ? { splitActiveTerminal: true } : this.defaultLocation + }); + return; + } else if (result.config && 'profileName' in result.config) { + if (keyMods?.alt && activeInstance) { + // create split, only valid if there's an active instance + instance = await this.createTerminal({ location: { parentTerminal: activeInstance }, config: result.config }); + } else { + instance = await this.createTerminal({ location: this.defaultLocation, config: result.config, cwd }); + } + } - if (isPersistentRemote) { - this._remoteTerminalsInitPromise = this._reconnectToRemoteTerminals(); - } else if (enableTerminalReconnection) { - this._localTerminalsInitPromise = this._reconnectToLocalTerminals(); - } else { - this._connectionState = TerminalConnectionState.Connected; + if (instance && this.defaultLocation !== TerminalLocation.Editor) { + this._terminalGroupService.showPanel(true); + this.setActiveInstance(instance); + return instance; + } } - - // Create async as the class depends on `this` - timeout(0).then(() => this._instantiationService.createInstance(TerminalEditorStyle, document.head)); + return undefined; } handleNewRegisteredBackend(backend: ITerminalBackend) { if (backend.remoteAuthority === this._environmentService.remoteAuthority) { this._primaryBackend = backend; + const enableTerminalReconnection = this.configHelper.config.enablePersistentSessions; + + // Connect to the extension host if it's there, set the connection state to connected when + // it's done. This should happen even when there is no extension host. + this._connectionState = TerminalConnectionState.Connecting; + + const isPersistentRemote = !!this._environmentService.remoteAuthority && enableTerminalReconnection; + + if (isPersistentRemote) { + this._remoteTerminalsInitPromise = this._reconnectToRemoteTerminals(); + } else if (enableTerminalReconnection) { + this._localTerminalsInitPromise = this._reconnectToLocalTerminals(); + } else { + this._connectionState = TerminalConnectionState.Connected; + } + backend.onDidRequestDetach(async (e) => { const instanceToDetach = this.getInstanceFromResource(getTerminalUri(e.workspaceId, e.instanceId)); if (instanceToDetach) { @@ -848,172 +882,6 @@ export class TerminalService implements ITerminalService { return !res.confirmed; } - private async _getPlatformKey(): Promise<string> { - const env = await this._remoteAgentService.getEnvironment(); - if (env) { - return env.os === OperatingSystem.Windows ? 'windows' : (env.os === OperatingSystem.Macintosh ? 'osx' : 'linux'); - } - return isWindows ? 'windows' : (isMacintosh ? 'osx' : 'linux'); - } - - async showProfileQuickPick(type: 'setDefault' | 'createInstance', cwd?: string | URI): Promise<ITerminalInstance | undefined> { - let keyMods: IKeyMods | undefined; - const profiles = this._terminalProfileService.availableProfiles; - const platformKey = await this._getPlatformKey(); - const profilesKey = `${TerminalSettingPrefix.Profiles}${platformKey}`; - const defaultProfileKey = `${TerminalSettingPrefix.DefaultProfile}${platformKey}`; - const defaultProfileName = this._configurationService.getValue<string>(defaultProfileKey); - - const options: IPickOptions<IProfileQuickPickItem> = { - placeHolder: type === 'createInstance' ? nls.localize('terminal.integrated.selectProfileToCreate', "Select the terminal profile to create") : nls.localize('terminal.integrated.chooseDefaultProfile', "Select your default terminal profile"), - onDidTriggerItemButton: async (context) => { - if ('command' in context.item.profile) { - return; - } - if ('id' in context.item.profile) { - return; - } - const configProfiles = this._configurationService.getValue<{ [key: string]: ITerminalProfileObject }>(profilesKey); - const existingProfiles = configProfiles ? Object.keys(configProfiles) : []; - const name = await this._quickInputService.input({ - prompt: nls.localize('enterTerminalProfileName', "Enter terminal profile name"), - value: context.item.profile.profileName, - validateInput: async input => { - if (existingProfiles.includes(input)) { - return nls.localize('terminalProfileAlreadyExists', "A terminal profile already exists with that name"); - } - return undefined; - } - }); - if (!name) { - return; - } - const newConfigValue: { [key: string]: ITerminalProfileObject } = { ...configProfiles } ?? {}; - newConfigValue[name] = { - path: context.item.profile.path, - args: context.item.profile.args - }; - await this._configurationService.updateValue(profilesKey, newConfigValue, ConfigurationTarget.USER); - }, - onKeyMods: mods => keyMods = mods - }; - - // Build quick pick items - const quickPickItems: (IProfileQuickPickItem | IQuickPickSeparator)[] = []; - const configProfiles = profiles.filter(e => !e.isAutoDetected); - const autoDetectedProfiles = profiles.filter(e => e.isAutoDetected); - - if (configProfiles.length > 0) { - quickPickItems.push({ type: 'separator', label: nls.localize('terminalProfiles', "profiles") }); - quickPickItems.push(...this._sortProfileQuickPickItems(configProfiles.map(e => this._createProfileQuickPickItem(e)), defaultProfileName)); - } - - quickPickItems.push({ type: 'separator', label: nls.localize('ICreateContributedTerminalProfileOptions', "contributed") }); - const contributedProfiles: IProfileQuickPickItem[] = []; - for (const contributed of this._terminalProfileService.contributedProfiles) { - if (typeof contributed.icon === 'string' && contributed.icon.startsWith('$(')) { - contributed.icon = contributed.icon.substring(2, contributed.icon.length - 1); - } - const icon = contributed.icon && typeof contributed.icon === 'string' ? (iconRegistry.get(contributed.icon) || Codicon.terminal) : Codicon.terminal; - const uriClasses = getUriClasses(contributed, this._themeService.getColorTheme().type, true); - const colorClass = getColorClass(contributed); - const iconClasses = []; - if (uriClasses) { - iconClasses.push(...uriClasses); - } - if (colorClass) { - iconClasses.push(colorClass); - } - contributedProfiles.push({ - label: `$(${icon.id}) ${contributed.title}`, - profile: { - extensionIdentifier: contributed.extensionIdentifier, - title: contributed.title, - icon: contributed.icon, - id: contributed.id, - color: contributed.color - }, - profileName: contributed.title, - iconClasses - }); - } - - if (contributedProfiles.length > 0) { - quickPickItems.push(...this._sortProfileQuickPickItems(contributedProfiles, defaultProfileName)); - } - - if (autoDetectedProfiles.length > 0) { - quickPickItems.push({ type: 'separator', label: nls.localize('terminalProfiles.detected', "detected") }); - quickPickItems.push(...this._sortProfileQuickPickItems(autoDetectedProfiles.map(e => this._createProfileQuickPickItem(e)), defaultProfileName)); - } - const styleElement = getColorStyleElement(this._themeService.getColorTheme()); - document.body.appendChild(styleElement); - - const value = await this._quickInputService.pick(quickPickItems, options); - document.body.removeChild(styleElement); - if (!value) { - return; - } - if (type === 'createInstance') { - const activeInstance = this.getDefaultInstanceHost().activeInstance; - let instance; - - if ('id' in value.profile) { - await this.createContributedTerminalProfile(value.profile.extensionIdentifier, value.profile.id, { - icon: value.profile.icon, - color: value.profile.color, - location: !!(keyMods?.alt && activeInstance) ? { splitActiveTerminal: true } : this.defaultLocation - }); - return; - } else { - if (keyMods?.alt && activeInstance) { - // create split, only valid if there's an active instance - instance = await this.createTerminal({ location: { parentTerminal: activeInstance }, config: value.profile }); - } else { - instance = await this.createTerminal({ location: this.defaultLocation, config: value.profile, cwd }); - } - } - - if (instance && this.defaultLocation !== TerminalLocation.Editor) { - this._terminalGroupService.showPanel(true); - this.setActiveInstance(instance); - return instance; - } - } else { // setDefault - if ('command' in value.profile) { - return; // Should never happen - } else if ('id' in value.profile) { - // extension contributed profile - await this._configurationService.updateValue(defaultProfileKey, value.profile.title, ConfigurationTarget.USER); - - this._terminalProfileService.registerContributedProfile(value.profile.extensionIdentifier, value.profile.id, value.profile.title, { - color: value.profile.color, - icon: value.profile.icon - }); - return; - } - } - - // Add the profile to settings if necessary - if (value.profile.isAutoDetected) { - const profilesConfig = await this._configurationService.getValue(profilesKey); - if (typeof profilesConfig === 'object') { - const newProfile: ITerminalProfileObject = { - path: value.profile.path - }; - if (value.profile.args) { - newProfile.args = value.profile.args; - } - (profilesConfig as { [key: string]: ITerminalProfileObject })[value.profile.profileName] = newProfile; - } - await this._configurationService.updateValue(profilesKey, profilesConfig, ConfigurationTarget.USER); - } - // Set the default profile - await this._configurationService.updateValue(defaultProfileKey, value.profile.profileName, ConfigurationTarget.USER); - return undefined; - } - - getDefaultInstanceHost(): ITerminalInstanceHost { if (this.defaultLocation === TerminalLocation.Editor) { return this._terminalEditorService; @@ -1042,46 +910,6 @@ export class TerminalService implements ITerminalService { return instance?.target === TerminalLocation.Editor ? this._terminalEditorService : this._terminalGroupService; } - private _createProfileQuickPickItem(profile: ITerminalProfile): IProfileQuickPickItem { - const buttons: IQuickInputButton[] = [{ - iconClass: ThemeIcon.asClassName(configureTerminalProfileIcon), - tooltip: nls.localize('createQuickLaunchProfile', "Configure Terminal Profile") - }]; - const icon = (profile.icon && ThemeIcon.isThemeIcon(profile.icon)) ? profile.icon : Codicon.terminal; - const label = `$(${icon.id}) ${profile.profileName}`; - const colorClass = getColorClass(profile); - const iconClasses = []; - if (colorClass) { - iconClasses.push(colorClass); - } - - if (profile.args) { - if (typeof profile.args === 'string') { - return { label, description: `${profile.path} ${profile.args}`, profile, profileName: profile.profileName, buttons, iconClasses }; - } - const argsString = profile.args.map(e => { - if (e.includes(' ')) { - return `"${e.replace('/"/g', '\\"')}"`; - } - return e; - }).join(' '); - return { label, description: `${profile.path} ${argsString}`, profile, profileName: profile.profileName, buttons, iconClasses }; - } - return { label, description: profile.path, profile, profileName: profile.profileName, buttons, iconClasses }; - } - - private _sortProfileQuickPickItems(items: IProfileQuickPickItem[], defaultProfileName: string) { - return items.sort((a, b) => { - if (b.profileName === defaultProfileName) { - return 1; - } - if (a.profileName === defaultProfileName) { - return -1; - } - return a.profileName.localeCompare(b.profileName); - }); - } - private _convertProfileToShellLaunchConfig(shellLaunchConfigOrProfile?: IShellLaunchConfig | ITerminalProfile, cwd?: string | URI): IShellLaunchConfig { if (shellLaunchConfigOrProfile && 'profileName' in shellLaunchConfigOrProfile) { const profile = shellLaunchConfigOrProfile; @@ -1297,11 +1125,6 @@ export class TerminalService implements ITerminalService { } } -interface IProfileQuickPickItem extends IQuickPickItem { - profile: ITerminalProfile | IExtensionTerminalProfile; - profileName: string; -} - class TerminalEditorStyle extends Themable { private _styleElement: HTMLElement; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index dfc69cec4fd..3b5aa91c7fe 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { ITheme, RendererType, Terminal as RawXtermTerminal } from 'xterm'; +import type { IBuffer, ITheme, RendererType, Terminal as RawXtermTerminal } from 'xterm'; import type { ISearchOptions, SearchAddon as SearchAddonType } from 'xterm-addon-search'; import type { Unicode11Addon as Unicode11AddonType } from 'xterm-addon-unicode11'; import type { WebglAddon as WebglAddonType } from 'xterm-addon-webgl'; @@ -157,27 +157,27 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { updateConfig(): void { const config = this._configHelper.config; - this._safeSetOption('altClickMovesCursor', config.altClickMovesCursor); + this.raw.options.altClickMovesCursor = config.altClickMovesCursor; this._setCursorBlink(config.cursorBlinking); this._setCursorStyle(config.cursorStyle); this._setCursorWidth(config.cursorWidth); - this._safeSetOption('scrollback', config.scrollback); - this._safeSetOption('drawBoldTextInBrightColors', config.drawBoldTextInBrightColors); - this._safeSetOption('minimumContrastRatio', config.minimumContrastRatio); - this._safeSetOption('fastScrollSensitivity', config.fastScrollSensitivity); - this._safeSetOption('scrollSensitivity', config.mouseWheelScrollSensitivity); - this._safeSetOption('macOptionIsMeta', config.macOptionIsMeta); + this.raw.options.scrollback = config.scrollback; + this.raw.options.drawBoldTextInBrightColors = config.drawBoldTextInBrightColors; + this.raw.options.minimumContrastRatio = config.minimumContrastRatio; + this.raw.options.fastScrollSensitivity = config.fastScrollSensitivity; + this.raw.options.scrollSensitivity = config.mouseWheelScrollSensitivity; + this.raw.options.macOptionIsMeta = config.macOptionIsMeta; const editorOptions = this._configurationService.getValue<IEditorOptions>('editor'); - this._safeSetOption('altClickMovesCursor', config.altClickMovesCursor && editorOptions.multiCursorModifier === 'alt'); - this._safeSetOption('macOptionClickForcesSelection', config.macOptionClickForcesSelection); - this._safeSetOption('rightClickSelectsWord', config.rightClickBehavior === 'selectWord'); - this._safeSetOption('wordSeparator', config.wordSeparators); - this._safeSetOption('customGlyphs', config.customGlyphs); + this.raw.options.altClickMovesCursor = config.altClickMovesCursor && editorOptions.multiCursorModifier === 'alt'; + this.raw.options.macOptionClickForcesSelection = config.macOptionClickForcesSelection; + this.raw.options.rightClickSelectsWord = config.rightClickBehavior === 'selectWord'; + this.raw.options.wordSeparator = config.wordSeparators; + this.raw.options.customGlyphs = config.customGlyphs; if ((!isSafari && config.gpuAcceleration === 'auto' && XtermTerminal._suggestedRendererType === undefined) || config.gpuAcceleration === 'on') { this._enableWebglRenderer(); } else { this._disposeOfWebglRenderer(); - this._safeSetOption('rendererType', this._getBuiltInXtermRenderer(config.gpuAcceleration, XtermTerminal._suggestedRendererType)); + this.raw.options.rendererType = this._getBuiltInXtermRenderer(config.gpuAcceleration, XtermTerminal._suggestedRendererType); } } @@ -222,6 +222,38 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { return this._configHelper.getFont(this._core); } + getLongestViewportWrappedLineLength(): number { + let maxLineLength = 0; + for (let i = this.raw.buffer.active.length - 1; i >= this.raw.buffer.active.viewportY; i--) { + const lineInfo = this._getWrappedLineCount(i, this.raw.buffer.active); + maxLineLength = Math.max(maxLineLength, ((lineInfo.lineCount * this.raw.cols) - lineInfo.endSpaces) || 0); + i = lineInfo.currentIndex; + } + return maxLineLength; + } + + private _getWrappedLineCount(index: number, buffer: IBuffer): { lineCount: number, currentIndex: number, endSpaces: number } { + let line = buffer.getLine(index); + if (!line) { + throw new Error('Could not get line'); + } + let currentIndex = index; + let endSpaces = 0; + // line.length may exceed cols as it doesn't necessarily trim the backing array on resize + for (let i = Math.min(line.length, this.raw.cols) - 1; i >= 0; i--) { + if (line && !line?.getCell(i)?.getChars()) { + endSpaces++; + } else { + break; + } + } + while (line?.isWrapped && currentIndex > 0) { + currentIndex--; + line = buffer.getLine(currentIndex); + } + return { lineCount: index - currentIndex + 1, currentIndex, endSpaces }; + } + scrollDownLine(): void { this.raw.scrollLines(1); } @@ -250,30 +282,23 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { this.raw.clear(); } - private _safeSetOption(key: string, value: any): void { - if (this.raw.getOption(key) !== value) { - this.raw.setOption(key, value); - } - } - private _setCursorBlink(blink: boolean): void { - if (this.raw.getOption('cursorBlink') !== blink) { - this.raw.setOption('cursorBlink', blink); + if (this.raw.options.cursorBlink !== blink) { + this.raw.options.cursorBlink = blink; this.raw.refresh(0, this.raw.rows - 1); } } - private _setCursorStyle(style: string): void { - if (this.raw.getOption('cursorStyle') !== style) { + private _setCursorStyle(style: 'block' | 'underline' | 'bar' | 'line'): void { + if (this.raw.options.cursorStyle !== style) { // 'line' is used instead of bar in VS Code to be consistent with editor.cursorStyle - const xtermOption = style === 'line' ? 'bar' : style; - this.raw.setOption('cursorStyle', xtermOption); + this.raw.options.cursorStyle = (style === 'line') ? 'bar' : style; } } private _setCursorWidth(width: number): void { - if (this.raw.getOption('cursorWidth') !== width) { - this.raw.setOption('cursorWidth', width); + if (this.raw.options.cursorWidth !== width) { + this.raw.options.cursorWidth = width; } } @@ -296,7 +321,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { this._webglAddon.onContextLoss(() => { this._logService.info(`Webgl lost context, disposing of webgl renderer`); this._disposeOfWebglRenderer(); - this._safeSetOption('rendererType', 'dom'); + this.raw.options.rendererType = 'dom'; }); } catch (e) { this._logService.warn(`Webgl could not be loaded. Falling back to the canvas renderer type.`, e); @@ -305,7 +330,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { if (!neverMeasureRenderTime && this._configHelper.config.gpuAcceleration !== 'off') { this._measureRenderTime(); } - this._safeSetOption('rendererType', 'canvas'); + this.raw.options.rendererType = 'canvas'; XtermTerminal._suggestedRendererType = 'canvas'; this._disposeOfWebglRenderer(); } diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts index 6812a399682..6bc36af3107 100644 --- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts @@ -261,11 +261,11 @@ export class RemoteTerminalChannelClient { return this._channel.call('$getWslPath', [original]); } - setTerminalLayoutInfo(layout: ITerminalsLayoutInfoById): Promise<void> { + setTerminalLayoutInfo(layout?: ITerminalsLayoutInfoById): Promise<void> { const workspace = this._workspaceContextService.getWorkspace(); const args: ISetTerminalLayoutInfoArgs = { workspaceId: workspace.id, - tabs: layout.tabs + tabs: layout ? layout.tabs : [] }; return this._channel.call<void>('$setTerminalLayoutInfo', args); } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index bf99ce95676..35bdcce9826 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -57,17 +57,22 @@ export interface ITerminalProfileResolverService { createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise<ITerminalProfile | string>; } +export interface IRegisterContributedProfileArgs { + extensionIdentifier: string, id: string, title: string, options: ICreateContributedTerminalProfileOptions; +} + export const ITerminalProfileService = createDecorator<ITerminalProfileService>('terminalProfileService'); export interface ITerminalProfileService { readonly _serviceBrand: undefined; readonly availableProfiles: ITerminalProfile[]; readonly contributedProfiles: IExtensionTerminalProfile[]; readonly profilesReady: Promise<void>; + getPlatformKey(): Promise<string>; refreshAvailableProfiles(): void; getDefaultProfileName(): string | undefined; onDidChangeAvailableProfiles: Event<ITerminalProfile[]>; getContributedDefaultProfile(shellLaunchConfig: IShellLaunchConfig): Promise<IExtensionTerminalProfile | undefined>; - registerContributedProfile(extensionIdentifier: string, id: string, title: string, options: ICreateContributedTerminalProfileOptions): Promise<void>; + registerContributedProfile(args: IRegisterContributedProfileArgs): Promise<void>; getContributedProfileProvider(extensionIdentifier: string, id: string): ITerminalProfileProvider | undefined; registerTerminalProfileProvider(extensionIdentifier: string, id: string, profileProvider: ITerminalProfileProvider): IDisposable; } diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.test.ts index e5e070b430e..7a92132511c 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalProfileService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { ITerminalConfiguration, ITerminalBackend } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalConfiguration, ITerminalBackend, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices'; import { TerminalProfileService } from 'vs/workbench/contrib/terminal/browser/terminalProfileService'; @@ -19,12 +19,16 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { Codicon } from 'vs/base/common/codicons'; import { deepStrictEqual } from 'assert'; import { Emitter } from 'vs/base/common/event'; +import { IProfileQuickPickItem, TerminalProfileQuickpick } from 'vs/workbench/contrib/terminal/browser/terminalProfileQuickpick'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { IPickOptions, IQuickInputService, Omit, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken } from 'vs/base/common/cancellation'; -class TestTerminalProfileService extends TerminalProfileService { +class TestTerminalProfileService extends TerminalProfileService implements Partial<ITerminalProfileService>{ hasRefreshedProfiles: Promise<void> | undefined; override refreshAvailableProfiles(): void { this.hasRefreshedProfiles = this._refreshAvailableProfilesNow(); @@ -38,6 +42,46 @@ class TestTerminalProfileService extends TerminalProfileService { } } +class MockTerminalProfileService implements Partial<ITerminalProfileService>{ + hasRefreshedProfiles: Promise<void> | undefined; + _defaultProfileName: string | undefined; + availableProfiles?: ITerminalProfile[] | undefined = []; + contributedProfiles?: IExtensionTerminalProfile[] | undefined = []; + async getPlatformKey(): Promise<string> { + return 'linux'; + } + getDefaultProfileName(): string | undefined { + return this._defaultProfileName; + } + setProfiles(profiles: ITerminalProfile[], contributed: IExtensionTerminalProfile[]): void { + this.availableProfiles = profiles; + this.contributedProfiles = contributed; + } + setDefaultProfileName(name: string): void { + this._defaultProfileName = name; + } +} + + +class MockQuickInputService implements Partial<IQuickInputService> { + _pick: IProfileQuickPickItem = powershellPick; + pick(picks: QuickPickInput<IProfileQuickPickItem>[] | Promise<QuickPickInput<IProfileQuickPickItem>[]>, options?: IPickOptions<IProfileQuickPickItem> & { canPickMany: true; }, token?: CancellationToken): Promise<IProfileQuickPickItem[] | undefined>; + pick(picks: QuickPickInput<IProfileQuickPickItem>[] | Promise<QuickPickInput<IProfileQuickPickItem>[]>, options?: IPickOptions<IProfileQuickPickItem> & { canPickMany: false; }, token?: CancellationToken): Promise<IProfileQuickPickItem | undefined>; + pick(picks: QuickPickInput<IProfileQuickPickItem>[] | Promise<QuickPickInput<IProfileQuickPickItem>[]>, options?: Omit<IPickOptions<IProfileQuickPickItem>, 'canPickMany'>, token?: CancellationToken): Promise<IProfileQuickPickItem | undefined>; + async pick(picks: any, options?: any, token?: any): Promise<IProfileQuickPickItem | IProfileQuickPickItem[] | undefined> { + Promise.resolve(picks); + return this._pick; + } + + setPick(pick: IProfileQuickPickItem) { + this._pick = pick; + } +} + +class TestTerminalProfileQuickpick extends TerminalProfileQuickpick { + +} + class TestTerminalExtensionService extends TestExtensionService { readonly _onDidChangeExtensions = new Emitter<void>(); } @@ -96,7 +140,8 @@ let jsdebugProfile = { id: 'extension.js-debug.debugTerminal', title: 'JavaScript Debug Terminal' }; - +let powershellPick = { label: 'Powershell', profile: powershellProfile, profileName: powershellProfile.profileName }; +let jsdebugPick = { label: 'Javascript Debug Terminal', profile: jsdebugProfile, profileName: jsdebugProfile.title }; suite('TerminalProfileService', () => { let configurationService: TestConfigurationService; @@ -115,6 +160,7 @@ suite('TerminalProfileService', () => { environmentService = { configuration: {}, remoteAuthority: undefined } as IWorkbenchEnvironmentService; instantiationService = new TestInstantiationService(); + let themeService = new TestThemeService(); let terminalContributionService = new TestTerminalContributionService(); let contextKeyService = new MockContextKeyService(); @@ -125,6 +171,7 @@ suite('TerminalProfileService', () => { instantiationService.stub(ITerminalContributionService, terminalContributionService); instantiationService.stub(ITerminalInstanceService, terminalInstanceService); instantiationService.stub(IWorkbenchEnvironmentService, environmentService); + instantiationService.stub(IThemeService, themeService); terminalProfileService = instantiationService.createInstance(TestTerminalProfileService); @@ -283,4 +330,62 @@ suite('TerminalProfileService', () => { deepStrictEqual(terminalProfileService.availableProfiles, [powershellProfile]); deepStrictEqual(terminalProfileService.contributedProfiles, [jsdebugProfile]); }); + suite('Profiles Quickpick', () => { + let quickInputService: MockQuickInputService; + let mockTerminalProfileService: MockTerminalProfileService; + let terminalProfileQuickpick: TestTerminalProfileQuickpick; + setup(async () => { + quickInputService = new MockQuickInputService(); + mockTerminalProfileService = new MockTerminalProfileService(); + instantiationService.stub(IQuickInputService, quickInputService); + instantiationService.stub(ITerminalProfileService, mockTerminalProfileService); + terminalProfileQuickpick = instantiationService.createInstance(TestTerminalProfileQuickpick); + }); + test('setDefault', async () => { + powershellProfile.isDefault = false; + mockTerminalProfileService.setProfiles([powershellProfile], [jsdebugProfile]); + mockTerminalProfileService.setDefaultProfileName(jsdebugProfile.title); + const result = await terminalProfileQuickpick.showAndGetResult('setDefault'); + deepStrictEqual(result, powershellProfile.profileName); + }); + test('setDefault to contributed', async () => { + mockTerminalProfileService.setDefaultProfileName(powershellProfile.profileName); + quickInputService.setPick(jsdebugPick); + const result = await terminalProfileQuickpick.showAndGetResult('setDefault'); + const expected = { + config: { + extensionIdentifier: jsdebugProfile.extensionIdentifier, + id: jsdebugProfile.id, + options: { color: undefined, icon: 'debug' }, + title: jsdebugProfile.title, + }, + keyMods: undefined + }; + deepStrictEqual(result, expected); + }); + + test('createInstance', async () => { + mockTerminalProfileService.setDefaultProfileName(powershellProfile.profileName); + const pick = { ...powershellPick, keyMods: { alt: true, ctrlCmd: false } }; + quickInputService.setPick(pick); + const result = await terminalProfileQuickpick.showAndGetResult('createInstance'); + deepStrictEqual(result, { config: powershellProfile, keyMods: { alt: true, ctrlCmd: false } }); + }); + + test('createInstance with contributed', async () => { + const pick = { ...jsdebugPick, keyMods: { alt: true, ctrlCmd: false } }; + quickInputService.setPick(pick); + const result = await terminalProfileQuickpick.showAndGetResult('createInstance'); + const expected = { + config: { + extensionIdentifier: jsdebugProfile.extensionIdentifier, + id: jsdebugProfile.id, + options: { color: undefined, icon: 'debug' }, + title: jsdebugProfile.title, + }, + keyMods: { alt: true, ctrlCmd: false } + }; + deepStrictEqual(result, expected); + }); + }); }); diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 26efb1ed53e..32e53a531b8 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -16,10 +16,18 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IWebviewPortMapping } from 'vs/platform/webview/common/webviewPortMapping'; /** - * Set when the find widget in a webview is visible. + * Set when the find widget in a webview in a webview is visible. */ export const KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE = new RawContextKey<boolean>('webviewFindWidgetVisible', false); + +/** + * Set when the find widget in a webview is focused. + */ export const KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED = new RawContextKey<boolean>('webviewFindWidgetFocused', false); + +/** + * Set when the find widget in a webview is enabled in a webview + */ export const KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_ENABLED = new RawContextKey<boolean>('webviewFindWidgetEnabled', false); export const IWebviewService = createDecorator<IWebviewService>('webviewService'); @@ -72,27 +80,58 @@ export const enum WebviewContentPurpose { WebviewView = 'webviewView', } -export type WebviewStyles = { [key: string]: string | number; }; +export type WebviewStyles = { readonly [key: string]: string | number; }; export interface WebviewOptions { - // The purpose of the webview; this is (currently) only used for filtering in js-debug + /** + * The purpose of the webview; this is (currently) only used for filtering in js-debug + */ readonly purpose?: WebviewContentPurpose; readonly customClasses?: string; readonly enableFindWidget?: boolean; readonly tryRestoreScrollPosition?: boolean; readonly retainContextWhenHidden?: boolean; - transformCssVariables?(styles: Readonly<WebviewStyles>): Readonly<WebviewStyles>; + transformCssVariables?(styles: WebviewStyles): WebviewStyles; } +/** + * + */ export interface WebviewContentOptions { + /** + * Should the webview allow `acquireVsCodeApi` to be called multiple times? Defaults to false. + */ readonly allowMultipleAPIAcquire?: boolean; + + /** + * Should scripts be enabled in the webview? Defaults to false. + */ readonly allowScripts?: boolean; + + /** + * Should forms be enabled in the webview? Defaults to the value of {@link allowScripts}. + */ readonly allowForms?: boolean; - readonly localResourceRoots?: ReadonlyArray<URI>; - readonly portMapping?: ReadonlyArray<IWebviewPortMapping>; + + /** + * Set of root paths from which the webview can load local resources. + */ + readonly localResourceRoots?: readonly URI[]; + + /** + * Set of localhost port mappings to apply inside the webview. + */ + readonly portMapping?: readonly IWebviewPortMapping[]; + + /** + * Are command uris enabled in the webview? Defaults to false. + */ readonly enableCommandUris?: boolean; } +/** + * Check if two {@link WebviewContentOptions} are equal. + */ export function areWebviewContentOptionsEqual(a: WebviewContentOptions, b: WebviewContentOptions): boolean { return ( a.allowMultipleAPIAcquire === b.allowMultipleAPIAcquire @@ -110,8 +149,8 @@ export interface WebviewExtensionDescription { } export interface IDataLinkClickEvent { - dataURL: string; - downloadName?: string; + readonly dataURL: string; + readonly downloadName?: string; } export interface WebviewMessageReceivedEvent { @@ -137,7 +176,7 @@ export interface IWebview extends IDisposable { readonly onDidDispose: Event<void>; readonly onDidClickLink: Event<string>; - readonly onDidScroll: Event<{ scrollYPercentage: number }>; + readonly onDidScroll: Event<{ readonly scrollYPercentage: number }>; readonly onDidWheel: Event<IMouseWheelEvent>; readonly onDidUpdateState: Event<string | undefined>; readonly onDidReload: Event<void>; diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css index 26d4d8a30db..85b0163d359 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css @@ -717,6 +717,8 @@ .monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlide .openAWalkthrough>button, .monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlide .showOnStartup { text-align: center; + display: flex; + justify-content: center; } .monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlide .getting-started-checkbox { diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts index 62c5ac5b7fb..2218e71c907 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts @@ -9,7 +9,7 @@ import { localize } from 'vs/nls'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; -import { OpenGettingStarted } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; const setupIcon = registerIcon('getting-started-setup', Codicon.zap, localize('getting-started-setup-icon', "Icon used for the setup category of welcome page")); @@ -483,7 +483,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ description: '', icon: setupIcon, isFeatured: false, - when: `config.${OpenGettingStarted} && userHasOpenedNotebook`, + when: `config.${NotebookSetting.openGettingStarted} && userHasOpenedNotebook`, content: { type: 'steps', steps: [ diff --git a/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts b/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts index 40094f38189..a4728e73b7d 100644 --- a/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts @@ -166,8 +166,8 @@ export class NativeDialogHandler implements IDialogHandler { const osProps = await this.nativeHostService.getOSProperties(); const detailString = (useAgo: boolean): string => { - return localize({ key: 'aboutDetail', comment: ['Electron, Chrome, Node.js and V8 are product names that need no translation'] }, - "Version: {0}\nCommit: {1}\nDate: {2}\nElectron: {3}\nChrome: {4}\nNode.js: {5}\nV8: {6}\nOS: {7}", + return localize({ key: 'aboutDetail', comment: ['Electron, Chromium, Node.js and V8 are product names that need no translation'] }, + "Version: {0}\nCommit: {1}\nDate: {2}\nElectron: {3}\nChromium: {4}\nNode.js: {5}\nV8: {6}\nOS: {7}", version, this.productService.commit || 'Unknown', this.productService.date ? `${this.productService.date}${useAgo ? ' (' + fromNow(new Date(this.productService.date), true) + ')' : ''}` : 'Unknown', diff --git a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts index 29b74dbc6a4..777123794b1 100644 --- a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts @@ -362,6 +362,8 @@ export class LocalProcessExtensionHost implements IExtensionHost { let startupTimeoutHandle: any; if (!this._environmentService.isBuilt && !this._environmentService.remoteAuthority || this._isExtensionDevHost) { startupTimeoutHandle = setTimeout(() => { + this._logService.error(`[LocalProcessExtensionHost]: Extension host did not start in 10 seconds (debugBrk: ${this._isExtensionDevDebugBrk})`); + const msg = this._isExtensionDevDebugBrk ? nls.localize('extensionHost.startupFailDebug', "Extension host did not start in 10 seconds, it might be stopped on the first line and needs a debugger to continue.") : nls.localize('extensionHost.startupFail', "Extension host did not start in 10 seconds, that might be a problem."); diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index f2b6d8c39eb..bad2c3faee7 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -28,7 +28,7 @@ export enum SettingValueType { Integer = 'integer', Number = 'number', Boolean = 'boolean', - StringOrEnumArray = 'string-or-enum-array', + Array = 'array', Exclude = 'exclude', Complex = 'complex', NullableInteger = 'nullable-integer', diff --git a/src/vs/workbench/services/preferences/common/preferencesValidation.ts b/src/vs/workbench/services/preferences/common/preferencesValidation.ts index c991e059885..b722a3cee43 100644 --- a/src/vs/workbench/services/preferences/common/preferencesValidation.ts +++ b/src/vs/workbench/services/preferences/common/preferencesValidation.ts @@ -26,15 +26,15 @@ export function createValidator(prop: IConfigurationPropertySchema): (value: any const numericValidations = getNumericValidators(prop); const stringValidations = getStringValidators(prop); - const stringArrayValidator = getArrayOfStringValidator(prop); + const arrayValidator = getArrayValidator(prop); const objectValidator = getObjectValidator(prop); return value => { if (isNullable && isNullOrEmpty(value)) { return ''; } const errors: string[] = []; - if (stringArrayValidator) { - const err = stringArrayValidator(value); + if (arrayValidator) { + const err = arrayValidator(value); if (err) { errors.push(err); } @@ -52,7 +52,7 @@ export function createValidator(prop: IConfigurationPropertySchema): (value: any } if (isNumeric) { - if (isNullOrEmpty(value) || isNaN(+value)) { + if (isNullOrEmpty(value) || typeof value === 'boolean' || Array.isArray(value) || isNaN(+value)) { errors.push(nls.localize('validations.expectedNumeric', "Value must be a number.")); } else { errors.push(...numericValidations.filter(validator => !validator.isValid(+value)).map(validator => validator.message)); @@ -205,7 +205,6 @@ function getNumericValidators(prop: IConfigurationPropertySchema): Validator<num isValid: ((value: number) => value > exclusiveMin!), message: nls.localize('validations.exclusiveMin', "Value must be strictly greater than {0}.", exclusiveMin) }, - { enabled: prop.maximum !== undefined && (exclusiveMax === undefined || exclusiveMax > prop.maximum), isValid: ((value: number) => value <= prop.maximum!), @@ -229,10 +228,10 @@ function getNumericValidators(prop: IConfigurationPropertySchema): Validator<num ].filter(validation => validation.enabled); } -function getArrayOfStringValidator(prop: IConfigurationPropertySchema): ((value: any) => (string | null)) | null { - if (prop.type === 'array' && prop.items && !isArray(prop.items) && prop.items.type === 'string') { +function getArrayValidator(prop: IConfigurationPropertySchema): ((value: any) => (string | null)) | null { + if (prop.type === 'array' && prop.items && !isArray(prop.items)) { const propItems = prop.items; - if (propItems && !isArray(propItems) && propItems.type === 'string') { + if (propItems && !isArray(propItems.type)) { const withQuotes = (s: string) => `'` + s + `'`; return value => { if (!value) { @@ -241,58 +240,72 @@ function getArrayOfStringValidator(prop: IConfigurationPropertySchema): ((value: let message = ''; - if (!isStringArray(value)) { - message += nls.localize('validations.stringArrayIncorrectType', 'Incorrect type. Expected a string array.'); + if (!isArray(value)) { + message += nls.localize('validations.arrayIncorrectType', 'Incorrect type. Expected an array.'); message += '\n'; return message; } - const stringArrayValue = value; - + const arrayValue = value as unknown[]; if (prop.uniqueItems) { - if (new Set(stringArrayValue).size < stringArrayValue.length) { + if (new Set(arrayValue).size < arrayValue.length) { message += nls.localize('validations.stringArrayUniqueItems', 'Array has duplicate items'); message += '\n'; } } - if (prop.minItems && stringArrayValue.length < prop.minItems) { + if (prop.minItems && arrayValue.length < prop.minItems) { message += nls.localize('validations.stringArrayMinItem', 'Array must have at least {0} items', prop.minItems); message += '\n'; } - if (prop.maxItems && stringArrayValue.length > prop.maxItems) { + if (prop.maxItems && arrayValue.length > prop.maxItems) { message += nls.localize('validations.stringArrayMaxItem', 'Array must have at most {0} items', prop.maxItems); message += '\n'; } - if (typeof propItems.pattern === 'string') { - const patternRegex = new RegExp(propItems.pattern); - stringArrayValue.forEach(v => { - if (!patternRegex.test(v)) { - message += - propItems.patternErrorMessage || - nls.localize( - 'validations.stringArrayItemPattern', - 'Value {0} must match regex {1}.', + if (propItems.type === 'string') { + if (!isStringArray(arrayValue)) { + message += nls.localize('validations.stringArrayIncorrectType', 'Incorrect type. Expected a string array.'); + message += '\n'; + return message; + } + + if (typeof propItems.pattern === 'string') { + const patternRegex = new RegExp(propItems.pattern); + arrayValue.forEach(v => { + if (!patternRegex.test(v)) { + message += + propItems.patternErrorMessage || + nls.localize( + 'validations.stringArrayItemPattern', + 'Value {0} must match regex {1}.', + withQuotes(v), + withQuotes(propItems.pattern!) + ); + } + }); + } + + const propItemsEnum = propItems.enum; + if (propItemsEnum) { + arrayValue.forEach(v => { + if (propItemsEnum.indexOf(v) === -1) { + message += nls.localize( + 'validations.stringArrayItemEnum', + 'Value {0} is not one of {1}', withQuotes(v), - withQuotes(propItems.pattern!) + '[' + propItemsEnum.map(withQuotes).join(', ') + ']' ); - } - }); - } - - const propItemsEnum = propItems.enum; - if (propItemsEnum) { - stringArrayValue.forEach(v => { - if (propItemsEnum.indexOf(v) === -1) { - message += nls.localize( - 'validations.stringArrayItemEnum', - 'Value {0} is not one of {1}', - withQuotes(v), - '[' + propItemsEnum.map(withQuotes).join(', ') + ']' - ); - message += '\n'; + message += '\n'; + } + }); + } + } else if (propItems.type === 'integer' || propItems.type === 'number') { + arrayValue.forEach(v => { + const errorMessage = getErrorsForSchema(propItems, v); + if (errorMessage) { + message += `${v}: ${errorMessage}\n`; } }); } diff --git a/src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts b/src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts index 0ee46bad8e7..a849aa2f780 100644 --- a/src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts +++ b/src/vs/workbench/services/preferences/test/common/preferencesValidation.test.ts @@ -279,6 +279,31 @@ suite('Preferences Validation', () => { } }); + test('numerical objects work', () => { + { + const obj = new Tester({ type: 'object', properties: { 'b': { type: 'number' } } }); + obj.accepts({ 'b': 2.5 }); + obj.accepts({ 'b': -2.5 }); + obj.accepts({ 'b': 0 }); + obj.accepts({ 'b': '0.12' }); + obj.rejects({ 'b': 'abc' }); + obj.rejects({ 'b': [] }); + obj.rejects({ 'b': false }); + obj.rejects({ 'b': null }); + obj.rejects({ 'b': undefined }); + } + { + const obj = new Tester({ type: 'object', properties: { 'b': { type: 'integer', minimum: 2, maximum: 5.5 } } }); + obj.accepts({ 'b': 2 }); + obj.accepts({ 'b': 3 }); + obj.accepts({ 'b': '3.0' }); + obj.accepts({ 'b': 5 }); + obj.rejects({ 'b': 1 }); + obj.rejects({ 'b': 6 }); + obj.rejects({ 'b': 5.5 }); + } + }); + test('patterns work', () => { { const urls = new Tester({ pattern: '^(hello)*$', type: 'string' }); @@ -312,7 +337,7 @@ suite('Preferences Validation', () => { this.validator = createValidator(settings)!; } - public accepts(input: string[]) { + public accepts(input: unknown[]) { assert.strictEqual(this.validator(input), '', `Expected ${JSON.stringify(this.settings)} to accept \`${JSON.stringify(input)}\`. Got ${this.validator(input)}.`); } @@ -365,6 +390,39 @@ suite('Preferences Validation', () => { } }); + test('array of numbers', () => { + // We accept parseable strings since the view handles strings + { + const arr = new ArrayTester({ type: 'array', items: { type: 'number' } }); + arr.accepts([]); + arr.accepts([2]); + arr.accepts([2, 3]); + arr.accepts(['2', '3']); + arr.accepts([6.6, '3', 7]); + arr.rejects(76); + arr.rejects(7.6); + arr.rejects([6, 'a', 7]); + } + { + const arr = new ArrayTester({ type: 'array', items: { type: 'integer', minimum: -2, maximum: 3 }, maxItems: 4 }); + arr.accepts([]); + arr.accepts([-2, 3]); + arr.accepts([2, 3]); + arr.accepts(['2', '3']); + arr.accepts(['-2', '0', '3']); + arr.accepts(['-2', 0.0, '3']); + arr.rejects(2); + arr.rejects(76); + arr.rejects([6, '3', 7]); + arr.rejects([2, 'a', 3]); + arr.rejects([-2, 4]); + arr.rejects([-1.2, 2.1]); + arr.rejects([-3, 3]); + arr.rejects([-3, 4]); + arr.rejects([2, 2, 2, 2, 2]); + } + }); + test('min-max and enum', () => { const arr = new ArrayTester({ type: 'array', items: { type: 'string', enum: ['a', 'b'] }, minItems: 1, maxItems: 2 }); diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 667b02954c4..d156f7dfea1 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -657,8 +657,10 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat if (this._cachedCurrentSessionId !== cachedSessionId) { this._cachedCurrentSessionId = cachedSessionId; if (cachedSessionId === undefined) { + this.logService.info('Settings Sync: Reset current session'); this.storageService.remove(UserDataSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.GLOBAL); } else { + this.logService.info('Settings Sync: Updated current session', cachedSessionId); this.storageService.store(UserDataSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, cachedSessionId, StorageScope.GLOBAL, StorageTarget.MACHINE); } } diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 02f02c68f19..6ee7e8240d8 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -1672,6 +1672,12 @@ declare module 'vscode' { * Always show this item. */ alwaysShow?: boolean; + + /** + * Optional buttons that will be rendered on this particular item. These buttons will trigger + * an {@link QuickPickItemButtonEvent} when clicked. + */ + buttons?: readonly QuickInputButton[]; } /** @@ -5657,6 +5663,13 @@ declare module 'vscode' { appendLine(value: string): void; /** + * Replaces all output from the channel with the given value. + * + * @param value A string, falsy values will not be printed. + */ + replace(value: string): void; + + /** * Removes all output from the channel. */ clear(): void; @@ -10193,6 +10206,12 @@ declare module 'vscode' { readonly onDidTriggerButton: Event<QuickInputButton>; /** + * An event signaling when a button in a particular {@link QuickPickItem} was triggered. + * This event does not fire for buttons in the title bar. + */ + readonly onDidTriggerItemButton: Event<QuickPickItemButtonEvent<T>>; + + /** * Items to pick from. This can be read and updated by the extension. */ items: readonly T[]; @@ -10212,6 +10231,11 @@ declare module 'vscode' { */ matchOnDetail: boolean; + /* + * An optional flag to maintain the scroll position of the quick pick when the quick pick items are updated. Defaults to false. + */ + keepScrollPosition?: boolean; + /** * Active items. This can be read and updated by the extension. */ @@ -10324,6 +10348,21 @@ declare module 'vscode' { } /** + * An event signaling when a button in a particular {@link QuickPickItem} was triggered. + * This event does not fire for buttons in the title bar. + */ + export interface QuickPickItemButtonEvent<T extends QuickPickItem> { + /** + * The button that was clicked. + */ + readonly button: QuickInputButton; + /** + * The item that the button belongs to. + */ + readonly item: T; + } + + /** * An event describing an individual change in the text of a {@link TextDocument document}. */ export interface TextDocumentContentChangeEvent { @@ -13849,6 +13888,17 @@ declare module 'vscode' { */ export interface AuthenticationGetSessionOptions { /** + * Whether the existing user session preference should be cleared. + * + * For authentication providers that support being signed into multiple accounts at once, the user will be + * prompted to select an account to use when {@link authentication.getSession getSession} is called. This preference + * is remembered until {@link authentication.getSession getSession} is called with this flag. + * + * Defaults to false. + */ + clearSessionPreference?: boolean; + + /** * Whether login should be performed if there is no matching session. * * If true, a modal dialog will be shown asking the user to sign in. If false, a numbered badge will be shown @@ -13859,19 +13909,23 @@ declare module 'vscode' { * will also result in an immediate modal dialog, and false will add a numbered badge to the accounts icon. * * Defaults to false. + * + * Note: you cannot use this option with {@link silent}. */ createIfNone?: boolean; + /** - * Whether the existing user session preference should be cleared. + * Whether we should show the indication to sign in in the Accounts menu. * - * For authentication providers that support being signed into multiple accounts at once, the user will be - * prompted to select an account to use when {@link authentication.getSession getSession} is called. This preference - * is remembered until {@link authentication.getSession getSession} is called with this flag. + * If false, the user will be shown a badge on the Accounts menu with an option to sign in for the extension. + * If true, no indication will be shown. * * Defaults to false. + * + * Note: you cannot use this option with any other options that prompt the user like {@link createIfNone}. */ - clearSessionPreference?: boolean; + silent?: boolean; } /** diff --git a/src/vscode-dts/vscode.proposed.d.ts b/src/vscode-dts/vscode.proposed.d.ts index f5f1304f3d9..9ee5a94af5d 100644 --- a/src/vscode-dts/vscode.proposed.d.ts +++ b/src/vscode-dts/vscode.proposed.d.ts @@ -16,8 +16,7 @@ declare module 'vscode' { - // eslint-disable-next-line vscode-dts-region-comments - //#region @alexdima - resolvers + //#region resolvers: @alexdima export interface MessageOptions { /** @@ -184,15 +183,6 @@ declare module 'vscode' { * Defaults to false. */ forceNewSession?: boolean | { detail: string }; - /** - * Whether we should show the indication to sign in in the Accounts menu. - * - * If false, the user will be shown a badge on the Accounts menu with an option to sign in for the extension. - * If true, no indication will be shown. - * - * Defaults to false. - */ - silent?: boolean; } export namespace authentication { @@ -277,7 +267,7 @@ declare module 'vscode' { //#endregion - //#region editor insets: https://github.com/microsoft/vscode/issues/85682 + //#region editorInsets: https://github.com/microsoft/vscode/issues/85682 export interface WebviewEditorInset { readonly editor: TextEditor; @@ -294,7 +284,7 @@ declare module 'vscode' { //#endregion - //#region read/write in chunks: https://github.com/microsoft/vscode/issues/84515 + //#region fsChunks: https://github.com/microsoft/vscode/issues/84515 export interface FileSystemProvider { open?(resource: Uri, options: { create: boolean; }): number | Thenable<number>; @@ -305,7 +295,7 @@ declare module 'vscode' { //#endregion - //#region TextSearchProvider: https://github.com/microsoft/vscode/issues/59921 + //#region textSearchProvider: https://github.com/microsoft/vscode/issues/59921 /** * The parameters of a query for text search. @@ -562,7 +552,7 @@ declare module 'vscode' { //#endregion - //#region FileSearchProvider: https://github.com/microsoft/vscode/issues/73524 + //#region fileSearchProvider: https://github.com/microsoft/vscode/issues/73524 /** * The parameters of a query for file search. @@ -728,7 +718,7 @@ declare module 'vscode' { //#endregion - //#region diff command: https://github.com/microsoft/vscode/issues/84899 + //#region diffCommand: https://github.com/microsoft/vscode/issues/84899 /** * The contiguous set of modified lines in a diff. @@ -805,8 +795,7 @@ declare module 'vscode' { //#endregion - // eslint-disable-next-line vscode-dts-region-comments - //#region @joaomoreno: SCM validation + // #region scmValidation: @joaomoreno: /** * Represents the validation type of the Source Control input. @@ -861,8 +850,7 @@ declare module 'vscode' { //#endregion - // eslint-disable-next-line vscode-dts-region-comments - //#region @joaomoreno: SCM selected provider + //#region scmSelectedProvider: @joaomoreno: export interface SourceControl { @@ -879,7 +867,7 @@ declare module 'vscode' { //#endregion - //#region Terminal data write event https://github.com/microsoft/vscode/issues/78502 + //#region terminalDataWriteEvent: https://github.com/microsoft/vscode/issues/78502 export interface TerminalDataWriteEvent { /** @@ -903,7 +891,7 @@ declare module 'vscode' { //#endregion - //#region Terminal dimensions property and change event https://github.com/microsoft/vscode/issues/55718 + //#region terminalDimensions: https://github.com/microsoft/vscode/issues/55718 /** * An {@link Event} which fires when a {@link Terminal}'s dimensions change. @@ -937,7 +925,7 @@ declare module 'vscode' { //#endregion - //#region Terminal location https://github.com/microsoft/vscode/issues/45407 + //#region terminalLocation: https://github.com/microsoft/vscode/issues/45407 export interface TerminalOptions { location?: TerminalLocation | TerminalEditorLocationOptions | TerminalSplitLocationOptions; @@ -977,7 +965,7 @@ declare module 'vscode' { //#endregion - //#region Terminal name change event https://github.com/microsoft/vscode/issues/114898 + //#region terminalNameChangeEvent: https://github.com/microsoft/vscode/issues/114898 export interface Pseudoterminal { /** @@ -1001,8 +989,7 @@ declare module 'vscode' { //#endregion - // eslint-disable-next-line vscode-dts-region-comments - //#region @jrieken -> exclusive document filters + //#region exclusiveDocumentFilters: @jrieken export interface DocumentFilter { readonly exclusive?: boolean; @@ -1010,13 +997,13 @@ declare module 'vscode' { //#endregion - //#region Tree View: https://github.com/microsoft/vscode/issues/61313 @alexr00 + //#region treeViewReveal: https://github.com/microsoft/vscode/issues/61313 @alexr00 export interface TreeView<T> extends Disposable { reveal(element: T | undefined, options?: { select?: boolean, focus?: boolean, expand?: boolean | number; }): Thenable<void>; } //#endregion - //#region Custom Tree View Drag and Drop https://github.com/microsoft/vscode/issues/32592 + //#region treeViewDragAndDrop: https://github.com/microsoft/vscode/issues/32592 /** * A data provider that provides tree data */ @@ -1080,7 +1067,7 @@ declare module 'vscode' { } //#endregion - //#region Task presentation group: https://github.com/microsoft/vscode/issues/47265 + //#region taskPresentationGroup: https://github.com/microsoft/vscode/issues/47265 export interface TaskPresentationOptions { /** * Controls whether the task is executed in a specific terminal group using split panes. @@ -1094,7 +1081,7 @@ declare module 'vscode' { } //#endregion - //#region Custom editor move https://github.com/microsoft/vscode/issues/86146 + //#region customEditorMove: https://github.com/microsoft/vscode/issues/86146 // TODO: Also for custom editor @@ -1118,7 +1105,7 @@ declare module 'vscode' { //#endregion - //#region allow QuickPicks to skip sorting: https://github.com/microsoft/vscode/issues/73904 + //#region quickPickSortByLabel: https://github.com/microsoft/vscode/issues/73904 export interface QuickPick<T extends QuickPickItem> extends QuickInput { /** @@ -1129,19 +1116,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/132068 - - export interface QuickPick<T extends QuickPickItem> extends QuickInput { - - /* - * An optional flag to maintain the scroll position of the quick pick when the quick pick items are updated. Defaults to false. - */ - keepScrollPosition?: boolean; - } - - //#endregion - - //#region https://github.com/microsoft/vscode/issues/124970, Cell Execution State + //#region notebookCellExecutionState: https://github.com/microsoft/vscode/issues/124970 /** * The execution state of a notebook cell. @@ -1188,7 +1163,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/106744, Notebook, deprecated & misc + //#region notebookDeprecated: https://github.com/microsoft/vscode/issues/106744 export interface NotebookCellOutput { id: string; @@ -1196,7 +1171,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/106744, NotebookEditor + //#region notebookEditor: https://github.com/microsoft/vscode/issues/106744 /** * Represents a notebook editor that is attached to a {@link NotebookDocument notebook}. @@ -1361,7 +1336,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/106744, NotebookEditorEdit + //#region notebookEditorEdit: https://github.com/microsoft/vscode/issues/106744 // todo@API add NotebookEdit-type which handles all these cases? // export class NotebookEdit { @@ -1409,7 +1384,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/106744, NotebookEditorDecorationType + //#region notebookEditorDecorationType: https://github.com/microsoft/vscode/issues/106744 export interface NotebookEditor { setDecorations(decorationType: NotebookEditorDecorationType, range: NotebookRange): void; @@ -1432,7 +1407,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/106744, NotebookConcatTextDocument + //#region notebookConcatTextDocument: https://github.com/microsoft/vscode/issues/106744 export namespace notebooks { /** @@ -1467,8 +1442,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/106744, NotebookContentProvider - + //#region notebookContentProvider: https://github.com/microsoft/vscode/issues/106744 interface NotebookDocumentBackup { /** @@ -1528,7 +1502,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/106744, LiveShare + //#region notebookLiveShare: https://github.com/microsoft/vscode/issues/106744 export interface NotebookRegistrationData { displayName: string; @@ -1545,7 +1519,7 @@ declare module 'vscode' { //#endregion - //#region @https://github.com/microsoft/vscode/issues/123601, notebook messaging + //#region notebookMessaging: https://github.com/microsoft/vscode/issues/123601 /** * Represents a script that is loaded into the notebook renderer before rendering output. This allows @@ -1612,7 +1586,7 @@ declare module 'vscode' { //#endregion - //#region @eamodio - timeline: https://github.com/microsoft/vscode/issues/84297 + //#region timeline: https://github.com/microsoft/vscode/issues/84297 export class TimelineItem { /** @@ -1770,7 +1744,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/91555 + //#region tokenInformation: https://github.com/microsoft/vscode/issues/91555 export enum StandardTokenType { Other = 0, @@ -1790,7 +1764,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/16221 + //#region inlayHints: https://github.com/microsoft/vscode/issues/16221 // todo@API Split between Inlay- and OverlayHints (InlayHint are for a position, OverlayHints for a non-empty range) // todo@API add "mini-markdown" for links and styles @@ -1873,7 +1847,7 @@ declare module 'vscode' { } //#endregion - //#region https://github.com/microsoft/vscode/issues/104436 + //#region extensionRuntime: https://github.com/microsoft/vscode/issues/104436 export enum ExtensionRuntime { /** @@ -1892,7 +1866,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/102091 + //#region textDocumentNotebook: https://github.com/microsoft/vscode/issues/102091 export interface TextDocument { @@ -1904,6 +1878,8 @@ declare module 'vscode' { } //#endregion + // TODO: @connor4312 split this up + // eslint-disable-next-line vscode-dts-region-comments //#region proposed test APIs https://github.com/microsoft/vscode/issues/107467 export namespace tests { /** @@ -2100,7 +2076,7 @@ declare module 'vscode' { //#endregion - //#region Opener service (https://github.com/microsoft/vscode/issues/109277) + //#region externalUriOpener: https://github.com/microsoft/vscode/issues/109277 /** * Details if an `ExternalUriOpener` can open a uri. @@ -2258,7 +2234,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/Microsoft/vscode/issues/15178 + //#region tabs: https://github.com/Microsoft/vscode/issues/15178 /** * Represents a tab within the window @@ -2353,7 +2329,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/120173 + //#region workspaceTrust: https://github.com/microsoft/vscode/issues/120173 /** * The object describing the properties of the workspace trust request */ @@ -2377,7 +2353,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/115616 @alexr00 + //#region portAttributesProvider: https://github.com/microsoft/vscode/issues/115616 @alexr00 export enum PortAutoForwardAction { Notify = 1, OpenBrowser = 2, @@ -2432,19 +2408,7 @@ declare module 'vscode' { } //#endregion - //#region https://github.com/microsoft/vscode/issues/119904 @eamodio - - export interface SourceControlInputBox { - - /** - * Sets focus to the input. - */ - focus(): void; - } - - //#endregion - - //#region https://github.com/microsoft/vscode/issues/124024 @hediet @alexdima + //#region inlineCompletionProvider: https://github.com/microsoft/vscode/issues/124024 @hediet @alexdima export namespace languages { /** @@ -2566,7 +2530,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/126280 @mjbvz + //#region notebookMime: https://github.com/microsoft/vscode/issues/126280 @mjbvz export interface NotebookCellData { /** @@ -2594,7 +2558,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/123713 @connor4312 + //#region testCoverage: https://github.com/microsoft/vscode/issues/123713 @connor4312 export interface TestRun { /** * Test coverage provider for this result. An extension can defer setting @@ -2785,7 +2749,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/129037 + //#region languageStatus: https://github.com/microsoft/vscode/issues/129037 enum LanguageStatusSeverity { Information = 0, @@ -2812,21 +2776,7 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/88716 - export interface QuickPickItem { - buttons?: QuickInputButton[]; - } - export interface QuickPick<T extends QuickPickItem> extends QuickInput { - readonly onDidTriggerItemButton: Event<QuickPickItemButtonEvent<T>>; - } - export interface QuickPickItemButtonEvent<T extends QuickPickItem> { - button: QuickInputButton; - item: T; - } - - //#endregion - - //#region @eamodio https://github.com/microsoft/vscode/issues/133935 + //#region scmActionButton: https://github.com/microsoft/vscode/issues/133935 export interface SourceControl { actionButton?: Command; @@ -2834,14 +2784,4 @@ declare module 'vscode' { //#endregion - //#region @sandy081 https://github.com/microsoft/vscode/issues/132183 - - export interface OutputChannel { - /* - * Replaces the existing contents of the channel with the given value. - */ - replace(value: string): void; - } - - //#endregion } diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index aa67c834e0d..6f7a8a7e9ee 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -128,6 +128,7 @@ export async function spawn(options: SpawnOptions): Promise<Code> { const env = { ...process.env }; const codePath = options.codePath; + const logsPath = path.join(repoPath, '.build', 'logs', options.remote ? 'smoke-tests-remote' : 'smoke-tests'); const outPath = codePath ? getBuildOutPath(codePath) : getDevOutPath(); const args = [ @@ -142,7 +143,7 @@ export async function spawn(options: SpawnOptions): Promise<Code> { '--disable-workspace-trust', `--extensions-dir=${options.extensionsPath}`, `--user-data-dir=${options.userDataDir}`, - `--logsPath=${path.join(repoPath, '.build', 'logs', 'smoke-tests')}`, + `--logsPath=${logsPath}`, '--driver', handle ]; @@ -170,6 +171,7 @@ export async function spawn(options: SpawnOptions): Promise<Code> { } env['TESTRESOLVER_DATA_FOLDER'] = remoteDataDir; + env['TESTRESOLVER_LOGS_FOLDER'] = path.join(logsPath, 'server'); } const spawnOptions: cp.SpawnOptions = { env }; diff --git a/test/smoke/README.md b/test/smoke/README.md index 93e35e4c1f7..84c2030a336 100644 --- a/test/smoke/README.md +++ b/test/smoke/README.md @@ -62,6 +62,7 @@ xattr -d com.apple.quarantine <path to server with web folder zip> - `--verbose` logs all the low level driver calls made to Code; - `-f PATTERN` (alias `-g PATTERN`) filters the tests to be run. You can also use pretty much any mocha argument; - `--screenshots SCREENSHOT_DIR` captures screenshots when tests fail. +- `--headless` will run playwright in headless mode when `--web` is used. **Note**: you can enable verbose logging of playwright library by setting a `DEBUG` environment variable before running the tests (https://playwright.dev/docs/debug#verbose-api-logs) diff --git a/test/smoke/src/areas/terminal/terminal-profiles.test.ts b/test/smoke/src/areas/terminal/terminal-profiles.test.ts new file mode 100644 index 00000000000..17c5d3d3525 --- /dev/null +++ b/test/smoke/src/areas/terminal/terminal-profiles.test.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ParsedArgs } from 'minimist'; +import { Application } from '../../../../automation'; +import { afterSuite, beforeSuite } from '../../utils'; + +export function setup(opts: ParsedArgs) { + describe.skip('Terminal Profiles', () => { + beforeSuite(opts); + afterSuite(opts); + + it.skip('should launch the default profile', async () => { + const app = this.app as Application; + console.log(app); + }); + }); +} diff --git a/test/smoke/src/areas/terminal/terminal-reconnection.test.ts b/test/smoke/src/areas/terminal/terminal-reconnection.test.ts new file mode 100644 index 00000000000..6a6db40a623 --- /dev/null +++ b/test/smoke/src/areas/terminal/terminal-reconnection.test.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ParsedArgs } from 'minimist'; +import { Application } from '../../../../automation'; +import { afterSuite, beforeSuite } from '../../utils'; + +export function setup(opts: ParsedArgs) { + describe('Terminal Reconnection', () => { + beforeSuite(opts); + afterSuite(opts); + + it.skip('should reconnect to a single terminal on reload', async () => { + const app = this.app as Application; + console.log(app); + }); + }); +} diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index b05d6742d01..bae2e43cc51 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -17,16 +17,17 @@ import { Quality, ApplicationOptions, MultiLogger, Logger, ConsoleLogger, FileLo import { setup as setupDataMigrationTests } from './areas/workbench/data-migration.test'; import { setup as setupDataLossTests } from './areas/workbench/data-loss.test'; -import { setup as setupDataPreferencesTests } from './areas/preferences/preferences.test'; -import { setup as setupDataSearchTests } from './areas/search/search.test'; -import { setup as setupDataNotebookTests } from './areas/notebook/notebook.test'; -import { setup as setupDataLanguagesTests } from './areas/languages/languages.test'; -import { setup as setupDataEditorTests } from './areas/editor/editor.test'; -import { setup as setupDataStatusbarTests } from './areas/statusbar/statusbar.test'; -import { setup as setupDataExtensionTests } from './areas/extensions/extensions.test'; -import { setup as setupDataMultirootTests } from './areas/multiroot/multiroot.test'; -import { setup as setupDataLocalizationTests } from './areas/workbench/localization.test'; +import { setup as setupPreferencesTests } from './areas/preferences/preferences.test'; +import { setup as setupSearchTests } from './areas/search/search.test'; +import { setup as setupNotebookTests } from './areas/notebook/notebook.test'; +import { setup as setupLanguagesTests } from './areas/languages/languages.test'; +import { setup as setupEditorTests } from './areas/editor/editor.test'; +import { setup as setupStatusbarTests } from './areas/statusbar/statusbar.test'; +import { setup as setupExtensionTests } from './areas/extensions/extensions.test'; +import { setup as setupMultirootTests } from './areas/multiroot/multiroot.test'; +import { setup as setupLocalizationTests } from './areas/workbench/localization.test'; import { setup as setupLaunchTests } from './areas/workbench/launch.test'; +import { setup as setupTerminalProfileTests } from './areas/terminal/terminal-profiles.test'; const testDataPath = path.join(os.tmpdir(), 'vscsmoke'); if (fs.existsSync(testDataPath)) { @@ -162,7 +163,11 @@ if (!opts.web) { quality = Quality.Stable; } - console.log(`Running desktop smoke tests against ${electronPath}`); + if (opts.remote) { + console.log(`Running desktop remote smoke tests against ${electronPath}`); + } else { + console.log(`Running desktop smoke tests against ${electronPath}`); + } } // @@ -342,14 +347,17 @@ if (!opts.web && opts['build'] && !opts['remote']) { describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => { if (!opts.web) { setupDataLossTests(opts); } - if (!opts.web) { setupDataPreferencesTests(opts); } - setupDataSearchTests(opts); - setupDataNotebookTests(opts); - setupDataLanguagesTests(opts); - setupDataEditorTests(opts); - setupDataStatusbarTests(opts); - setupDataExtensionTests(opts); - if (!opts.web) { setupDataMultirootTests(opts); } - if (!opts.web) { setupDataLocalizationTests(opts); } + if (!opts.web) { setupPreferencesTests(opts); } + setupSearchTests(opts); + setupNotebookTests(opts); + setupLanguagesTests(opts); + setupEditorTests(opts); + setupStatusbarTests(opts); + setupExtensionTests(opts); + if (!opts.web) { setupMultirootTests(opts); } + if (!opts.web) { setupLocalizationTests(opts); } if (!opts.web) { setupLaunchTests(); } + + // TODO: Enable terminal tests for non-web + if (opts.web) { setupTerminalProfileTests(opts); } }); diff --git a/yarn.lock b/yarn.lock index 5218af6c280..06debad293e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -417,10 +417,10 @@ dependencies: "@octokit/openapi-types" "^10.2.2" -"@parcel/watcher@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.0.tgz#ebe992a4838b35c3da9a568eb95a71cb26ddf551" - integrity sha512-ByalKmRRXNNAhwZ0X1r0XeIhh1jG8zgdlvjgHk9ZV3YxiersEGNQkwew+RfqJbIL4gOJfvC2ey6lg5kaeRainw== +"@parcel/watcher@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.1.tgz#ec4bb6c43d9588a1ffd3d2abe6df5b501463c62d" + integrity sha512-XegFF4L8sFn1RzU5KKOZxXUuzgOSwd6+X2ez3Cy6MVhYMbiLZ1moceMTqDhuT3N8DNbdumK3zP1wojsIsnX40w== dependencies: node-addon-api "^3.2.1" node-gyp-build "^4.3.0" @@ -10874,35 +10874,35 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-search@0.9.0-beta.5: - version "0.9.0-beta.5" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.5.tgz#e0e60a203d1c9d6c8af933648a46865dba299302" - integrity sha512-ylfqim0ISBvuuX83LQwgu/06p5GC545QsAo9SssXw03TPpIrcd0zwaVMEnhOftSIzM9EKRRsyx3GbBjgUdiF5w== - -xterm-addon-serialize@0.7.0-beta.2: - version "0.7.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.7.0-beta.2.tgz#ced9f664c74ab88448e7b63850721bc272aa6806" - integrity sha512-KuSwdx2AAliUv7SvjKYUKHrB7vscbHLv8QsmwSDI3pgL1BpjyLJ8LR99iFFfuNpPW9CG4TX6adKPIJXtqiN3Vg== - -xterm-addon-unicode11@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0.tgz#e4435c3c91a5294a7eb8b79c380acbb28a659463" - integrity sha512-x5fHDZT2j9tlTlHnzPHt++9uKZ2kJ/lYQOj3L6xJA22xoJsS8UQRw/5YIFg2FUHqEAbV77Z1fZij/9NycMSH/A== - -xterm-addon-webgl@0.12.0-beta.15: - version "0.12.0-beta.15" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.15.tgz#9ae82127f2a39b3cb7f5ae45a6af223810c933d4" - integrity sha512-LWZ3iLspQOCc26OoT8qa+SuyuIcn2cAMRbBkinOuQCk4aW5kjovIrGovj9yVAcXNvOBnPm3sUqmnwGlN579kDA== - -xterm-headless@4.15.0-beta.10: - version "4.15.0-beta.10" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.15.0-beta.10.tgz#2dbcb40dfda7ecfdacc7b63889c80da965480ce7" - integrity sha512-kDAzmaeFX8hAJvbPUJc4dW4SoVBSg4onCVOPyi8QTmxZz1o7I9mX4U7DX1v3PceyfrU27A9k6zXjuTuPjxCCSQ== - -xterm@4.15.0-beta.10: - version "4.15.0-beta.10" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.15.0-beta.10.tgz#8cda3d7885e8345f2fc6cf9275a43f3833d29acf" - integrity sha512-valoh5ZcY/y7Pe+ffgcSAEFeuZfjzVeUUXcthdxTTsrGEiU1s4QR2EOg4U5jn5wye/Nc6mSfLW3s79R6Ac186w== +xterm-addon-search@0.9.0-beta.6: + version "0.9.0-beta.6" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.6.tgz#8b016baac5580dc0ba93bb52610bc4f5776d3b17" + integrity sha512-UAEzas4O+NrF7BSGf0C9N5ngAkmbtr/hSTFvLAM/Rw7EfLUatf8aLMqAWZTggRGMwDjuqR0GXJI4+e5QrJhQfw== + +xterm-addon-serialize@0.7.0-beta.3: + version "0.7.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.7.0-beta.3.tgz#a8ce52a59685041bd3b6d6a2a77a3df8bc3daf29" + integrity sha512-fgB0h8JiSN1cOMh3slenysprnGfFwbDZ/D38WA0Pdjxf3YDy4j2SwoUajlvXpkFWR7sHjVHmgpw/nHggO731KA== + +xterm-addon-unicode11@0.4.0-beta.1: + version "0.4.0-beta.1" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.1.tgz#aeefd26e87bad15d8dfd8a1e0b804fe408c9b882" + integrity sha512-pG8mpxnqpYDry0e20vuEFKhd4kKIcLLNwdNftNvfo+R/EjYRnTYnF+H8L+7eQHq6hqDH61xCEP4H4qR2CyT4pg== + +xterm-addon-webgl@0.12.0-beta.16: + version "0.12.0-beta.16" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.16.tgz#63a0f1f5be9e66286e035448e2011e3065769ad5" + integrity sha512-g6v3RegOhSsD9Zt8ArWBMNT30QyPUlIWEIvP/xLHAluUZ1S5sDjFyZDB0nJAyn9MwQozJpwb0ylYO1nznN/TzA== + +xterm-headless@4.16.0-beta.2: + version "4.16.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.16.0-beta.2.tgz#62e66a655a30c814e3a311f3542d42c87446cecd" + integrity sha512-g92HDaIZcu1TQFlrjq2CHtt7A2qAwSD6s8RwncU/7u1kaq2e7rc9O3OKfu5v3QzgaRSKuugtquMr0OTKjkmLUg== + +xterm@4.16.0-beta.2: + version "4.16.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.16.0-beta.2.tgz#251beef21a232143f272da74c7005bc4d832ca79" + integrity sha512-PD0agueJ7qvbn1/QhZriAQXf+ykaoPKgQN9qiIGf88VMxHs8T47MYHW/+qPsrXagTmbrENtncughTIzOzv8Q5Q== y18n@^3.2.1: version "3.2.2" |