diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5e990067f1d1aa..017708844a4710 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,6 +1,8 @@ name: "Code Scanning" -on: [push, pull_request] +on: + schedule: + - cron: '0 0 * * 2' jobs: CodeQL-Build: diff --git a/.github/workflows/rich-navigation.yml b/.github/workflows/rich-navigation.yml index 185c770dd0f2f7..38afb0d7f4929a 100644 --- a/.github/workflows/rich-navigation.yml +++ b/.github/workflows/rich-navigation.yml @@ -10,16 +10,10 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v2 - - name: Use Node.js - uses: actions/setup-node@v1 - name: Install dependencies run: yarn --frozen-lockfile env: CHILD_CONCURRENCY: 1 - - name: Install .NET Core 2.2 - uses: actions/setup-dotnet@v1.5.0 - with: - dotnet-version: 2.2 - uses: microsoft/RichCodeNavIndexer@v0.1 with: languages: typescript diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index 787b4d1749701f..b74a91843a3b7d 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -17,7 +17,6 @@ "web" ], "activationEvents": [ - "*", "onAuthenticationRequest:github" ], "contributes": { @@ -34,7 +33,13 @@ "when": "false" } ] - } + }, + "authentication": [ + { + "label": "GitHub", + "id": "github" + } + ] }, "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "main": "./out/extension.js", diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index 81ed5c32e4f766..64becb50468897 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -12,7 +12,6 @@ ], "enableProposedApi": true, "activationEvents": [ - "*", "onAuthenticationRequest:microsoft" ], "extensionKind": [ @@ -20,6 +19,14 @@ "workspace", "web" ], + "contributes": { + "authentication": [ + { + "label": "Microsoft", + "id": "microsoft" + } + ] + }, "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "main": "./out/extension.js", "browser": "./dist/browser/extension.js", diff --git a/extensions/vscode-notebook-tests/package.json b/extensions/vscode-notebook-tests/package.json index 53ba12569a986c..4454b25fb2af7a 100644 --- a/extensions/vscode-notebook-tests/package.json +++ b/extensions/vscode-notebook-tests/package.json @@ -78,6 +78,12 @@ "group": "inline@1" } ] - } + }, + "jsonValidation": [ + { + "fileMatch": "vscode://vscode-notebook-cell-metadata/*", + "url": "vscode://schemas/notebook/cellmetadata" + } + ] } } diff --git a/product.json b/product.json index 3b18619d1fe31d..9e975d59c1b4bf 100644 --- a/product.json +++ b/product.json @@ -76,7 +76,7 @@ }, { "name": "ms-vscode.js-debug-companion", - "version": "1.0.5", + "version": "1.0.6", "repo": "https://github.com/microsoft/vscode-js-debug-companion", "metadata": { "id": "99cb0b7f-7354-4278-b8da-6cc79972169d", diff --git a/resources/web/code-web.js b/resources/web/code-web.js index 74dd39e8cb8e32..8818b5d9efd01b 100644 --- a/resources/web/code-web.js +++ b/resources/web/code-web.js @@ -27,7 +27,7 @@ const BUILTIN_MARKETPLACE_EXTENSIONS_ROOT = path.join(APP_ROOT, '.build', 'built const WEB_DEV_EXTENSIONS_ROOT = path.join(APP_ROOT, '.build', 'builtInWebDevExtensions'); const WEB_MAIN = path.join(APP_ROOT, 'src', 'vs', 'code', 'browser', 'workbench', 'workbench-dev.html'); -const WEB_PLAYGROUND_VERSION = '0.0.2'; +const WEB_PLAYGROUND_VERSION = '0.0.4'; const args = minimist(process.argv, { boolean: [ diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index 292764c8807e84..fa034af58f25a7 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -24,6 +24,7 @@ export interface IPaneOptions { expanded?: boolean; orientation?: Orientation; title: string; + titleDescription?: string; } export interface IPaneStyles { diff --git a/src/vs/base/common/fuzzyScorer.ts b/src/vs/base/common/fuzzyScorer.ts index 12c3e4ad7396c2..11fc4bc0ed3e58 100644 --- a/src/vs/base/common/fuzzyScorer.ts +++ b/src/vs/base/common/fuzzyScorer.ts @@ -463,6 +463,7 @@ function doScoreItemFuzzySingle(label: string, description: string | undefined, if (preferLabelMatches || !description) { const [labelScore, labelPositions] = scoreFuzzy(label, query.normalized, query.normalizedLowercase, fuzzy); if (labelScore) { + // If we have a prefix match on the label, we give a much // higher baseScore to elevate these matches over others // This ensures that typing a file name wins over results @@ -472,6 +473,14 @@ function doScoreItemFuzzySingle(label: string, description: string | undefined, let baseScore: number; if (labelPrefixMatch) { baseScore = LABEL_PREFIX_SCORE_THRESHOLD; + + // We give another boost to labels that are short, e.g. given + // files "window.ts" and "windowActions.ts" and a query of + // "window", we want "window.ts" to receive a higher score. + // As such we compute the percentage the query has within the + // label and add that to the baseScore. + const prefixLengthBoost = Math.round((query.normalized.length / label.length) * 100); + baseScore += prefixLengthBoost; } else { baseScore = LABEL_SCORE_THRESHOLD; } diff --git a/src/vs/base/test/common/fuzzyScorer.test.ts b/src/vs/base/test/common/fuzzyScorer.test.ts index 31f211e7ce794a..5aac1fb98b4713 100644 --- a/src/vs/base/test/common/fuzzyScorer.test.ts +++ b/src/vs/base/test/common/fuzzyScorer.test.ts @@ -1040,6 +1040,36 @@ suite('Fuzzy Scorer', () => { } }); + test('compareFilesByScore - boost shorter prefix match if multiple queries are used', function () { + const resourceA = URI.file('src/vs/workbench/browser/actions/windowActions.ts'); + const resourceB = URI.file('src/vs/workbench/electron-browser/window.ts'); + + for (const query of ['window browser', 'window.ts browser']) { + let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); + assert.equal(res[0], resourceB); + assert.equal(res[1], resourceA); + + res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); + assert.equal(res[0], resourceB); + assert.equal(res[1], resourceA); + } + }); + + test('compareFilesByScore - boost shorter prefix match if multiple queries are used (#99171)', function () { + const resourceA = URI.file('mesh_editor_lifetime_job.h'); + const resourceB = URI.file('lifetime_job.h'); + + for (const query of ['m life, life m']) { + let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); + assert.equal(res[0], resourceB); + assert.equal(res[1], resourceA); + + res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); + assert.equal(res[0], resourceB); + assert.equal(res[1], resourceA); + } + }); + test('prepareQuery', () => { assert.equal(scorer.prepareQuery(' f*a ').normalized, 'fa'); assert.equal(scorer.prepareQuery('model Tester.ts').original, 'model Tester.ts'); diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts index fd820cf9f58486..cc33ad9c397b27 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -8,7 +8,7 @@ import 'vs/base/browser/ui/codicons/codiconStyles'; // make sure codicon css is import { ElectronService, IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; import { ipcRenderer, process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox/window'; -import { $, windowOpenNoOpener, addClass } from 'vs/base/browser/dom'; +import { $, reset, windowOpenNoOpener, addClass } from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel'; import * as collections from 'vs/base/common/collections'; @@ -277,33 +277,25 @@ export class IssueReporter extends Disposable { } private updateSettingsSearchDetails(data: ISettingsSearchIssueReporterData): void { - const target = document.querySelector('.block-settingsSearchResults .block-info'); + const target = document.querySelector('.block-settingsSearchResults .block-info'); if (target) { - const details = ` -
-
Query: "${data.query}"
-
Literal match count: ${data.filterResultCount}
-
- `; - - let table = ` - - Setting - Extension - Score - `; - - data.actualSearchResults - .forEach(setting => { - table += ` - - ${setting.key} - ${setting.extensionId} - ${String(setting.score).slice(0, 5)} - `; - }); - - target.innerHTML = `${details}${table}
`; + const queryDiv = $('div', undefined, `Query: "${data.query}"` as string); + const countDiv = $('div', undefined, `Literal match count: ${data.filterResultCount}` as string); + const detailsDiv = $('.block-settingsSearchResults-details', undefined, queryDiv, countDiv); + + const table = $('table', undefined, + $('tr', undefined, + $('th', undefined, 'Setting'), + $('th', undefined, 'Extension'), + $('th', undefined, 'Score'), + ), + ...data.actualSearchResults.map(setting => $('tr', undefined, + $('td', undefined, setting.key), + $('td', undefined, setting.extensionId), + $('td', undefined, String(setting.score).slice(0, 5)), + )) + ); + reset(target, detailsDiv, table); } } @@ -654,9 +646,9 @@ export class IssueReporter extends Disposable { issueState.appendChild(issueIcon); issueState.appendChild(issueStateLabel); - item = $('div.issue', {}, issueState, link); + item = $('div.issue', undefined, issueState, link); } else { - item = $('div.issue', {}, link); + item = $('div.issue', undefined, link); } issues.appendChild(item); @@ -672,19 +664,19 @@ export class IssueReporter extends Disposable { } private setUpTypes(): void { - const makeOption = (issueType: IssueType, description: string) => ``; + const makeOption = (issueType: IssueType, description: string) => $('option', { 'value': issueType.valueOf() }, escape(description)); const typeSelect = this.getElementById('issue-type')! as HTMLSelectElement; const { issueType } = this.issueReporterModel.getData(); if (issueType === IssueType.SettingsSearchIssue) { - typeSelect.innerHTML = makeOption(IssueType.SettingsSearchIssue, localize('settingsSearchIssue', "Settings Search Issue")); + reset(typeSelect, makeOption(IssueType.SettingsSearchIssue, localize('settingsSearchIssue', "Settings Search Issue"))); typeSelect.disabled = true; } else { - typeSelect.innerHTML = [ + reset(typeSelect, makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")), makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request")), makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue")) - ].join('\n'); + ); } typeSelect.value = issueType.toString(); @@ -774,9 +766,8 @@ export class IssueReporter extends Disposable { } else { show(extensionsBlock); } - - descriptionTitle.innerHTML = `${localize('stepsToReproduce', "Steps to Reproduce")} *`; - descriptionSubtitle.innerHTML = localize('bugDescription', "Share the steps needed to reliably reproduce the problem. Please include actual and expected results. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub."); + reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce"), $('span.required-input', undefined, '*')); + reset(descriptionSubtitle, localize('bugDescription', "Share the steps needed to reliably reproduce the problem. Please include actual and expected results. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); } else if (issueType === IssueType.PerformanceIssue) { show(blockContainer); show(systemBlock); @@ -790,11 +781,11 @@ export class IssueReporter extends Disposable { show(extensionsBlock); } - descriptionTitle.innerHTML = `${localize('stepsToReproduce', "Steps to Reproduce")} *`; - descriptionSubtitle.innerHTML = localize('performanceIssueDesciption', "When did this performance issue happen? Does it occur on startup or after a specific series of actions? We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub."); + reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce"), $('span.required-input', undefined, '*')); + reset(descriptionSubtitle, localize('performanceIssueDesciption', "When did this performance issue happen? Does it occur on startup or after a specific series of actions? We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); } else if (issueType === IssueType.FeatureRequest) { - descriptionTitle.innerHTML = `${localize('description', "Description")} *`; - descriptionSubtitle.innerHTML = localize('featureRequestDescription', "Please describe the feature you would like to see. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub."); + reset(descriptionTitle, localize('description', "Description"), $('span.required-input', undefined, '*')); + reset(descriptionSubtitle, localize('featureRequestDescription', "Please describe the feature you would like to see. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); show(problemSource); if (fileOnExtension) { @@ -805,8 +796,8 @@ export class IssueReporter extends Disposable { show(searchedExtensionsBlock); show(settingsSearchResultsBlock); - descriptionTitle.innerHTML = `${localize('expectedResults', "Expected Results")} *`; - descriptionSubtitle.innerHTML = localize('settingsSearchResultsDescription', "Please list the results that you were expecting to see when you searched with this query. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub."); + reset(descriptionTitle, localize('expectedResults', "Expected Results"), $('span.required-input', undefined, '*')); + reset(descriptionSubtitle, localize('settingsSearchResultsDescription', "Please list the results that you were expecting to see when you searched with this query. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); } } @@ -928,42 +919,82 @@ export class IssueReporter extends Disposable { } private updateSystemInfo(state: IssueReporterModelData) { - const target = document.querySelector('.block-system .block-info'); + const target = document.querySelector('.block-system .block-info'); + if (target) { const systemInfo = state.systemInfo!; - let renderedData = ` - - - - - - - - -
CPUs${systemInfo.cpus}
GPU Status${Object.keys(systemInfo.gpuStatus).map(key => `${key}: ${systemInfo.gpuStatus[key]}`).join('
')}
Load (avg)${systemInfo.load}
Memory (System)${systemInfo.memory}
Process Argv${systemInfo.processArgs}
Screen Reader${systemInfo.screenReader}
VM${systemInfo.vmHint}
`; + const renderedDataTable = $('table', undefined, + $('tr', undefined, + $('td', undefined, 'CPUs'), + $('td', undefined, systemInfo.cpus || ''), + ), + $('tr', undefined, + $('td', undefined, 'GPU Status' as string), + $('td', undefined, Object.keys(systemInfo.gpuStatus).map(key => `${key}: ${systemInfo.gpuStatus[key]}`).join('\n')), + ), + $('tr', undefined, + $('td', undefined, 'Load (avg)' as string), + $('td', undefined, systemInfo.load || ''), + ), + $('tr', undefined, + $('td', undefined, 'Memory (System)' as string), + $('td', undefined, systemInfo.memory), + ), + $('tr', undefined, + $('td', undefined, 'Process Argv' as string), + $('td', undefined, systemInfo.processArgs), + ), + $('tr', undefined, + $('td', undefined, 'Screen Reader' as string), + $('td', undefined, systemInfo.screenReader), + ), + $('tr', undefined, + $('td', undefined, 'VM'), + $('td', undefined, systemInfo.vmHint), + ), + ); + reset(target, renderedDataTable); systemInfo.remoteData.forEach(remote => { + target.appendChild($('hr')); if (isRemoteDiagnosticError(remote)) { - renderedData += ` -
- - - -
Remote${remote.hostName}
${remote.errorMessage}
`; + const remoteDataTable = $('table', undefined, + $('tr', undefined, + $('td', undefined, 'Remote'), + $('td', undefined, remote.hostName) + ), + $('tr', undefined, + $('td', undefined, ''), + $('td', undefined, remote.errorMessage) + ) + ); + target.appendChild(remoteDataTable); } else { - renderedData += ` -
- - - - - - -
Remote${remote.hostName}
OS${remote.machineInfo.os}
CPUs${remote.machineInfo.cpus}
Memory (System)${remote.machineInfo.memory}
VM${remote.machineInfo.vmHint}
`; + const remoteDataTable = $('table', undefined, + $('tr', undefined, + $('td', undefined, 'Remote'), + $('td', undefined, remote.hostName) + ), + $('tr', undefined, + $('td', undefined, 'OS'), + $('td', undefined, remote.machineInfo.os) + ), + $('tr', undefined, + $('td', undefined, 'CPUs'), + $('td', undefined, remote.machineInfo.cpus || '') + ), + $('tr', undefined, + $('td', undefined, 'Memory (System)' as string), + $('td', undefined, remote.machineInfo.memory) + ), + $('tr', undefined, + $('td', undefined, 'VM'), + $('td', undefined, remote.machineInfo.vmHint) + ), + ); + target.appendChild(remoteDataTable); } }); - - target.innerHTML = renderedData; } } @@ -995,15 +1026,18 @@ export class IssueReporter extends Disposable { return 0; }); - const makeOption = (extension: IOption, selectedExtension?: IssueReporterExtensionData) => { + const makeOption = (extension: IOption, selectedExtension?: IssueReporterExtensionData): HTMLOptionElement => { const selected = selectedExtension && extension.id === selectedExtension.id; - return ``; + return $('option', { + 'value': extension.id, + 'selected': selected || '' + }, extension.name); }; const extensionsSelector = this.getElementById('extension-selector'); if (extensionsSelector) { const { selectedExtension } = this.issueReporterModel.getData(); - extensionsSelector.innerHTML = '' + extensionOptions.map(extension => makeOption(extension, selectedExtension)).join('\n'); + reset(extensionsSelector, $('option'), ...extensionOptions.map(extension => makeOption(extension, selectedExtension))); this.addEventListener('extension-selector', 'change', (e: Event) => { const selectedExtensionId = (e.target).value; @@ -1071,9 +1105,9 @@ export class IssueReporter extends Disposable { } private updateProcessInfo(state: IssueReporterModelData) { - const target = document.querySelector('.block-process .block-info'); + const target = document.querySelector('.block-process .block-info') as HTMLElement; if (target) { - target.innerHTML = `${state.processInfo}`; + reset(target, $('code', undefined, state.processInfo)); } } @@ -1085,7 +1119,7 @@ export class IssueReporter extends Disposable { const target = document.querySelector('.block-extensions .block-info'); if (target) { if (this.configuration.disableExtensions) { - target.innerHTML = localize('disabledExtensions', "Extensions are disabled"); + reset(target, localize('disabledExtensions', "Extensions are disabled")); return; } @@ -1097,8 +1131,7 @@ export class IssueReporter extends Disposable { return; } - const table = this.getExtensionTableHtml(extensions); - target.innerHTML = `${table}
${themeExclusionStr}`; + reset(target, this.getExtensionTableHtml(extensions), document.createTextNode(themeExclusionStr)); } } @@ -1111,28 +1144,24 @@ export class IssueReporter extends Disposable { } const table = this.getExtensionTableHtml(extensions); - target.innerHTML = `${table}
`; + target.innerText = ''; + target.appendChild(table); } } - private getExtensionTableHtml(extensions: IssueReporterExtensionData[]): string { - let table = ` - - Extension - Author (truncated) - Version - `; - - table += extensions.map(extension => { - return ` - - ${extension.name} - ${extension.publisher.substr(0, 3)} - ${extension.version} - `; - }).join(''); - - return table; + private getExtensionTableHtml(extensions: IssueReporterExtensionData[]): HTMLTableElement { + return $('table', undefined, + $('tr', undefined, + $('th', undefined, 'Extension'), + $('th', undefined, 'Author (truncated)' as string), + $('th', undefined, 'Version'), + ), + ...extensions.map(extension => $('tr', undefined, + $('td', undefined, extension.name), + $('td', undefined, extension.publisher.substr(0, 3)), + $('td', undefined, extension.version), + )) + ); } private openLink(event: MouseEvent): void { diff --git a/src/vs/code/electron-sandbox/processExplorer/media/collapsed.svg b/src/vs/code/electron-sandbox/processExplorer/media/collapsed.svg deleted file mode 100644 index 3a63808c3585c9..00000000000000 --- a/src/vs/code/electron-sandbox/processExplorer/media/collapsed.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/code/electron-sandbox/processExplorer/media/expanded.svg b/src/vs/code/electron-sandbox/processExplorer/media/expanded.svg deleted file mode 100644 index 75f73adbb02cac..00000000000000 --- a/src/vs/code/electron-sandbox/processExplorer/media/expanded.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css b/src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css index fbb637e83cd352..add9cdae262e22 100644 --- a/src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css +++ b/src/vs/code/electron-sandbox/processExplorer/media/processExplorer.css @@ -58,6 +58,7 @@ table { width: 100%; table-layout: fixed; } + th[scope='col'] { vertical-align: bottom; border-bottom: 1px solid #cccccc; @@ -65,6 +66,7 @@ th[scope='col'] { border-top: 1px solid #cccccc; cursor: default; } + td { padding: .25rem; vertical-align: top; @@ -100,7 +102,6 @@ tbody > tr:hover { display: none; } -img { - width: 16px; - margin-right: 4px; +.header { + display: flex; } diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index b921e461ca926b..1abe745c1d0921 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/processExplorer'; +import 'vs/base/browser/ui/codicons/codiconStyles'; // make sure codicon css is loaded import { ElectronService, IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { localize } from 'vs/nls'; @@ -16,6 +17,7 @@ import { addDisposableListener, addClass } from 'vs/base/browser/dom'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel'; const DEBUG_FLAGS_PATTERN = /\s--(inspect|debug)(-brk|port)?=(\d+)?/; const DEBUG_PORT_PATTERN = /\s--(inspect|debug)-port=(\d+)/; @@ -156,15 +158,15 @@ class ProcessExplorer { return maxProcessId; } - private updateSectionCollapsedState(shouldExpand: boolean, body: HTMLElement, twistie: HTMLImageElement, sectionName: string) { + private updateSectionCollapsedState(shouldExpand: boolean, body: HTMLElement, twistie: CodiconLabel, sectionName: string) { if (shouldExpand) { body.classList.remove('hidden'); this.collapsedStateCache.set(sectionName, false); - twistie.src = './media/expanded.svg'; + twistie.text = '$(chevron-down)'; } else { body.classList.add('hidden'); this.collapsedStateCache.set(sectionName, true); - twistie.src = './media/collapsed.svg'; + twistie.text = '$(chevron-right)'; } } @@ -191,18 +193,27 @@ class ProcessExplorer { private renderProcessGroupHeader(sectionName: string, body: HTMLElement, container: HTMLElement) { const headerRow = document.createElement('tr'); - const data = document.createElement('td'); - data.textContent = sectionName; - data.colSpan = 4; - headerRow.appendChild(data); - const twistie = document.createElement('img'); - this.updateSectionCollapsedState(!this.collapsedStateCache.get(sectionName), body, twistie, sectionName); - data.prepend(twistie); + const headerData = document.createElement('td'); + headerData.colSpan = 4; + headerRow.appendChild(headerData); + + const headerContainer = document.createElement('div'); + headerContainer.className = 'header'; + headerData.appendChild(headerContainer); + + const twistieContainer = document.createElement('div'); + const twistieCodicon = new CodiconLabel(twistieContainer); + this.updateSectionCollapsedState(!this.collapsedStateCache.get(sectionName), body, twistieCodicon, sectionName); + headerContainer.appendChild(twistieContainer); + + const headerLabel = document.createElement('span'); + headerLabel.textContent = sectionName; + headerContainer.appendChild(headerLabel); - this.listeners.add(addDisposableListener(data, 'click', (e) => { + this.listeners.add(addDisposableListener(headerData, 'click', (e) => { const isHidden = body.classList.contains('hidden'); - this.updateSectionCollapsedState(isHidden, body, twistie, sectionName); + this.updateSectionCollapsedState(isHidden, body, twistieCodicon, sectionName); })); container.appendChild(headerRow); diff --git a/src/vs/editor/common/services/getIconClasses.ts b/src/vs/editor/common/services/getIconClasses.ts index fbdc31d6d5c2bf..b20a0b7c58bfe6 100644 --- a/src/vs/editor/common/services/getIconClasses.ts +++ b/src/vs/editor/common/services/getIconClasses.ts @@ -54,6 +54,11 @@ export function getIconClasses(modelService: IModelService, modeService: IModeSe return classes; } + +export function getIconClassesForModeId(modeId: string): string[] { + return ['file-icon', `${cssEscape(modeId)}-lang-file-icon`]; +} + export function detectModeId(modelService: IModelService, modeService: IModeService, resource: uri): string | null { if (!resource) { return null; // we need a resource at least diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index f364eb97380ca9..7e452f6a6cdb38 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -20,7 +20,6 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { optional } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -371,7 +370,6 @@ export class CommonFindController extends Disposable implements IEditorContribut public async getGlobalBufferTerm(): Promise { if (this._editor.getOption(EditorOption.find).globalFindClipboard - && this._clipboardService && this._editor.hasModel() && !this._editor.getModel().isTooLargeForSyncing() ) { @@ -382,7 +380,6 @@ export class CommonFindController extends Disposable implements IEditorContribut public setGlobalBufferTerm(text: string): void { if (this._editor.getOption(EditorOption.find).globalFindClipboard - && this._clipboardService && this._editor.hasModel() && !this._editor.getModel().isTooLargeForSyncing() ) { @@ -406,7 +403,7 @@ export class FindController extends CommonFindController implements IFindControl @INotificationService private readonly _notificationService: INotificationService, @IStorageService _storageService: IStorageService, @IStorageKeysSyncRegistryService private readonly _storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, - @optional(IClipboardService) clipboardService: IClipboardService, + @IClipboardService clipboardService: IClipboardService, ) { super(editor, _contextKeyService, _storageService, clipboardService); this._widget = null; diff --git a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts index f219c6cafec162..31108f99ddb4a3 100644 --- a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts +++ b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./inspectTokens'; +import { $, append, reset } from 'vs/base/browser/dom'; import { CharCode } from 'vs/base/common/charCode'; import { Color } from 'vs/base/common/color'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; -import { escape } from 'vs/base/common/strings'; import { ContentWidgetPositionPreference, IActiveCodeEditor, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; @@ -115,23 +115,11 @@ function renderTokenText(tokenText: string): string { let charCode = tokenText.charCodeAt(charIndex); switch (charCode) { case CharCode.Tab: - result += '→'; + result += '\u2192'; // → break; case CharCode.Space: - result += '·'; - break; - - case CharCode.LessThan: - result += '<'; - break; - - case CharCode.GreaterThan: - result += '>'; - break; - - case CharCode.Ampersand: - result += '&'; + result += '\u00B7'; // · break; default: @@ -211,8 +199,6 @@ class InspectTokensWidget extends Disposable implements IContentWidget { } } - let result = ''; - let lineContent = this._model.getLineContent(position.lineNumber); let tokenText = ''; if (token1Index < data.tokens1.length) { @@ -220,26 +206,43 @@ class InspectTokensWidget extends Disposable implements IContentWidget { let tokenEndIndex = token1Index + 1 < data.tokens1.length ? data.tokens1[token1Index + 1].offset : lineContent.length; tokenText = lineContent.substring(tokenStartIndex, tokenEndIndex); } - result += `

${renderTokenText(tokenText)}(${tokenText.length} ${tokenText.length === 1 ? 'char' : 'chars'})

`; - - result += `
`; - - let metadata = (token2Index << 1) + 1 < data.tokens2.length ? this._decodeMetadata(data.tokens2[(token2Index << 1) + 1]) : null; - result += ``; - result += ``; - result += ``; - result += ``; - result += ``; - result += ``; - result += ``; - - result += `
`; + reset(this._domNode, + $('h2.tm-token', undefined, renderTokenText(tokenText), + $('span.tm-token-length', undefined, `${tokenText.length} ${tokenText.length === 1 ? 'char' : 'chars'}`))); + + append(this._domNode, $('hr.tokens-inspect-separator', { 'style': 'clear:both' })); + + const metadata = (token2Index << 1) + 1 < data.tokens2.length ? this._decodeMetadata(data.tokens2[(token2Index << 1) + 1]) : null; + append(this._domNode, $('table.tm-metadata-table', undefined, + $('tbody', undefined, + $('tr', undefined, + $('td.tm-metadata-key', undefined, 'language'), + $('td.tm-metadata-value', undefined, `${metadata ? metadata.languageIdentifier.language : '-?-'}`) + ), + $('tr', undefined, + $('td.tm-metadata-key', undefined, 'token type' as string), + $('td.tm-metadata-value', undefined, `${metadata ? this._tokenTypeToString(metadata.tokenType) : '-?-'}`) + ), + $('tr', undefined, + $('td.tm-metadata-key', undefined, 'font style' as string), + $('td.tm-metadata-value', undefined, `${metadata ? this._fontStyleToString(metadata.fontStyle) : '-?-'}`) + ), + $('tr', undefined, + $('td.tm-metadata-key', undefined, 'foreground'), + $('td.tm-metadata-value', undefined, `${metadata ? Color.Format.CSS.formatHex(metadata.foreground) : '-?-'}`) + ), + $('tr', undefined, + $('td.tm-metadata-key', undefined, 'background'), + $('td.tm-metadata-value', undefined, `${metadata ? Color.Format.CSS.formatHex(metadata.background) : '-?-'}`) + ) + ) + )); + append(this._domNode, $('hr.tokens-inspect-separator')); if (token1Index < data.tokens1.length) { - result += `${escape(data.tokens1[token1Index].type)}`; + append(this._domNode, $('span.tm-token-type', undefined, data.tokens1[token1Index].type)); } - this._domNode.innerHTML = result; this._editor.layoutContentWidget(this); } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 687cefd9ef1c01..94d8bff6c761e9 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -123,6 +123,8 @@ export class MenuId { static readonly NotebookCellInsert = new MenuId('NotebookCellInsert'); static readonly NotebookCellBetween = new MenuId('NotebookCellBetween'); static readonly NotebookCellListTop = new MenuId('NotebookCellTop'); + static readonly NotebookDiffCellMetadataTitle = new MenuId('NotebookDiffCellMetadataTitle'); + static readonly NotebookDiffCellOutputsTitle = new MenuId('NotebookDiffCellOutputsTitle'); static readonly BulkEditTitle = new MenuId('BulkEditTitle'); static readonly BulkEditContext = new MenuId('BulkEditContext'); static readonly TimelineItemContext = new MenuId('TimelineItemContext'); diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 6024a71fb108d3..73c21c8824f313 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -108,6 +108,11 @@ export interface ICodeActionContribution { readonly actions: readonly ICodeActionContributionAction[]; } +export interface IAuthenticationContribution { + readonly id: string; + readonly label: string; +} + export interface IExtensionContributions { commands?: ICommand[]; configuration?: IConfiguration | IConfiguration[]; @@ -126,6 +131,7 @@ export interface IExtensionContributions { localizations?: ILocalization[]; readonly customEditors?: readonly IWebviewEditor[]; readonly codeActions?: readonly ICodeActionContribution[]; + authentication?: IAuthenticationContribution[]; } export type ExtensionKind = 'ui' | 'workspace' | 'web'; diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 1e1c6fcb583140..3a8d6feef5744e 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -89,7 +89,7 @@ export class TelemetryService implements ITelemetryService { } private _updateUserOptIn(): void { - const config = this._configurationService.getValue(TELEMETRY_SECTION_ID); + const config = this._configurationService?.getValue(TELEMETRY_SECTION_ID); this._userOptIn = config ? config.enableTelemetry : this._userOptIn; } diff --git a/src/vs/platform/webview/common/webviewManagerService.ts b/src/vs/platform/webview/common/webviewManagerService.ts index 56171e56fc05d3..8963865f3b6f35 100644 --- a/src/vs/platform/webview/common/webviewManagerService.ts +++ b/src/vs/platform/webview/common/webviewManagerService.ts @@ -14,7 +14,7 @@ export const IWebviewManagerService = createDecorator('w export interface IWebviewManagerService { _serviceBrand: unknown; - registerWebview(id: string, webContentsId: number | undefined, windowId: number, metadata: RegisterWebviewMetadata): Promise; + registerWebview(id: string, windowId: number, metadata: RegisterWebviewMetadata): Promise; unregisterWebview(id: string): Promise; updateWebviewMetadata(id: string, metadataDelta: Partial): Promise; diff --git a/src/vs/platform/webview/electron-main/webviewMainService.ts b/src/vs/platform/webview/electron-main/webviewMainService.ts index 0e29b1cbaff7ca..c3b49724aba2eb 100644 --- a/src/vs/platform/webview/electron-main/webviewMainService.ts +++ b/src/vs/platform/webview/electron-main/webviewMainService.ts @@ -33,7 +33,7 @@ export class WebviewMainService extends Disposable implements IWebviewManagerSer this.portMappingProvider = this._register(new WebviewPortMappingProvider(tunnelService)); } - public async registerWebview(id: string, webContentsId: number | undefined, windowId: number, metadata: RegisterWebviewMetadata): Promise { + public async registerWebview(id: string, windowId: number, metadata: RegisterWebviewMetadata): Promise { const extensionLocation = metadata.extensionLocation ? URI.from(metadata.extensionLocation) : undefined; this.protocolProvider.registerWebview(id, { @@ -43,7 +43,7 @@ export class WebviewMainService extends Disposable implements IWebviewManagerSer localResourceRoots: metadata.localResourceRoots.map(x => URI.from(x)) }); - this.portMappingProvider.registerWebview(id, webContentsId, { + this.portMappingProvider.registerWebview(id, { extensionLocation, mappings: metadata.portMappings, resolvedAuthority: metadata.remoteConnectionData, diff --git a/src/vs/platform/webview/electron-main/webviewPortMappingProvider.ts b/src/vs/platform/webview/electron-main/webviewPortMappingProvider.ts index 94dc3036ebc713..da60a2bc5075c4 100644 --- a/src/vs/platform/webview/electron-main/webviewPortMappingProvider.ts +++ b/src/vs/platform/webview/electron-main/webviewPortMappingProvider.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { session } from 'electron'; +import { OnBeforeRequestListenerDetails, session } from 'electron'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IAddress } from 'vs/platform/remote/common/remoteAgentConnection'; @@ -11,6 +11,10 @@ import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { webviewPartitionId } from 'vs/platform/webview/common/resourceLoader'; import { IWebviewPortMapping, WebviewPortMappingManager } from 'vs/platform/webview/common/webviewPortMapping'; +interface OnBeforeRequestListenerDetails_Extended extends OnBeforeRequestListenerDetails { + readonly lastCommittedOrigin?: string; +} + interface PortMappingData { readonly extensionLocation: URI | undefined; readonly mappings: readonly IWebviewPortMapping[]; @@ -20,13 +24,10 @@ interface PortMappingData { export class WebviewPortMappingProvider extends Disposable { private readonly _webviewData = new Map(); - private _webContentsIdsToWebviewIds = new Map(); - constructor( @ITunnelService private readonly _tunnelService: ITunnelService, ) { @@ -40,12 +41,15 @@ export class WebviewPortMappingProvider extends Disposable { '*://127.0.0.1:*/*', '*://0.0.0.0:*/*', ] - }, async (details, callback) => { - const webviewId = details.webContentsId && this._webContentsIdsToWebviewIds.get(details.webContentsId); - if (!webviewId) { + }, async (details: OnBeforeRequestListenerDetails_Extended, callback) => { + let origin: URI; + try { + origin = URI.parse(details.lastCommittedOrigin!); + } catch { return callback({}); } + const webviewId = origin.authority; const entry = this._webviewData.get(webviewId); if (!entry) { return callback({}); @@ -56,16 +60,13 @@ export class WebviewPortMappingProvider extends Disposable { }); } - public async registerWebview(id: string, webContentsId: number | undefined, metadata: PortMappingData): Promise { + public async registerWebview(id: string, metadata: PortMappingData): Promise { const manager = new WebviewPortMappingManager( () => this._webviewData.get(id)?.metadata.extensionLocation, () => this._webviewData.get(id)?.metadata.mappings || [], this._tunnelService); - this._webviewData.set(id, { webContentsId, metadata, manager }); - if (typeof webContentsId === 'number') { - this._webContentsIdsToWebviewIds.set(webContentsId, id); - } + this._webviewData.set(id, { metadata, manager }); } public unregisterWebview(id: string): void { @@ -73,9 +74,6 @@ export class WebviewPortMappingProvider extends Disposable { if (existing) { existing.manager.dispose(); this._webviewData.delete(id); - if (typeof existing.webContentsId === 'number') { - this._webContentsIdsToWebviewIds.delete(existing.webContentsId); - } } } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index c898362a2e4668..485789a4ffbb60 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -112,6 +112,7 @@ declare module 'vscode' { export function registerAuthenticationProvider(provider: AuthenticationProvider): Disposable; /** + * @deprecated - getSession should now trigger extension activation. * Fires with the provider id that was registered or unregistered. */ export const onDidChangeAuthenticationProviders: Event; @@ -1179,7 +1180,7 @@ declare module 'vscode' { export interface NotebookCellMetadata { /** - * Controls if the content of a cell is editable or not. + * Controls whether a cell's editor is editable/readonly. */ editable?: boolean; @@ -1463,6 +1464,11 @@ declare module 'vscode' { readonly cell: NotebookCell; } + export interface NotebookEditorSelectionChangeEvent { + readonly notebookEditor: NotebookEditor; + readonly selection?: NotebookCell; + } + export interface NotebookCellData { readonly cellKind: CellKind; readonly source: string; @@ -1679,11 +1685,12 @@ declare module 'vscode' { */ export const notebookDocuments: ReadonlyArray; - export let visibleNotebookEditors: NotebookEditor[]; + export const visibleNotebookEditors: NotebookEditor[]; export const onDidChangeVisibleNotebookEditors: Event; - export let activeNotebookEditor: NotebookEditor | undefined; + export const activeNotebookEditor: NotebookEditor | undefined; export const onDidChangeActiveNotebookEditor: Event; + export const onDidChangeNotebookEditorSelection: Event; export const onDidChangeNotebookCells: Event; export const onDidChangeCellOutputs: Event; export const onDidChangeCellLanguage: Event; diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index fb2e5ddfeda1ab..639c613ad67fd8 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -232,6 +232,12 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(info => { this._proxy.$onDidChangeAuthenticationProviders([], [info]); })); + + this._proxy.$setProviders(this.authenticationService.declaredProviders); + + this._register(this.authenticationService.onDidChangeDeclaredProviders(e => { + this._proxy.$setProviders(e); + })); } $getProviderIds(): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts index 9bd40eeb40dadc..9351d89b4bdceb 100644 --- a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts @@ -29,10 +29,12 @@ import { CustomDocumentBackupData } from 'vs/workbench/contrib/customEditor/brow import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { CustomTextEditorModel } from 'vs/workbench/contrib/customEditor/common/customTextEditorModel'; import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -48,9 +50,10 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc private readonly _editorProviders = new Map(); constructor( + context: extHostProtocol.IExtHostContext, private readonly mainThreadWebview: MainThreadWebviews, private readonly mainThreadWebviewPanels: MainThreadWebviewPanels, - context: extHostProtocol.IExtHostContext, + @IExtensionService extensionService: IExtensionService, @IWorkingCopyService workingCopyService: IWorkingCopyService, @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, @ICustomEditorService private readonly _customEditorService: ICustomEditorService, @@ -63,7 +66,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc this._proxyCustomEditors = context.getProxy(extHostProtocol.ExtHostContext.ExtHostCustomEditors); - workingCopyFileService.registerWorkingCopyProvider((editorResource) => { + this._register(workingCopyFileService.registerWorkingCopyProvider((editorResource) => { const matchedWorkingCopies: IWorkingCopy[] = []; for (const workingCopy of workingCopyService.workingCopies) { @@ -74,7 +77,18 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc } } return matchedWorkingCopies; - }); + })); + + // This reviver's only job is to activate custom editor extensions. + this._register(_webviewWorkbenchService.registerResolver({ + canResolve: (webview: WebviewInput) => { + if (webview instanceof CustomEditorInput) { + extensionService.activateByEvent(`onCustomEditor:${webview.viewType}`); + } + return false; + }, + resolveWebview: () => { throw new Error('not implemented'); } + })); } dispose() { diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 91d4c528fde422..7b67595e6ac70a 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -17,7 +17,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, DisplayOrderKey, ICellEditOperation, IEditor, INotebookDocumentFilter, INotebookKernelInfo, INotebookKernelInfoDto, NotebookCellMetadata, NotebookCellOutputsSplice, NotebookDocumentMetadata, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, DisplayOrderKey, ICellEditOperation, IEditor, INotebookDocumentFilter, INotebookKernelInfo, NotebookCellMetadata, NotebookCellOutputsSplice, NotebookDocumentMetadata, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, MainContext, MainThreadNotebookShape, NotebookExtensionDescription } from '../common/extHost.protocol'; @@ -376,9 +376,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo // } } - async $registerNotebookProvider(_extension: NotebookExtensionDescription, _viewType: string, _supportBackup: boolean, _kernel: INotebookKernelInfoDto | undefined, options: { transientOutputs: boolean; transientMetadata: TransientMetadata }): Promise { + async $registerNotebookProvider(_extension: NotebookExtensionDescription, _viewType: string, _supportBackup: boolean, options: { transientOutputs: boolean; transientMetadata: TransientMetadata }): Promise { const controller: IMainNotebookController = { - kernel: _kernel, supportBackup: _supportBackup, options: options, reloadNotebook: async (mainthreadTextModel: NotebookTextModel) => { @@ -427,24 +426,12 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo resolveNotebookEditor: async (viewType: string, uri: URI, editorId: string) => { await this._proxy.$resolveNotebookEditor(viewType, uri, editorId); }, - executeNotebookByAttachedKernel: async (viewType: string, uri: URI) => { - return this.executeNotebookByAttachedKernel(viewType, uri, undefined); - }, - cancelNotebookByAttachedKernel: async (viewType: string, uri: URI) => { - return this.cancelNotebookByAttachedKernel(viewType, uri, undefined); - }, onDidReceiveMessage: (editorId: string, rendererType: string | undefined, message: unknown) => { this._proxy.$onDidReceiveMessage(editorId, rendererType, message); }, removeNotebookDocument: async (uri: URI) => { return this.removeNotebookTextModel(uri); }, - executeNotebookCell: async (uri: URI, handle: number) => { - return this.executeNotebookByAttachedKernel(_viewType, uri, handle); - }, - cancelNotebookCell: async (uri: URI, handle: number) => { - return this.cancelNotebookByAttachedKernel(_viewType, uri, handle); - }, save: async (uri: URI, token: CancellationToken) => { return this._proxy.$saveNotebook(_viewType, uri, token); }, @@ -567,16 +554,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo } } - async executeNotebookByAttachedKernel(viewType: string, uri: URI, handle: number | undefined): Promise { - this.logService.debug('MainthreadNotebooks#executeNotebookByAttachedKernel', uri.path, handle); - return this._proxy.$executeNotebookByAttachedKernel(viewType, uri, handle); - } - - async cancelNotebookByAttachedKernel(viewType: string, uri: URI, handle: number | undefined): Promise { - this.logService.debug('MainthreadNotebooks#cancelNotebookByAttachedKernel', uri.path, handle); - return this._proxy.$cancelNotebookByAttachedKernel(viewType, uri, handle); - } - async $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise { const editor = this._notebookService.getNotebookEditor(editorId) as INotebookEditor | undefined; if (editor?.isNotebookEditor) { diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index b64f47fad6d3b4..c5c03a867863ff 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -178,6 +178,10 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._linkProvider = undefined; } + public $registerProcessSupport(isSupported: boolean): void { + this._terminalService.registerProcessSupport(isSupported); + } + private _onActiveTerminalChanged(terminalId: number | null): void { this._proxy.$acceptActiveTerminalChanged(terminalId); } diff --git a/src/vs/workbench/api/browser/mainThreadWebviewManager.ts b/src/vs/workbench/api/browser/mainThreadWebviewManager.ts index abe953dd010c3a..225f15b104cef7 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewManager.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewManager.ts @@ -23,13 +23,13 @@ export class MainThreadWebviewManager extends Disposable { const webviews = this._register(instantiationService.createInstance(MainThreadWebviews, context)); context.set(extHostProtocol.MainContext.MainThreadWebviews, webviews); - const webviewPanels = this._register(instantiationService.createInstance(MainThreadWebviewPanels, webviews, context)); + const webviewPanels = this._register(instantiationService.createInstance(MainThreadWebviewPanels, context, webviews)); context.set(extHostProtocol.MainContext.MainThreadWebviewPanels, webviewPanels); - const customEditors = this._register(instantiationService.createInstance(MainThreadCustomEditors, webviews, webviewPanels, context)); + const customEditors = this._register(instantiationService.createInstance(MainThreadCustomEditors, context, webviews, webviewPanels)); context.set(extHostProtocol.MainContext.MainThreadCustomEditors, customEditors); - const webviewViews = this._register(instantiationService.createInstance(MainThreadWebviewsViews, webviews, context)); + const webviewViews = this._register(instantiationService.createInstance(MainThreadWebviewsViews, context, webviews)); context.set(extHostProtocol.MainContext.MainThreadWebviewViews, webviewViews); } } diff --git a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts index b089b04b6dda20..84107c980a3c3b 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts @@ -12,7 +12,6 @@ import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; import { IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; import { WebviewIcons } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; @@ -87,8 +86,8 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc private readonly _revivers = new Map(); constructor( - private readonly _mainThreadWebviews: MainThreadWebviews, context: extHostProtocol.IExtHostContext, + private readonly _mainThreadWebviews: MainThreadWebviews, @IExtensionService extensionService: IExtensionService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IEditorService private readonly _editorService: IEditorService, @@ -116,11 +115,6 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc // This should trigger the real reviver to be registered from the extension host side. this._register(_webviewWorkbenchService.registerResolver({ canResolve: (webview: WebviewInput) => { - if (webview instanceof CustomEditorInput) { - extensionService.activateByEvent(`onCustomEditor:${webview.viewType}`); - return false; - } - const viewType = this.webviewPanelViewType.toExternal(webview.viewType); if (typeof viewType === 'string') { extensionService.activateByEvent(`onWebviewPanel:${viewType}`); diff --git a/src/vs/workbench/api/browser/mainThreadWebviewViews.ts b/src/vs/workbench/api/browser/mainThreadWebviewViews.ts index df46e3c6e091a5..248af0860e6a57 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewViews.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewViews.ts @@ -13,19 +13,19 @@ import { IWebviewViewService, WebviewView } from 'vs/workbench/contrib/webviewVi export class MainThreadWebviewsViews extends Disposable implements extHostProtocol.MainThreadWebviewViewsShape { - private readonly _proxyViews: extHostProtocol.ExtHostWebviewViewsShape; + private readonly _proxy: extHostProtocol.ExtHostWebviewViewsShape; private readonly _webviewViews = new Map(); private readonly _webviewViewProviders = new Map(); constructor( - private readonly mainThreadWebviews: MainThreadWebviews, context: extHostProtocol.IExtHostContext, + private readonly mainThreadWebviews: MainThreadWebviews, @IWebviewViewService private readonly _webviewViewService: IWebviewViewService, ) { super(); - this._proxyViews = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviewViews); + this._proxy = context.getProxy(extHostProtocol.ExtHostContext.ExtHostWebviewViews); } public $setWebviewViewTitle(handle: extHostProtocol.WebviewHandle, value: string | undefined): void { @@ -62,16 +62,16 @@ export class MainThreadWebviewsViews extends Disposable implements extHostProtoc } webviewView.onDidChangeVisibility(visible => { - this._proxyViews.$onDidChangeWebviewViewVisibility(handle, visible); + this._proxy.$onDidChangeWebviewViewVisibility(handle, visible); }); webviewView.onDispose(() => { - this._proxyViews.$disposeWebviewView(handle); + this._proxy.$disposeWebviewView(handle); this._webviewViews.delete(handle); }); try { - await this._proxyViews.$resolveWebviewView(handle, viewType, state, cancellation); + await this._proxy.$resolveWebviewView(handle, viewType, state, cancellation); } catch (error) { onUnexpectedError(error); webviewView.webview.html = this.mainThreadWebviews.getWebviewResolvedFailedContent(viewType); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 1a7edca93702ca..d9943430885d40 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -977,6 +977,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostNotebook.onDidChangeNotebookCells(listener, thisArgs, disposables); }, + onDidChangeNotebookEditorSelection(listener, thisArgs?, disposables?) { + checkProposedApiEnabled(extension); + return extHostNotebook.onDidChangeNotebookEditorSelection(listener, thisArgs, disposables); + }, onDidChangeCellOutputs(listener, thisArgs?, disposables?) { checkProposedApiEnabled(extension); return extHostNotebook.onDidChangeCellOutputs(listener, thisArgs, disposables); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 6b001f1df780b1..2ff5e70e14a0fa 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -51,7 +51,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; -import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, INotebookKernelInfoDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { Dto } from 'vs/base/common/types'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; @@ -450,6 +450,7 @@ export interface MainThreadTerminalServiceShape extends IDisposable { $stopSendingDataEvents(): void; $startLinkProvider(): void; $stopLinkProvider(): void; + $registerProcessSupport(isSupported: boolean): void; $setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined): void; // Process @@ -722,7 +723,7 @@ export type NotebookCellOutputsSplice = [ export type INotebookCellStatusBarEntryDto = Dto; export interface MainThreadNotebookShape extends IDisposable { - $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, kernelInfoDto: INotebookKernelInfoDto | undefined, options: { transientOutputs: boolean; transientMetadata: TransientMetadata }): Promise; + $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, options: { transientOutputs: boolean; transientMetadata: TransientMetadata }): Promise; $onNotebookChange(viewType: string, resource: UriComponents): Promise; $unregisterNotebookProvider(viewType: string): Promise; $registerNotebookKernel(extension: NotebookExtensionDescription, id: string, label: string, selectors: (string | IRelativePattern)[], preloads: UriComponents[]): Promise; @@ -1055,6 +1056,7 @@ export interface ExtHostAuthenticationShape { $logout(id: string, sessionId: string): Promise; $onDidChangeAuthenticationSessions(id: string, label: string, event: modes.AuthenticationSessionsChangeEvent): Promise; $onDidChangeAuthenticationProviders(added: modes.AuthenticationProviderInformation[], removed: modes.AuthenticationProviderInformation[]): Promise; + $setProviders(providers: modes.AuthenticationProviderInformation[]): Promise; } export interface ExtHostSearchShape { @@ -1657,8 +1659,6 @@ export interface ExtHostNotebookShape { $resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise; $provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise; $resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise; - $executeNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise; - $cancelNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise; $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise; $cancelNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise; $executeNotebook2(kernelId: string, viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise; diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index da8a6820ca069d..c7dd0f2d5b2d32 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -28,6 +28,11 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication); } + $setProviders(providers: vscode.AuthenticationProviderInformation[]): Promise { + this._providers = providers; + return Promise.resolve(); + } + getProviderIds(): Promise> { return this._proxy.$getProviderIds(); } diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 17db2365cf1000..ae6d7860f6275b 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -862,7 +862,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private static _notebookKernelProviderHandlePool: number = 0; private readonly _proxy: MainThreadNotebookShape; - private readonly _notebookContentProviders = new Map(); + private readonly _notebookContentProviders = new Map(); private readonly _notebookKernels = new Map(); private readonly _notebookKernelProviders = new Map(); private readonly _documents = new ResourceMap(); @@ -870,6 +870,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private readonly _editors = new Map(); private readonly _webviewComm = new Map(); private readonly _commandsConverter: CommandsConverter; + private readonly _onDidChangeNotebookEditorSelection = new Emitter(); + readonly onDidChangeNotebookEditorSelection = this._onDidChangeNotebookEditorSelection.event; private readonly _onDidChangeNotebookCells = new Emitter(); readonly onDidChangeNotebookCells = this._onDidChangeNotebookCells.event; private readonly _onDidChangeCellOutputs = new Emitter(); @@ -948,7 +950,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN registerNotebookContentProvider( extension: IExtensionDescription, viewType: string, - provider: vscode.NotebookContentProvider & { kernel?: vscode.NotebookKernel }, + provider: vscode.NotebookContentProvider, options?: { transientOutputs: boolean; transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean }; @@ -959,10 +961,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN throw new Error(`Notebook provider for '${viewType}' already registered`); } - // if ((provider).executeCell) { - // throw new Error('NotebookContentKernel.executeCell is removed, please use vscode.notebook.registerNotebookKernel instead.'); - // } - this._notebookContentProviders.set(viewType, { extension, provider }); const listener = provider.onDidChangeNotebook @@ -984,7 +982,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN const supportBackup = !!provider.backupNotebook; - this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, supportBackup, provider.kernel ? { id: viewType, label: provider.kernel.label, extensionLocation: extension.extensionLocation, preloads: provider.kernel.preloads } : undefined, { transientOutputs: options?.transientOutputs || false, transientMetadata: options?.transientMetadata || {} }); + this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, viewType, supportBackup, { transientOutputs: options?.transientOutputs || false, transientMetadata: options?.transientMetadata || {} }); return new extHostTypes.Disposable(() => { listener.dispose(); @@ -1123,48 +1121,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN await provider.provider.resolveNotebook(document.notebookDocument, webComm.contentProviderComm); } - async $executeNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise { - const document = this._documents.get(URI.revive(uri)); - - if (!document) { - return; - } - - if (this._notebookContentProviders.has(viewType)) { - const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; - const provider = this._notebookContentProviders.get(viewType)!.provider; - - if (provider.kernel) { - if (cell) { - return withToken(token => (provider.kernel!.executeCell as any)(document, cell, token)); - } else { - return withToken(token => (provider.kernel!.executeAllCells as any)(document, token)); - } - } - } - } - - async $cancelNotebookByAttachedKernel(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise { - const document = this._documents.get(URI.revive(uri)); - - if (!document) { - return; - } - - if (this._notebookContentProviders.has(viewType)) { - const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; - const provider = this._notebookContentProviders.get(viewType)!.provider; - - if (provider.kernel) { - if (cell) { - return provider.kernel.cancelCellExecution(document.notebookDocument, cell.cell); - } else { - return provider.kernel.cancelAllCellsExecution(document.notebookDocument); - } - } - } - } - async $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise { await this._withAdapter(handle, uri, async (adapter, document) => { const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined; @@ -1331,6 +1287,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } else { editor.editor.selection = undefined; } + + this._onDidChangeNotebookEditorSelection.fire({ + notebookEditor: editor.editor, + selection: editor.editor.selection + }); } if (data.metadata) { diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index ad449efd0da740..7107003f8f7df1 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -339,6 +339,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ public get onDidWriteTerminalData(): Event { return this._onDidWriteTerminalData && this._onDidWriteTerminalData.event; } constructor( + supportsProcesses: boolean, @IExtHostRpcService extHostRpc: IExtHostRpcService ) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadTerminalService); @@ -347,6 +348,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ onFirstListenerAdd: () => this._proxy.$startSendingDataEvents(), onLastListenerRemove: () => this._proxy.$stopSendingDataEvents() }); + this._proxy.$registerProcessSupport(supportsProcesses); } public abstract createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal; @@ -805,6 +807,12 @@ export class EnvironmentVariableCollection implements vscode.EnvironmentVariable } export class WorkerExtHostTerminalService extends BaseExtHostTerminalService { + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService + ) { + super(false, extHostRpc); + } + public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { throw new NotSupportedError(); } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index c777688550a8fb..54d99efad93ec7 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -39,7 +39,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { @IExtHostDocumentsAndEditors private _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, @ILogService private _logService: ILogService ) { - super(extHostRpc); + super(true, extHostRpc); this._updateLastActiveWorkspace(); this._updateVariableResolver(); this._registerListeners(); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index dd1e2146344952..58e2817e7e0ac1 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1539,6 +1539,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi setPanelHidden(hidden: boolean, skipLayout?: boolean): void { this.state.panel.hidden = hidden; + // Return if not initialized fully #105480 + if (!this.workbenchGrid) { + return; + } + // Adjust CSS if (hidden) { addClass(this.container, Classes.PANEL_HIDDEN); @@ -1632,7 +1637,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.menuBar.visibility = visibility; // Layout - if (!skipLayout) { + if (!skipLayout && this.workbenchGrid) { this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); } } diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 14dca63bc68465..cc847e3c1e2976 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -26,7 +26,6 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IFileService, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; -import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { TabFocus } from 'vs/editor/common/config/commonEditorConfig'; @@ -43,7 +42,7 @@ import { ICodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { Schemas } from 'vs/base/common/network'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; +import { getIconClassesForModeId } from 'vs/editor/common/services/getIconClasses'; import { timeout } from 'vs/base/common/async'; import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { Event } from 'vs/base/common/event'; @@ -1042,7 +1041,6 @@ export class ChangeModeAction extends Action { actionId: string, actionLabel: string, @IModeService private readonly modeService: IModeService, - @IModelService private readonly modelService: IModelService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @@ -1069,26 +1067,27 @@ export class ChangeModeAction extends Action { } // Compute mode + let currentLanguageId: string | undefined; let currentModeId: string | undefined; - let modeId: string | undefined; if (textModel) { - modeId = textModel.getLanguageIdentifier().language; - currentModeId = withNullAsUndefined(this.modeService.getLanguageName(modeId)); + currentModeId = textModel.getLanguageIdentifier().language; + currentLanguageId = withNullAsUndefined(this.modeService.getLanguageName(currentModeId)); } // All languages are valid picks const languages = this.modeService.getRegisteredLanguageNames(); const picks: QuickPickInput[] = languages.sort().map((lang, index) => { + const modeId = this.modeService.getModeIdForLanguageName(lang.toLowerCase()) || 'unknown'; let description: string; - if (currentModeId === lang) { - description = nls.localize('languageDescription', "({0}) - Configured Language", this.modeService.getModeIdForLanguageName(lang.toLowerCase())); + if (currentLanguageId === lang) { + description = nls.localize('languageDescription', "({0}) - Configured Language", modeId); } else { - description = nls.localize('languageDescriptionConfigured', "({0})", this.modeService.getModeIdForLanguageName(lang.toLowerCase())); + description = nls.localize('languageDescriptionConfigured', "({0})", modeId); } return { label: lang, - iconClasses: getIconClasses(this.modelService, this.modeService, this.getFakeResource(lang)), + iconClasses: getIconClassesForModeId(modeId), description }; }); @@ -1109,7 +1108,7 @@ export class ChangeModeAction extends Action { picks.unshift(galleryAction); } - configureModeSettings = { label: nls.localize('configureModeSettings', "Configure '{0}' language based settings...", currentModeId) }; + configureModeSettings = { label: nls.localize('configureModeSettings', "Configure '{0}' language based settings...", currentLanguageId) }; picks.unshift(configureModeSettings); configureModeAssociations = { label: nls.localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext) }; picks.unshift(configureModeAssociations); @@ -1144,7 +1143,7 @@ export class ChangeModeAction extends Action { // User decided to configure settings for current language if (pick === configureModeSettings) { - this.preferencesService.openGlobalSettings(true, { editSetting: `[${withUndefinedAsNull(modeId)}]` }); + this.preferencesService.openGlobalSettings(true, { editSetting: `[${withUndefinedAsNull(currentModeId)}]` }); return; } @@ -1182,12 +1181,12 @@ export class ChangeModeAction extends Action { const languages = this.modeService.getRegisteredLanguageNames(); const picks: IQuickPickItem[] = languages.sort().map((lang, index) => { - const id = withNullAsUndefined(this.modeService.getModeIdForLanguageName(lang.toLowerCase())); + const id = withNullAsUndefined(this.modeService.getModeIdForLanguageName(lang.toLowerCase())) || 'unknown'; return { id, label: lang, - iconClasses: getIconClasses(this.modelService, this.modeService, this.getFakeResource(lang)), + iconClasses: getIconClassesForModeId(id), description: (id === currentAssociation) ? nls.localize('currentAssociation', "Current Association") : undefined }; }); @@ -1218,22 +1217,6 @@ export class ChangeModeAction extends Action { } }, 50 /* quick input is sensitive to being opened so soon after another */); } - - private getFakeResource(lang: string): URI | undefined { - let fakeResource: URI | undefined; - - const extensions = this.modeService.getExtensions(lang); - if (extensions?.length) { - fakeResource = URI.file(extensions[0]); - } else { - const filenames = this.modeService.getFilenames(lang); - if (filenames?.length) { - fakeResource = URI.file(filenames[0]); - } - } - - return fakeResource; - } } export interface IChangeEOLEntry extends IQuickPickItem { diff --git a/src/vs/workbench/browser/parts/views/media/paneviewlet.css b/src/vs/workbench/browser/parts/views/media/paneviewlet.css index 3adeb7731481b3..fea8488c9c1bc9 100644 --- a/src/vs/workbench/browser/parts/views/media/paneviewlet.css +++ b/src/vs/workbench/browser/parts/views/media/paneviewlet.css @@ -38,6 +38,26 @@ -webkit-margin-after: 0; } +.monaco-pane-view .pane > .pane-header .description { + display: block; + font-weight: normal; + margin-left: 10px; + opacity: 0.6; + overflow: hidden; + text-overflow: ellipsis; + text-transform: none; + white-space: nowrap; +} + +.monaco-pane-view .pane > .pane-header .description .codicon { + font-size: 9px; + margin-left: 2px; +} + +.monaco-pane-view .pane > .pane-header:not(.expanded) .description { + display: none; +} + .monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header h3.title, .monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header .description { display: none; diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 81a0bd9617a716..fb3ca8ac4746b3 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -184,6 +184,11 @@ export abstract class ViewPane extends Pane implements IView { return this._title; } + private _titleDescription: string | undefined; + public get titleDescription(): string | undefined { + return this._titleDescription; + } + private readonly menuActions: ViewMenuActions; private progressBar!: ProgressBar; private progressIndicator!: IProgressIndicator; @@ -192,6 +197,7 @@ export abstract class ViewPane extends Pane implements IView { private readonly showActionsAlways: boolean = false; private headerContainer?: HTMLElement; private titleContainer?: HTMLElement; + private titleDescriptionContainer?: HTMLElement; private iconContainer?: HTMLElement; protected twistiesContainer?: HTMLElement; @@ -216,6 +222,7 @@ export abstract class ViewPane extends Pane implements IView { this.id = options.id; this._title = options.title; + this._titleDescription = options.titleDescription; this.showActionsAlways = !!options.showActionsAlways; this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService); @@ -360,6 +367,11 @@ export abstract class ViewPane extends Pane implements IView { const calculatedTitle = this.calculateTitle(title); this.titleContainer = append(container, $('h3.title', undefined, calculatedTitle)); + + if (this._titleDescription) { + this.setTitleDescription(this._titleDescription, container); + } + this.iconContainer.title = calculatedTitle; this.iconContainer.setAttribute('aria-label', calculatedTitle); } @@ -379,6 +391,22 @@ export abstract class ViewPane extends Pane implements IView { this._onDidChangeTitleArea.fire(); } + private setTitleDescription(description: string | undefined, headerContainer: HTMLElement | undefined = this.headerContainer) { + if (this.titleDescriptionContainer) { + this.titleDescriptionContainer.textContent = description ?? ''; + } + else if (description && headerContainer) { + this.titleDescriptionContainer = append(headerContainer, $('span.description', undefined, description)); + } + } + + protected updateTitleDescription(description?: string | undefined): void { + this.setTitleDescription(description); + + this._titleDescription = description; + this._onDidChangeTitleArea.fire(); + } + private calculateTitle(title: string): string { const viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!; const model = this.viewDescriptorService.getViewContainerModel(viewContainer); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 502a12a626afa6..b0198b8aab404a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -904,6 +904,7 @@ export class ExtensionEditor extends EditorPane { this.renderViews(content, manifest, layout), this.renderLocalizations(content, manifest, layout), this.renderCustomEditors(content, manifest, layout), + this.renderAuthentication(content, manifest, layout), ]; scrollableContent.scanDomNode(); @@ -1151,6 +1152,32 @@ export class ExtensionEditor extends EditorPane { return true; } + private renderAuthentication(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { + const authentication = manifest.contributes?.authentication || []; + if (!authentication.length) { + return false; + } + + const details = $('details', { open: true, ontoggle: onDetailsToggle }, + $('summary', { tabindex: '0' }, localize('authentication', "Authentication ({0})", authentication.length)), + $('table', undefined, + $('tr', undefined, + $('th', undefined, localize('authentication.label', "Label")), + $('th', undefined, localize('authentication.id', "Id")) + ), + ...authentication.map(action => + $('tr', undefined, + $('td', undefined, action.label), + $('td', undefined, action.id) + ) + ) + ) + ); + + append(container, details); + return true; + } + private renderColorThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean { const contrib = manifest.contributes?.themes || []; if (!contrib.length) { diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 8f260d90da098e..e2f71dede95bc2 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -101,7 +101,7 @@ async function setupTest() { instantiationService.stub(IExtensionManagementServerService, new class extends ExtensionManagementServerService { #localExtensionManagementServer: IExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', id: 'vscode-local' }; constructor() { - super(instantiationService.get(ISharedProcessService), instantiationService, instantiationService.get(IRemoteAgentService), instantiationService.get(ILabelService)); + super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService), instantiationService.get(ILabelService), instantiationService.get(IExtensionGalleryService), instantiationService.get(IProductService), instantiationService.get(IConfigurationService), instantiationService.get(ILogService)); } get localExtensionManagementServer(): IExtensionManagementServer { return this.#localExtensionManagementServer; } set localExtensionManagementServer(server: IExtensionManagementServer) { } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index 6c077780df1138..a6c10ab0fc2f7c 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -45,6 +45,7 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe import { IMenuService } from 'vs/platform/actions/common/actions'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IProductService } from 'vs/platform/product/common/productService'; suite('ExtensionsListView Tests', () => { @@ -103,7 +104,7 @@ suite('ExtensionsListView Tests', () => { instantiationService.stub(IExtensionManagementServerService, new class extends ExtensionManagementServerService { #localExtensionManagementServer: IExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', id: 'vscode-local' }; constructor() { - super(instantiationService.get(ISharedProcessService), instantiationService, instantiationService.get(IRemoteAgentService), instantiationService.get(ILabelService)); + super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService), instantiationService.get(ILabelService), instantiationService.get(IExtensionGalleryService), instantiationService.get(IProductService), instantiationService.get(IConfigurationService), instantiationService.get(ILogService)); } get localExtensionManagementServer(): IExtensionManagementServer { return this.#localExtensionManagementServer; } set localExtensionManagementServer(server: IExtensionManagementServer) { } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts index 814dc3a2527599..9d888af88b8156 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts @@ -9,15 +9,15 @@ import * as DOM from 'vs/base/browser/dom'; import { CellFoldingState, FoldingModel } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; -import { registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { MenuRegistry, MenuId, ICommandAction } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { getActiveNotebookEditor, NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import { localize } from 'vs/nls'; +import { FoldingRegion } from 'vs/editor/contrib/folding/foldingRanges'; export class FoldingController extends Disposable implements INotebookEditorContribution { static id: string = 'workbench.notebook.findController'; @@ -65,19 +65,31 @@ export class FoldingController extends Disposable implements INotebookEditorCont this._updateEditorFoldingRanges(); } - setFoldingState(index: number, state: CellFoldingState) { - if (!this._foldingModel) { - return; + setFoldingStateDown(index: number, state: CellFoldingState, levels: number) { + const doCollapse = state === CellFoldingState.Collapsed; + let region = this._foldingModel!.getRegionAtLine(index + 1); + let regions: FoldingRegion[] = []; + if (region) { + if (region.isCollapsed !== doCollapse) { + regions.push(region); + } + if (levels > 1) { + let regionsInside = this._foldingModel!.getRegionsInside(region, (r, level: number) => r.isCollapsed !== doCollapse && level < levels); + regions.push(...regionsInside); + } } - const range = this._foldingModel.regions.findRange(index + 1); - const startIndex = this._foldingModel.regions.getStartLineNumber(range) - 1; + regions.forEach(r => this._foldingModel!.setCollapsed(r.regionIndex, state === CellFoldingState.Collapsed)); + this._updateEditorFoldingRanges(); + } - if (startIndex !== index) { + setFoldingStateUp(index: number, state: CellFoldingState, levels: number) { + if (!this._foldingModel) { return; } - this._foldingModel.setCollapsed(range, state === CellFoldingState.Collapsed); + let regions = this._foldingModel.getAllRegionsAtLine(index + 1, (region, level) => region.isCollapsed !== (state === CellFoldingState.Collapsed) && level <= levels); + regions.forEach(r => this._foldingModel!.setCollapsed(r.regionIndex, state === CellFoldingState.Collapsed)); this._updateEditorFoldingRanges(); } @@ -121,7 +133,7 @@ export class FoldingController extends Disposable implements INotebookEditorCont return; } - this.setFoldingState(modelIndex, state === CellFoldingState.Collapsed ? CellFoldingState.Expanded : CellFoldingState.Collapsed); + this.setFoldingStateUp(modelIndex, state === CellFoldingState.Collapsed ? CellFoldingState.Expanded : CellFoldingState.Collapsed, 1); } return; @@ -130,28 +142,47 @@ export class FoldingController extends Disposable implements INotebookEditorCont registerNotebookContribution(FoldingController.id, FoldingController); -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'notebook.fold', - title: { value: localize('fold.cell', "Fold Cell"), original: 'Fold Cell' }, - category: NOTEBOOK_ACTIONS_CATEGORY, - keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET, - mac: { - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_OPEN_SQUARE_BRACKET, - secondary: [KeyCode.LeftArrow], - }, - secondary: [KeyCode.LeftArrow], - weight: KeybindingWeight.WorkbenchContrib - }, - precondition: NOTEBOOK_IS_ACTIVE_EDITOR, - f1: true - }); - } - - async run(accessor: ServicesAccessor): Promise { +const NOTEBOOK_UNFOLD_COMMAND_ID = 'notebook.unfold'; +const NOTEBOOK_UNFOLD_COMMAND_LABEL = localize('unfold.cell', "Unfold Cell"); +const NOTEBOOK_FOLD_COMMAND_ID = 'notebook.fold'; +const NOTEBOOK_FOLD_COMMAND_LABEL = localize('fold.cell', "Fold Cell"); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + weight: KeybindingWeight.WorkbenchContrib, + when: null, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET, + mac: { + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_CLOSE_SQUARE_BRACKET, + secondary: [KeyCode.RightArrow], + }, + secondary: [KeyCode.RightArrow], + id: NOTEBOOK_UNFOLD_COMMAND_ID, + description: { + description: NOTEBOOK_UNFOLD_COMMAND_LABEL, + args: [ + { + name: 'index', description: 'The cell index', schema: { + 'type': 'object', + 'required': ['index', 'direction'], + 'properties': { + 'index': { + 'type': 'number' + }, + 'direction': { + 'type': 'string', + 'enum': ['up', 'down'], + 'default': 'down' + }, + 'levels': { + 'type': 'number', + 'default': 1 + }, + } + } + } + ] + }, + handler: async (accessor, args?: { index: number, levels: number, direction: 'up' | 'down' }) => { const editorService = accessor.get(IEditorService); const editor = getActiveNotebookEditor(editorService); @@ -159,43 +190,78 @@ registerAction2(class extends Action2 { return; } - const activeCell = editor.getActiveCell(); - if (!activeCell) { - return; + const levels = args && args.levels || 1; + const direction = args && args.direction === 'up' ? 'up' : 'down'; + let index: number | undefined = undefined; + + if (args) { + index = args.index; + } else { + const activeCell = editor.getActiveCell(); + if (!activeCell) { + return; + } + index = editor.viewModel?.viewCells.indexOf(activeCell); } const controller = editor.getContribution(FoldingController.id); - - const index = editor.viewModel?.viewCells.indexOf(activeCell); - if (index !== undefined) { - controller.setFoldingState(index, CellFoldingState.Collapsed); + if (direction === 'up') { + controller.setFoldingStateUp(index, CellFoldingState.Expanded, levels); + } else { + controller.setFoldingStateDown(index, CellFoldingState.Expanded, levels); + } } } }); -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'notebook.unfold', - title: { value: localize('unfold.cell', "Unfold Cell"), original: 'Unfold Cell' }, - category: NOTEBOOK_ACTIONS_CATEGORY, - keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET, - mac: { - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_CLOSE_SQUARE_BRACKET, - secondary: [KeyCode.RightArrow], - }, - secondary: [KeyCode.RightArrow], - weight: KeybindingWeight.WorkbenchContrib - }, - precondition: NOTEBOOK_IS_ACTIVE_EDITOR, - f1: true - }); - } - - async run(accessor: ServicesAccessor): Promise { +const unfoldCommand: ICommandAction = { + id: NOTEBOOK_UNFOLD_COMMAND_ID, + title: { value: NOTEBOOK_UNFOLD_COMMAND_LABEL, original: 'Unfold Cell' }, + category: NOTEBOOK_ACTIONS_CATEGORY, + precondition: NOTEBOOK_IS_ACTIVE_EDITOR +}; + +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: unfoldCommand, when: unfoldCommand.precondition }); +MenuRegistry.addCommand(unfoldCommand); + + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET, + mac: { + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_OPEN_SQUARE_BRACKET, + secondary: [KeyCode.LeftArrow], + }, + secondary: [KeyCode.LeftArrow], + weight: KeybindingWeight.WorkbenchContrib, + id: NOTEBOOK_FOLD_COMMAND_ID, + description: { + description: NOTEBOOK_FOLD_COMMAND_LABEL, + args: [ + { + name: 'index', description: 'The cell index', schema: { + 'type': 'object', + 'required': ['index', 'direction'], + 'properties': { + 'index': { + 'type': 'number' + }, + 'direction': { + 'type': 'string', + 'enum': ['up', 'down'], + 'default': 'down' + }, + 'levels': { + 'type': 'number', + 'default': 1 + }, + } + } + } + ] + }, + handler: async (accessor, args?: { index: number, levels: number, direction: 'up' | 'down' }) => { const editorService = accessor.get(IEditorService); const editor = getActiveNotebookEditor(editorService); @@ -203,17 +269,37 @@ registerAction2(class extends Action2 { return; } - const activeCell = editor.getActiveCell(); - if (!activeCell) { - return; + const levels = args && args.levels || 1; + const direction = args && args.direction === 'up' ? 'up' : 'down'; + let index: number | undefined = undefined; + + if (args) { + index = args.index; + } else { + const activeCell = editor.getActiveCell(); + if (!activeCell) { + return; + } + index = editor.viewModel?.viewCells.indexOf(activeCell); } const controller = editor.getContribution(FoldingController.id); - - const index = editor.viewModel?.viewCells.indexOf(activeCell); - if (index !== undefined) { - controller.setFoldingState(index, CellFoldingState.Expanded); + if (direction === 'up') { + controller.setFoldingStateUp(index, CellFoldingState.Collapsed, levels); + } else { + controller.setFoldingStateDown(index, CellFoldingState.Collapsed, levels); + } } } }); + +const foldCommand: ICommandAction = { + id: NOTEBOOK_FOLD_COMMAND_ID, + title: { value: NOTEBOOK_FOLD_COMMAND_LABEL, original: 'Fold Cell' }, + category: NOTEBOOK_ACTIONS_CATEGORY, + precondition: NOTEBOOK_IS_ACTIVE_EDITOR +}; + +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: foldCommand, when: foldCommand.precondition }); +MenuRegistry.addCommand(foldCommand); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts index 18f853b05e5093..fade3589aa0023 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts @@ -6,12 +6,16 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { TrackedRangeStickiness } from 'vs/editor/common/model'; -import { FoldingRegions } from 'vs/editor/contrib/folding/foldingRanges'; +import { FoldingRegion, FoldingRegions } from 'vs/editor/contrib/folding/foldingRanges'; import { IFoldingRangeData, sanitizeRanges } from 'vs/editor/contrib/folding/syntaxRangeProvider'; import { ICellRange } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +type RegionFilter = (r: FoldingRegion) => boolean; +type RegionFilterWithLevel = (r: FoldingRegion, level: number) => boolean; + + export class FoldingModel extends Disposable { private _viewModel: NotebookViewModel | null = null; private _viewModelStore = new DisposableStore(); @@ -73,7 +77,70 @@ export class FoldingModel extends Disposable { this.recompute(); } - public setCollapsed(index: number, newState: boolean) { + getRegionAtLine(lineNumber: number): FoldingRegion | null { + if (this._regions) { + let index = this._regions.findRange(lineNumber); + if (index >= 0) { + return this._regions.toRegion(index); + } + } + return null; + } + + getRegionsInside(region: FoldingRegion | null, filter?: RegionFilter | RegionFilterWithLevel): FoldingRegion[] { + let result: FoldingRegion[] = []; + let index = region ? region.regionIndex + 1 : 0; + let endLineNumber = region ? region.endLineNumber : Number.MAX_VALUE; + + if (filter && filter.length === 2) { + const levelStack: FoldingRegion[] = []; + for (let i = index, len = this._regions.length; i < len; i++) { + let current = this._regions.toRegion(i); + if (this._regions.getStartLineNumber(i) < endLineNumber) { + while (levelStack.length > 0 && !current.containedBy(levelStack[levelStack.length - 1])) { + levelStack.pop(); + } + levelStack.push(current); + if (filter(current, levelStack.length)) { + result.push(current); + } + } else { + break; + } + } + } else { + for (let i = index, len = this._regions.length; i < len; i++) { + let current = this._regions.toRegion(i); + if (this._regions.getStartLineNumber(i) < endLineNumber) { + if (!filter || (filter as RegionFilter)(current)) { + result.push(current); + } + } else { + break; + } + } + } + return result; + } + + getAllRegionsAtLine(lineNumber: number, filter?: (r: FoldingRegion, level: number) => boolean): FoldingRegion[] { + let result: FoldingRegion[] = []; + if (this._regions) { + let index = this._regions.findRange(lineNumber); + let level = 1; + while (index >= 0) { + let current = this._regions.toRegion(index); + if (!filter || filter(current, level)) { + result.push(current); + } + level++; + index = current.parentIndex; + } + } + return result; + } + + setCollapsed(index: number, newState: boolean) { this._regions.setCollapsed(index, newState); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts index bb6be90bb179b8..dca894ed1b953c 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts @@ -76,27 +76,6 @@ registerAction2(class extends Action2 { }; }); - const provider = notebookService.getContributedNotebookProviders(editor.viewModel!.uri)[0]; - - if (provider.kernel) { - picks.unshift({ - id: provider.id, - label: provider.displayName, - picked: !activeKernel, // no active kernel, the builtin kernel of the provider is used - description: activeKernel === undefined - ? nls.localize('currentActiveBuiltinKernel', " (Currently Active)") - : '', - kernelProviderId: provider.providerExtensionId, - run: () => { - editor.activeKernel = undefined; - }, - buttons: [{ - iconClass: 'codicon-settings-gear', - tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", editor.viewModel!.viewType) - }] - }); - } - const picker = quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>(); picker.items = picks; picker.activeItems = picks.filter(pick => (pick as IQuickPickItem).picked) as (IQuickPickItem & { run(): void; kernelProviderId?: string; })[]; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts index 868c3ab9a8ecf5..d831793d8b5151 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts @@ -17,8 +17,17 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { format } from 'vs/base/common/jsonFormatter'; import { applyEdits } from 'vs/base/common/jsonEdit'; -import { NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellUri, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { hash } from 'vs/base/common/hash'; +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'; +import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IAction } from 'vs/base/common/actions'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; const fixedEditorOptions: IEditorOptions = { padding: { @@ -61,6 +70,8 @@ const fixedDiffEditorOptions: IDiffEditorOptions = { class PropertyHeader extends Disposable { protected _foldingIndicator!: HTMLElement; protected _statusSpan!: HTMLElement; + protected _toolbar!: ToolBar; + protected _menu!: IMenu; constructor( readonly cell: CellDiffViewModel, @@ -74,7 +85,13 @@ class PropertyHeader extends Disposable { unChangedLabel: string; changedLabel: string; prefix: string; - } + menuId: MenuId; + }, + @IContextMenuService readonly contextMenuService: IContextMenuService, + @IKeybindingService readonly keybindingService: IKeybindingService, + @INotificationService readonly notificationService: INotificationService, + @IMenuService readonly menuService: IMenuService, + @IContextKeyService readonly contextKeyService: IContextKeyService ) { super(); } @@ -83,8 +100,6 @@ class PropertyHeader extends Disposable { let metadataChanged = this.accessor.checkIfModified(this.cell); this._foldingIndicator = DOM.append(this.metadataHeaderContainer, DOM.$('.property-folding-indicator')); DOM.addClass(this._foldingIndicator, this.accessor.prefix); - - this._updateFoldingIcon(); const metadataStatus = DOM.append(this.metadataHeaderContainer, DOM.$('div.property-status')); this._statusSpan = DOM.append(metadataStatus, DOM.$('span')); @@ -97,6 +112,29 @@ class PropertyHeader extends Disposable { this._statusSpan.textContent = this.accessor.unChangedLabel; } + const cellToolbarContainer = DOM.append(this.metadataHeaderContainer, DOM.$('div.property-toolbar')); + this._toolbar = new ToolBar(cellToolbarContainer, this.contextMenuService, { + actionViewItemProvider: action => { + if (action instanceof MenuItemAction) { + const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + return item; + } + + return undefined; + } + }); + this._toolbar.context = { + cell: this.cell + }; + + this._menu = this.menuService.createMenu(this.accessor.menuId, this.contextKeyService); + + if (metadataChanged) { + const actions: IAction[] = []; + createAndFillInActionBarActions(this._menu, { shouldForwardArgs: true }, actions); + this._toolbar.setActions(actions); + } + this._register(this.notebookEditor.onMouseUp(e => { if (!e.event.target) { return; @@ -138,6 +176,22 @@ class PropertyHeader extends Disposable { this.accessor.updateInfoRendering(); } + refresh() { + let metadataChanged = this.accessor.checkIfModified(this.cell); + if (metadataChanged) { + this._statusSpan.textContent = this.accessor.changedLabel; + this._statusSpan.style.fontWeight = 'bold'; + DOM.addClass(this.metadataHeaderContainer, 'modified'); + const actions: IAction[] = []; + createAndFillInActionBarActions(this._menu, undefined, actions); + this._toolbar.setActions(actions); + } else { + this._statusSpan.textContent = this.accessor.unChangedLabel; + this._statusSpan.style.fontWeight = 'normal'; + this._toolbar.setActions([]); + } + } + private _updateFoldingIcon() { if (this.accessor.getFoldingState(this.cell) === PropertyFoldingState.Collapsed) { this._foldingIndicator.innerHTML = renderCodicons('$(chevron-right)'); @@ -229,14 +283,15 @@ abstract class AbstractCellRenderer extends Disposable { this._metadataHeaderContainer = DOM.append(this._diffEditorContainer, DOM.$('.metadata-header-container')); this._metadataInfoContainer = DOM.append(this._diffEditorContainer, DOM.$('.metadata-info-container')); - this._metadataHeader = new PropertyHeader( + this._metadataHeader = this.instantiationService.createInstance( + PropertyHeader, this.cell, this._metadataHeaderContainer, this.notebookEditor, { updateInfoRendering: this.updateMetadataRendering.bind(this), checkIfModified: (cell) => { - return cell.type === 'modified' && hash(this._getFormatedMetadataJSON(cell.original?.metadata || {})) !== hash(this._getFormatedMetadataJSON(cell.modified?.metadata ?? {})); + return hash(this._getFormatedMetadataJSON(cell.original?.metadata || {}, cell.original?.language)) !== hash(this._getFormatedMetadataJSON(cell.modified?.metadata ?? {}, cell.modified?.language)); }, getFoldingState: (cell) => { return cell.metadataFoldingState; @@ -246,7 +301,8 @@ abstract class AbstractCellRenderer extends Disposable { }, unChangedLabel: 'Metadata', changedLabel: 'Metadata changed', - prefix: 'metadata' + prefix: 'metadata', + menuId: MenuId.NotebookDiffCellMetadataTitle } ); this._register(this._metadataHeader); @@ -255,7 +311,8 @@ abstract class AbstractCellRenderer extends Disposable { this._outputHeaderContainer = DOM.append(this._diffEditorContainer, DOM.$('.output-header-container')); this._outputInfoContainer = DOM.append(this._diffEditorContainer, DOM.$('.output-info-container')); - this._outputHeader = new PropertyHeader( + this._outputHeader = this.instantiationService.createInstance( + PropertyHeader, this.cell, this._outputHeaderContainer, this.notebookEditor, @@ -272,7 +329,8 @@ abstract class AbstractCellRenderer extends Disposable { }, unChangedLabel: 'Outputs', changedLabel: 'Outputs changed', - prefix: 'output' + prefix: 'output', + menuId: MenuId.NotebookDiffCellOutputsTitle } ); this._register(this._outputHeader); @@ -310,7 +368,6 @@ abstract class AbstractCellRenderer extends Disposable { this._outputEditorContainer = DOM.append(this._outputInfoContainer, DOM.$('.output-editor-container')); this._buildOutputEditor(); } else { - console.log(this.cell); this._layoutInfo.outputHeight = this._outputEditor.getContentHeight(); this.layout({ outputEditor: true }); } @@ -323,21 +380,7 @@ abstract class AbstractCellRenderer extends Disposable { } protected _getFormatedMetadataJSON(metadata: NotebookCellMetadata, language?: string) { - let filteredMetadata: { [key: string]: any } = {}; - if (this.notebookEditor.textModel) { - const transientMetadata = this.notebookEditor.textModel!.transientOptions.transientMetadata; - - const keys = new Set([...Object.keys(metadata)]); - for (let key of keys) { - if (!(transientMetadata[key as keyof NotebookCellMetadata]) - ) { - filteredMetadata[key] = metadata[key as keyof NotebookCellMetadata]; - } - } - } else { - filteredMetadata = metadata; - } - + const filteredMetadata: { [key: string]: any } = metadata; const content = JSON.stringify({ language, ...filteredMetadata @@ -349,39 +392,124 @@ abstract class AbstractCellRenderer extends Disposable { return metadataSource; } + private _applySanitizedMetadataChanges(currentMetadata: NotebookCellMetadata, newMetadata: any) { + let result: { [key: string]: any } = {}; + let newLangauge: string | undefined = undefined; + try { + const newMetadataObj = JSON.parse(newMetadata); + const keys = new Set([...Object.keys(newMetadataObj)]); + for (let key of keys) { + switch (key as keyof NotebookCellMetadata) { + case 'breakpointMargin': + case 'editable': + case 'hasExecutionOrder': + case 'inputCollapsed': + case 'outputCollapsed': + case 'runnable': + // boolean + if (typeof newMetadataObj[key] === 'boolean') { + result[key] = newMetadataObj[key]; + } else { + result[key] = currentMetadata[key as keyof NotebookCellMetadata]; + } + break; + + case 'executionOrder': + case 'lastRunDuration': + // number + if (typeof newMetadataObj[key] === 'number') { + result[key] = newMetadataObj[key]; + } else { + result[key] = currentMetadata[key as keyof NotebookCellMetadata]; + } + break; + case 'runState': + // enum + if (typeof newMetadataObj[key] === 'number' && [1, 2, 3, 4].indexOf(newMetadataObj[key]) >= 0) { + result[key] = newMetadataObj[key]; + } else { + result[key] = currentMetadata[key as keyof NotebookCellMetadata]; + } + break; + case 'statusMessage': + // string + if (typeof newMetadataObj[key] === 'string') { + result[key] = newMetadataObj[key]; + } else { + result[key] = currentMetadata[key as keyof NotebookCellMetadata]; + } + break; + default: + if (key === 'language') { + newLangauge = newMetadataObj[key]; + } + result[key] = newMetadataObj[key]; + break; + } + } + + if (newLangauge !== undefined && newLangauge !== this.cell.modified!.language) { + this.notebookEditor.textModel!.changeCellLanguage(this.cell.modified!.handle, newLangauge); + } + this.notebookEditor.textModel!.changeCellMetadata(this.cell.modified!.handle, result, false); + } catch { + } + } + private _buildMetadataEditor() { - if (this.cell.type === 'modified') { + if (this.cell.type === 'modified' || this.cell.type === 'unchanged') { const originalMetadataSource = this._getFormatedMetadataJSON(this.cell.original?.metadata || {}, this.cell.original?.language); const modifiedMetadataSource = this._getFormatedMetadataJSON(this.cell.modified?.metadata || {}, this.cell.modified?.language); - if (originalMetadataSource !== modifiedMetadataSource) { - this._metadataEditor = this.instantiationService.createInstance(DiffEditorWidget, this._metadataEditorContainer!, { - ...fixedDiffEditorOptions, - overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), - readOnly: true - }); + this._metadataEditor = this.instantiationService.createInstance(DiffEditorWidget, this._metadataEditorContainer!, { + ...fixedDiffEditorOptions, + overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), + readOnly: false, + originalEditable: false + }); - DOM.addClass(this._metadataEditorContainer!, 'diff'); + DOM.addClass(this._metadataEditorContainer!, 'diff'); - const mode = this.modeService.create('json'); - const originalMetadataModel = this.modelService.createModel(originalMetadataSource, mode, undefined, true); - const modifiedMetadataModel = this.modelService.createModel(modifiedMetadataSource, mode, undefined, true); - this._metadataEditor.setModel({ - original: originalMetadataModel, - modified: modifiedMetadataModel - }); + const mode = this.modeService.create('json'); + const originalMetadataModel = this.modelService.createModel(originalMetadataSource, mode, CellUri.generateCellMetadataUri(this.cell.original!.uri, this.cell.original!.handle), false); + const modifiedMetadataModel = this.modelService.createModel(modifiedMetadataSource, mode, CellUri.generateCellMetadataUri(this.cell.modified!.uri, this.cell.modified!.handle), false); + this._metadataEditor.setModel({ + original: originalMetadataModel, + modified: modifiedMetadataModel + }); - this._layoutInfo.metadataHeight = this._metadataEditor.getContentHeight(); - this.layout({ metadataEditor: true }); + this._register(originalMetadataModel); + this._register(modifiedMetadataModel); - this._register(this._metadataEditor.onDidContentSizeChange((e) => { - if (e.contentHeightChanged && this.cell.metadataFoldingState === PropertyFoldingState.Expanded) { - this._layoutInfo.metadataHeight = e.contentHeight; - this.layout({ metadataEditor: true }); - } - })); + this._layoutInfo.metadataHeight = this._metadataEditor.getContentHeight(); + this.layout({ metadataEditor: true }); - return; - } + this._register(this._metadataEditor.onDidContentSizeChange((e) => { + if (e.contentHeightChanged && this.cell.metadataFoldingState === PropertyFoldingState.Expanded) { + this._layoutInfo.metadataHeight = e.contentHeight; + this.layout({ metadataEditor: true }); + } + })); + + let respondingToContentChange = false; + + this._register(modifiedMetadataModel.onDidChangeContent(() => { + respondingToContentChange = true; + const value = modifiedMetadataModel.getValue(); + this._applySanitizedMetadataChanges(this.cell.modified!.metadata, value); + this._metadataHeader.refresh(); + respondingToContentChange = false; + })); + + this._register(this.cell.modified!.onDidChangeMetadata(() => { + if (respondingToContentChange) { + return; + } + + const modifiedMetadataSource = this._getFormatedMetadataJSON(this.cell.modified?.metadata || {}, this.cell.modified?.language); + modifiedMetadataModel.setValue(modifiedMetadataSource); + })); + + return; } this._metadataEditor = this.instantiationService.createInstance(CodeEditorWidget, this._metadataEditorContainer!, { @@ -390,16 +518,26 @@ abstract class AbstractCellRenderer extends Disposable { width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, true), height: 0 }, - overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode() + overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode(), + readOnly: false }, {}); - const mode = this.modeService.create('json'); + const mode = this.modeService.create('jsonc'); const originalMetadataSource = this._getFormatedMetadataJSON( this.cell.type === 'insert' ? this.cell.modified!.metadata || {} : this.cell.original!.metadata || {}); - const metadataModel = this.modelService.createModel(originalMetadataSource, mode, undefined, true); + const uri = this.cell.type === 'insert' + ? this.cell.modified!.uri + : this.cell.original!.uri; + const handle = this.cell.type === 'insert' + ? this.cell.modified!.handle + : this.cell.original!.handle; + + const modelUri = CellUri.generateCellMetadataUri(uri, handle); + const metadataModel = this.modelService.createModel(originalMetadataSource, mode, modelUri, false); this._metadataEditor.setModel(metadataModel); + this._register(metadataModel); this._layoutInfo.metadataHeight = this._metadataEditor.getContentHeight(); this.layout({ metadataEditor: true }); @@ -422,7 +560,7 @@ abstract class AbstractCellRenderer extends Disposable { } private _buildOutputEditor() { - if (this.cell.type === 'modified' && !this.notebookEditor.textModel!.transientOptions.transientOutputs) { + 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 || []); if (originalOutputsSource !== modifiedOutputsSource) { @@ -452,6 +590,12 @@ abstract class AbstractCellRenderer extends Disposable { } })); + this._register(this.cell.modified!.onDidChangeOutputs(() => { + const modifiedOutputsSource = this._getFormatedOutputJSON(this.cell.modified?.outputs || []); + modifiedModel.setValue(modifiedOutputsSource); + this._outputHeader.refresh(); + })); + return; } } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css index dda6833c78895a..113ad130dae665 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css @@ -79,6 +79,18 @@ cursor: pointer; } +.notebook-text-diff-editor .cell-diff-editor-container .output-header-container, +.notebook-text-diff-editor .cell-diff-editor-container .metadata-header-container { + display: flex; + flex-direction: row; + align-items: center; +} + +.notebook-text-diff-editor .cell-diff-editor-container .output-header-container .property-toolbar, +.notebook-text-diff-editor .cell-diff-editor-container .metadata-header-container .property-toolbar { + margin-left: auto; +} + .notebook-text-diff-editor .cell-diff-editor-container .output-header-container .property-status, .notebook-text-diff-editor .cell-diff-editor-container .metadata-header-container .property-status { font-size: 12px; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts index 2ffd15bb0c2e90..856b0aae9bf195 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffActions.ts @@ -8,6 +8,7 @@ import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/act import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; import { ActiveEditorContext } from 'vs/workbench/common/editor'; +import { CellDiffViewModel } from 'vs/workbench/contrib/notebook/browser/diff/celllDiffViewModel'; import { NotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor'; import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditorInput'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -48,3 +49,63 @@ registerAction2(class extends Action2 { } } }); + +registerAction2(class extends Action2 { + constructor() { + super( + { + id: 'notebook.diff.cell.revertMetadata', + title: localize('notebook.diff.cell.revertMetadata', "Revert Metadata"), + icon: { id: 'codicon/discard' }, + f1: false, + menu: { + id: MenuId.NotebookDiffCellMetadataTitle + } + } + ); + } + run(accessor: ServicesAccessor, context?: { cell: CellDiffViewModel }) { + if (!context) { + return; + } + + const original = context.cell.original; + const modified = context.cell.modified; + + if (!original || !modified) { + return; + } + + modified.metadata = original.metadata; + } +}); + +registerAction2(class extends Action2 { + constructor() { + super( + { + id: 'notebook.diff.cell.revertOutputs', + title: localize('notebook.diff.cell.revertOutputs', "Revert Outputs"), + icon: { id: 'codicon/discard' }, + f1: false, + menu: { + id: MenuId.NotebookDiffCellOutputsTitle + } + } + ); + } + run(accessor: ServicesAccessor, context?: { cell: CellDiffViewModel }) { + if (!context) { + return; + } + + const original = context.cell.original; + const modified = context.cell.modified; + + if (!original || !modified) { + return; + } + + modified.spliceNotebookCellOutputs([[0, modified.outputs.length, original.outputs]]); + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 74df84fbeb1828..828c1b46615d2c 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 { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl'; -import { CellKind, CellToolbarLocKey, CellUri, DisplayOrderKey, getCellUndoRedoComparisonKey, NotebookDocumentBackupData, NotebookEditorPriority, ShowCellStatusbarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellToolbarLocKey, CellUri, DisplayOrderKey, getCellUndoRedoComparisonKey, NotebookDocumentBackupData, NotebookEditorPriority, NotebookTextDiffEditorPreview, ShowCellStatusbarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; @@ -48,6 +48,10 @@ import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/comm import { NotebookEditorWorkerServiceImpl } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; import { NotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/browser/notebookCellStatusBarServiceImpl'; +import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { Event } from 'vs/base/common/event'; // Editor Contribution @@ -247,7 +251,7 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri return undefined; } - if (originalInput instanceof DiffEditorInput && this.configurationService.getValue('notebook.diff.enablePreview')) { + if (originalInput instanceof DiffEditorInput && this.configurationService.getValue(NotebookTextDiffEditorPreview)) { return this._handleDiffEditorInput(originalInput, options, group); } @@ -440,9 +444,115 @@ class CellContentProvider implements ITextModelContentProvider { } } +class RegisterSchemasContribution extends Disposable implements IWorkbenchContribution { + constructor() { + super(); + this.registerMetadataSchemas(); + } + + private registerMetadataSchemas(): void { + const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); + const metadataSchema: IJSONSchema = { + properties: { + ['language']: { + type: 'string', + description: 'The language for the cell' + }, + ['editable']: { + type: 'boolean', + description: `Controls whether a cell's editor is editable/readonly` + }, + ['runnable']: { + type: 'boolean', + description: 'Controls if the cell is executable' + }, + ['breakpointMargin']: { + type: 'boolean', + description: 'Controls if the cell has a margin to support the breakpoint UI' + }, + ['hasExecutionOrder']: { + type: 'boolean', + description: 'Whether the execution order indicator will be displayed' + }, + ['executionOrder']: { + type: 'number', + description: 'The order in which this cell was executed' + }, + ['statusMessage']: { + type: 'string', + description: `A status message to be shown in the cell's status bar` + }, + ['runState']: { + type: 'integer', + description: `The cell's current run state` + }, + ['runStartTime']: { + type: 'number', + description: 'If the cell is running, the time at which the cell started running' + }, + ['lastRunDuration']: { + type: 'number', + description: `The total duration of the cell's last run` + }, + ['inputCollapsed']: { + type: 'boolean', + description: `Whether a code cell's editor is collapsed` + }, + ['outputCollapsed']: { + type: 'boolean', + description: `Whether a code cell's outputs are collapsed` + } + }, + // patternProperties: allSettings.patternProperties, + additionalProperties: true, + allowTrailingCommas: true, + allowComments: true + }; + + jsonRegistry.registerSchema('vscode://schemas/notebook/cellmetadata', metadataSchema); + } +} + +// makes sure that every dirty notebook gets an editor +class NotebookFileTracker implements IWorkbenchContribution { + + private readonly _dirtyListener: IDisposable; + + constructor( + @INotebookService private readonly _notebookService: INotebookService, + @IEditorService private readonly _editorService: IEditorService, + @IWorkingCopyService workingCopyService: IWorkingCopyService, + ) { + this._dirtyListener = Event.debounce(workingCopyService.onDidChangeDirty, () => { }, 100)(() => { + const inputs = this._createMissingNotebookEditors(); + this._editorService.openEditors(inputs); + }); + } + + dispose(): void { + this._dirtyListener.dispose(); + } + + private _createMissingNotebookEditors(): IResourceEditorInput[] { + const result: IResourceEditorInput[] = []; + + for (const notebook of this._notebookService.getNotebookTextModels()) { + if (notebook.isDirty && !this._editorService.isOpen({ resource: notebook.uri })) { + result.push({ + resource: notebook.uri, + options: { inactive: true, preserveFocus: true, pinned: true } + }); + } + } + return result; + } +} + const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookContribution, LifecyclePhase.Starting); workbenchContributionsRegistry.registerWorkbenchContribution(CellContentProvider, LifecyclePhase.Starting); +workbenchContributionsRegistry.registerWorkbenchContribution(RegisterSchemasContribution, LifecyclePhase.Starting); +workbenchContributionsRegistry.registerWorkbenchContribution(NotebookFileTracker, LifecyclePhase.Ready); registerSingleton(INotebookService, NotebookService); registerSingleton(INotebookEditorWorkerService, NotebookEditorWorkerServiceImpl); @@ -474,6 +584,11 @@ configurationRegistry.registerConfiguration({ description: nls.localize('notebook.showCellStatusbar.description', "Whether the cell statusbar should be shown."), type: 'boolean', default: true + }, + [NotebookTextDiffEditorPreview]: { + description: nls.localize('notebook.diff.enablePreview.description', "Whether to use the enhanced text diff editor for notebook."), + type: 'boolean', + default: true } } }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 7bdc292f3bf5fa..f12b346c3e6443 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -6,11 +6,16 @@ import { getZoomLevel } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; +import { IAction, Separator } from 'vs/base/common/actions'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { combinedDisposable, DisposableStore, Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { combinedDisposable, Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ScrollEvent } from 'vs/base/common/scrollable'; +import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; import 'vs/css!./media/notebook'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; @@ -18,48 +23,40 @@ import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { Range } from 'vs/editor/common/core/range'; import { IEditor } from 'vs/editor/common/editorCommon'; import * as nls from 'vs/nls'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { contrastBorder, editorBackground, focusBorder, foreground, registerColor, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground, errorForeground, transparent, listFocusBackground, listInactiveSelectionBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground, diffRemoved, diffInserted } from 'vs/platform/theme/common/colorRegistry'; +import { contrastBorder, diffInserted, diffRemoved, editorBackground, errorForeground, focusBorder, foreground, listFocusBackground, listInactiveSelectionBackground, registerColor, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground, transparent } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { EditorMemento } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorMemento } from 'vs/workbench/common/editor'; -import { CELL_MARGIN, CELL_RUN_GUTTER, CELL_TOP_MARGIN, SCROLLABLE_ELEMENT_PADDING_TOP, BOTTOM_CELL_TOOLBAR_GAP, CELL_BOTTOM_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, BOTTOM_CELL_TOOLBAR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, INotebookCellList, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_OUTPUT_FOCUSED, INotebookDeltaDecoration, NotebookEditorOptions, INotebookEditorCreationOptions, INotebookEditorContributionDescription } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { Memento, MementoObject } from 'vs/workbench/common/memento'; +import { PANEL_BORDER } from 'vs/workbench/common/theme'; +import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugToolBar'; +import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CellEditState, CellFocusMode, ICellRange, ICellViewModel, INotebookCellList, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; +import { NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; -import { CodeCellRenderer, MarkdownCellRenderer, NotebookCellListDelegate, ListTopCellToolbar } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; +import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; +import { CodeCellRenderer, ListTopCellToolbar, MarkdownCellRenderer, NotebookCellListDelegate } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; +import { CellDragAndDropController } from 'vs/workbench/contrib/notebook/browser/view/renderers/dnd'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellKind, IProcessedOutput, INotebookKernelInfo, INotebookKernelInfoDto, INotebookKernelInfo2, NotebookRunState, NotebookCellRunState, IInsetRenderOutput, CellToolbarLocKey, ShowCellStatusbarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; -import { generateUuid } from 'vs/base/common/uuid'; -import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { URI } from 'vs/base/common/uri'; -import { PANEL_BORDER } from 'vs/workbench/common/theme'; -import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugToolBar'; -import { CellContextKeyManager } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellContextKeys'; +import { CellKind, CellToolbarLocKey, IInsetRenderOutput, INotebookKernelInfo, INotebookKernelInfo2, INotebookKernelInfoDto, IProcessedOutput, NotebookCellRunState, NotebookRunState, ShowCellStatusbarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; -import { notebookKernelProviderAssociationsSettingId, NotebookKernelProviderAssociations } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; -import { ScrollEvent } from 'vs/base/common/scrollable'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator'; -import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; -import { IAction, Separator } from 'vs/base/common/actions'; -import { isMacintosh, isNative } from 'vs/base/common/platform'; -import { getTitleBarStyle } from 'vs/platform/windows/common/windows'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { CellDragAndDropController } from 'vs/workbench/contrib/notebook/browser/view/renderers/dnd'; +import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; const $ = DOM.$; @@ -222,7 +219,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor @IStorageService storageService: IStorageService, @INotebookService private notebookService: INotebookService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, @IContextKeyService readonly contextKeyService: IContextKeyService, @ILayoutService private readonly layoutService: ILayoutService, @IContextMenuService private readonly contextMenuService: IContextMenuService, @@ -441,7 +437,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor multipleSelectionSupport: false, enableKeyboardNavigation: true, additionalScrollHeight: 0, - transformOptimization: (isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native', + transformOptimization: false, //(isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native', styleController: (_suffix: string) => { return this._list!; }, overrideStyles: { listBackground: editorBackground, @@ -704,10 +700,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return; } - if (provider.kernel && (availableKernels.length + availableKernels2.length) > 0) { - this._notebookHasMultipleKernels!.set(true); - this.multipleKernelsAvailable = true; - } else if ((availableKernels.length + availableKernels2.length) > 1) { + if ((availableKernels.length + availableKernels2.length) > 1) { this._notebookHasMultipleKernels!.set(true); this.multipleKernelsAvailable = true; } else { @@ -715,14 +708,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.multipleKernelsAvailable = false; } - // @deprecated - if (provider && provider.kernel) { - // it has a builtin kernel, don't automatically choose a kernel - await this._loadKernelPreloads(provider.providerExtensionLocation, provider.kernel); - tokenSource.dispose(); - return; - } - const activeKernelStillExist = [...availableKernels2, ...availableKernels].find(kernel => kernel.id === this.activeKernel?.id && this.activeKernel?.id !== undefined); if (activeKernelStillExist) { @@ -1429,15 +1414,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private async _cancelNotebookExecution(): Promise { const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; - if (provider) { - const viewType = provider.id; - const notebookUri = this._notebookViewModel!.uri; - - if (this._activeKernel) { - await (this._activeKernel as INotebookKernelInfo2).cancelNotebookCell!(this._notebookViewModel!.uri, undefined); - } else if (provider.kernel) { - return await this.notebookService.cancelNotebook(viewType, notebookUri); - } + if (provider && this._activeKernel) { + await (this._activeKernel as INotebookKernelInfo2).cancelNotebookCell!(this._notebookViewModel!.uri, undefined); } } @@ -1451,23 +1429,16 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private async _executeNotebook(): Promise { const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; - if (provider) { - const viewType = provider.id; - const notebookUri = this._notebookViewModel!.uri; - - if (this._activeKernel) { - // TODO@rebornix temp any cast, should be removed once we remove legacy kernel support - if ((this._activeKernel as INotebookKernelInfo2).executeNotebookCell) { - if (this._activeKernelResolvePromise) { - await this._activeKernelResolvePromise; - } - - await (this._activeKernel as INotebookKernelInfo2).executeNotebookCell!(this._notebookViewModel!.uri, undefined); - } else { - await this.notebookService.executeNotebook2(this._notebookViewModel!.viewType, this._notebookViewModel!.uri, this._activeKernel.id); + if (provider && this._activeKernel) { + // TODO@rebornix temp any cast, should be removed once we remove legacy kernel support + if ((this._activeKernel as INotebookKernelInfo2).executeNotebookCell) { + if (this._activeKernelResolvePromise) { + await this._activeKernelResolvePromise; } - } else if (provider.kernel) { - return await this.notebookService.executeNotebook(viewType, notebookUri); + + await (this._activeKernel as INotebookKernelInfo2).executeNotebookCell!(this._notebookViewModel!.uri, undefined); + } else { + await this.notebookService.executeNotebook(this._notebookViewModel!.viewType, this._notebookViewModel!.uri, this._activeKernel.id); } } } @@ -1491,15 +1462,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private async _cancelNotebookCell(cell: ICellViewModel): Promise { const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; - if (provider) { - const viewType = provider.id; - const notebookUri = this._notebookViewModel!.uri; - - if (this._activeKernel) { - return await (this._activeKernel as INotebookKernelInfo2).cancelNotebookCell!(this._notebookViewModel!.uri, cell.handle); - } else if (provider.kernel) { - return await this.notebookService.cancelNotebookCell(viewType, notebookUri, cell.handle); - } + if (provider && this._activeKernel) { + return await (this._activeKernel as INotebookKernelInfo2).cancelNotebookCell!(this._notebookViewModel!.uri, cell.handle); } } @@ -1528,11 +1492,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor await (this._activeKernel as INotebookKernelInfo2).executeNotebookCell!(this._notebookViewModel!.uri, cell.handle); } else { - return await this.notebookService.executeNotebookCell2(viewType, notebookUri, cell.handle, this._activeKernel.id); + return await this.notebookService.executeNotebookCell(viewType, notebookUri, cell.handle, this._activeKernel.id); } - } else if (provider.kernel) { - return await this.notebookService.executeNotebookCell(viewType, notebookUri, cell.handle); } + } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index b994093ec72cfe..defd17ca549383 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -538,6 +538,9 @@ export class NotebookService extends Disposable implements INotebookService, ICu // notebook providers/kernels/renderers might use `*` as activation event. await this._extensionService.activateByEvent(`*`); // this awaits full activation of all matching extensions + await this._extensionService.activateByEvent(`onNotebook:${viewType}`); + + // TODO@jrieken deprecated, remove this await this._extensionService.activateByEvent(`onNotebookEditor:${viewType}`); } return this._notebookProviders.has(viewType); @@ -545,7 +548,6 @@ export class NotebookService extends Disposable implements INotebookService, ICu registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController) { this._notebookProviders.set(viewType, { extensionData, controller }); - this.notebookProviderInfoStore.get(viewType)!.kernel = controller.kernel; this._onDidChangeViewTypes.fire(); } @@ -704,6 +706,10 @@ export class NotebookService extends Disposable implements INotebookService, ICu return this._models.get(uri)?.model; } + getNotebookTextModels(): Iterable { + return Iterable.map(this._models.values(), data => data.model); + } + private async transformTextModelOutputs(textModel: NotebookTextModel) { for (let i = 0; i < textModel.cells.length; i++) { const cell = textModel.cells[i]; @@ -817,48 +823,14 @@ export class NotebookService extends Disposable implements INotebookService, ICu return this.notebookRenderersInfoStore.getContributedRenderer(mimeType); } - async executeNotebook(viewType: string, uri: URI): Promise { - const provider = this._notebookProviders.get(viewType); - - if (provider) { - return provider.controller.executeNotebookByAttachedKernel(viewType, uri); - } - - return; - } - - async executeNotebookCell(viewType: string, uri: URI, handle: number): Promise { - const provider = this._notebookProviders.get(viewType); - if (provider) { - await provider.controller.executeNotebookCell(uri, handle); - } - } - - async cancelNotebook(viewType: string, uri: URI): Promise { - const provider = this._notebookProviders.get(viewType); - - if (provider) { - return provider.controller.cancelNotebookByAttachedKernel(viewType, uri); - } - - return; - } - - async cancelNotebookCell(viewType: string, uri: URI, handle: number): Promise { - const provider = this._notebookProviders.get(viewType); - if (provider) { - await provider.controller.cancelNotebookCell(uri, handle); - } - } - - async executeNotebook2(viewType: string, uri: URI, kernelId: string): Promise { + async executeNotebook(viewType: string, uri: URI, kernelId: string): Promise { const kernel = this._notebookKernels.get(kernelId); if (kernel) { await kernel.executeNotebook(viewType, uri, undefined); } } - async executeNotebookCell2(viewType: string, uri: URI, handle: number, kernelId: string): Promise { + async executeNotebookCell(viewType: string, uri: URI, handle: number, kernelId: string): Promise { const kernel = this._notebookKernels.get(kernelId); if (kernel) { await kernel.executeNotebook(viewType, uri, handle); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index eb5c7a65cb1cd4..d5aeb2571afd00 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -660,7 +660,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende width: 0, height: 0 }, - overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode() + // overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode() }, {}); disposables.add(this.editorOptions.onDidChange(newValue => editor.updateOptions(newValue))); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts index 524f3326aaf2fa..9ed49e9330029e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -17,7 +17,7 @@ import { EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/ import { CellFocusMode, CodeCellRenderTemplate, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; -import { BUILTIN_RENDERER_ID, CellOutputKind, IProcessedOutput, IRenderOutput, ITransformedDisplayOutputDto, outputHasDynamicHeight, RenderOutputType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { BUILTIN_RENDERER_ID, CellOutputKind, IInsetRenderOutput, IProcessedOutput, IRenderOutput, ITransformedDisplayOutputDto, outputHasDynamicHeight, RenderOutputType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; interface IMimeTypeRenderer extends IQuickPickItem { @@ -196,7 +196,7 @@ export class CodeCell extends Disposable { // newly added element const currIndex = this.viewCell.outputs.indexOf(output); this.renderOutput(output, currIndex, prevElement); - prevElement = this.outputElements.get(output)!.element; + prevElement = this.outputElements.get(output)?.element; }); const editorHeight = templateData.editor!.getContentHeight(); @@ -326,13 +326,14 @@ export class CodeCell extends Disposable { const renderedOutput = this.outputElements.get(currOutput); if (renderedOutput) { if (renderedOutput.renderResult.type !== RenderOutputType.None) { - // Show inset in webview, or render output that isn't rendered - // TODO@roblou skipHeightInit flag is a hack - the webview only sends the real height once. Don't wipe it out here. - this.renderOutput(currOutput, index, undefined); + this.modifyInsetQueue = this.modifyInsetQueue.finally(() => this.notebookEditor.createInset(this.viewCell, renderedOutput.renderResult as IInsetRenderOutput, this.viewCell.getOutputOffset(index))); } else { // Anything else, just update the height this.viewCell.updateOutputHeight(index, renderedOutput.element.clientHeight); } + } else { + // Wasn't previously rendered, render it now + this.renderOutput(currOutput, index); } } @@ -438,6 +439,10 @@ export class CodeCell extends Disposable { } private renderOutput(currOutput: IProcessedOutput, index: number, beforeElement?: HTMLElement) { + if (this.viewCell.metadata.outputCollapsed) { + return; + } + if (!this.outputResizeListeners.has(currOutput)) { this.outputResizeListeners.set(currOutput, new DisposableStore()); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts index a1e81ae72e5234..a0fea876cd370b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -190,7 +190,7 @@ export class StatefulMarkdownCell extends Disposable { width: width, height: editorHeight }, - overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode() + // overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode() }, {}); this.templateData.currentEditor = this.editor; diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index c4e05bd2be4845..8f3e2d757d9f52 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -474,6 +474,7 @@ export function getCellUndoRedoComparisonKey(uri: URI) { export namespace CellUri { export const scheme = Schemas.vscodeNotebookCell; + const _regex = /^\d{7,}/; export function generate(notebook: URI, handle: number): URI { @@ -483,6 +484,14 @@ export namespace CellUri { }); } + export function generateCellMetadataUri(notebook: URI, handle: number): URI { + return notebook.with({ + scheme: Schemas.vscode, + authority: 'vscode-notebook-cell-metadata', + fragment: `${handle.toString().padStart(7, '0')}${notebook.scheme !== Schemas.file ? notebook.scheme : ''}` + }); + } + export function parse(cell: URI): { notebook: URI, handle: number } | undefined { if (cell.scheme !== scheme) { return undefined; @@ -780,6 +789,7 @@ export interface INotebookCellStatusBarEntry { export const DisplayOrderKey = 'notebook.displayOrder'; export const CellToolbarLocKey = 'notebook.cellToolbarLocation'; export const ShowCellStatusbarKey = 'notebook.showCellStatusbar'; +export const NotebookTextDiffEditorPreview = 'notebook.diff.enablePreview'; export const enum CellStatusbarAlignment { LEFT, diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts index 11587e612427a8..2206d05e61e1e7 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts @@ -7,9 +7,10 @@ import { createDecorator, IInstantiationService } from 'vs/platform/instantiatio import { URI } from 'vs/base/common/uri'; import { INotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; -import { IReference, ReferenceCollection } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, IReference, ReferenceCollection } from 'vs/base/common/lifecycle'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { ILogService } from 'vs/platform/log/common/log'; +import { Event } from 'vs/base/common/event'; export const INotebookEditorModelResolverService = createDecorator('INotebookModelResolverService'); @@ -70,15 +71,27 @@ export class NotebookModelResolverService implements INotebookEditorModelResolve async resolve(resource: URI, viewType?: string, editorId?: string | undefined): Promise> { const reference = this._data.acquire(resource.toString(), viewType, editorId); const model = await reference.object; + NotebookModelResolverService._autoReferenceDirtyModel(model, () => this._data.acquire(resource.toString(), viewType, editorId)); return { object: model, dispose() { reference.dispose(); } }; } -} -// notebookService.onDidAddDocument + private static _autoReferenceDirtyModel(model: INotebookEditorModel, ref: () => IDisposable) { -// resolve() + const references = new DisposableStore(); + const listener = model.notebook.onDidChangeDirty(() => { + if (model.notebook.isDirty) { + references.add(ref()); + } else { + references.clear(); + } + }); -// notebookService.onDidRemoveDocument ... + Event.once(model.notebook.onWillDispose)(() => { + listener.dispose(); + references.dispose(); + }); + } +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts index 2ffcad9a9096c9..cab62ec09c90d9 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookProvider.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookProvider.ts @@ -36,7 +36,6 @@ export class NotebookProviderInfo implements NotebookEditorDescriptor { readonly providerDescription?: string; readonly providerDisplayName: string; readonly providerExtensionLocation: URI; - kernel?: INotebookKernelInfoDto; constructor(descriptor: NotebookEditorDescriptor) { this.id = descriptor.id; diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index 9e7a32ec7a1aac..f9acd633254972 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -9,7 +9,7 @@ import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/noteb import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Event } from 'vs/base/common/event'; import { - INotebookTextModel, INotebookRendererInfo, INotebookKernelInfo, INotebookKernelInfoDto, + INotebookTextModel, INotebookRendererInfo, INotebookKernelInfo, IEditor, ICellEditOperation, NotebookCellOutputsSplice, INotebookKernelProvider, INotebookKernelInfo2, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -22,17 +22,12 @@ import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common export const INotebookService = createDecorator('notebookService'); export interface IMainNotebookController { - kernel: INotebookKernelInfoDto | undefined; supportBackup: boolean; options: { transientOutputs: boolean; transientMetadata: TransientMetadata; }; createNotebook(textModel: NotebookTextModel, editorId?: string, backupId?: string): Promise; reloadNotebook(mainthreadTextModel: NotebookTextModel): Promise; resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise; - executeNotebookByAttachedKernel(viewType: string, uri: URI): Promise; - cancelNotebookByAttachedKernel(viewType: string, uri: URI): Promise; onDidReceiveMessage(editorId: string, rendererType: string | undefined, message: any): void; - executeNotebookCell(uri: URI, handle: number): Promise; - cancelNotebookCell(uri: URI, handle: number): Promise; removeNotebookDocument(uri: URI): Promise; save(uri: URI, token: CancellationToken): Promise; saveAs(uri: URI, target: URI, token: CancellationToken): Promise; @@ -65,12 +60,9 @@ export interface INotebookService { resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string, backupId?: string): Promise; getNotebookTextModel(uri: URI): NotebookTextModel | undefined; - executeNotebook(viewType: string, uri: URI): Promise; - cancelNotebook(viewType: string, uri: URI): Promise; - executeNotebookCell(viewType: string, uri: URI, handle: number): Promise; - cancelNotebookCell(viewType: string, uri: URI, handle: number): Promise; - executeNotebook2(viewType: string, uri: URI, kernelId: string): Promise; - executeNotebookCell2(viewType: string, uri: URI, handle: number, kernelId: string): Promise; + getNotebookTextModels(): Iterable; + executeNotebook(viewType: string, uri: URI, kernelId: string): Promise; + executeNotebookCell(viewType: string, uri: URI, handle: number, kernelId: string): Promise; getContributedNotebookProviders(resource: URI): readonly NotebookProviderInfo[]; getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined; getNotebookProviderResourceRoots(): URI[]; diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 43de91e60fbe15..8c394244f1595e 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -196,3 +196,7 @@ padding: 0 22px 0 6px; } +/* HACK: Can remove when fixed upstream https://github.com/xtermjs/xterm.js/issues/3058 */ +.xterm-helper-textarea { + border: 0px; +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index a2915b88d671e4..9a9fa34d16e795 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -21,7 +21,7 @@ import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/wor import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; import { registerTerminalActions, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, KillTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, ToggleTerminalAction, terminalSendSequenceCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView'; -import { KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE_KEY, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_VIEW_ID, TERMINAL_ACTION_CATEGORY, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import { KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE_KEY, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_VIEW_ID, TERMINAL_ACTION_CATEGORY, TERMINAL_COMMAND_ID, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED } from 'vs/workbench/contrib/terminal/common/terminal'; import { registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { setupTerminalCommands } from 'vs/workbench/contrib/terminal/browser/terminalCommands'; import { setupTerminalMenu } from 'vs/workbench/contrib/terminal/common/terminalMenu'; @@ -84,7 +84,7 @@ Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); registerTerminalActions(); const category = TERMINAL_ACTION_CATEGORY; -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(KillTerminalAction), 'Terminal: Kill the Active Terminal Instance', category); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(KillTerminalAction), 'Terminal: Kill the Active Terminal Instance', category, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(CreateNewTerminalAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKTICK, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_BACKTICK } @@ -97,7 +97,7 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(SelectAllTermin // behavior anyway when handed to xterm.js, having this handled by VS Code // makes it easier for users to see how it works though. mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_A } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Select All', category); +}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Select All', category, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleTerminalAction, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKTICK, mac: { primary: KeyMod.WinCtrl | KeyCode.US_BACKTICK } @@ -107,16 +107,16 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleTerminalA actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ClearTerminalAction, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_K } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KeybindingWeight.WorkbenchContrib + 1), 'Terminal: Clear', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(SelectDefaultShellWindowsTerminalAction), 'Terminal: Select Default Shell', category); +}, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KeybindingWeight.WorkbenchContrib + 1), 'Terminal: Clear', category, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(SelectDefaultShellWindowsTerminalAction), 'Terminal: Select Default Shell', category, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(SplitTerminalAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_5, mac: { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH, secondary: [KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_5] } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Split Terminal', category); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(SplitInActiveWorkspaceTerminalAction), 'Terminal: Split Terminal (In Active Workspace)', category); +}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Split Terminal', category, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED); +actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(SplitInActiveWorkspaceTerminalAction), 'Terminal: Split Terminal (In Active Workspace)', category, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED); // Commands might be affected by Web restrictons if (BrowserFeatures.clipboard.writeText) { @@ -124,7 +124,7 @@ if (BrowserFeatures.clipboard.writeText) { primary: KeyMod.CtrlCmd | KeyCode.KEY_C, win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_C, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C] }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C } - }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FOCUS)), 'Terminal: Copy Selection', category); + }, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FOCUS)), 'Terminal: Copy Selection', category, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED); } function registerSendSequenceKeybinding(text: string, rule: { when?: ContextKeyExpression } & IKeybindings): void { @@ -149,7 +149,7 @@ if (BrowserFeatures.clipboard.readText) { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V] }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V } - }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Paste into Active Terminal', category); + }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Paste into Active Terminal', category, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED); // An extra Windows-only ctrl+v keybinding is used for pwsh that sends ctrl+v directly to the // shell, this gets handled by PSReadLine which properly handles multi-line pastes. This is // disabled in accessibility mode as PowerShell does not run PSReadLine when it detects a screen diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index a1f25f55fb9b5f..a7db7423cfa905 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -76,6 +76,7 @@ export interface ITerminalService { configHelper: ITerminalConfigHelper; terminalInstances: ITerminalInstance[]; terminalTabs: ITerminalTab[]; + isProcessSupportRegistered: boolean; onActiveTabChanged: Event; onTabDisposed: Event; @@ -90,6 +91,7 @@ export interface ITerminalService { onInstanceTitleChanged: Event; onActiveInstanceChanged: Event; onRequestAvailableShells: Event; + onDidRegisterProcessSupport: Event; /** * Creates a terminal. @@ -136,6 +138,7 @@ export interface ITerminalService { findNext(): void; findPrevious(): void; + registerProcessSupport(isSupported: boolean): void; /** * Registers a link provider that enables integrators to add links to the terminal. * @param linkProvider When registered, the link provider is asked whenever a cell is hovered diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index dd4b2216ff2e30..b532ed7aae59aa 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -6,7 +6,7 @@ import { Action, IAction } from 'vs/base/common/actions'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { TERMINAL_VIEW_ID, ITerminalConfigHelper, TitleEventSource, TERMINAL_COMMAND_ID, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TERMINAL_VIEW_ID, ITerminalConfigHelper, TitleEventSource, TERMINAL_COMMAND_ID, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED } from 'vs/workbench/contrib/terminal/common/terminal'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -367,7 +367,7 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem { this._register(_terminalService.onActiveTabChanged(this._updateItems, this)); this._register(_terminalService.onInstanceTitleChanged(this._updateItems, this)); this._register(_terminalService.onTabDisposed(this._updateItems, this)); - this._register(attachSelectBoxStyler(this.selectBox, _themeService)); + this._register(attachSelectBoxStyler(this.selectBox, this._themeService)); } render(container: HTMLElement): void { @@ -466,7 +466,8 @@ export function registerTerminalActions() { }, when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor) { @@ -491,7 +492,8 @@ export function registerTerminalActions() { }, when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor) { @@ -512,7 +514,8 @@ export function registerTerminalActions() { mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow }, when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor) { @@ -531,7 +534,8 @@ export function registerTerminalActions() { mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow }, when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor) { @@ -549,7 +553,8 @@ export function registerTerminalActions() { mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.UpArrow }, when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor) { @@ -567,7 +572,8 @@ export function registerTerminalActions() { mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.DownArrow }, when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor) { @@ -580,7 +586,8 @@ export function registerTerminalActions() { id: TERMINAL_COMMAND_ID.FOCUS, title: { value: localize('workbench.action.terminal.focus', "Focus Terminal"), original: 'Focus Terminal' }, f1: true, - category + category, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor) { @@ -599,7 +606,8 @@ export function registerTerminalActions() { id: TERMINAL_COMMAND_ID.FOCUS_NEXT, title: { value: localize('workbench.action.terminal.focusNext', "Focus Next Terminal"), original: 'Focus Next Terminal' }, f1: true, - category + category, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor) { @@ -614,7 +622,8 @@ export function registerTerminalActions() { id: TERMINAL_COMMAND_ID.FOCUS_PREVIOUS, title: { value: localize('workbench.action.terminal.focusPrevious', "Focus Previous Terminal"), original: 'Focus Previous Terminal' }, f1: true, - category + category, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor) { @@ -629,7 +638,8 @@ export function registerTerminalActions() { id: TERMINAL_COMMAND_ID.RUN_SELECTED_TEXT, title: { value: localize('workbench.action.terminal.runSelectedText', "Run Selected Text In Active Terminal"), original: 'Run Selected Text In Active Terminal' }, f1: true, - category + category, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor) { @@ -659,7 +669,8 @@ export function registerTerminalActions() { id: TERMINAL_COMMAND_ID.RUN_ACTIVE_FILE, title: { value: localize('workbench.action.terminal.runActiveFile', "Run Active File In Active Terminal"), original: 'Run Active File In Active Terminal' }, f1: true, - category + category, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor) { @@ -699,7 +710,8 @@ export function registerTerminalActions() { linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow }, when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -718,7 +730,8 @@ export function registerTerminalActions() { mac: { primary: KeyCode.PageDown }, when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -737,7 +750,8 @@ export function registerTerminalActions() { linux: { primary: KeyMod.Shift | KeyCode.End }, when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -756,7 +770,8 @@ export function registerTerminalActions() { linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow }, when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -775,7 +790,8 @@ export function registerTerminalActions() { mac: { primary: KeyCode.PageUp }, when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -794,7 +810,8 @@ export function registerTerminalActions() { linux: { primary: KeyMod.Shift | KeyCode.Home }, when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -812,7 +829,8 @@ export function registerTerminalActions() { primary: KeyCode.Escape, when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED), weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -833,7 +851,8 @@ export function registerTerminalActions() { ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED) ), weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -854,7 +873,8 @@ export function registerTerminalActions() { ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED) ), weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -872,7 +892,8 @@ export function registerTerminalActions() { primary: KeyCode.Escape, when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE), weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -888,7 +909,8 @@ export function registerTerminalActions() { id: TERMINAL_COMMAND_ID.MANAGE_WORKSPACE_SHELL_PERMISSIONS, title: { value: localize('workbench.action.terminal.manageWorkspaceShellPermissions', "Manage Workspace Shell Permissions"), original: 'Manage Workspace Shell Permissions' }, f1: true, - category + category, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -901,7 +923,8 @@ export function registerTerminalActions() { id: TERMINAL_COMMAND_ID.RENAME, title: { value: localize('workbench.action.terminal.rename', "Rename"), original: 'Rename' }, f1: true, - category + category, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor) { @@ -927,7 +950,8 @@ export function registerTerminalActions() { primary: KeyMod.CtrlCmd | KeyCode.KEY_F, when: ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FOCUS), weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -946,7 +970,8 @@ export function registerTerminalActions() { secondary: [KeyMod.Shift | KeyCode.Escape], when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE), weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -959,7 +984,8 @@ export function registerTerminalActions() { id: TERMINAL_COMMAND_ID.QUICK_OPEN_TERM, title: { value: localize('quickAccessTerminal', "Switch Active Terminal"), original: 'Switch Active Terminal' }, f1: true, - category + category, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -977,7 +1003,8 @@ export function registerTerminalActions() { mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }, when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -998,7 +1025,8 @@ export function registerTerminalActions() { mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }, when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -1019,7 +1047,8 @@ export function registerTerminalActions() { mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow }, when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -1040,7 +1069,8 @@ export function registerTerminalActions() { mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow }, when: KEYBINDING_CONTEXT_TERMINAL_FOCUS, weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -1056,7 +1086,8 @@ export function registerTerminalActions() { id: TERMINAL_COMMAND_ID.SELECT_TO_PREVIOUS_LINE, title: { value: localize('workbench.action.terminal.selectToPreviousLine', "Select To Previous Line"), original: 'Select To Previous Line' }, f1: true, - category + category, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -1072,7 +1103,8 @@ export function registerTerminalActions() { id: TERMINAL_COMMAND_ID.SELECT_TO_NEXT_LINE, title: { value: localize('workbench.action.terminal.selectToNextLine', "Select To Next Line"), original: 'Select To Next Line' }, f1: true, - category + category, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -1088,7 +1120,8 @@ export function registerTerminalActions() { id: TERMINAL_COMMAND_ID.TOGGLE_ESCAPE_SEQUENCE_LOGGING, title: { value: localize('workbench.action.terminal.toggleEscapeSequenceLogging', "Toggle Escape Sequence Logging"), original: 'Toggle Escape Sequence Logging' }, f1: true, - category + category, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -1114,7 +1147,8 @@ export function registerTerminalActions() { }, } }] - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor, args?: { text?: string }) { @@ -1143,7 +1177,8 @@ export function registerTerminalActions() { }, } }] - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor, args?: { cwd?: string }) { @@ -1179,7 +1214,8 @@ export function registerTerminalActions() { } } }] - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor, args?: { name?: string }) { @@ -1203,7 +1239,8 @@ export function registerTerminalActions() { mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R }, when: ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED), weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -1224,6 +1261,7 @@ export function registerTerminalActions() { when: ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED), weight: KeybindingWeight.WorkbenchContrib }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -1243,7 +1281,8 @@ export function registerTerminalActions() { mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C }, when: ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED), weight: KeybindingWeight.WorkbenchContrib - } + }, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -1270,7 +1309,8 @@ export function registerTerminalActions() { when: KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, weight: KeybindingWeight.WorkbenchContrib } - ] + ], + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -1296,7 +1336,8 @@ export function registerTerminalActions() { when: KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, weight: KeybindingWeight.WorkbenchContrib } - ] + ], + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -1309,7 +1350,8 @@ export function registerTerminalActions() { id: TERMINAL_COMMAND_ID.RELAUNCH, title: { value: localize('workbench.action.terminal.relaunch', "Relaunch Active Terminal"), original: 'Relaunch Active Terminal' }, f1: true, - category + category, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { @@ -1322,7 +1364,8 @@ export function registerTerminalActions() { id: TERMINAL_COMMAND_ID.SHOW_ENVIRONMENT_INFORMATION, title: { value: localize('workbench.action.terminal.showEnvironmentInformation', "Show Environment Information"), original: 'Show Environment Information' }, f1: true, - category + category, + precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } run(accessor: ServicesAccessor) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 15154160cbde53..de6a1f38ff7001 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { TERMINAL_VIEW_ID, IShellLaunchConfig, ITerminalConfigHelper, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalProcessExtHostProxy, IShellDefinition, LinuxDistro, KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE, ITerminalLaunchError, ITerminalNativeWindowsDelegate } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TERMINAL_VIEW_ID, IShellLaunchConfig, ITerminalConfigHelper, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, ITerminalProcessExtHostProxy, IShellDefinition, LinuxDistro, KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE, ITerminalLaunchError, ITerminalNativeWindowsDelegate } from 'vs/workbench/contrib/terminal/common/terminal'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -23,12 +23,13 @@ import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; -import { isWindows, isMacintosh, OperatingSystem } from 'vs/base/common/platform'; +import { isWindows, isMacintosh, OperatingSystem, isWeb } from 'vs/base/common/platform'; import { basename } from 'vs/base/common/path'; import { find } from 'vs/base/common/arrays'; import { timeout } from 'vs/base/common/async'; import { IViewsService, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; interface IExtHostReadyEntry { promise: Promise; @@ -52,10 +53,12 @@ export class TerminalService implements ITerminalService { private _activeTabIndex: number; private _linkProviders: Set = new Set(); private _linkProviderDisposables: Map = new Map(); + private _processSupportContextKey: IContextKey; public get activeTabIndex(): number { return this._activeTabIndex; } public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } public get terminalTabs(): ITerminalTab[] { return this._terminalTabs; } + public get isProcessSupportRegistered(): boolean { return !!this._processSupportContextKey.get(); } private _configHelper: TerminalConfigHelper; private _terminalContainer: HTMLElement | undefined; @@ -91,6 +94,8 @@ export class TerminalService implements ITerminalService { public get onTabDisposed(): Event { return this._onTabDisposed.event; } private readonly _onRequestAvailableShells = new Emitter(); public get onRequestAvailableShells(): Event { return this._onRequestAvailableShells.event; } + private readonly _onDidRegisterProcessSupport = new Emitter(); + public get onDidRegisterProcessSupport(): Event { return this._onDidRegisterProcessSupport.event; } constructor( @IContextKeyService private _contextKeyService: IContextKeyService, @@ -103,7 +108,8 @@ export class TerminalService implements ITerminalService { @IQuickInputService private _quickInputService: IQuickInputService, @IConfigurationService private _configurationService: IConfigurationService, @IViewsService private _viewsService: IViewsService, - @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService + @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { this._activeTabIndex = 0; this._isShuttingDown = false; @@ -121,7 +127,9 @@ export class TerminalService implements ITerminalService { }); this.onInstanceLinksReady(instance => this._setInstanceLinkProviders(instance)); - this._handleContextKeys(); + this._handleInstanceContextKeys(); + this._processSupportContextKey = KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED.bindTo(this._contextKeyService); + this._processSupportContextKey.set(!isWeb || this._remoteAgentService.getConnection() !== null); } public setNativeWindowsDelegate(delegate: ITerminalNativeWindowsDelegate): void { @@ -132,13 +140,11 @@ export class TerminalService implements ITerminalService { this._configHelper.setLinuxDistro(linuxDistro); } - private _handleContextKeys(): void { + private _handleInstanceContextKeys(): void { const terminalIsOpenContext = KEYBINDING_CONTEXT_TERMINAL_IS_OPEN.bindTo(this._contextKeyService); - const updateTerminalContextKeys = () => { terminalIsOpenContext.set(this.terminalInstances.length > 0); }; - this.onInstancesChanged(() => updateTerminalContextKeys()); } @@ -411,6 +417,14 @@ export class TerminalService implements ITerminalService { instance.addDisposable(instance.onFocus(this._onActiveInstanceChanged.fire, this._onActiveInstanceChanged)); } + public registerProcessSupport(isSupported: boolean): void { + if (!isSupported) { + return; + } + this._processSupportContextKey.set(isSupported); + this._onDidRegisterProcessSupport.fire(); + } + public registerLinkProvider(linkProvider: ITerminalExternalLinkProvider): IDisposable { const disposables: IDisposable[] = []; this._linkProviders.add(linkProvider); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 55d0d5e1865e4c..ae87e42d990e43 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -19,7 +19,6 @@ import { URI } from 'vs/base/common/uri'; import { TERMINAL_BACKGROUND_COLOR, TERMINAL_BORDER_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { DataTransfers } from 'vs/base/browser/dnd'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; -import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -56,14 +55,25 @@ export class TerminalViewPane extends ViewPane { @IThemeService protected readonly themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, @INotificationService private readonly _notificationService: INotificationService, - @IStorageService storageService: IStorageService, @IOpenerService openerService: IOpenerService, ) { super(options, keybindingService, _contextMenuService, configurationService, contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService, telemetryService); + this._terminalService.onDidRegisterProcessSupport(() => { + if (this._actions) { + for (const action of this._actions) { + action.enabled = true; + } + } + this._onDidChangeViewWelcomeState.fire(); + }); } protected renderBody(container: HTMLElement): void { super.renderBody(container); + if (this.shouldShowWelcome()) { + return; + } + this._parentDomElement = container; dom.addClass(this._parentDomElement, 'integrated-terminal'); this._fontStyleElement = document.createElement('style'); @@ -120,6 +130,10 @@ export class TerminalViewPane extends ViewPane { protected layoutBody(height: number, width: number): void { super.layoutBody(height, width); + if (this.shouldShowWelcome()) { + return; + } + this._bodyDimensions.width = width; this._bodyDimensions.height = height; this._terminalService.terminalTabs.forEach(t => t.layout(width, height)); @@ -138,9 +152,12 @@ export class TerminalViewPane extends ViewPane { this._splitTerminalAction, this._instantiationService.createInstance(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.PANEL_LABEL) ]; - this._actions.forEach(a => { - this._register(a); - }); + for (const action of this._actions) { + if (!this._terminalService.isProcessSupportRegistered) { + action.enabled = false; + } + this._register(action); + } } return this._actions; } @@ -188,10 +205,7 @@ export class TerminalViewPane extends ViewPane { } public focus(): void { - const activeInstance = this._terminalService.getActiveInstance(); - if (activeInstance) { - activeInstance.focusWhenReady(true); - } + this._terminalService.getActiveInstance()?.focusWhenReady(true); } public focusFindWidget() { @@ -331,9 +345,11 @@ export class TerminalViewPane extends ViewPane { theme = this.themeService.getColorTheme(); } - if (this._findWidget) { - this._findWidget.updateTheme(theme); - } + this._findWidget?.updateTheme(theme); + } + + shouldShowWelcome(): boolean { + return !this._terminalService.isProcessSupportRegistered; } } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 5edbd197dbd5b1..9c4323340a02a5 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -12,7 +12,7 @@ import { OperatingSystem } from 'vs/base/common/platform'; import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { IExtensionPointDescriptor } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -export const TERMINAL_VIEW_ID = 'workbench.panel.terminal'; +export const TERMINAL_VIEW_ID = 'terminal'; /** A context key that is set when there is at least one opened integrated terminal. */ export const KEYBINDING_CONTEXT_TERMINAL_IS_OPEN = new RawContextKey('terminalIsOpen', false); @@ -46,6 +46,8 @@ export const KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED = new RawContextKey('terminalProcessSupported', false); + export const IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY = 'terminal.integrated.isWorkspaceShellAllowed'; export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverMeasureRenderTime'; diff --git a/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css b/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css index 7d965ad50da4d6..1a41705614cef6 100644 --- a/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css +++ b/src/vs/workbench/contrib/timeline/browser/media/timelinePane.css @@ -7,26 +7,6 @@ position: relative; } -.monaco-workbench .timeline-view.pane-header .description { - display: block; - font-weight: normal; - margin-left: 10px; - opacity: 0.6; - overflow: hidden; - text-overflow: ellipsis; - text-transform: none; - white-space: nowrap; -} - -.monaco-workbench .timeline-view.pane-header:not(.expanded) .description { - display: none; -} - -.monaco-workbench .timeline-view.pane-header .description span.codicon { - font-size: 9px; - margin-left: 2px; -} - .monaco-workbench .timeline-tree-view .message.timeline-subtle { opacity: 0.5; padding: 10px 22px 0 22px; diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 153a46de95ec36..8ba8fc64af5685 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -219,7 +219,6 @@ export class TimelinePane extends ViewPane { private $container!: HTMLElement; private $message!: HTMLDivElement; - private $titleDescription!: HTMLSpanElement; private $tree!: HTMLDivElement; private tree!: WorkbenchObjectTree; private treeRenderer: TimelineTreeRenderer | undefined; @@ -276,7 +275,7 @@ export class TimelinePane extends ViewPane { this._followActiveEditor = value; this.followActiveEditorContext.set(value); - this.titleDescription = this.titleDescription; + this.updateFilename(this._filename); if (value) { this.onActiveEditorChanged(); @@ -315,7 +314,7 @@ export class TimelinePane extends ViewPane { } this.uri = uri; - this.titleDescription = uri ? basename(uri.fsPath) : ''; + this.updateFilename(uri ? basename(uri.fsPath) : undefined); this.treeRenderer?.setUri(uri); this.loadTimeline(true); } @@ -407,17 +406,13 @@ export class TimelinePane extends ViewPane { } } - private _titleDescription: string | undefined; - get titleDescription(): string | undefined { - return this._titleDescription; - } - - set titleDescription(description: string | undefined) { - this._titleDescription = description; - if (this.followActiveEditor || !description) { - this.$titleDescription.textContent = description ?? ''; + private _filename: string | undefined; + updateFilename(filename: string | undefined) { + this._filename = filename; + if (this.followActiveEditor || !filename) { + this.updateTitleDescription(filename); } else { - this.$titleDescription.textContent = `${description} (pinned)`; + this.updateTitleDescription(`${filename} (pinned)`); } } @@ -781,17 +776,17 @@ export class TimelinePane extends ViewPane { this._isEmpty = !this.hasVisibleItems; if (this.uri === undefined) { - this.titleDescription = undefined; + this.updateFilename(undefined); this.message = localize('timeline.editorCannotProvideTimeline', "The active editor cannot provide timeline information."); } else if (this._isEmpty) { if (this.pendingRequests.size !== 0) { this.setLoadingUriMessage(); } else { - this.titleDescription = basename(this.uri.fsPath); + this.updateFilename(basename(this.uri.fsPath)); this.message = localize('timeline.noTimelineInfo', "No timeline information was provided."); } } else { - this.titleDescription = basename(this.uri.fsPath); + this.updateFilename(basename(this.uri.fsPath)); this.message = undefined; } @@ -849,7 +844,6 @@ export class TimelinePane extends ViewPane { super.renderHeaderTitle(container, this.title); DOM.addClass(container, 'timeline-view'); - this.$titleDescription = DOM.append(container, DOM.$('span.description', undefined, this.titleDescription ?? '')); } protected renderBody(container: HTMLElement): void { @@ -956,7 +950,7 @@ export class TimelinePane extends ViewPane { setLoadingUriMessage() { const file = this.uri && basename(this.uri.fsPath); - this.titleDescription = file ?? ''; + this.updateFilename(file); this.message = file ? localize('timeline.loading', "Loading timeline for {0}...", file) : ''; } diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 8cd6211afde4f9..41e39ecd9e24f8 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -19,6 +19,11 @@ (function () { 'use strict'; + const isSafari = navigator.vendor && navigator.vendor.indexOf('Apple') > -1 && + navigator.userAgent && + navigator.userAgent.indexOf('CriOS') === -1 && + navigator.userAgent.indexOf('FxiOS') === -1; + /** * Use polling to track focus of main webview and iframes within the webview * @@ -480,7 +485,7 @@ const newFrame = document.createElement('iframe'); newFrame.setAttribute('id', 'pending-frame'); newFrame.setAttribute('frameborder', '0'); - newFrame.setAttribute('sandbox', options.allowScripts ? 'allow-scripts allow-forms allow-same-origin' : 'allow-same-origin'); + newFrame.setAttribute('sandbox', options.allowScripts ? 'allow-scripts allow-forms allow-same-origin allow-pointer-lock' : 'allow-same-origin allow-pointer-lock'); if (host.fakeLoad) { // We should just be able to use srcdoc, but I wasn't // seeing the service worker applying properly. @@ -514,7 +519,7 @@ }, 0); } - if (host.fakeLoad && false) { + if (host.fakeLoad && !options.allowScripts && isSafari) { // On Safari for iframes with scripts disabled, the `DOMContentLoaded` never seems to be fired. // Use polling instead. const interval = setInterval(() => { @@ -524,7 +529,7 @@ return; } - if (newFrame.contentDocument.readyState === 'complete') { + if (newFrame.contentDocument.readyState !== 'loading') { clearInterval(interval); onFrameLoaded(newFrame.contentDocument); } diff --git a/src/vs/workbench/contrib/webview/electron-browser/iframeWebviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/iframeWebviewElement.ts index 03d68b88996a88..9e776c2122d73f 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/iframeWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/iframeWebviewElement.ts @@ -52,7 +52,7 @@ export class ElectronIframeWebview extends IFrameWebview { super(id, options, contentOptions, extension, webviewThemeDataProvider, noficationService, tunnelService, fileService, requestService, telemetryService, environmentService, _workbenchEnvironmentService, _remoteAuthorityResolverService, logService); - this._resourceRequestManager = this._register(instantiationService.createInstance(WebviewResourceRequestManager, id, extension, this.content.options, Promise.resolve(undefined))); + this._resourceRequestManager = this._register(instantiationService.createInstance(WebviewResourceRequestManager, id, extension, this.content.options)); } protected createElement(options: WebviewOptions, contentOptions: WebviewContentOptions) { diff --git a/src/vs/workbench/contrib/webview/electron-browser/resourceLoading.ts b/src/vs/workbench/contrib/webview/electron-browser/resourceLoading.ts index e2d4a407857958..4f62d6314fa1c5 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/resourceLoading.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/resourceLoading.ts @@ -58,7 +58,6 @@ export class WebviewResourceRequestManager extends Disposable { private readonly id: string, private readonly extension: WebviewExtensionDescription | undefined, initialContentOptions: WebviewContentOptions, - getWebContentsId: Promise, @ILogService private readonly _logService: ILogService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @@ -79,15 +78,13 @@ export class WebviewResourceRequestManager extends Disposable { const remoteAuthority = environmentService.configuration.remoteAuthority; const remoteConnectionData = remoteAuthority ? remoteAuthorityResolverService.getConnectionData(remoteAuthority) : null; - this._ready = getWebContentsId.then(async (webContentsId) => { - this._logService.debug(`WebviewResourceRequestManager(${this.id}): did-start-loading`); - await this._webviewManagerService.registerWebview(this.id, webContentsId, electronService.windowId, { - extensionLocation: this.extension?.location.toJSON(), - localResourceRoots: this._localResourceRoots.map(x => x.toJSON()), - remoteConnectionData: remoteConnectionData, - portMappings: this._portMappings, - }); - + this._logService.debug(`WebviewResourceRequestManager(${this.id}): did-start-loading`); + this._ready = this._webviewManagerService.registerWebview(this.id, electronService.windowId, { + extensionLocation: this.extension?.location.toJSON(), + localResourceRoots: this._localResourceRoots.map(x => x.toJSON()), + remoteConnectionData: remoteConnectionData, + portMappings: this._portMappings, + }).then(() => { this._logService.debug(`WebviewResourceRequestManager(${this.id}): did register`); }); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 2ef9b4d1bcf446..0bf81242f3b079 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -142,17 +142,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme this._myLogService.debug(`Webview(${this.id}): init`); - const webviewId = new Promise((resolve, reject) => { - const sub = this._register(addDisposableListener(this.element!, 'dom-ready', once(() => { - if (!this.element) { - reject(); - throw new Error('No element'); - } - resolve(this.element.getWebContentsId()); - sub.dispose(); - }))); - }); - this._resourceRequestManager = this._register(instantiationService.createInstance(WebviewResourceRequestManager, id, extension, this.content.options, webviewId)); + this._resourceRequestManager = this._register(instantiationService.createInstance(WebviewResourceRequestManager, id, extension, this.content.options)); this._register(addDisposableListener(this.element!, 'dom-ready', once(() => { this._register(ElectronWebviewBasedWebview.getWebviewKeyboardHandler(configurationService, mainProcessService).add(this.element!)); diff --git a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts index 33bdbf1ba9d4ac..c1c19309f36311 100644 --- a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts +++ b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts @@ -38,43 +38,43 @@ interface Key { const keys: Key[] = [ { id: 'explorer', - arrow: '←', + arrow: '←', label: localize('welcomeOverlay.explorer', "File explorer"), command: 'workbench.view.explorer' }, { id: 'search', - arrow: '←', + arrow: '←', label: localize('welcomeOverlay.search', "Search across files"), command: 'workbench.view.search' }, { id: 'git', - arrow: '←', + arrow: '←', label: localize('welcomeOverlay.git', "Source code management"), command: 'workbench.view.scm' }, { id: 'debug', - arrow: '←', + arrow: '←', label: localize('welcomeOverlay.debug', "Launch and debug"), command: 'workbench.view.debug' }, { id: 'extensions', - arrow: '←', + arrow: '←', label: localize('welcomeOverlay.extensions', "Manage extensions"), command: 'workbench.view.extensions' }, // { // id: 'watermark', - // arrow: '⤹', + // arrow: '⤹', // label: localize('welcomeOverlay.watermark', "Command Hints"), // withEditor: false // }, { id: 'problems', - arrow: '⤹', + arrow: '⤹', label: localize('welcomeOverlay.problems', "View errors and warnings"), command: 'workbench.actions.view.problems' }, @@ -85,20 +85,20 @@ const keys: Key[] = [ }, // { // id: 'openfile', - // arrow: '⤸', + // arrow: '⤸', // label: localize('welcomeOverlay.openfile', "File Properties"), // arrowLast: true, // withEditor: true // }, { id: 'commandPalette', - arrow: '↖', + arrow: '↖', label: localize('welcomeOverlay.commandPalette', "Find and run all commands"), command: ShowAllCommandsAction.ID }, { id: 'notifications', - arrow: '⤵', + arrow: '⤵', arrowLast: true, label: localize('welcomeOverlay.notifications', "Show notifications"), command: 'notifications.showList' @@ -186,7 +186,7 @@ class WelcomeOverlay extends Disposable { .forEach(({ id, arrow, label, command, arrowLast }) => { const div = dom.append(this._overlay, $(`.key.${id}`)); if (arrow && !arrowLast) { - dom.append(div, $('span.arrow')).innerHTML = arrow; + dom.append(div, $('span.arrow', {}, arrow)); } dom.append(div, $('span.label')).textContent = label; if (command) { @@ -196,7 +196,7 @@ class WelcomeOverlay extends Disposable { } } if (arrow && arrowLast) { - dom.append(div, $('span.arrow')).innerHTML = arrow; + dom.append(div, $('span.arrow', {}, arrow)); } }); } diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 8791faa468174c..5b609209058e80 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -18,6 +18,11 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IProductService } from 'vs/platform/product/common/productService'; import { isString } from 'vs/base/common/types'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { flatten } from 'vs/base/common/arrays'; +import { isFalsyOrWhitespace } from 'vs/base/common/strings'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; export function getAuthenticationProviderActivationEvent(id: string): string { return `onAuthenticationRequest:${id}`; } @@ -55,6 +60,10 @@ export interface IAuthenticationService { readonly onDidUnregisterAuthenticationProvider: Event; readonly onDidChangeSessions: Event<{ providerId: string, label: string, event: AuthenticationSessionsChangeEvent }>; + + declaredProviders: AuthenticationProviderInformation[]; + readonly onDidChangeDeclaredProviders: Event; + getSessions(providerId: string): Promise>; getLabel(providerId: string): string; supportsMultipleAccounts(providerId: string): boolean; @@ -96,6 +105,30 @@ CommandsRegistry.registerCommand('workbench.getCodeExchangeProxyEndpoints', func return environmentService.options?.codeExchangeProxyEndpoints; }); +const authenticationDefinitionSchema: IJSONSchema = { + type: 'object', + additionalProperties: false, + properties: { + id: { + type: 'string', + description: nls.localize('authentication.id', 'The id of the authentication provider.') + }, + label: { + type: 'string', + description: nls.localize('authentication.label', 'The human readable name of the authentication provider.'), + } + } +}; + +const authenticationExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'authentication', + jsonSchema: { + description: nls.localize('authenticationExtensionPoint', 'Contributes authentication'), + type: 'array', + items: authenticationDefinitionSchema + } +}); + export class AuthenticationService extends Disposable implements IAuthenticationService { declare readonly _serviceBrand: undefined; private _placeholderMenuItem: IDisposable | undefined; @@ -105,6 +138,11 @@ export class AuthenticationService extends Disposable implements IAuthentication private _authenticationProviders: Map = new Map(); + /** + * All providers that have been statically declared by extensions. These may not be registered. + */ + declaredProviders: AuthenticationProviderInformation[] = []; + private _onDidRegisterAuthenticationProvider: Emitter = this._register(new Emitter()); readonly onDidRegisterAuthenticationProvider: Event = this._onDidRegisterAuthenticationProvider.event; @@ -114,7 +152,13 @@ export class AuthenticationService extends Disposable implements IAuthentication private _onDidChangeSessions: Emitter<{ providerId: string, label: string, event: AuthenticationSessionsChangeEvent }> = this._register(new Emitter<{ providerId: string, label: string, event: AuthenticationSessionsChangeEvent }>()); readonly onDidChangeSessions: Event<{ providerId: string, label: string, event: AuthenticationSessionsChangeEvent }> = this._onDidChangeSessions.event; - constructor(@IActivityService private readonly activityService: IActivityService) { + private _onDidChangeDeclaredProviders: Emitter = this._register(new Emitter()); + readonly onDidChangeDeclaredProviders: Event = this._onDidChangeDeclaredProviders.event; + + constructor( + @IActivityService private readonly activityService: IActivityService, + @IExtensionService private readonly extensionService: IExtensionService + ) { super(); this._placeholderMenuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, { command: { @@ -123,6 +167,38 @@ export class AuthenticationService extends Disposable implements IAuthentication precondition: ContextKeyExpr.false() }, }); + + authenticationExtPoint.setHandler((extensions, { added, removed }) => { + added.forEach(point => { + for (const provider of point.value) { + if (isFalsyOrWhitespace(provider.id)) { + point.collector.error(nls.localize('authentication.missingId', 'An authentication contribution must specify an id.')); + continue; + } + + if (isFalsyOrWhitespace(provider.label)) { + point.collector.error(nls.localize('authentication.missingLabel', 'An authentication contribution must specify a label.')); + continue; + } + + if (!this.declaredProviders.some(p => p.id === provider.id)) { + this.declaredProviders.push(provider); + } else { + point.collector.error(nls.localize('authentication.idConflict', "This authentication id '{0}' has already been registered", provider.id)); + } + } + }); + + const removedExtPoints = flatten(removed.map(r => r.value)); + removedExtPoints.forEach(point => { + const index = this.declaredProviders.findIndex(provider => provider.id === point.id); + if (index > -1) { + this.declaredProviders.splice(index, 1); + } + }); + + this._onDidChangeDeclaredProviders.fire(this.declaredProviders); + }); } getProviderIds(): string[] { @@ -339,11 +415,11 @@ export class AuthenticationService extends Disposable implements IAuthentication } } getLabel(id: string): string { - const authProvider = this._authenticationProviders.get(id); + const authProvider = this.declaredProviders.find(provider => provider.id === id); if (authProvider) { return authProvider.label; } else { - throw new Error(`No authentication provider '${id}' is currently registered.`); + throw new Error(`No authentication provider '${id}' has been declared.`); } } @@ -357,6 +433,8 @@ export class AuthenticationService extends Disposable implements IAuthentication } async getSessions(id: string): Promise> { + await this.extensionService.activateByEvent(getAuthenticationProviderActivationEvent(id)); + const authProvider = this._authenticationProviders.get(id); if (authProvider) { return await authProvider.getSessions(); @@ -366,6 +444,8 @@ export class AuthenticationService extends Disposable implements IAuthentication } async login(id: string, scopes: string[]): Promise { + await this.extensionService.activateByEvent(getAuthenticationProviderActivationEvent(id)); + const authProvider = this._authenticationProviders.get(id); if (authProvider) { return authProvider.login(scopes); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts index dfee9ed2d2afd7..2fa59ee92f48a3 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts @@ -15,6 +15,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { WebExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/webExtensionManagementService'; import { IExtension } from 'vs/platform/extensions/common/extensions'; import { WebRemoteExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/remoteExtensionManagementService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IProductService } from 'vs/platform/product/common/productService'; export class ExtensionManagementServerService implements IExtensionManagementServerService { @@ -27,11 +30,14 @@ export class ExtensionManagementServerService implements IExtensionManagementSer constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, @ILabelService labelService: ILabelService, + @IExtensionGalleryService galleryService: IExtensionGalleryService, + @IProductService productService: IProductService, + @IConfigurationService configurationService: IConfigurationService, @IInstantiationService instantiationService: IInstantiationService, ) { const remoteAgentConnection = remoteAgentService.getConnection(); if (remoteAgentConnection) { - const extensionManagementService = instantiationService.createInstance(WebRemoteExtensionManagementService, remoteAgentConnection!.getChannel('extensions')); + const extensionManagementService = new WebRemoteExtensionManagementService(remoteAgentConnection.getChannel('extensions'), galleryService, configurationService, productService); this.remoteExtensionManagementServer = { id: 'remote', extensionManagementService, diff --git a/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService.ts index fb573f0618f43f..7aabfa3b995bba 100644 --- a/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService.ts @@ -15,7 +15,10 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { DesktopRemoteExtensionManagementService } from 'vs/workbench/services/extensionManagement/electron-browser/remoteExtensionManagementService'; import { ILabelService } from 'vs/platform/label/common/label'; import { IExtension } from 'vs/platform/extensions/common/extensions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILogService } from 'vs/platform/log/common/log'; export class ExtensionManagementServerService implements IExtensionManagementServerService { @@ -28,16 +31,19 @@ export class ExtensionManagementServerService implements IExtensionManagementSer constructor( @ISharedProcessService sharedProcessService: ISharedProcessService, - @IInstantiationService instantiationService: IInstantiationService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, @ILabelService labelService: ILabelService, + @IExtensionGalleryService galleryService: IExtensionGalleryService, + @IProductService productService: IProductService, + @IConfigurationService configurationService: IConfigurationService, + @ILogService logService: ILogService, ) { const localExtensionManagementService = new ExtensionManagementChannelClient(sharedProcessService.getChannel('extensions')); this._localExtensionManagementServer = { extensionManagementService: localExtensionManagementService, id: 'local', label: localize('local', "Local") }; const remoteAgentConnection = remoteAgentService.getConnection(); if (remoteAgentConnection) { - const extensionManagementService = instantiationService.createInstance(DesktopRemoteExtensionManagementService, remoteAgentConnection.getChannel('extensions'), this.localExtensionManagementServer); + const extensionManagementService = new DesktopRemoteExtensionManagementService(remoteAgentConnection.getChannel('extensions'), this.localExtensionManagementServer, logService, galleryService, configurationService, productService); this.remoteExtensionManagementServer = { id: 'remote', extensionManagementService,