Merge branch 'master' into ben/pinned-tabs-setting

This commit is contained in:
Benjamin Pasero
2020-09-10 08:54:15 +02:00
154 changed files with 2254 additions and 1982 deletions
+7
View File
@@ -0,0 +1,7 @@
{
"label-to-subscribe-to": [
"list of usernames to subscribe",
"such as:",
"JacksonKearl"
]
}
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v34
ref: v35
path: ./actions
- name: Install Actions
if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'author-verification-requested')
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v34
ref: v35
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Run Commands
@@ -11,7 +11,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v34
ref: v35
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v34
ref: v35
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions
@@ -11,7 +11,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v34
ref: v35
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v34
ref: v35
path: ./actions
- name: Install Actions
if: contains(github.event.issue.labels.*.name, '*english-please')
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v34
ref: v35
- name: Install Actions
if: github.event_name != 'issues' || contains(github.event.issue.labels.*.name, 'feature-request')
run: npm install --production --prefix ./actions
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v34
ref: v35
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Install Storage Module
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v34
ref: v35
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Run Locker
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v34
ref: v35
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Run Needs More Info Closer
+8 -1
View File
@@ -11,7 +11,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v34
ref: v35
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions
@@ -40,6 +40,13 @@ jobs:
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
config-path: commands
# only here.
- name: Run Subscribers
uses: ./actions/topic-subscribe
with:
appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}}
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
config-path: subscribers
# source of truth in ./feature-request.yml
- name: Run Feature Request Manager
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v34
ref: v35
path: ./actions
- name: Install Actions
run: npm install --production --prefix ./actions
@@ -13,7 +13,7 @@ jobs:
uses: actions/checkout@v2
with:
repository: 'microsoft/vscode-github-triage-actions'
ref: v34
ref: v35
path: ./actions
- name: Checkout Repo
if: github.event_name != 'issues'
@@ -14,7 +14,7 @@ jobs:
with:
repository: 'microsoft/vscode-github-triage-actions'
path: ./actions
ref: v34
ref: v35
- name: Install Actions
if: contains(github.event.issue.labels.*.name, 'testplan-item') || contains(github.event.issue.labels.*.name, 'invalid-testplan-item')
run: npm install --production --prefix ./actions
+50 -179
View File
@@ -4,7 +4,7 @@
# Excluding: *.test.ts
# ContextLines: 3
57 results - 39 files
42 results - 25 files
src/vs/base/browser/markdownRenderer.ts:
156 const strValue = values[0];
@@ -23,15 +23,6 @@ src/vs/base/browser/markdownRenderer.ts:
243 // allowedTags should included everything that markdown renders to.
244 // Since we have our own sanitize function for marked, it's possible we missed some tag so let insane make sure.
src/vs/base/browser/ui/codicons/codiconStyles.ts:
20 for (let c of iconRegistry.all) {
21 rules.push(formatRule(c));
22 }
23: codiconStyleSheet.innerHTML = rules.join('\n');
24 }
25
26 const delayer = new RunOnceScheduler(updateAll, 0);
src/vs/base/browser/ui/contextview/contextview.ts:
157 this.shadowRootHostElement = DOM.$('.shadow-root-host');
158 this.container.appendChild(this.shadowRootHostElement);
@@ -41,15 +32,6 @@ src/vs/base/browser/ui/contextview/contextview.ts:
162 ${SHADOW_ROOT_CSS}
163 </style>
src/vs/base/browser/ui/list/listWidget.ts:
826
827 const newStyles = content.join('\n');
828 if (newStyles !== this.styleElement.innerHTML) {
829: this.styleElement.innerHTML = newStyles;
830 }
831 }
832 }
src/vs/base/browser/ui/menu/menu.ts:
229 private initializeStyleSheet(container: HTMLElement): void {
230 if (isInShadowDOM(container)) {
@@ -74,15 +56,6 @@ src/vs/base/browser/ui/menu/menubar.ts:
555
556 let mnemonicMatches = MENU_MNEMONIC_REGEX.exec(label);
src/vs/base/browser/ui/sash/sash.ts:
266 }
267 }
268
269: style.innerHTML = `* { cursor: ${cursor} !important; }`;
270 };
271
272 const disposables = new DisposableStore();
src/vs/base/browser/ui/selectBox/selectBoxCustom.ts:
367 content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-disabled:hover { outline: none !important; }`);
368 }
@@ -92,22 +65,13 @@ src/vs/base/browser/ui/selectBox/selectBoxCustom.ts:
372 this.applyStyles();
373 }
705 });
706
707
708: container.innerHTML = this.options[longest].text + (!!this.options[longest].decoratorRight ? (this.options[longest].decoratorRight + ' ') : '');
709 elementWidth = dom.getTotalWidth(container);
710 }
711
src/vs/base/browser/ui/tree/abstractTree.ts:
1476
1477 const newStyles = content.join('\n');
1478 if (newStyles !== this.styleElement.innerHTML) {
1479: this.styleElement.innerHTML = newStyles;
1480 }
1481
1482 this.view.style(styles);
680 });
681
682
683: container.innerHTML = this.options[longest].text + (!!this.options[longest].decoratorRight ? (this.options[longest].decoratorRight + ' ') : '');
684 elementWidth = dom.getTotalWidth(container);
685 }
686
src/vs/base/parts/quickinput/browser/quickInput.ts:
1693 }
@@ -227,29 +191,29 @@ src/vs/editor/standalone/browser/colorizer.ts:
46 }
src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts:
211 if (!this._globalStyleElement) {
212 this._globalStyleElement = dom.createStyleSheet();
213 this._globalStyleElement.className = 'monaco-colors';
214: this._globalStyleElement.innerHTML = this._css;
215 this._styleElements.push(this._globalStyleElement);
216 }
217 return Disposable.None;
212 if (!this._globalStyleElement) {
213 this._globalStyleElement = dom.createStyleSheet();
214 this._globalStyleElement.className = 'monaco-colors';
215: this._globalStyleElement.innerHTML = this._css;
216 this._styleElements.push(this._globalStyleElement);
217 }
218 return Disposable.None;
220 private _registerShadowDomContainer(domNode: HTMLElement): IDisposable {
221 const styleElement = dom.createStyleSheet(domNode);
222 styleElement.className = 'monaco-colors';
223: styleElement.innerHTML = this._css;
224 this._styleElements.push(styleElement);
225 return {
226 dispose: () => {
221 private _registerShadowDomContainer(domNode: HTMLElement): IDisposable {
222 const styleElement = dom.createStyleSheet(domNode);
223 styleElement.className = 'monaco-colors';
224: styleElement.innerHTML = this._css;
225 this._styleElements.push(styleElement);
226 return {
227 dispose: () => {
290 ruleCollector.addRule(generateTokensCSSForColorMap(colorMap));
291
292 this._css = cssRules.join('\n');
293: this._styleElements.forEach(styleElement => styleElement.innerHTML = this._css);
294
295 TokenizationRegistry.setColorMap(colorMap);
296 this._onColorThemeChange.fire(theme);
291 ruleCollector.addRule(generateTokensCSSForColorMap(colorMap));
292
293 this._css = cssRules.join('\n');
294: this._styleElements.forEach(styleElement => styleElement.innerHTML = this._css);
295
296 TokenizationRegistry.setColorMap(colorMap);
297 this._onColorThemeChange.fire(theme);
src/vs/editor/test/browser/controller/imeTester.ts:
55 let content = this._model.getModelLineContent(i);
@@ -268,24 +232,6 @@ src/vs/editor/test/browser/controller/imeTester.ts:
74
75 let startBtn = document.createElement('button');
src/vs/workbench/browser/parts/compositePart.ts:
414 const $this = this;
415 return {
416 updateTitle: (id, title, keybinding) => {
417: titleLabel.innerHTML = strings.escape(title);
418 titleLabel.title = keybinding ? nls.localize('titleTooltip', "{0} ({1})", title, keybinding) : title;
419 },
420
src/vs/workbench/browser/parts/statusbar/statusbarPart.ts:
672 this.styleElement = createStyleSheet(container);
673 }
674
675: this.styleElement.innerHTML = `.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; }`;
676 }
677
678 private doCreateStatusItem(id: string, alignment: StatusbarAlignment, ...extraClasses: string[]): HTMLElement {
src/vs/workbench/contrib/comments/browser/commentsView.ts:
109 content.push(`.comments-panel .comments-panel-container .text code { color: ${codeTextForegroundColor}; }`);
110 }
@@ -321,66 +267,30 @@ src/vs/workbench/contrib/debug/browser/repl.ts:
311 font-size: ${fontSize}px;
312 font-family: ${fontFamily};
src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts:
84 this.styleElement = createStyleSheet(container);
85 }
86
87: this.styleElement.innerHTML = `.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor} !important; }`;
88 }
89
90 private getColorKey(noFolderColor: string, debuggingColor: string, normalColor: string): string {
src/vs/workbench/contrib/files/browser/views/explorerView.ts:
812
813 const newStyles = content.join('\n');
814 if (newStyles !== this.styleElement.innerHTML) {
815: this.styleElement.innerHTML = newStyles;
816 }
817 }
818
src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts:
222
223 const newStyles = content.join('\n');
224 if (newStyles !== this.styleElement.innerHTML) {
225: this.styleElement.innerHTML = newStyles;
226 }
227 }
228 }
src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts:
1033
1034 const newStyles = content.join('\n');
1035 if (newStyles !== this.styleElement.innerHTML) {
1036: this.styleElement.innerHTML = newStyles;
1037 }
1038 }
1039
src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts:
443
444 private getMarkdownDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement {
445 const dragImageContainer = DOM.$('.cell-drag-image.monaco-list-row.focused.markdown-cell-row');
446: dragImageContainer.innerHTML = templateData.container.outerHTML;
447
448 // Remove all rendered content nodes after the
449 const markdownContent = dragImageContainer.querySelector('.cell.markdown')!;
454
455 private getMarkdownDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement {
456 const dragImageContainer = DOM.$('.cell-drag-image.monaco-list-row.focused.markdown-cell-row');
457: dragImageContainer.innerHTML = templateData.container.outerHTML;
458
459 // Remove all rendered content nodes after the
460 const markdownContent = dragImageContainer.querySelector('.cell.markdown')!;
587
588 private getDragImageImpl(templateData: BaseCellRenderTemplate, editor: ICodeEditor, type: 'code' | 'markdown'): HTMLElement | null {
589 const dragImageContainer = DOM.$(`.cell-drag-image.monaco-list-row.focused.${type}-cell-row`);
590: dragImageContainer.innerHTML = templateData.container.innerHTML;
591
592 const editorContainer = dragImageContainer.querySelector('.cell-editor-container');
593 if (!editorContainer) {
598
599 private getDragImageImpl(templateData: BaseCellRenderTemplate, editor: ICodeEditor, type: 'code' | 'markdown'): HTMLElement | null {
600 const dragImageContainer = DOM.$(`.cell-drag-image.monaco-list-row.focused.${type}-cell-row`);
601: dragImageContainer.innerHTML = templateData.container.innerHTML;
602
603 const editorContainer = dragImageContainer.querySelector('.cell-editor-container');
604 if (!editorContainer) {
599 return null;
600 }
601
602: editorContainer.innerHTML = richEditorText;
603
604 return dragImageContainer;
605 }
610 return null;
611 }
612
613: editorContainer.innerHTML = richEditorText;
614
615 return dragImageContainer;
616 }
src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts:
375 addMouseoverListeners(outputNode, outputId);
@@ -400,24 +310,6 @@ src/vs/workbench/contrib/sash/browser/sash.ts:
40 .monaco-sash.horizontal { cursor: ns-resize; left: 0; width: 100%; height: ${size}px; }
41 .monaco-sash:not(.disabled).orthogonal-start::before, .monaco-sash:not(.disabled).orthogonal-end::after { content: ' '; height: ${size * 2}px; width: ${size * 2}px; z-index: 100; display: block; cursor: all-scroll; position: absolute; }
src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts:
1301
1302 private setViewState(state: IViewState): void {
1303 this.viewState = state;
1304: this.stylesheet.innerHTML = `
1305 .monaco-editor .dirty-diff-modified,.monaco-editor .dirty-diff-added{border-left-width:${state.width}px;}
1306 .monaco-editor .dirty-diff-modified, .monaco-editor .dirty-diff-added, .monaco-editor .dirty-diff-deleted {
1307 opacity: ${state.visibility === 'always' ? 1 : 0};
src/vs/workbench/contrib/watermark/browser/watermark.ts:
141 const dd = dom.append(dl, $('dd'));
142 const keybinding = new KeybindingLabel(dd, OS, { renderUnboundKeybindings: true });
143 keybinding.set(this.keybindingService.lookupKeybinding(entry.id));
144: dd.innerHTML = keybinding.element.outerHTML;
145 });
146 };
147
src/vs/workbench/contrib/webview/browser/webviewIconManager.ts:
61 }
62 }
@@ -460,24 +352,3 @@ src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts:
307 this.content.appendChild(innerContent);
308
309 model.snippets.forEach((snippet, i) => {
src/vs/workbench/services/textMate/browser/abstractTextMateService.ts:
312 grammarFactory.setTheme(theme, tokenColorMap);
313 let colorMap = AbstractTextMateService._toColorMap(tokenColorMap);
314 let cssRules = generateTokensCSSForColorMap(colorMap);
315: this._styleElement.innerHTML = cssRules;
316 TokenizationRegistry.setColorMap(colorMap);
317 }
318
src/vs/workbench/services/themes/browser/workbenchThemeService.ts:
692 const elStyle = document.createElement('style');
693 elStyle.type = 'text/css';
694 elStyle.className = rulesClassName;
695: elStyle.innerHTML = styleSheetContent;
696 document.head.appendChild(elStyle);
697 } else {
698: (<HTMLStyleElement>themeStyles[0]).innerHTML = styleSheetContent;
699 }
700 }
701
+15 -15
View File
@@ -5,21 +5,21 @@
14 results - 4 files
src/vs/base/browser/dom.ts:
81 };
82
83: /** @deprecated ES6 - use classList*/
84 export const hasClass: (node: HTMLElement | SVGElement, className: string) => boolean = _classList.hasClass.bind(_classList);
83 };
84
85: /** @deprecated ES6 - use classList*/
86 export const addClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.addClass.bind(_classList);
86 export const hasClass: (node: HTMLElement | SVGElement, className: string) => boolean = _classList.hasClass.bind(_classList);
87: /** @deprecated ES6 - use classList*/
88 export const addClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.addClasses.bind(_classList);
88 export const addClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.addClass.bind(_classList);
89: /** @deprecated ES6 - use classList*/
90 export const removeClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.removeClass.bind(_classList);
90 export const addClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.addClasses.bind(_classList);
91: /** @deprecated ES6 - use classList*/
92 export const removeClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.removeClasses.bind(_classList);
92 export const removeClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.removeClass.bind(_classList);
93: /** @deprecated ES6 - use classList*/
94 export const toggleClass: (node: HTMLElement | SVGElement, className: string, shouldHaveIt?: boolean) => void = _classList.toggleClass.bind(_classList);
95
94 export const removeClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.removeClasses.bind(_classList);
95: /** @deprecated ES6 - use classList*/
96 export const toggleClass: (node: HTMLElement | SVGElement, className: string, shouldHaveIt?: boolean) => void = _classList.toggleClass.bind(_classList);
97
src/vs/base/common/arrays.ts:
401
@@ -66,8 +66,8 @@ src/vs/base/common/strings.ts:
170 */
171 export function endsWith(haystack: string, needle: string): boolean {
861
862 /**
863: * @deprecated ES6
864 */
865 export function repeat(s: string, count: number): string {
857
858 /**
859: * @deprecated ES6
860 */
861 export function repeat(s: string, count: number): string {
+1 -1
View File
@@ -53,7 +53,7 @@ async function uploadBlob(blobService: azure.BlobService, quality: string, blobN
}
};
await new Promise((c, e) => blobService.createBlockBlobFromLocalFile(quality, blobName, filePath, blobOptions, err => err ? e(err) : c()));
await new Promise<void>((c, e) => blobService.createBlockBlobFromLocalFile(quality, blobName, filePath, blobOptions, err => err ? e(err) : c()));
}
function getEnv(name: string): string {
@@ -17,7 +17,7 @@ const fileNames = [
];
async function assertContainer(blobService: azure.BlobService, container: string): Promise<void> {
await new Promise((c, e) => blobService.createContainerIfNotExists(container, { publicAccessLevel: 'blob' }, err => err ? e(err) : c()));
await new Promise<void>((c, e) => blobService.createContainerIfNotExists(container, { publicAccessLevel: 'blob' }, err => err ? e(err) : c()));
}
async function doesBlobExist(blobService: azure.BlobService, container: string, blobName: string): Promise<boolean | undefined> {
@@ -33,7 +33,7 @@ async function uploadBlob(blobService: azure.BlobService, container: string, blo
}
};
await new Promise((c, e) => blobService.createBlockBlobFromLocalFile(container, blobName, file, blobOptions, err => err ? e(err) : c()));
await new Promise<void>((c, e) => blobService.createBlockBlobFromLocalFile(container, blobName, file, blobOptions, err => err ? e(err) : c()));
}
async function publish(commit: string, files: readonly string[]): Promise<void> {
+12 -2
View File
@@ -52,6 +52,11 @@ function prepareDebPackage(arch) {
.pipe(replace('@@LICENSE@@', product.licenseName))
.pipe(rename('usr/share/appdata/' + product.applicationName + '.appdata.xml'));
const workspaceMime = gulp.src('resources/code-workspace.xml', { base: '.' })
.pipe(replace('@@NAME_LONG@@', product.nameLong))
.pipe(replace('@@NAME@@', product.applicationName))
.pipe(rename('usr/share/mime/packages/' + product.applicationName + '-workspace.xml'));
const icon = gulp.src('resources/linux/code.png', { base: '.' })
.pipe(rename('usr/share/pixmaps/' + product.linuxIconName + '.png'));
@@ -95,7 +100,7 @@ function prepareDebPackage(arch) {
.pipe(replace('@@UPDATEURL@@', product.updateUrl || '@@UPDATEURL@@'))
.pipe(rename('DEBIAN/postinst'));
const all = es.merge(control, postinst, postrm, prerm, desktops, appdata, icon, bash_completion, zsh_completion, code);
const all = es.merge(control, postinst, postrm, prerm, desktops, appdata, workspaceMime, icon, bash_completion, zsh_completion, code);
return all.pipe(vfs.dest(destination));
};
@@ -143,6 +148,11 @@ function prepareRpmPackage(arch) {
.pipe(replace('@@LICENSE@@', product.licenseName))
.pipe(rename('usr/share/appdata/' + product.applicationName + '.appdata.xml'));
const workspaceMime = gulp.src('resources/code-workspace.xml', { base: '.' })
.pipe(replace('@@NAME_LONG@@', product.nameLong))
.pipe(replace('@@NAME@@', product.applicationName))
.pipe(rename('BUILD/usr/share/mime/packages/' + product.applicationName + '-workspace.xml'));
const icon = gulp.src('resources/linux/code.png', { base: '.' })
.pipe(rename('BUILD/usr/share/pixmaps/' + product.linuxIconName + '.png'));
@@ -173,7 +183,7 @@ function prepareRpmPackage(arch) {
const specIcon = gulp.src('resources/linux/rpm/code.xpm', { base: '.' })
.pipe(rename('SOURCES/' + product.applicationName + '.xpm'));
const all = es.merge(code, desktops, appdata, icon, bash_completion, zsh_completion, spec, specIcon);
const all = es.merge(code, desktops, appdata, workspaceMime, icon, bash_completion, zsh_completion, spec, specIcon);
return all.pipe(vfs.dest(getRpmBuildPath(rpmArch)));
};
+1 -1
View File
@@ -996,7 +996,7 @@ function createResource(project: string, slug: string, xlfFile: File, apiHostnam
* https://dev.befoolish.co/tx-docs/public/projects/updating-content#what-happens-when-you-update-files
*/
function updateResource(project: string, slug: string, xlfFile: File, apiHostname: string, credentials: string): Promise<any> {
return new Promise((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
const data = JSON.stringify({ content: xlfFile.contents.toString() });
const options = {
hostname: apiHostname,
+1 -1
View File
@@ -15,7 +15,7 @@ const yarn = process.platform === 'win32' ? 'yarn.cmd' : 'yarn';
const rootDir = path.resolve(__dirname, '..', '..');
function runProcess(command: string, args: ReadonlyArray<string> = []) {
return new Promise((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
const child = spawn(command, args, { cwd: rootDir, stdio: 'inherit', env: process.env });
child.on('exit', err => !err ? resolve() : process.exit(err ?? 1));
child.on('error', reject);
+9 -3
View File
@@ -6,7 +6,7 @@
import { lstat, Stats } from 'fs';
import * as os from 'os';
import * as path from 'path';
import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env } from 'vscode';
import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection } from 'vscode';
import TelemetryReporter from 'vscode-extension-telemetry';
import * as nls from 'vscode-nls';
import { Branch, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourceProvider } from './api/git';
@@ -1002,6 +1002,9 @@ export class CommandCenter {
}
await this._stageChanges(textEditor, [changes[index]]);
const firstStagedLine = changes[index].modifiedStartLineNumber - 1;
textEditor.selections = [new Selection(firstStagedLine, 0, firstStagedLine, 0)];
}
@command('git.stageSelectedRanges', { diff: true })
@@ -1049,6 +1052,9 @@ export class CommandCenter {
}
await this._revertChanges(textEditor, [...changes.slice(0, index), ...changes.slice(index + 1)]);
const firstStagedLine = changes[index].modifiedStartLineNumber - 1;
textEditor.selections = [new Selection(firstStagedLine, 0, firstStagedLine, 0)];
}
@command('git.revertSelectedRanges', { diff: true })
@@ -1070,7 +1076,9 @@ export class CommandCenter {
return;
}
const selectionsBeforeRevert = textEditor.selections;
await this._revertChanges(textEditor, selectedChanges);
textEditor.selections = selectionsBeforeRevert;
}
private async _revertChanges(textEditor: TextEditor, changes: LineChange[]): Promise<void> {
@@ -1083,7 +1091,6 @@ export class CommandCenter {
const originalUri = toGitUri(modifiedUri, '~');
const originalDocument = await workspace.openTextDocument(originalUri);
const selectionsBeforeRevert = textEditor.selections;
const visibleRangesBeforeRevert = textEditor.visibleRanges;
const result = applyLineChanges(originalDocument, modifiedDocument, changes);
@@ -1093,7 +1100,6 @@ export class CommandCenter {
await modifiedDocument.save();
textEditor.selections = selectionsBeforeRevert;
textEditor.revealRange(visibleRangesBeforeRevert[0]);
}
+1 -1
View File
@@ -21,7 +21,7 @@ class GitIgnoreDecorationProvider implements DecorationProvider {
constructor(private model: Model) {
this.onDidChangeDecorations = fireEvent(anyEvent<any>(
filterEvent(workspace.onDidSaveTextDocument, e => e.fileName.endsWith('.gitignore')),
filterEvent(workspace.onDidSaveTextDocument, e => /\.gitignore$|\.git\/info\/exclude$/.test(e.uri.path)),
model.onDidOpenRepository,
model.onDidCloseRepository
));
@@ -318,7 +318,6 @@ class RequestRouter {
'open',
'updateOpen',
'configure',
'configurePlugin',
]);
constructor(
@@ -490,7 +489,8 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ
private static readonly semanticCommands = new Set<keyof TypeScriptRequests>([
'geterr',
'geterrForProject',
'projectInfo'
'projectInfo',
'configurePlugin',
]);
/**
@@ -349,7 +349,7 @@ import { doesNotThrow, equal, ok, deepEqual, throws } from 'assert';
term1Write.fire('write1');
// Wait until the data is written
await new Promise(resolve => { resolveOnceDataWritten = resolve; });
await new Promise<void>(resolve => { resolveOnceDataWritten = resolve; });
term1Close.fire();
@@ -690,11 +690,18 @@ suite('notebook workflow', () => {
await vscode.commands.executeCommand('notebook.focusTop');
const cell = editor.document.cells[0];
assert.equal(cell.outputs.length, 0);
let metadataChangeEvent = getEventOncePromise<vscode.NotebookCellMetadataChangeEvent>(vscode.notebook.onDidChangeCellMetadata);
cell.metadata.runnable = false;
await metadataChangeEvent;
await vscode.commands.executeCommand('notebook.cell.execute');
assert.equal(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work
metadataChangeEvent = getEventOncePromise<vscode.NotebookCellMetadataChangeEvent>(vscode.notebook.onDidChangeCellMetadata);
cell.metadata.runnable = true;
await metadataChangeEvent;
await vscode.commands.executeCommand('notebook.cell.execute');
assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked
@@ -711,11 +718,17 @@ suite('notebook workflow', () => {
const cell = editor.document.cells[0];
assert.equal(cell.outputs.length, 0);
let metadataChangeEvent = getEventOncePromise<vscode.NotebookDocumentMetadataChangeEvent>(vscode.notebook.onDidChangeNotebookDocumentMetadata);
editor.document.metadata.runnable = false;
await metadataChangeEvent;
await vscode.commands.executeCommand('notebook.execute');
assert.equal(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work
metadataChangeEvent = getEventOncePromise<vscode.NotebookDocumentMetadataChangeEvent>(vscode.notebook.onDidChangeNotebookDocumentMetadata);
editor.document.metadata.runnable = true;
await metadataChangeEvent;
await vscode.commands.executeCommand('notebook.execute');
assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked
@@ -1032,15 +1045,14 @@ suite('metadata', () => {
});
suite('regression', () => {
test('microsoft/vscode-github-issue-notebooks#26. Insert template cell in the new empty document', async function () {
assertInitalState();
const resource = await createRandomFile('', undefined, 'empty', '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), '');
assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript');
await saveFileAndCloseAll(resource);
});
// test('microsoft/vscode-github-issue-notebooks#26. Insert template cell in the new empty document', async function () {
// assertInitalState();
// await vscode.commands.executeCommand('workbench.action.files.newUntitledFile', { "viewType": "notebookCoreTest" });
// assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first');
// assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), '');
// assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript');
// await vscode.commands.executeCommand('workbench.action.closeAllEditors');
// });
test('#97830, #97764. Support switch to other editor types', async function () {
assertInitalState();
@@ -1073,7 +1085,7 @@ suite('regression', () => {
// now it's dirty, open the resource with notebook editor should open a new one
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.notEqual(vscode.notebook.activeNotebookEditor, undefined, 'notebook first');
assert.notEqual(vscode.window.activeTextEditor, undefined);
// assert.notEqual(vscode.window.activeTextEditor, undefined);
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
+1 -1
View File
@@ -76,7 +76,7 @@
},
{
"name": "ms-vscode.js-debug-companion",
"version": "1.0.7",
"version": "1.0.8",
"repo": "https://github.com/microsoft/vscode-js-debug-companion",
"metadata": {
"id": "99cb0b7f-7354-4278-b8da-6cc79972169d",
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-@@NAME@@-workspace">
<comment>@@NAME_LONG@@ Workspace</comment>
<glob pattern="*.code-workspace"/>
</mime-type>
</mime-info>
+1 -1
View File
@@ -8,7 +8,7 @@ Type=Application
StartupNotify=false
StartupWMClass=@@NAME_SHORT@@
Categories=Utility;TextEditor;Development;IDE;
MimeType=text/plain;inode/directory;
MimeType=text/plain;inode/directory;application/x-@@NAME@@-workspace;
Actions=new-empty-window;
Keywords=vscode;
+5
View File
@@ -18,6 +18,11 @@ if hash desktop-file-install 2>/dev/null; then
desktop-file-install /usr/share/applications/@@NAME@@-url-handler.desktop
fi
# Update mimetype database to pickup workspace mimetype
if hash update-mime-database 2>/dev/null; then
update-mime-database /usr/share/mime
fi
if [ "@@NAME@@" != "code-oss" ]; then
# Remove the legacy bin command if this is the stable build
if [ "@@NAME@@" = "code" ]; then
+6 -1
View File
@@ -3,4 +3,9 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
rm -f /usr/bin/@@NAME@@
rm -f /usr/bin/@@NAME@@
# Update mimetype database for removed workspace mimetype
if hash update-mime-database 2>/dev/null; then
update-mime-database /usr/share/mime
fi
+8
View File
@@ -23,6 +23,7 @@ mkdir -p %{buildroot}/usr/share/zsh/site-functions
cp -r usr/share/@@NAME@@/* %{buildroot}/usr/share/@@NAME@@
cp -r usr/share/applications/@@NAME@@.desktop %{buildroot}/usr/share/applications
cp -r usr/share/applications/@@NAME@@-url-handler.desktop %{buildroot}/usr/share/applications
cp -r usr/share/mime/packages/@@NAME@@-workspace.xml %{buildroot}/usr/share/mime/packages/@@NAME-workspace.xml
cp -r usr/share/pixmaps/@@ICON@@.png %{buildroot}/usr/share/pixmaps
cp usr/share/bash-completion/completions/@@NAME@@ %{buildroot}/usr/share/bash-completion/completions/@@NAME@@
cp usr/share/zsh/site-functions/_@@NAME@@ %{buildroot}/usr/share/zsh/site-functions/_@@NAME@@
@@ -46,17 +47,24 @@ ln -sf /usr/share/@@NAME@@/bin/@@NAME@@ %{_bindir}/@@NAME@@
# fi
#fi
# Update mimetype database to pickup workspace mimetype
update-mime-database /usr/share/mime &> /dev/null || :
%postun
if [ $1 = 0 ]; then
rm -f /usr/bin/@@NAME@@
fi
# Update mimetype database for removed workspace mimetype
update-mime-database /usr/share/mime &> /dev/null || :
%files
%defattr(-,root,root)
/usr/share/@@NAME@@/
/usr/share/applications/@@NAME@@.desktop
/usr/share/applications/@@NAME@@-url-handler.desktop
/usr/share/mime/packages/@@NAME-workspace.xml
/usr/share/pixmaps/@@ICON@@.png
/usr/share/bash-completion/completions/@@NAME@@
/usr/share/zsh/site-functions/_@@NAME@@
Binary file not shown.
@@ -20,7 +20,7 @@ function initialize() {
for (let c of iconRegistry.all) {
rules.push(formatRule(c));
}
codiconStyleSheet.innerHTML = rules.join('\n');
codiconStyleSheet.textContent = rules.join('\n');
}
const delayer = new RunOnceScheduler(updateAll, 0);
+1 -4
View File
@@ -824,10 +824,7 @@ export class DefaultStyleController implements IStyleController {
content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`);
}
const newStyles = content.join('\n');
if (newStyles !== this.styleElement.innerHTML) {
this.styleElement.innerHTML = newStyles;
}
this.styleElement.textContent = content.join('\n');
}
}
+14 -8
View File
@@ -532,25 +532,31 @@ export class MenuBar extends Disposable {
// Update the button label to reflect mnemonics
if (this.options.enableMnemonics) {
let innerHtml = strings.escape(label);
let cleanLabel = strings.escape(label);
// This is global so reset it
MENU_ESCAPED_MNEMONIC_REGEX.lastIndex = 0;
let escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(innerHtml);
let escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(cleanLabel);
// We can't use negative lookbehind so we match our negative and skip
while (escMatch && escMatch[1]) {
escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(innerHtml);
escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(cleanLabel);
}
const replaceDoubleEscapes = (str: string) => str.replace(/&amp;&amp;/g, '&amp;');
if (escMatch) {
innerHtml = `${innerHtml.substr(0, escMatch.index)}<mnemonic aria-hidden="true">${escMatch[3]}</mnemonic>${innerHtml.substr(escMatch.index + escMatch[0].length)}`;
titleElement.innerText = '';
titleElement.append(
strings.ltrim(replaceDoubleEscapes(cleanLabel.substr(0, escMatch.index)), ' '),
$('mnemonic', { 'aria-hidden': 'true' }, escMatch[3]),
strings.rtrim(replaceDoubleEscapes(cleanLabel.substr(escMatch.index + escMatch[0].length)), ' ')
);
} else {
titleElement.innerText = replaceDoubleEscapes(cleanLabel).trim();
}
innerHtml = innerHtml.replace(/&amp;&amp;/g, '&amp;');
titleElement.innerHTML = innerHtml;
} else {
titleElement.innerHTML = cleanMenuLabel.replace(/&&/g, '&');
titleElement.innerText = cleanMenuLabel.replace(/&&/g, '&');
}
let mnemonicMatches = MENU_MNEMONIC_REGEX.exec(label);
+1 -1
View File
@@ -266,7 +266,7 @@ export class Sash extends Disposable {
}
}
style.innerHTML = `* { cursor: ${cursor} !important; }`;
style.textContent = `* { cursor: ${cursor} !important; }`;
};
const disposables = new DisposableStore();
+1 -5
View File
@@ -1474,11 +1474,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
content.push(`.monaco-list${suffix} .monaco-tl-indent > .indent-guide.active { border-color: ${styles.treeIndentGuidesStroke}; }`);
}
const newStyles = content.join('\n');
if (newStyles !== this.styleElement.innerHTML) {
this.styleElement.innerHTML = newStyles;
}
this.styleElement.textContent = content.join('\n');
this.view.style(styles);
}
+2
View File
@@ -477,6 +477,8 @@ export namespace Codicon {
export const vmConnect = new Codicon('vm-connect', { character: '\\eba9' });
export const cloud = new Codicon('cloud', { character: '\\ebaa' });
export const merge = new Codicon('merge', { character: '\\ebab' });
export const exportIcon = new Codicon('export', { character: '\\ebac' });
export const graphLeft = new Codicon('graph-left', { character: '\\ebad' });
}
+10 -1
View File
@@ -193,6 +193,11 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
processInfo.load = parseFloat(cpuUsage[i]);
}
if (!rootItem) {
reject(new Error(`Root process ${rootPid} not found`));
return;
}
resolve(rootItem);
}
});
@@ -228,7 +233,11 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
if (process.platform === 'linux') {
calculateLinuxCpuUsage();
} else {
resolve(rootItem);
if (!rootItem) {
reject(new Error(`Root process ${rootPid} not found`));
} else {
resolve(rootItem);
}
}
}
});
+2 -2
View File
@@ -40,7 +40,7 @@ const JSCHARDET_TO_ICONV_ENCODINGS: { [name: string]: string } = {
const UTF8 = 'utf8';
export async function resolveTerminalEncoding(verbose?: boolean): Promise<string> {
let rawEncodingPromise: Promise<string>;
let rawEncodingPromise: Promise<string | undefined>;
// Support a global environment variable to win over other mechanics
const cliEncodingEnv = process.env['VSCODE_CLI_ENCODING'];
@@ -54,7 +54,7 @@ export async function resolveTerminalEncoding(verbose?: boolean): Promise<string
// Windows: educated guess
else if (isWindows) {
rawEncodingPromise = new Promise<string>(resolve => {
rawEncodingPromise = new Promise<string | undefined>(resolve => {
if (verbose) {
console.log('Running "chcp" to detect terminal encoding...');
}
+5 -4
View File
@@ -12,6 +12,7 @@ import { mkdirp, rimraf } from 'vs/base/node/pfs';
import { open as _openZip, Entry, ZipFile } from 'yauzl';
import * as yazl from 'yazl';
import { CancellationToken } from 'vs/base/common/cancellation';
import { assertIsDefined } from 'vs/base/common/types';
export interface IExtractOptions {
overwrite?: boolean;
@@ -161,24 +162,24 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, tok
}
function openZip(zipFile: string, lazy: boolean = false): Promise<ZipFile> {
return new Promise((resolve, reject) => {
return new Promise<ZipFile>((resolve, reject) => {
_openZip(zipFile, lazy ? { lazyEntries: true } : undefined!, (error?: Error, zipfile?: ZipFile) => {
if (error) {
reject(toExtractError(error));
} else {
resolve(zipfile);
resolve(assertIsDefined(zipfile));
}
});
});
}
function openZipStream(zipFile: ZipFile, entry: Entry): Promise<Readable> {
return new Promise((resolve, reject) => {
return new Promise<Readable>((resolve, reject) => {
zipFile.openReadStream(entry, (error?: Error, stream?: Readable) => {
if (error) {
reject(toExtractError(error));
} else {
resolve(stream);
resolve(assertIsDefined(stream));
}
});
});
@@ -1416,7 +1416,7 @@ export class QuickInputController extends Disposable {
}
input(options: IInputOptions = {}, token: CancellationToken = CancellationToken.None): Promise<string | undefined> {
return new Promise<string>((resolve, reject) => {
return new Promise<string | undefined>((resolve) => {
if (token.isCancellationRequested) {
resolve(undefined);
return;
+1 -1
View File
@@ -527,7 +527,7 @@ suite('Async', () => {
r1Queue.queue(syncPromiseFactory);
return new Promise(c => setTimeout(() => c(), 0)).then(() => {
return new Promise<void>(c => setTimeout(() => c(), 0)).then(() => {
const r1Queue2 = queue.queueFor(URI.file('/some/path'));
assert.notEqual(r1Queue, r1Queue2); // previous one got disposed after finishing
});
+3 -3
View File
@@ -25,21 +25,21 @@ export class DeferredPromise<T> {
}
public complete(value: T) {
return new Promise(resolve => {
return new Promise<void>(resolve => {
this.completeCallback(value);
resolve();
});
}
public error(err: any) {
return new Promise(resolve => {
return new Promise<void>(resolve => {
this.errorCallback(err);
resolve();
});
}
public cancel() {
new Promise(resolve => {
new Promise<void>(resolve => {
this.errorCallback(canceled());
resolve();
});
+1 -1
View File
@@ -138,7 +138,7 @@ export async function main(argv: string[]): Promise<any> {
child.stdout!.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
child.stderr!.on('data', (data: Buffer) => console.log(data.toString('utf8').trim()));
await new Promise(c => child.once('exit', () => c()));
await new Promise<void>(c => child.once('exit', () => c()));
});
}
@@ -395,7 +395,7 @@ export class ThemeTrieElement {
}
}
export function generateTokensCSSForColorMap(colorMap: Color[]): string {
export function generateTokensCSSForColorMap(colorMap: readonly Color[]): string {
let rules: string[] = [];
for (let i = 1, len = colorMap.length; i < len; i++) {
let color = colorMap[i];
+13 -3
View File
@@ -13,6 +13,7 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { isDisposable, Disposable } from 'vs/base/common/lifecycle';
import { coalesce } from 'vs/base/common/arrays';
import { assertType } from 'vs/base/common/types';
export class Link implements ILink {
@@ -152,10 +153,13 @@ export function getLinks(model: ITextModel, token: CancellationToken): Promise<L
CommandsRegistry.registerCommand('_executeLinkProvider', async (accessor, ...args): Promise<ILink[]> => {
const [uri] = args;
if (!(uri instanceof URI)) {
return [];
let [uri, resolveCount] = args;
assertType(uri instanceof URI);
if (typeof resolveCount !== 'number') {
resolveCount = 0;
}
const model = accessor.get(IModelService).getModel(uri);
if (!model) {
return [];
@@ -164,6 +168,12 @@ CommandsRegistry.registerCommand('_executeLinkProvider', async (accessor, ...arg
if (!list) {
return [];
}
// resolve links
for (let i = 0; i < Math.min(resolveCount, list.links.length); i++) {
await list.links[i].resolve(CancellationToken.None);
}
const result = list.links.slice(0);
list.dispose();
return result;
@@ -290,7 +290,7 @@ suite('ParameterHintsModel', () => {
hintsModel.trigger({ triggerKind: modes.SignatureHelpTriggerKind.Invoke }, 0);
assert.strictEqual(-1, didRequestCancellationOf);
return new Promise((resolve, reject) =>
return new Promise<void>((resolve, reject) =>
hintsModel.onChangedHints(newParamterHints => {
try {
assert.strictEqual(0, didRequestCancellationOf);
@@ -104,7 +104,7 @@ suite('On type rename', () => {
await operations(testEditor);
return new Promise((resolve) => {
return new Promise<void>((resolve) => {
setTimeout(() => {
if (typeof expectedEndText === 'string') {
assert.equal(editor.getModel()!.getValue(), expectedEndText);
@@ -19,8 +19,8 @@ export class BracketSelectionRangeProvider implements SelectionRangeProvider {
result.push(bucket);
const ranges = new Map<string, LinkedList<Range>>();
await new Promise(resolve => BracketSelectionRangeProvider._bracketsRightYield(resolve, 0, model, position, ranges));
await new Promise(resolve => BracketSelectionRangeProvider._bracketsLeftYield(resolve, 0, model, position, ranges, bucket));
await new Promise<void>(resolve => BracketSelectionRangeProvider._bracketsRightYield(resolve, 0, model, position, ranges));
await new Promise<void>(resolve => BracketSelectionRangeProvider._bracketsLeftYield(resolve, 0, model, position, ranges, bucket));
}
return result;
@@ -89,7 +89,7 @@ suite('ConfigurationService', () => {
test('trigger configuration change event when file does not exist', async () => {
const testObject = disposables.add(new ConfigurationService(settingsResource, fileService));
await testObject.initialize();
return new Promise(async (c, e) => {
return new Promise<void>(async (c) => {
disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(() => {
assert.equal(testObject.getValue('foo'), 'bar');
c();
@@ -104,7 +104,7 @@ suite('ConfigurationService', () => {
await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }'));
await testObject.initialize();
return new Promise((c, e) => {
return new Promise<void>((c) => {
disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(async (e) => {
assert.equal(testObject.getValue('foo'), 'barz');
c();
@@ -101,7 +101,7 @@ export class ElectronExtensionHostDebugBroadcastChannel<TContext> extends Extens
});
});
await new Promise(r => server.listen(0, r));
await new Promise<void>(r => server.listen(0, r));
codeWindow.win.on('close', () => server.close());
return { rendererDebugPort: (server.address() as AddressInfo).port };
+1 -1
View File
@@ -186,5 +186,5 @@ export abstract class BaseWindowDriver implements IWindowDriver {
return { x, y };
}
abstract async openDevTools(): Promise<void>;
abstract openDevTools(): Promise<void>;
}
@@ -45,7 +45,6 @@ import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common
import { ExtensionsDownloader } from 'vs/platform/extensionManagement/node/extensionDownloader';
import { ExtensionsScanner, IMetadata } from 'vs/platform/extensionManagement/node/extensionsScanner';
import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle';
import { IStringDictionary } from 'vs/base/common/collections';
const INSTALL_ERROR_UNSET_UNINSTALLED = 'unsetUninstalled';
const INSTALL_ERROR_DOWNLOADING = 'downloading';
@@ -66,7 +65,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
private readonly extensionsScanner: ExtensionsScanner;
private reportedExtensions: Promise<IReportedExtension[]> | undefined;
private lastReportTimestamp = 0;
private readonly installingExtensions: Map<string, CancelablePromise<ILocalExtension>> = new Map<string, CancelablePromise<ILocalExtension>>();
private readonly installingExtensions = new Map<string, CancelablePromise<ILocalExtension>>();
private readonly uninstallingExtensions: Map<string, CancelablePromise<void>> = new Map<string, CancelablePromise<void>>();
private readonly manifestCache: ExtensionsManifestCache;
private readonly extensionsDownloader: ExtensionsDownloader;
@@ -105,16 +104,17 @@ export class ExtensionManagementService extends Disposable implements IExtension
}));
}
zip(extension: ILocalExtension): Promise<URI> {
async zip(extension: ILocalExtension): Promise<URI> {
this.logService.trace('ExtensionManagementService#zip', extension.identifier.id);
return this.collectFiles(extension)
.then(files => zip(path.join(tmpdir(), generateUuid()), files))
.then<URI>(path => URI.file(path));
const files = await this.collectFiles(extension);
const location = await zip(path.join(tmpdir(), generateUuid()), files);
return URI.file(location);
}
unzip(zipLocation: URI): Promise<IExtensionIdentifier> {
async unzip(zipLocation: URI): Promise<IExtensionIdentifier> {
this.logService.trace('ExtensionManagementService#unzip', zipLocation.toString());
return this.install(zipLocation).then(local => local.identifier);
const local = await this.install(zipLocation);
return local.identifier;
}
async getManifest(vsix: URI): Promise<IExtensionManifest> {
@@ -123,7 +123,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
return getManifest(zipPath);
}
private collectFiles(extension: ILocalExtension): Promise<IFile[]> {
private async collectFiles(extension: ILocalExtension): Promise<IFile[]> {
const collectFilesFromDirectory = async (dir: string): Promise<string[]> => {
let entries = await pfs.readdir(dir);
@@ -144,97 +144,103 @@ export class ExtensionManagementService extends Disposable implements IExtension
return promise;
};
return collectFilesFromDirectory(extension.location.fsPath)
.then(files => files.map(f => (<IFile>{ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f })));
const files = await collectFilesFromDirectory(extension.location.fsPath);
return files.map(f => (<IFile>{ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f }));
}
install(vsix: URI, isMachineScoped?: boolean): Promise<ILocalExtension> {
async install(vsix: URI, isMachineScoped?: boolean): Promise<ILocalExtension> {
this.logService.trace('ExtensionManagementService#install', vsix.toString());
return createCancelablePromise(token => {
return this.downloadVsix(vsix).then(downloadLocation => {
const zipPath = path.resolve(downloadLocation.fsPath);
return createCancelablePromise(async token => {
return getManifest(zipPath)
.then(manifest => {
const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
let operation: InstallOperation = InstallOperation.Install;
if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, product.version)) {
return Promise.reject(new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", identifier.id, product.version)));
}
const identifierWithVersion = new ExtensionIdentifierWithVersion(identifier, manifest.version);
return this.getInstalled(ExtensionType.User)
.then(installedExtensions => {
const existing = installedExtensions.filter(i => areSameExtensions(identifier, i.identifier))[0];
if (existing) {
isMachineScoped = isMachineScoped || existing.isMachineScoped;
operation = InstallOperation.Update;
if (identifierWithVersion.equals(new ExtensionIdentifierWithVersion(existing.identifier, existing.manifest.version))) {
return this.extensionsScanner.removeExtension(existing, 'existing').then(null, e => Promise.reject(new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", manifest.displayName || manifest.name))));
} else if (semver.gt(existing.manifest.version, manifest.version)) {
return this.uninstallExtension(existing);
}
} else {
// Remove the extension with same version if it is already uninstalled.
// Installing a VSIX extension shall replace the existing extension always.
return this.unsetUninstalledAndGetLocal(identifierWithVersion)
.then(existing => {
if (existing) {
return this.extensionsScanner.removeExtension(existing, 'existing').then(null, e => Promise.reject(new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", manifest.displayName || manifest.name))));
}
return undefined;
});
}
return undefined;
})
.then(() => {
this.logService.info('Installing the extension:', identifier.id);
this._onInstallExtension.fire({ identifier, zipPath });
return this.getGalleryMetadata(getGalleryExtensionId(manifest.publisher, manifest.name))
.then(
metadata => this.installFromZipPath(identifierWithVersion, zipPath, isMachineScoped ? { ...metadata, isMachineScoped } : metadata, operation, token),
() => this.installFromZipPath(identifierWithVersion, zipPath, isMachineScoped ? { isMachineScoped } : undefined, operation, token))
.then(
local => { this.logService.info('Successfully installed the extension:', identifier.id); return local; },
e => {
this.logService.error('Failed to install the extension:', identifier.id, e.message);
return Promise.reject(e);
});
});
});
});
const downloadLocation = await this.downloadVsix(vsix);
const zipPath = path.resolve(downloadLocation.fsPath);
const manifest = await getManifest(zipPath);
const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
let operation: InstallOperation = InstallOperation.Install;
if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, product.version)) {
throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", identifier.id, product.version));
}
const identifierWithVersion = new ExtensionIdentifierWithVersion(identifier, manifest.version);
const installedExtensions = await this.getInstalled(ExtensionType.User);
const existing = installedExtensions.find(i => areSameExtensions(identifier, i.identifier));
if (existing) {
isMachineScoped = isMachineScoped || existing.isMachineScoped;
operation = InstallOperation.Update;
if (identifierWithVersion.equals(new ExtensionIdentifierWithVersion(existing.identifier, existing.manifest.version))) {
try {
await this.extensionsScanner.removeExtension(existing, 'existing');
} catch (e) {
throw new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", manifest.displayName || manifest.name));
}
} else if (semver.gt(existing.manifest.version, manifest.version)) {
await this.uninstallExtension(existing);
}
} else {
// Remove the extension with same version if it is already uninstalled.
// Installing a VSIX extension shall replace the existing extension always.
const existing = await this.unsetUninstalledAndGetLocal(identifierWithVersion);
if (existing) {
try {
await this.extensionsScanner.removeExtension(existing, 'existing');
} catch (e) {
throw new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", manifest.displayName || manifest.name));
}
}
}
this.logService.info('Installing the extension:', identifier.id);
this._onInstallExtension.fire({ identifier, zipPath });
let metadata: IGalleryMetadata | undefined;
try {
metadata = await this.getGalleryMetadata(getGalleryExtensionId(manifest.publisher, manifest.name));
} catch (e) { /* Ignore */ }
try {
const local = await this.installFromZipPath(identifierWithVersion, zipPath, isMachineScoped ? { ...(metadata || {}), isMachineScoped } : metadata, operation, token);
this.logService.info('Successfully installed the extension:', identifier.id);
return local;
} catch (e) {
this.logService.error('Failed to install the extension:', identifier.id, e.message);
throw e;
}
});
}
private downloadVsix(vsix: URI): Promise<URI> {
private async downloadVsix(vsix: URI): Promise<URI> {
if (vsix.scheme === Schemas.file) {
return Promise.resolve(vsix);
return vsix;
}
if (!this.downloadService) {
throw new Error('Download service is not available');
}
const downloadedLocation = path.join(tmpdir(), generateUuid());
return this.downloadService.download(vsix, URI.file(downloadedLocation)).then(() => URI.file(downloadedLocation));
const downloadedLocation = URI.file(path.join(tmpdir(), generateUuid()));
await this.downloadService.download(vsix, downloadedLocation);
return downloadedLocation;
}
private installFromZipPath(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, metadata: IMetadata | undefined, operation: InstallOperation, token: CancellationToken): Promise<ILocalExtension> {
return this.toNonCancellablePromise(this.installExtension({ zipPath, identifierWithVersion, metadata }, token)
.then(local => this.installDependenciesAndPackExtensions(local, undefined)
.then(
() => local,
error => {
if (isNonEmptyArray(local.manifest.extensionDependencies)) {
this.logService.warn(`Cannot install dependencies of extension:`, local.identifier.id, error.message);
}
if (isNonEmptyArray(local.manifest.extensionPack)) {
this.logService.warn(`Cannot install packed extensions of extension:`, local.identifier.id, error.message);
}
return local;
}))
.then(
local => { this._onDidInstallExtension.fire({ identifier: identifierWithVersion.identifier, zipPath, local, operation }); return local; },
error => { this._onDidInstallExtension.fire({ identifier: identifierWithVersion.identifier, zipPath, operation, error }); return Promise.reject(error); }
));
private async installFromZipPath(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, metadata: IMetadata | undefined, operation: InstallOperation, token: CancellationToken): Promise<ILocalExtension> {
try {
const local = await this.installExtension({ zipPath, identifierWithVersion, metadata }, token);
try {
await this.installDependenciesAndPackExtensions(local, undefined);
} catch (error) {
if (isNonEmptyArray(local.manifest.extensionDependencies)) {
this.logService.warn(`Cannot install dependencies of extension:`, local.identifier.id, error.message);
}
if (isNonEmptyArray(local.manifest.extensionPack)) {
this.logService.warn(`Cannot install packed extensions of extension:`, local.identifier.id, error.message);
}
}
this._onDidInstallExtension.fire({ identifier: identifierWithVersion.identifier, zipPath, local, operation });
return local;
} catch (error) {
this._onDidInstallExtension.fire({ identifier: identifierWithVersion.identifier, zipPath, operation, error });
throw error;
}
}
async canInstall(extension: IGalleryExtension): Promise<boolean> {
@@ -243,17 +249,68 @@ export class ExtensionManagementService extends Disposable implements IExtension
async installFromGallery(extension: IGalleryExtension, isMachineScoped?: boolean): Promise<ILocalExtension> {
if (!this.galleryService.isEnabled()) {
return Promise.reject(new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")));
throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"));
}
const startTime = new Date().getTime();
const onDidInstallExtensionSuccess = (extension: IGalleryExtension, operation: InstallOperation, local: ILocalExtension) => {
try {
extension = await this.checkAndGetCompatibleVersion(extension);
} catch (error) {
const errorCode = error && (<ExtensionManagementError>error).code ? (<ExtensionManagementError>error).code : ERROR_UNKNOWN;
this.logService.error(`Failed to install extension:`, extension.identifier.id, error ? error.message : errorCode);
this.reportTelemetry(this.getTelemetryEvent(InstallOperation.Install), getGalleryExtensionTelemetryData(extension), undefined, error);
if (error instanceof Error) {
error.name = errorCode;
}
throw error;
}
const key = new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key();
let cancellablePromise = this.installingExtensions.get(key);
if (!cancellablePromise) {
cancellablePromise = createCancelablePromise(token => this.doInstallFromGallery(extension, !!isMachineScoped, token));
this.installingExtensions.set(key, cancellablePromise);
cancellablePromise.finally(() => this.installingExtensions.delete(key));
}
return cancellablePromise;
}
private async doInstallFromGallery(extension: IGalleryExtension, isMachineScoped: boolean, token: CancellationToken): Promise<ILocalExtension> {
const startTime = new Date().getTime();
let operation: InstallOperation = InstallOperation.Install;
this.logService.info('Installing extension:', extension.identifier.id);
this._onInstallExtension.fire({ identifier: extension.identifier, gallery: extension });
try {
const installed = await this.getInstalled(ExtensionType.User);
const existingExtension = installed.find(i => areSameExtensions(i.identifier, extension.identifier));
if (existingExtension) {
operation = InstallOperation.Update;
}
const installableExtension = await this.downloadInstallableExtension(extension, operation);
installableExtension.metadata.isMachineScoped = isMachineScoped || existingExtension?.isMachineScoped;
const local = await this.installExtension(installableExtension, token);
try { await this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)); } catch (error) { /* Ignore */ }
try {
await this.installDependenciesAndPackExtensions(local, existingExtension);
} catch (error) {
try { await this.uninstall(local); } catch (error) { /* Ignore */ }
throw error;
}
if (existingExtension && semver.neq(existingExtension.manifest.version, extension.version)) {
await this.setUninstalled(existingExtension);
}
this.logService.info(`Extensions installed successfully:`, extension.identifier.id);
this._onDidInstallExtension.fire({ identifier: extension.identifier, gallery: extension, local, operation });
this.reportTelemetry(this.getTelemetryEvent(operation), getGalleryExtensionTelemetryData(extension), new Date().getTime() - startTime, undefined);
};
return local;
const onDidInstallExtensionFailure = (extension: IGalleryExtension, operation: InstallOperation, error: Error) => {
} catch (error) {
const errorCode = error && (<ExtensionManagementError>error).code ? (<ExtensionManagementError>error).code : ERROR_UNKNOWN;
this.logService.error(`Failed to install extension:`, extension.identifier.id, error ? error.message : errorCode);
this._onDidInstallExtension.fire({ identifier: extension.identifier, gallery: extension, operation, error: errorCode });
@@ -261,162 +318,107 @@ export class ExtensionManagementService extends Disposable implements IExtension
if (error instanceof Error) {
error.name = errorCode;
}
};
try {
extension = await this.checkAndGetCompatibleVersion(extension);
} catch (error) {
onDidInstallExtensionFailure(extension, InstallOperation.Install, error);
return Promise.reject(error);
throw error;
}
const key = new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key();
let cancellablePromise = this.installingExtensions.get(key);
if (!cancellablePromise) {
this.logService.info('Installing extension:', extension.identifier.id);
this._onInstallExtension.fire({ identifier: extension.identifier, gallery: extension });
let operation: InstallOperation = InstallOperation.Install;
let cancellationToken: CancellationToken, successCallback: (local: ILocalExtension) => void, errorCallback: (e?: any) => any | null;
cancellablePromise = createCancelablePromise(token => { cancellationToken = token; return new Promise((c, e) => { successCallback = c; errorCallback = e; }); });
this.installingExtensions.set(key, cancellablePromise);
try {
const installed = await this.getInstalled(ExtensionType.User);
const existingExtension = installed.find(i => areSameExtensions(i.identifier, extension.identifier));
if (existingExtension) {
operation = InstallOperation.Update;
}
this.downloadInstallableExtension(extension, operation)
.then(installableExtension => {
installableExtension.metadata.isMachineScoped = isMachineScoped || existingExtension?.isMachineScoped;
return this.installExtension(installableExtension, cancellationToken)
.then(local => this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)).finally(() => { }).then(() => local));
})
.then(local => this.installDependenciesAndPackExtensions(local, existingExtension)
.then(() => local, error => this.uninstall(local).then(() => Promise.reject(error), () => Promise.reject(error))))
.then(
async local => {
if (existingExtension && semver.neq(existingExtension.manifest.version, extension.version)) {
await this.setUninstalled(existingExtension);
}
this.installingExtensions.delete(key);
onDidInstallExtensionSuccess(extension, operation, local);
successCallback(local);
},
error => {
this.installingExtensions.delete(key);
onDidInstallExtensionFailure(extension, operation, error);
errorCallback(error);
});
} catch (error) {
this.installingExtensions.delete(key);
onDidInstallExtensionFailure(extension, operation, error);
return Promise.reject(error);
}
}
return cancellablePromise;
}
private async checkAndGetCompatibleVersion(extension: IGalleryExtension): Promise<IGalleryExtension> {
if (await this.isMalicious(extension)) {
return Promise.reject(new ExtensionManagementError(nls.localize('malicious extension', "Can't install extension since it was reported to be problematic."), INSTALL_ERROR_MALICIOUS));
throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install extension since it was reported to be problematic."), INSTALL_ERROR_MALICIOUS);
}
const compatibleExtension = await this.galleryService.getCompatibleExtension(extension);
if (!compatibleExtension) {
return Promise.reject(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, product.version), INSTALL_ERROR_INCOMPATIBLE));
throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, product.version), INSTALL_ERROR_INCOMPATIBLE);
}
return compatibleExtension;
}
reinstallFromGallery(extension: ILocalExtension): Promise<void> {
async reinstallFromGallery(extension: ILocalExtension): Promise<void> {
this.logService.trace('ExtensionManagementService#reinstallFromGallery', extension.identifier.id);
if (!this.galleryService.isEnabled()) {
return Promise.reject(new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")));
throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"));
}
return this.findGalleryExtension(extension)
.then(galleryExtension => {
if (galleryExtension) {
return this.setUninstalled(extension)
.then(() => this.extensionsScanner.removeUninstalledExtension(extension)
.then(
() => this.installFromGallery(galleryExtension).then(),
e => Promise.reject(new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e))))));
}
return Promise.reject(new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled")));
});
const galleryExtension = await this.findGalleryExtension(extension);
if (!galleryExtension) {
throw new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled"));
}
await this.setUninstalled(extension);
try {
await this.extensionsScanner.removeUninstalledExtension(extension);
} catch (e) {
throw new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e)));
}
await this.installFromGallery(galleryExtension);
}
private getTelemetryEvent(operation: InstallOperation): string {
return operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install';
}
private isMalicious(extension: IGalleryExtension): Promise<boolean> {
return this.getExtensionsReport()
.then(report => getMaliciousExtensionsSet(report).has(extension.identifier.id));
private async isMalicious(extension: IGalleryExtension): Promise<boolean> {
const report = await this.getExtensionsReport();
return getMaliciousExtensionsSet(report).has(extension.identifier.id);
}
private downloadInstallableExtension(extension: IGalleryExtension, operation: InstallOperation): Promise<Required<InstallableExtension>> {
private async downloadInstallableExtension(extension: IGalleryExtension, operation: InstallOperation): Promise<Required<InstallableExtension>> {
const metadata = <IGalleryMetadata>{
id: extension.identifier.uuid,
publisherId: extension.publisherId,
publisherDisplayName: extension.publisherDisplayName,
};
this.logService.trace('Started downloading extension:', extension.identifier.id);
return this.extensionsDownloader.downloadExtension(extension, operation)
.then(
zip => {
const zipPath = zip.fsPath;
this.logService.info('Downloaded extension:', extension.identifier.id, zipPath);
return getManifest(zipPath)
.then(
manifest => (<Required<InstallableExtension>>{ zipPath, identifierWithVersion: new ExtensionIdentifierWithVersion(extension.identifier, manifest.version), metadata }),
error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING))
);
},
error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING)));
let zipPath;
try {
this.logService.trace('Started downloading extension:', extension.identifier.id);
const zip = await this.extensionsDownloader.downloadExtension(extension, operation);
this.logService.info('Downloaded extension:', extension.identifier.id, zipPath);
zipPath = zip.fsPath;
} catch (error) {
throw new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING);
}
try {
const manifest = await getManifest(zipPath);
return (<Required<InstallableExtension>>{ zipPath, identifierWithVersion: new ExtensionIdentifierWithVersion(extension.identifier, manifest.version), metadata });
} catch (error) {
throw new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING);
}
}
private installExtension(installableExtension: InstallableExtension, token: CancellationToken): Promise<ILocalExtension> {
return this.unsetUninstalledAndGetLocal(installableExtension.identifierWithVersion)
.then(
local => {
if (local) {
return local;
}
return this.extractAndInstall(installableExtension, token);
},
e => {
if (isMacintosh) {
return Promise.reject(new ExtensionManagementError(nls.localize('quitCode', "Unable to install the extension. Please Quit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED));
}
return Promise.reject(new ExtensionManagementError(nls.localize('exitCode', "Unable to install the extension. Please Exit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED));
});
private async installExtension(installableExtension: InstallableExtension, token: CancellationToken): Promise<ILocalExtension> {
try {
const local = await this.unsetUninstalledAndGetLocal(installableExtension.identifierWithVersion);
if (local) {
return local;
}
} catch (e) {
if (isMacintosh) {
throw new ExtensionManagementError(nls.localize('quitCode', "Unable to install the extension. Please Quit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED);
} else {
throw new ExtensionManagementError(nls.localize('exitCode', "Unable to install the extension. Please Exit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED);
}
}
return this.extractAndInstall(installableExtension, token);
}
private unsetUninstalledAndGetLocal(identifierWithVersion: ExtensionIdentifierWithVersion): Promise<ILocalExtension | null> {
return this.isUninstalled(identifierWithVersion)
.then<ILocalExtension | null>(isUninstalled => {
if (isUninstalled) {
this.logService.trace('Removing the extension from uninstalled list:', identifierWithVersion.identifier.id);
// If the same version of extension is marked as uninstalled, remove it from there and return the local.
return this.unsetUninstalled(identifierWithVersion)
.then(() => {
this.logService.info('Removed the extension from uninstalled list:', identifierWithVersion.identifier.id);
return this.getInstalled(ExtensionType.User);
})
.then(installed => installed.filter(i => new ExtensionIdentifierWithVersion(i.identifier, i.manifest.version).equals(identifierWithVersion))[0]);
}
return null;
});
private async unsetUninstalledAndGetLocal(identifierWithVersion: ExtensionIdentifierWithVersion): Promise<ILocalExtension | null> {
const isUninstalled = await this.isUninstalled(identifierWithVersion);
if (!isUninstalled) {
return null;
}
this.logService.trace('Removing the extension from uninstalled list:', identifierWithVersion.identifier.id);
// If the same version of extension is marked as uninstalled, remove it from there and return the local.
await this.unsetUninstalled(identifierWithVersion);
this.logService.info('Removed the extension from uninstalled list:', identifierWithVersion.identifier.id);
const installed = await this.getInstalled(ExtensionType.User);
return installed.find(i => new ExtensionIdentifierWithVersion(i.identifier, i.manifest.version).equals(identifierWithVersion)) || null;
}
private async extractAndInstall({ zipPath, identifierWithVersion, metadata }: InstallableExtension, token: CancellationToken): Promise<ILocalExtension> {
@@ -430,57 +432,56 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
private async installDependenciesAndPackExtensions(installed: ILocalExtension, existing: ILocalExtension | undefined): Promise<void> {
if (this.galleryService.isEnabled()) {
const dependenciesAndPackExtensions: string[] = installed.manifest.extensionDependencies || [];
if (installed.manifest.extensionPack) {
for (const extension of installed.manifest.extensionPack) {
// add only those extensions which are new in currently installed extension
if (!(existing && existing.manifest.extensionPack && existing.manifest.extensionPack.some(old => areSameExtensions({ id: old }, { id: extension })))) {
if (dependenciesAndPackExtensions.every(e => !areSameExtensions({ id: e }, { id: extension }))) {
dependenciesAndPackExtensions.push(extension);
}
if (!this.galleryService.isEnabled()) {
return;
}
const dependenciesAndPackExtensions: string[] = installed.manifest.extensionDependencies || [];
if (installed.manifest.extensionPack) {
for (const extension of installed.manifest.extensionPack) {
// add only those extensions which are new in currently installed extension
if (!(existing && existing.manifest.extensionPack && existing.manifest.extensionPack.some(old => areSameExtensions({ id: old }, { id: extension })))) {
if (dependenciesAndPackExtensions.every(e => !areSameExtensions({ id: e }, { id: extension }))) {
dependenciesAndPackExtensions.push(extension);
}
}
}
if (dependenciesAndPackExtensions.length) {
return this.getInstalled()
.then(installed => {
// filter out installed extensions
const names = dependenciesAndPackExtensions.filter(id => installed.every(({ identifier: galleryIdentifier }) => !areSameExtensions(galleryIdentifier, { id })));
if (names.length) {
return this.galleryService.query({ names, pageSize: dependenciesAndPackExtensions.length }, CancellationToken.None)
.then(galleryResult => {
const extensionsToInstall = galleryResult.firstPage;
return Promise.all(extensionsToInstall.map(e => this.installFromGallery(e)))
.then(undefined, errors => this.rollback(extensionsToInstall).then(() => Promise.reject(errors), () => Promise.reject(errors)));
});
}
return;
});
}
if (dependenciesAndPackExtensions.length) {
const installed = await this.getInstalled();
// filter out installed extensions
const names = dependenciesAndPackExtensions.filter(id => installed.every(({ identifier: galleryIdentifier }) => !areSameExtensions(galleryIdentifier, { id })));
if (names.length) {
const galleryResult = await this.galleryService.query({ names, pageSize: dependenciesAndPackExtensions.length }, CancellationToken.None);
const extensionsToInstall = galleryResult.firstPage;
try {
await Promise.all(extensionsToInstall.map(e => this.installFromGallery(e)));
} catch (error) {
try { await this.rollback(extensionsToInstall); } catch (e) { /* ignore */ }
throw error;
}
}
}
return Promise.resolve(undefined);
}
private rollback(extensions: IGalleryExtension[]): Promise<void> {
return this.getInstalled(ExtensionType.User)
.then(installed =>
Promise.all(installed.filter(local => extensions.some(galleryExtension => new ExtensionIdentifierWithVersion(local.identifier, local.manifest.version).equals(new ExtensionIdentifierWithVersion(galleryExtension.identifier, galleryExtension.version)))) // Check with version because we want to rollback the exact version
.map(local => this.uninstall(local))))
.then(() => undefined, () => undefined);
private async rollback(extensions: IGalleryExtension[]): Promise<void> {
const installed = await this.getInstalled(ExtensionType.User);
const extensionsToUninstall = installed.filter(local => extensions.some(galleryExtension => new ExtensionIdentifierWithVersion(local.identifier, local.manifest.version).equals(new ExtensionIdentifierWithVersion(galleryExtension.identifier, galleryExtension.version)))); // Check with version because we want to rollback the exact version
await Promise.all(extensionsToUninstall.map(local => this.uninstall(local)));
}
uninstall(extension: ILocalExtension): Promise<void> {
async uninstall(extension: ILocalExtension): Promise<void> {
this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
return this.toNonCancellablePromise(this.getInstalled(ExtensionType.User)
.then(installed => {
const extensionToUninstall = installed.filter(e => areSameExtensions(e.identifier, extension.identifier))[0];
if (extensionToUninstall) {
return this.checkForDependenciesAndUninstall(extensionToUninstall, installed).then(undefined, error => Promise.reject(this.joinErrors(error)));
} else {
return Promise.reject(new Error(nls.localize('notInstalled', "Extension '{0}' is not installed.", extension.manifest.displayName || extension.manifest.name)));
}
}));
const installed = await this.getInstalled(ExtensionType.User);
const extensionToUninstall = installed.find(e => areSameExtensions(e.identifier, extension.identifier));
if (!extensionToUninstall) {
throw new Error(nls.localize('notInstalled', "Extension '{0}' is not installed.", extension.manifest.displayName || extension.manifest.name));
}
try {
await this.checkForDependenciesAndUninstall(extensionToUninstall, installed);
} catch (error) {
throw this.joinErrors(error);
}
}
async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension> {
@@ -490,25 +491,27 @@ export class ExtensionManagementService extends Disposable implements IExtension
return local;
}
private getGalleryMetadata(extensionName: string): Promise<IGalleryMetadata | undefined> {
return this.findGalleryExtensionByName(extensionName)
.then(galleryExtension => galleryExtension ? <IGalleryMetadata>{ id: galleryExtension.identifier.uuid, publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId } : undefined);
private async getGalleryMetadata(extensionName: string): Promise<IGalleryMetadata | undefined> {
const galleryExtension = await this.findGalleryExtensionByName(extensionName);
return galleryExtension ? <IGalleryMetadata>{ id: galleryExtension.identifier.uuid, publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId } : undefined;
}
private findGalleryExtension(local: ILocalExtension): Promise<IGalleryExtension> {
private async findGalleryExtension(local: ILocalExtension): Promise<IGalleryExtension> {
if (local.identifier.uuid) {
return this.findGalleryExtensionById(local.identifier.uuid)
.then(galleryExtension => galleryExtension ? galleryExtension : this.findGalleryExtensionByName(local.identifier.id));
const galleryExtension = await this.findGalleryExtensionById(local.identifier.uuid);
return galleryExtension ? galleryExtension : this.findGalleryExtensionByName(local.identifier.id);
}
return this.findGalleryExtensionByName(local.identifier.id);
}
private findGalleryExtensionById(uuid: string): Promise<IGalleryExtension> {
return this.galleryService.query({ ids: [uuid], pageSize: 1 }, CancellationToken.None).then(galleryResult => galleryResult.firstPage[0]);
private async findGalleryExtensionById(uuid: string): Promise<IGalleryExtension> {
const galleryResult = await this.galleryService.query({ ids: [uuid], pageSize: 1 }, CancellationToken.None);
return galleryResult.firstPage[0];
}
private findGalleryExtensionByName(name: string): Promise<IGalleryExtension> {
return this.galleryService.query({ names: [name], pageSize: 1 }, CancellationToken.None).then(galleryResult => galleryResult.firstPage[0]);
private async findGalleryExtensionByName(name: string): Promise<IGalleryExtension> {
const galleryResult = await this.galleryService.query({ names: [name], pageSize: 1 }, CancellationToken.None);
return galleryResult.firstPage[0];
}
private joinErrors(errorOrErrors: (Error | string) | (Array<Error | string>)): Error {
@@ -521,20 +524,20 @@ export class ExtensionManagementService extends Disposable implements IExtension
}, new Error(''));
}
private checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[]): Promise<void> {
return this.preUninstallExtension(extension)
.then(() => {
const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed);
if (packedExtensions.length) {
return this.uninstallExtensions(extension, packedExtensions, installed);
}
return this.uninstallExtensions(extension, [], installed);
})
.then(() => this.postUninstallExtension(extension),
error => {
this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
return Promise.reject(error);
});
private async checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[]): Promise<void> {
try {
await this.preUninstallExtension(extension);
const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed);
if (packedExtensions.length) {
await this.uninstallExtensions(extension, packedExtensions, installed);
} else {
await this.uninstallExtensions(extension, [], installed);
}
} catch (error) {
await this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
throw error;
}
await this.postUninstallExtension(extension);
}
private async uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[]): Promise<void> {
@@ -605,33 +608,36 @@ export class ExtensionManagementService extends Disposable implements IExtension
return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier)));
}
private doUninstall(extension: ILocalExtension): Promise<void> {
return this.preUninstallExtension(extension)
.then(() => this.uninstallExtension(extension))
.then(() => this.postUninstallExtension(extension),
error => {
this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
return Promise.reject(error);
});
private async doUninstall(extension: ILocalExtension): Promise<void> {
try {
await this.preUninstallExtension(extension);
await this.uninstallExtension(extension);
} catch (error) {
await this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
throw error;
}
await this.postUninstallExtension(extension);
}
private preUninstallExtension(extension: ILocalExtension): Promise<void> {
return Promise.resolve(pfs.exists(extension.location.fsPath))
.then(exists => exists ? null : Promise.reject(new Error(nls.localize('notExists', "Could not find extension"))))
.then(() => {
this.logService.info('Uninstalling extension:', extension.identifier.id);
this._onUninstallExtension.fire(extension.identifier);
});
private async preUninstallExtension(extension: ILocalExtension): Promise<void> {
const exists = await pfs.exists(extension.location.fsPath);
if (!exists) {
throw new Error(nls.localize('notExists', "Could not find extension"));
}
this.logService.info('Uninstalling extension:', extension.identifier.id);
this._onUninstallExtension.fire(extension.identifier);
}
private uninstallExtension(local: ILocalExtension): Promise<void> {
private async uninstallExtension(local: ILocalExtension): Promise<void> {
let promise = this.uninstallingExtensions.get(local.identifier.id);
if (!promise) {
// Set all versions of the extension as uninstalled
promise = createCancelablePromise(token => this.extensionsScanner.scanUserExtensions(false)
.then(userExtensions => this.setUninstalled(...userExtensions.filter(u => areSameExtensions(u.identifier, local.identifier))))
.then(() => { this.uninstallingExtensions.delete(local.identifier.id); }));
promise = createCancelablePromise(async () => {
const userExtensions = await this.extensionsScanner.scanUserExtensions(false);
await this.setUninstalled(...userExtensions.filter(u => areSameExtensions(u.identifier, local.identifier)));
});
this.uninstallingExtensions.set(local.identifier.id, promise);
promise.finally(() => this.uninstallingExtensions.delete(local.identifier.id));
}
return promise;
}
@@ -643,7 +649,9 @@ export class ExtensionManagementService extends Disposable implements IExtension
this.logService.info('Successfully uninstalled extension:', extension.identifier.id);
// only report if extension has a mapped gallery extension. UUID identifies the gallery extension.
if (extension.identifier.uuid) {
await this.galleryService.reportStatistic(extension.manifest.publisher, extension.manifest.name, extension.manifest.version, StatisticType.Uninstall);
try {
await this.galleryService.reportStatistic(extension.manifest.publisher, extension.manifest.name, extension.manifest.version, StatisticType.Uninstall);
} catch (error) { /* ignore */ }
}
}
this.reportTelemetry('extensionGallery:uninstall', getLocalExtensionTelemetryData(extension), undefined, error);
@@ -659,8 +667,9 @@ export class ExtensionManagementService extends Disposable implements IExtension
return this.extensionsScanner.cleanUp();
}
private isUninstalled(identifier: ExtensionIdentifierWithVersion): Promise<boolean> {
return this.filterUninstalled(identifier).then(uninstalled => uninstalled.length === 1);
private async isUninstalled(identifier: ExtensionIdentifierWithVersion): Promise<boolean> {
const uninstalled = await this.filterUninstalled(identifier);
return uninstalled.length === 1;
}
private filterUninstalled(...identifiers: ExtensionIdentifierWithVersion[]): Promise<string[]> {
@@ -678,8 +687,8 @@ export class ExtensionManagementService extends Disposable implements IExtension
private setUninstalled(...extensions: ILocalExtension[]): Promise<{ [id: string]: boolean }> {
const ids: ExtensionIdentifierWithVersion[] = extensions.map(e => new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version));
return this.extensionsScanner.withUninstalledExtensions(uninstalled => {
const newUninstalled = (ids.reduce<IStringDictionary<boolean>>((result, id) => { result[id.key()] = true; return result; }, {}));
return { ...uninstalled, ...newUninstalled };
ids.forEach(id => uninstalled[id.key()] = true);
return uninstalled;
});
}
@@ -698,21 +707,16 @@ export class ExtensionManagementService extends Disposable implements IExtension
return this.reportedExtensions;
}
private updateReportCache(): Promise<IReportedExtension[]> {
this.logService.trace('ExtensionManagementService.refreshReportedCache');
return this.galleryService.getExtensionsReport()
.then(result => {
this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`);
return result;
}, err => {
this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report');
return [];
});
}
private toNonCancellablePromise<T>(promise: Promise<T>): Promise<T> {
return new Promise((c, e) => promise.then(result => c(result), error => e(error)));
private async updateReportCache(): Promise<IReportedExtension[]> {
try {
this.logService.trace('ExtensionManagementService.refreshReportedCache');
const result = await this.galleryService.getExtensionsReport();
this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`);
return result;
} catch (err) {
this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report');
return [];
}
}
private reportTelemetry(eventName: string, extensionData: any, duration?: number, error?: Error): void {
@@ -69,7 +69,12 @@ export class ExtensionsScanner extends Disposable {
promises.push(this.scanUserExtensions(true).then(null, e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, ERROR_SCANNING_USER_EXTENSIONS))));
}
return Promise.all<ILocalExtension[]>(promises).then(flatten, errors => Promise.reject(this.joinErrors(errors)));
try {
const result = await Promise.all(promises);
return flatten(result);
} catch (error) {
throw this.joinErrors(error);
}
}
async scanUserExtensions(excludeOutdated: boolean): Promise<ILocalExtension[]> {
@@ -145,20 +150,31 @@ export class ExtensionsScanner extends Disposable {
async withUninstalledExtensions<T>(fn: (uninstalled: IStringDictionary<boolean>) => T): Promise<T> {
return this.uninstalledFileLimiter.queue(async () => {
let result: T | null = null;
return pfs.readFile(this.uninstalledPath, 'utf8')
.then(undefined, err => err.code === 'ENOENT' ? Promise.resolve('{}') : Promise.reject(err))
.then<{ [id: string]: boolean }>(raw => { try { return JSON.parse(raw); } catch (e) { return {}; } })
.then(uninstalled => { result = fn(uninstalled); return uninstalled; })
.then(uninstalled => {
if (Object.keys(uninstalled).length === 0) {
return pfs.rimraf(this.uninstalledPath);
} else {
const raw = JSON.stringify(uninstalled);
return pfs.writeFile(this.uninstalledPath, raw);
}
})
.then(() => result);
let raw: string | undefined;
try {
raw = await pfs.readFile(this.uninstalledPath, 'utf8');
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
}
let uninstalled = {};
if (raw) {
try {
uninstalled = JSON.parse(raw);
} catch (e) { /* ignore */ }
}
const result = fn(uninstalled);
if (Object.keys(uninstalled).length) {
await pfs.writeFile(this.uninstalledPath, JSON.stringify(uninstalled));
} else {
await pfs.rimraf(this.uninstalledPath);
}
return result;
});
}
@@ -173,27 +189,35 @@ export class ExtensionsScanner extends Disposable {
await this.withUninstalledExtensions(uninstalled => delete uninstalled[new ExtensionIdentifierWithVersion(extension.identifier, extension.manifest.version).key()]);
}
private extractAtLocation(identifier: IExtensionIdentifier, zipPath: string, location: string, token: CancellationToken): Promise<void> {
private async extractAtLocation(identifier: IExtensionIdentifier, zipPath: string, location: string, token: CancellationToken): Promise<void> {
this.logService.trace(`Started extracting the extension from ${zipPath} to ${location}`);
return pfs.rimraf(location)
.then(
() => extract(zipPath, location, { sourcePath: 'extension', overwrite: true }, token)
.then(
() => this.logService.info(`Extracted extension to ${location}:`, identifier.id),
e => pfs.rimraf(location).finally(() => { })
.then(() => Promise.reject(new ExtensionManagementError(e.message, e instanceof ExtractError && e.type ? e.type : INSTALL_ERROR_EXTRACTING)))),
e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING)));
// Clean the location
try {
await pfs.rimraf(location);
} catch (e) {
throw new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING);
}
try {
await extract(zipPath, location, { sourcePath: 'extension', overwrite: true }, token);
this.logService.info(`Extracted extension to ${location}:`, identifier.id);
} catch (e) {
try { await pfs.rimraf(location); } catch (e) { /* Ignore */ }
throw new ExtensionManagementError(e.message, e instanceof ExtractError && e.type ? e.type : INSTALL_ERROR_EXTRACTING);
}
}
private rename(identifier: IExtensionIdentifier, extractPath: string, renamePath: string, retryUntil: number): Promise<void> {
return pfs.rename(extractPath, renamePath)
.then(undefined, error => {
if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) {
this.logService.info(`Failed renaming ${extractPath} to ${renamePath} with 'EPERM' error. Trying again...`, identifier.id);
return this.rename(identifier, extractPath, renamePath, retryUntil);
}
return Promise.reject(new ExtensionManagementError(error.message || localize('renameError', "Unknown error while renaming {0} to {1}", extractPath, renamePath), error.code || INSTALL_ERROR_RENAMING));
});
private async rename(identifier: IExtensionIdentifier, extractPath: string, renamePath: string, retryUntil: number): Promise<void> {
try {
await pfs.rename(extractPath, renamePath);
} catch (error) {
if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) {
this.logService.info(`Failed renaming ${extractPath} to ${renamePath} with 'EPERM' error. Trying again...`, identifier.id);
return this.rename(identifier, extractPath, renamePath, retryUntil);
}
throw new ExtensionManagementError(error.message || localize('renameError', "Unknown error while renaming {0} to {1}", extractPath, renamePath), error.code || INSTALL_ERROR_RENAMING);
}
}
private async scanSystemExtensions(): Promise<ILocalExtension[]> {
@@ -230,7 +230,7 @@ export class LaunchMainService implements ILaunchMainService {
getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise<(IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]> {
const windows = this.windowsMainService.getWindows();
const promises: Promise<IDiagnosticInfo | IRemoteDiagnosticError | undefined>[] = windows.map(window => {
return new Promise((resolve, reject) => {
return new Promise<IDiagnosticInfo | IRemoteDiagnosticError | undefined>((resolve) => {
const remoteAuthority = window.remoteAuthority;
if (remoteAuthority) {
const replyChannel = `vscode:getDiagnosticInfoResponse${window.id}`;
@@ -254,7 +254,7 @@ export class LaunchMainService implements ILaunchMainService {
resolve({ hostName: remoteAuthority, errorMessage: `Fetching remote diagnostics for '${remoteAuthority}' timed out.` });
}, 5000);
} else {
resolve();
resolve(undefined);
}
});
});
+11
View File
@@ -3820,6 +3820,13 @@ declare module 'vscode' {
* A string that should be used when comparing this item
* with other items. When `falsy` the [label](#CompletionItem.label)
* is used.
*
* Note that `sortText` is only used for the initial ordering of completion
* items. When having a leading word (prefix) ordering is based on how
* well completion match that prefix and the initial ordering is only used
* when completions match equal. The prefix is defined by the
* [`range`](#CompletionItem.range)-property and can therefore be different
* for each completion.
*/
sortText?: string;
@@ -3827,6 +3834,10 @@ declare module 'vscode' {
* A string that should be used when filtering a set of
* completion items. When `falsy` the [label](#CompletionItem.label)
* is used.
*
* Note that the filter text is matched against the leading word (prefix) which is defined
* by the [`range`](#CompletionItem.range)-property.
* prefix.
*/
filterText?: string;
+16 -4
View File
@@ -1350,17 +1350,24 @@ declare module 'vscode' {
}
export interface WorkspaceEdit {
replaceNotebookMetadata(uri: Uri, value: NotebookDocumentMetadata): void;
replaceCells(uri: Uri, start: number, end: number, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata): void;
replaceCellOutput(uri: Uri, index: number, outputs: CellOutput[], metadata?: WorkspaceEditEntryMetadata): void;
replaceCellMetadata(uri: Uri, index: number, cellMetadata: NotebookCellMetadata, metadata?: WorkspaceEditEntryMetadata): void;
}
export interface NotebookEditorCellEdit {
export interface NotebookEditorEdit {
replaceNotebookMetadata(value: NotebookDocumentMetadata): void;
replaceCells(start: number, end: number, cells: NotebookCellData[]): void;
replaceOutput(index: number, outputs: CellOutput[]): void;
replaceMetadata(index: number, metadata: NotebookCellMetadata): void;
replaceCellOutput(index: number, outputs: CellOutput[]): void;
replaceCellMetadata(index: number, metadata: NotebookCellMetadata): void;
/** @deprecated */
replaceOutput(index: number, outputs: CellOutput[]): void;
/** @deprecated */
replaceMetadata(index: number, metadata: NotebookCellMetadata): void;
/** @deprecated */
insert(index: number, content: string | string[], language: string, type: CellKind, outputs: CellOutput[], metadata: NotebookCellMetadata | undefined): void;
/** @deprecated */
@@ -1448,7 +1455,7 @@ declare module 'vscode' {
*/
asWebviewUri(localResource: Uri): Uri;
edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable<boolean>;
edit(callback: (editBuilder: NotebookEditorEdit) => void): Thenable<boolean>;
revealRange(range: NotebookCellRange, revealType?: NotebookEditorRevealType): void;
}
@@ -1463,6 +1470,10 @@ declare module 'vscode' {
outputId: string;
}
export interface NotebookDocumentMetadataChangeEvent {
readonly document: NotebookDocument;
}
export interface NotebookCellsChangeData {
readonly start: number;
readonly deletedCount: number;
@@ -1740,6 +1751,7 @@ declare module 'vscode' {
export const onDidChangeActiveNotebookEditor: Event<NotebookEditor | undefined>;
export const onDidChangeNotebookEditorSelection: Event<NotebookEditorSelectionChangeEvent>;
export const onDidChangeNotebookEditorVisibleRanges: Event<NotebookEditorVisibleRangesChangeEvent>;
export const onDidChangeNotebookDocumentMetadata: Event<NotebookDocumentMetadataChangeEvent>;
export const onDidChangeNotebookCells: Event<NotebookCellsChangeEvent>;
export const onDidChangeCellOutputs: Event<NotebookCellOutputsChangeEvent>;
export const onDidChangeCellLanguage: Event<NotebookCellLanguageChangeEvent>;
@@ -15,6 +15,7 @@ import { TokenClassificationExtensionPoints } from 'vs/workbench/services/themes
import { LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint';
// --- mainThread participants
import './mainThreadBulkEdits';
import './mainThreadCodeInsets';
import './mainThreadClipboard';
import './mainThreadCommands';
@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IBulkEditService, ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
import { IExtHostContext, IWorkspaceEditDto, WorkspaceEditType, MainThreadBulkEditsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { revive } from 'vs/base/common/marshalling';
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] {
if (!data?.edits) {
return [];
}
const result: ResourceEdit[] = [];
for (let edit of revive<IWorkspaceEditDto>(data).edits) {
if (edit._type === WorkspaceEditType.File) {
result.push(new ResourceFileEdit(edit.oldUri, edit.newUri, edit.options, edit.metadata));
} else if (edit._type === WorkspaceEditType.Text) {
result.push(new ResourceTextEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata));
} else if (edit._type === WorkspaceEditType.Cell) {
result.push(new ResourceNotebookCellEdit(edit.resource, edit.edit, edit.notebookMetadata, edit.notebookVersionId, edit.metadata));
}
}
return result;
}
@extHostNamedCustomer(MainContext.MainThreadBulkEdits)
export class MainThreadBulkEdits implements MainThreadBulkEditsShape {
constructor(
_extHostContext: IExtHostContext,
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
) { }
dispose(): void { }
$tryApplyWorkspaceEdit(dto: IWorkspaceEditDto): Promise<boolean> {
const edits = reviveWorkspaceEditDto2(dto);
return this._bulkEditService.apply(edits).then(() => true, _err => false);
}
}
@@ -153,7 +153,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc
return;
}
webviewInput.webview.onDispose(() => {
webviewInput.webview.onDidDispose(() => {
// If the model is still dirty, make sure we have time to save it
if (modelRef.object.isDirty()) {
const sub = modelRef.object.onDidChangeDirty(() => {
@@ -44,7 +44,7 @@ function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceE
} else if (edit._type === WorkspaceEditType.Text) {
result.push(new ResourceTextEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata));
} else if (edit._type === WorkspaceEditType.Cell) {
result.push(new ResourceNotebookCellEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata));
result.push(new ResourceNotebookCellEdit(edit.resource, edit.edit, edit.notebookMetadata, edit.notebookVersionId, edit.metadata));
}
}
return result;
@@ -39,9 +39,9 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
}
}
private _showMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[], extension: IExtensionDescription | undefined): Promise<number> {
private _showMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[], extension: IExtensionDescription | undefined): Promise<number | undefined> {
return new Promise<number>(resolve => {
return new Promise<number | undefined>(resolve => {
const primaryActions: MessageItemAction[] = [];
@@ -8,6 +8,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter } from 'vs/base/common/event';
import { combinedDisposable, Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { ResourceMap } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -17,9 +18,10 @@ import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookB
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService';
import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, DisplayOrderKey, ICellEditOperation, ICellRange, IEditor, IMainCellDto, INotebookDocumentFilter, NotebookCellMetadata, NotebookCellOutputsSplice, NotebookCellsChangeType, NotebookDocumentMetadata, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, DisplayOrderKey, ICellEditOperation, ICellRange, IEditor, IMainCellDto, INotebookDocumentFilter, NotebookCellOutputsSplice, NotebookCellsChangeType, 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 { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, INotebookModelAddedData, MainContext, MainThreadNotebookShape, NotebookEditorRevealType, NotebookExtensionDescription } from '../common/extHost.protocol';
class DocumentAndEditorState {
@@ -146,29 +148,29 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
@IEditorService private readonly editorService: IEditorService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
@ILogService private readonly logService: ILogService,
@INotebookCellStatusBarService private readonly cellStatusBarService: INotebookCellStatusBarService
@INotebookCellStatusBarService private readonly cellStatusBarService: INotebookCellStatusBarService,
@IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService,
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook);
this.registerListeners();
}
async $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[]): Promise<boolean> {
async $tryApplyEdits(_viewType: string, resource: UriComponents, modelVersionId: number, cellEdits: ICellEditOperation[], newMetadata: NotebookDocumentMetadata | undefined): Promise<boolean> {
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (textModel) {
this._notebookService.transformEditsOutputs(textModel, edits);
return textModel.applyEdit(modelVersionId, edits, true);
if (!textModel) {
return false;
}
return false;
}
async removeNotebookTextModel(uri: URI): Promise<void> {
// TODO@rebornix remove this? obsolete?
// TODO@rebornix, remove cell should use emitDelta as well to ensure document/editor events are sent together
this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [uri] });
this._documentEventListenersMapping.get(uri)?.dispose();
this._documentEventListenersMapping.delete(uri);
this._notebookService.transformEditsOutputs(textModel, cellEdits);
if (newMetadata) {
textModel.applyEdit(textModel.versionId, [
{
editType: CellEditType.DocumentMetadata,
metadata: newMetadata
}
], true);
}
return textModel.applyEdit(modelVersionId, cellEdits, true);
}
private _isDeltaEmpty(delta: INotebookDocumentsAndEditorsDelta) {
@@ -299,10 +301,19 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
}
: e
);
this._proxy.$acceptModelChanged(textModel.uri, data, textModel.isDirty);
this._proxy.$acceptDocumentPropertiesChanged(textModel.uri, { metadata: null });
}));
/**
* TODO@rebornix, @jrieken
* When a document is modified, it will trigger onDidChangeContent events.
* The first event listener is this one, which doesn't know if the text model is dirty or not. It can ask `workingCopyService` but get the wrong result
* The second event listener is `NotebookEditorModel`, which will then set `isDirty` to `true`.
* Since `e.transient` decides if the model should be dirty or not, we will use the same logic here.
*/
this._proxy.$acceptModelChanged(textModel.uri, data, !e.transient);
if (e.kind === NotebookCellsChangeType.ChangeDocumentMetadata) {
this._proxy.$acceptDocumentPropertiesChanged(textModel.uri, { metadata: textModel.metadata });
}
}));
this._editorEventListenersMapping.set(textModel!.uri.toString(), disposableStore);
}
};
@@ -452,22 +463,12 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
});
});
},
createNotebook: async (textModel: NotebookTextModel, backupId?: string) => {
// open notebook document
const data = await this._proxy.$resolveNotebookData(textModel.viewType, textModel.uri, backupId);
textModel.updateLanguages(data.languages);
textModel.metadata = data.metadata;
textModel.transientOptions = options;
if (data.cells.length) {
textModel.initialize(data!.cells);
} else {
const mainCell = textModel.createCellTextModel('', textModel.resolvedLanguages.length ? textModel.resolvedLanguages[0] : '', CellKind.Code, [], undefined);
textModel.insertTemplateCell(mainCell);
}
this._proxy.$acceptDocumentPropertiesChanged(textModel.uri, { metadata: textModel.metadata });
return;
resolveNotebookDocument: async (viewType: string, uri: URI, backupId?: string) => {
const data = await this._proxy.$resolveNotebookData(viewType, uri, backupId);
return {
data,
transientOptions: options
};
},
resolveNotebookEditor: async (viewType: string, uri: URI, editorId: string) => {
await this._proxy.$resolveNotebookEditor(viewType, uri, editorId);
@@ -475,9 +476,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
onDidReceiveMessage: (editorId: string, rendererType: string | undefined, message: unknown) => {
this._proxy.$onDidReceiveMessage(editorId, rendererType, message);
},
removeNotebookDocument: async (uri: URI) => {
return this.removeNotebookTextModel(uri);
},
save: async (uri: URI, token: CancellationToken) => {
return this._proxy.$saveNotebook(viewType, uri, token);
},
@@ -567,27 +565,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
textModel?.updateLanguages(languages);
}
async $updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise<void> {
this.logService.debug('MainThreadNotebooks#updateNotebookMetadata', resource.path, metadata);
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
textModel?.updateNotebookMetadata(metadata);
}
async $updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata): Promise<void> {
this.logService.debug('MainThreadNotebooks#updateNotebookCellMetadata', resource.path, handle, metadata);
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (!textModel) {
return;
}
const index = textModel.cells.findIndex(cell => cell.handle === handle);
if (index < 0) {
return;
}
textModel.applyEdit(textModel.versionId, [{ editType: CellEditType.Metadata, index, metadata }], true);
}
async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[]): Promise<void> {
this.logService.debug('MainThreadNotebooks#spliceNotebookCellOutputs', resource.path, cellHandle);
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
@@ -612,10 +589,12 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (textModel) {
textModel.handleEdit(label, () => {
return this._proxy.$undoNotebook(textModel.viewType, textModel.uri, editId, textModel.isDirty);
textModel.handleUnknownEdit(label, () => {
const isDirty = this._workingCopyService.isDirty(textModel.uri.with({ scheme: Schemas.vscodeNotebook }));
return this._proxy.$undoNotebook(textModel.viewType, textModel.uri, editId, isDirty);
}, () => {
return this._proxy.$redoNotebook(textModel.viewType, textModel.uri, editId, textModel.isDirty);
const isDirty = this._workingCopyService.isDirty(textModel.uri.with({ scheme: Schemas.vscodeNotebook }));
return this._proxy.$redoNotebook(textModel.viewType, textModel.uri, editId, isDirty);
});
}
}
@@ -73,7 +73,7 @@ export class MainThreadProgress implements MainThreadProgressShape {
private _createTask(handle: number) {
return (progress: IProgress<IProgressStep>) => {
return new Promise<any>(resolve => {
return new Promise<void>(resolve => {
this._progress.set(handle, { resolve, progress });
});
};
@@ -140,7 +140,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc
this._webviewInputs.add(handle, input);
this._mainThreadWebviews.addWebview(handle, input.webview);
input.webview.onDispose(() => {
input.webview.onDidDispose(() => {
this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => {
this._webviewInputs.delete(handle);
});
@@ -78,7 +78,7 @@ export class MainThreadWebviewsViews extends Disposable implements extHostProtoc
});
try {
await this._proxy.$resolveWebviewView(handle, viewType, state, cancellation);
await this._proxy.$resolveWebviewView(handle, viewType, webviewView.title, state, cancellation);
} catch (error) {
onUnexpectedError(error);
webviewView.webview.html = this.mainThreadWebviews.getWebviewResolvedFailedContent(viewType);
@@ -69,7 +69,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
disposables.add(webview.onMessage((message: any) => { this._proxy.$onMessage(handle, message); }));
disposables.add(webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value)));
disposables.add(webview.onDispose(() => {
disposables.add(webview.onDidDispose(() => {
disposables.dispose();
this._webviews.delete(handle);
}));
@@ -79,6 +79,7 @@ import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileS
import { ExtHostWebviewViews } from 'vs/workbench/api/common/extHostWebviewView';
import { ExtHostCustomEditors } from 'vs/workbench/api/common/extHostCustomEditors';
import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels';
import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits';
export interface IExtensionApiFactory {
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
@@ -127,9 +128,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol));
const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));
const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService));
const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadTextEditors)));
const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadBulkEdits)));
const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, initData.environment, extHostLogService, extensionStoragePaths));
const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors, extHostNotebook));
const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors));
const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService));
const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.environment));
const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol, extHostLogService));
@@ -154,6 +155,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
rpcProtocol.assertRegistered(expected);
// Other instances
const extHostBulkEdits = new ExtHostBulkEdits(rpcProtocol, extHostDocumentsAndEditors, extHostNotebook);
const extHostClipboard = new ExtHostClipboard(rpcProtocol);
const extHostMessageService = new ExtHostMessageService(rpcProtocol, extHostLogService);
const extHostDialogs = new ExtHostDialogs(rpcProtocol);
@@ -691,7 +693,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return extHostWorkspace.saveAll(includeUntitled);
},
applyEdit(edit: vscode.WorkspaceEdit): Thenable<boolean> {
return extHostEditors.applyWorkspaceEdit(edit);
return extHostBulkEdits.applyWorkspaceEdit(edit);
},
createFileSystemWatcher: (pattern, ignoreCreate, ignoreChange, ignoreDelete): vscode.FileSystemWatcher => {
return extHostFileSystemEvent.createFileSystemWatcher(typeConverters.GlobPattern.from(pattern), ignoreCreate, ignoreChange, ignoreDelete);
@@ -969,6 +971,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeActiveNotebookEditor(listener, thisArgs, disposables);
},
onDidChangeNotebookDocumentMetadata(listener, thisArgs?, disposables?) {
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeNotebookDocumentMetadata(listener, thisArgs, disposables);
},
onDidChangeNotebookCells(listener, thisArgs?, disposables?) {
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeNotebookCells(listener, thisArgs, disposables);
@@ -263,6 +263,10 @@ export interface ITextDocumentShowOptions {
selection?: IRange;
}
export interface MainThreadBulkEditsShape extends IDisposable {
$tryApplyWorkspaceEdit(workspaceEditDto: IWorkspaceEditDto): Promise<boolean>;
}
export interface MainThreadTextEditorsShape extends IDisposable {
$tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): Promise<string | undefined>;
$registerTextEditorDecorationType(key: string, options: editorCommon.IDecorationRenderOptions): void;
@@ -275,7 +279,6 @@ export interface MainThreadTextEditorsShape extends IDisposable {
$tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): Promise<void>;
$trySetSelections(id: string, selections: ISelection[]): Promise<void>;
$tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], opts: IApplyEditsOptions): Promise<boolean>;
$tryApplyWorkspaceEdit(workspaceEditDto: IWorkspaceEditDto): Promise<boolean>;
$tryInsertSnippet(id: string, template: string, selections: readonly IRange[], opts: IUndoStopOptions): Promise<boolean>;
$getDiffInformation(id: string): Promise<editorCommon.ILineChange[]>;
}
@@ -683,7 +686,7 @@ export interface ExtHostCustomEditorsShape {
}
export interface ExtHostWebviewViewsShape {
$resolveWebviewView(webviewHandle: WebviewHandle, viewType: string, state: any, cancellation: CancellationToken): Promise<void>;
$resolveWebviewView(webviewHandle: WebviewHandle, viewType: string, title: string | undefined, state: any, cancellation: CancellationToken): Promise<void>;
$onDidChangeWebviewViewVisibility(webviewHandle: WebviewHandle, visible: boolean): void;
@@ -738,10 +741,8 @@ export interface MainThreadNotebookShape extends IDisposable {
$registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise<void>;
$unregisterNotebookKernelProvider(handle: number): Promise<void>;
$onNotebookKernelChange(handle: number, uri: UriComponents | undefined): void;
$tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[]): Promise<boolean>;
$tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], metadata: NotebookDocumentMetadata | undefined): Promise<boolean>;
$updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void>;
$updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise<void>;
$updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata | undefined): Promise<void>;
$spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[]): Promise<void>;
$postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise<boolean>;
$setStatusBarEntry(id: number, statusBarEntry: INotebookCellStatusBarEntryDto): Promise<void>;
@@ -1274,8 +1275,9 @@ export interface IWorkspaceTextEditDto {
export interface IWorkspaceCellEditDto {
_type: WorkspaceEditType.Cell;
resource: UriComponents;
edit: ICellEditOperation;
modelVersionId?: number;
edit?: ICellEditOperation;
notebookMetadata?: NotebookDocumentMetadata;
notebookVersionId?: number;
metadata?: IWorkspaceEditEntryMetadataDto;
}
@@ -1725,6 +1727,7 @@ export interface ExtHostTimelineShape {
export const MainContext = {
MainThreadAuthentication: createMainId<MainThreadAuthenticationShape>('MainThreadAuthentication'),
MainThreadBulkEdits: createMainId<MainThreadBulkEditsShape>('MainThreadBulkEdits'),
MainThreadClipboard: createMainId<MainThreadClipboardShape>('MainThreadClipboard'),
MainThreadCommands: createMainId<MainThreadCommandsShape>('MainThreadCommands'),
MainThreadComments: createMainId<MainThreadCommentsShape>('MainThreadComments'),
@@ -228,10 +228,15 @@ const newCommands: ApiCommand[] = [
}
return typeConverters.WorkspaceEdit.to(value);
})
),
// --- links
new ApiCommand(
'vscode.executeLinkProvider', '_executeLinkProvider', 'Execute document link provider.',
[ApiCommandArgument.Uri, new ApiCommandArgument('linkResolveCount', '(optional) Number of links that should be resolved, only when links are unresolved.', v => typeof v === 'number' || typeof v === 'undefined', v => v)],
new ApiCommandResult<modes.ILink[], vscode.DocumentLink[]>('A promise that resolves to an array of DocumentLink-instances.', value => value.map(typeConverters.DocumentLink.to))
)
];
//#endregion
@@ -289,13 +294,6 @@ export class ExtHostApiCommands {
returns: 'A promise that resolves to an array of CodeLens-instances.'
});
this._register('vscode.executeLinkProvider', this._executeDocumentLinkProvider, {
description: 'Execute document link provider.',
args: [
{ name: 'uri', description: 'Uri of a text document', constraint: URI }
],
returns: 'A promise that resolves to an array of DocumentLink-instances.'
});
this._register('vscode.executeDocumentColorProvider', this._executeDocumentColorProvider, {
description: 'Execute document color provider.',
args: [
@@ -478,12 +476,6 @@ export class ExtHostApiCommands {
typeConverters.Range.to(item.range),
item.command ? this._commands.converter.fromInternal(item.command) : undefined);
}));
}
private _executeDocumentLinkProvider(resource: URI): Promise<vscode.DocumentLink[] | undefined> {
return this._commands.executeCommand<modes.ILink[]>('_executeLinkProvider', resource)
.then(tryMapWith(typeConverters.DocumentLink.to));
}
}
@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MainContext, MainThreadBulkEditsShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { WorkspaceEdit } from 'vs/workbench/api/common/extHostTypeConverters';
import type * as vscode from 'vscode';
export class ExtHostBulkEdits {
private readonly _proxy: MainThreadBulkEditsShape;
constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
private readonly _extHostNotebooks: ExtHostNotebookController,
) {
this._proxy = extHostRpc.getProxy(MainContext.MainThreadBulkEdits);
}
applyWorkspaceEdit(edit: vscode.WorkspaceEdit): Promise<boolean> {
const dto = WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors, this._extHostNotebooks);
return this._proxy.$tryApplyWorkspaceEdit(dto);
}
}
@@ -6,7 +6,7 @@
import { Event } from 'vs/base/common/event';
import { URI, UriComponents } from 'vs/base/common/uri';
import { illegalState } from 'vs/base/common/errors';
import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, IWorkspaceEditDto, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocumentSaveParticipantShape, IWorkspaceEditDto, WorkspaceEditType, MainThreadBulkEditsShape } from 'vs/workbench/api/common/extHost.protocol';
import { TextEdit } from 'vs/workbench/api/common/extHostTypes';
import { Range, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/common/extHostTypeConverters';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
@@ -26,7 +26,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic
constructor(
private readonly _logService: ILogService,
private readonly _documents: ExtHostDocuments,
private readonly _mainThreadEditors: MainThreadTextEditorsShape,
private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape,
private readonly _thresholds: { timeout: number; errors: number; } = { timeout: 1500, errors: 3 }
) {
//
@@ -165,7 +165,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic
}
if (version === document.version) {
return this._mainThreadEditors.$tryApplyWorkspaceEdit(dto);
return this._mainThreadBulkEdits.$tryApplyWorkspaceEdit(dto);
}
return Promise.reject(new Error('concurrent_edits'));
@@ -747,7 +747,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme
protected abstract _beforeAlmostReadyToRunExtensions(): Promise<void>;
protected abstract _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined;
protected abstract _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
public abstract async $setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
public abstract $setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
}
@@ -8,7 +8,7 @@ import { IRelativePattern, parse } from 'vs/base/common/glob';
import { URI } from 'vs/base/common/uri';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import type * as vscode from 'vscode';
import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, MainThreadTextEditorsShape, SourceTargetPair, IWorkspaceEditDto } from './extHost.protocol';
import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, SourceTargetPair, IWorkspaceEditDto, MainThreadBulkEditsShape } from './extHost.protocol';
import * as typeConverter from './extHostTypeConverters';
import { Disposable, WorkspaceEdit } from './extHostTypes';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
@@ -123,7 +123,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
mainContext: IMainContext,
private readonly _logService: ILogService,
private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
private readonly _mainThreadTextEditors: MainThreadTextEditorsShape = mainContext.getProxy(MainContext.MainThreadTextEditors)
private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape = mainContext.getProxy(MainContext.MainThreadBulkEdits)
) {
//
}
@@ -222,7 +222,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors);
dto.edits = dto.edits.concat(edits);
}
return this._mainThreadTextEditors.$tryApplyWorkspaceEdit(dto);
return this._mainThreadBulkEdits.$tryApplyWorkspaceEdit(dto);
}
}
}
+20 -705
View File
@@ -4,562 +4,24 @@
*--------------------------------------------------------------------------------------------*/
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { readonly } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { hash } from 'vs/base/common/hash';
import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { joinPath } from 'vs/base/common/resources';
import { ISplice } from 'vs/base/common/sequence';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import * as UUID from 'vs/base/common/uuid';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { CellKind, ExtHostNotebookShape, ICommandDto, IMainContext, IModelAddedData, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookEditorPropertiesChangeData, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostNotebookShape, ICommandDto, IMainContext, IModelAddedData, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookEditorPropertiesChangeData, MainContext, MainThreadBulkEditsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
import { ILogService } from 'vs/platform/log/common/log';
import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
import { addIdToOutput, CellEditType, CellOutputKind, CellStatusbarAlignment, CellUri, diff, ICellEditOperation, ICellReplaceEdit, IMainCellDto, INotebookCellStatusBarEntry, INotebookDisplayOrder, INotebookEditData, INotebookKernelInfoDto2, IProcessedOutput, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookCellsSplice2, NotebookDataDto, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { addIdToOutput, CellStatusbarAlignment, CellUri, INotebookCellStatusBarEntry, INotebookDisplayOrder, INotebookKernelInfoDto2, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import * as vscode from 'vscode';
import { Cache } from './cache';
import { ResourceMap } from 'vs/base/common/map';
interface IObservable<T> {
proxy: T;
onDidChange: Event<void>;
}
function getObservable<T extends Object>(obj: T): IObservable<T> {
const onDidChange = new Emitter<void>();
const proxy = new Proxy(obj, {
set(target: T, p: PropertyKey, value: any, _receiver: any): boolean {
target[p as keyof T] = value;
onDidChange.fire();
return true;
}
});
return {
proxy,
onDidChange: onDidChange.event
};
}
interface INotebookEventEmitter {
emitModelChange(events: vscode.NotebookCellsChangeEvent): void;
emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void;
emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void;
emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void;
}
export class ExtHostCell extends Disposable {
public static asModelAddData(notebook: vscode.NotebookDocument, cell: IMainCellDto): IExtHostModelAddedData {
return {
EOL: cell.eol,
lines: cell.source,
modeId: cell.language,
uri: cell.uri,
isDirty: false,
versionId: 1,
notebook
};
}
private _onDidDispose = new Emitter<void>();
readonly onDidDispose: Event<void> = this._onDidDispose.event;
private _onDidChangeOutputs = new Emitter<ISplice<IProcessedOutput>[]>();
readonly onDidChangeOutputs: Event<ISplice<IProcessedOutput>[]> = this._onDidChangeOutputs.event;
private _outputs: any[];
private _outputMapping = new WeakMap<vscode.CellOutput, string | undefined /* output ID */>();
private _metadata: vscode.NotebookCellMetadata;
private _metadataChangeListener: IDisposable;
readonly handle: number;
readonly uri: URI;
readonly cellKind: CellKind;
private _cell: vscode.NotebookCell | undefined;
constructor(
private readonly _proxy: MainThreadNotebookShape,
private readonly _notebook: ExtHostNotebookDocument,
private readonly _extHostDocument: ExtHostDocumentsAndEditors,
private readonly _cellData: IMainCellDto,
) {
super();
this.handle = _cellData.handle;
this.uri = URI.revive(_cellData.uri);
this.cellKind = _cellData.cellKind;
this._outputs = _cellData.outputs;
for (const output of this._outputs) {
this._outputMapping.set(output, output.outputId);
delete output.outputId;
}
const observableMetadata = getObservable(_cellData.metadata ?? {});
this._metadata = observableMetadata.proxy;
this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => {
this._updateMetadata();
}));
}
get cell(): vscode.NotebookCell {
if (!this._cell) {
const that = this;
const document = this._extHostDocument.getDocument(this.uri)!.document;
this._cell = Object.freeze({
notebook: that._notebook.notebookDocument,
uri: that.uri,
cellKind: this._cellData.cellKind,
document,
language: document.languageId,
get outputs() { return that._outputs; },
set outputs(value) { that._updateOutputs(value); },
get metadata() { return that._metadata; },
set metadata(value) {
that.setMetadata(value);
that._updateMetadata();
},
});
}
return this._cell;
}
dispose() {
super.dispose();
this._onDidDispose.fire();
}
setOutputs(newOutputs: vscode.CellOutput[]): void {
this._outputs = newOutputs;
}
private _updateOutputs(newOutputs: vscode.CellOutput[]) {
const rawDiffs = diff<vscode.CellOutput>(this._outputs || [], newOutputs || [], (a) => {
return this._outputMapping.has(a);
});
const transformedDiffs: ISplice<IProcessedOutput>[] = rawDiffs.map(diff => {
for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
this._outputMapping.delete(this._outputs[i]);
}
return {
deleteCount: diff.deleteCount,
start: diff.start,
toInsert: diff.toInsert.map((output): IProcessedOutput => {
if (output.outputKind === CellOutputKind.Rich) {
const uuid = UUID.generateUuid();
this._outputMapping.set(output, uuid);
return { ...output, outputId: uuid };
}
this._outputMapping.set(output, undefined);
return output;
})
};
});
this._outputs = newOutputs;
this._onDidChangeOutputs.fire(transformedDiffs);
}
setMetadata(newMetadata: vscode.NotebookCellMetadata): void {
// Don't apply metadata defaults here, 'undefined' means 'inherit from document metadata'
this._metadataChangeListener.dispose();
const observableMetadata = getObservable(newMetadata);
this._metadata = observableMetadata.proxy;
this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => {
this._updateMetadata();
}));
}
private _updateMetadata(): Promise<void> {
return this._proxy.$updateNotebookCellMetadata(this._notebook.notebookDocument.viewType, this._notebook.uri, this.handle, this._metadata);
}
}
class RawContentChangeEvent {
constructor(readonly start: number, readonly deletedCount: number, readonly deletedItems: ExtHostCell[], readonly items: ExtHostCell[]) { }
static asApiEvent(event: RawContentChangeEvent): vscode.NotebookCellsChangeData {
return Object.freeze({
start: event.start,
deletedCount: event.deletedCount,
deletedItems: event.deletedItems.map(data => data.cell),
items: event.items.map(data => data.cell)
});
}
}
export class ExtHostNotebookDocument extends Disposable {
private static _handlePool: number = 0;
readonly handle = ExtHostNotebookDocument._handlePool++;
private _cells: ExtHostCell[] = [];
private _cellDisposableMapping = new Map<number, DisposableStore>();
private _notebook: vscode.NotebookDocument | undefined;
private _metadata: Required<vscode.NotebookDocumentMetadata>;
private _metadataChangeListener: IDisposable;
private _versionId = 0;
private _isDirty: boolean = false;
private _backupCounter = 1;
private _backup?: vscode.NotebookDocumentBackup;
private _disposed = false;
private _languages: string[] = [];
private readonly _edits = new Cache<vscode.NotebookDocumentEditEvent>('notebook documents');
constructor(
private readonly _proxy: MainThreadNotebookShape,
private readonly _documentsAndEditors: ExtHostDocumentsAndEditors,
private readonly _emitter: INotebookEventEmitter,
private readonly _viewType: string,
metadata: Required<vscode.NotebookDocumentMetadata>,
public readonly uri: URI,
public readonly renderingHandler: ExtHostNotebookOutputRenderingHandler,
private readonly _storagePath: URI | undefined
) {
super();
const observableMetadata = getObservable(metadata);
this._metadata = observableMetadata.proxy;
this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => {
this._tryUpdateMetadata();
}));
}
dispose() {
this._disposed = true;
super.dispose();
dispose(this._cellDisposableMapping.values());
}
private _updateMetadata(newMetadata: Required<vscode.NotebookDocumentMetadata>) {
this._metadataChangeListener.dispose();
newMetadata = {
...notebookDocumentMetadataDefaults,
...newMetadata
};
if (this._metadataChangeListener) {
this._metadataChangeListener.dispose();
}
const observableMetadata = getObservable(newMetadata);
this._metadata = observableMetadata.proxy;
this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => {
this._tryUpdateMetadata();
}));
this._tryUpdateMetadata();
}
private _tryUpdateMetadata() {
this._proxy.$updateNotebookMetadata(this._viewType, this.uri, this._metadata);
}
get notebookDocument(): vscode.NotebookDocument {
if (!this._notebook) {
const that = this;
this._notebook = Object.freeze({
get uri() { return that.uri; },
get version() { return that._versionId; },
get fileName() { return that.uri.fsPath; },
get viewType() { return that._viewType; },
get isDirty() { return that._isDirty; },
get isUntitled() { return that.uri.scheme === Schemas.untitled; },
get cells(): ReadonlyArray<vscode.NotebookCell> { return that._cells.map(cell => cell.cell); },
get languages() { return that._languages; },
set languages(value: string[]) { that._trySetLanguages(value); },
get metadata() { return that._metadata; },
set metadata(value: Required<vscode.NotebookDocumentMetadata>) { that._updateMetadata(value); },
});
}
return this._notebook;
}
private _trySetLanguages(newLanguages: string[]) {
this._languages = newLanguages;
this._proxy.$updateNotebookLanguages(this._viewType, this.uri, this._languages);
}
getNewBackupUri(): URI {
if (!this._storagePath) {
throw new Error('Backup requires a valid storage path');
}
const fileName = hashPath(this.uri) + (this._backupCounter++);
return joinPath(this._storagePath, fileName);
}
updateBackup(backup: vscode.NotebookDocumentBackup): void {
this._backup?.delete();
this._backup = backup;
}
disposeBackup(): void {
this._backup?.delete();
this._backup = undefined;
}
acceptModelChanged(event: NotebookCellsChangedEventDto, isDirty: boolean): void {
this._versionId = event.versionId;
this._isDirty = isDirty;
if (event.kind === NotebookCellsChangeType.Initialize) {
this._spliceNotebookCells(event.changes, true);
} if (event.kind === NotebookCellsChangeType.ModelChange) {
this._spliceNotebookCells(event.changes, false);
} else if (event.kind === NotebookCellsChangeType.Move) {
this._moveCell(event.index, event.newIdx);
} else if (event.kind === NotebookCellsChangeType.Output) {
this._setCellOutputs(event.index, event.outputs);
} else if (event.kind === NotebookCellsChangeType.ChangeLanguage) {
this._changeCellLanguage(event.index, event.language);
} else if (event.kind === NotebookCellsChangeType.ChangeCellMetadata) {
this._changeCellMetadata(event.index, event.metadata);
}
}
private _spliceNotebookCells(splices: NotebookCellsSplice2[], initialization: boolean): void {
if (this._disposed) {
return;
}
const contentChangeEvents: RawContentChangeEvent[] = [];
const addedCellDocuments: IExtHostModelAddedData[] = [];
const removedCellDocuments: URI[] = [];
splices.reverse().forEach(splice => {
const cellDtos = splice[2];
const newCells = cellDtos.map(cell => {
const extCell = new ExtHostCell(this._proxy, this, this._documentsAndEditors, cell);
if (!initialization) {
addedCellDocuments.push(ExtHostCell.asModelAddData(this.notebookDocument, cell));
}
if (!this._cellDisposableMapping.has(extCell.handle)) {
const store = new DisposableStore();
store.add(extCell);
this._cellDisposableMapping.set(extCell.handle, store);
}
const store = this._cellDisposableMapping.get(extCell.handle)!;
store.add(extCell.onDidChangeOutputs((diffs) => {
this.eventuallyUpdateCellOutputs(extCell, diffs);
}));
return extCell;
});
for (let j = splice[0]; j < splice[0] + splice[1]; j++) {
this._cellDisposableMapping.get(this._cells[j].handle)?.dispose();
this._cellDisposableMapping.delete(this._cells[j].handle);
}
const deletedItems = this._cells.splice(splice[0], splice[1], ...newCells);
for (let cell of deletedItems) {
removedCellDocuments.push(cell.uri);
}
contentChangeEvents.push(new RawContentChangeEvent(splice[0], splice[1], deletedItems, newCells));
});
this._documentsAndEditors.acceptDocumentsAndEditorsDelta({
addedDocuments: addedCellDocuments,
removedDocuments: removedCellDocuments
});
if (!initialization) {
this._emitter.emitModelChange({
document: this.notebookDocument,
changes: contentChangeEvents.map(RawContentChangeEvent.asApiEvent)
});
}
}
private _moveCell(index: number, newIdx: number): void {
const cells = this._cells.splice(index, 1);
this._cells.splice(newIdx, 0, ...cells);
const changes: vscode.NotebookCellsChangeData[] = [{
start: index,
deletedCount: 1,
deletedItems: cells.map(data => data.cell),
items: []
}, {
start: newIdx,
deletedCount: 0,
deletedItems: [],
items: cells.map(data => data.cell)
}];
this._emitter.emitModelChange({
document: this.notebookDocument,
changes
});
}
private _setCellOutputs(index: number, outputs: IProcessedOutput[]): void {
const cell = this._cells[index];
cell.setOutputs(outputs);
this._emitter.emitCellOutputsChange({ document: this.notebookDocument, cells: [cell.cell] });
}
private _changeCellLanguage(index: number, language: string): void {
const cell = this._cells[index];
const event: vscode.NotebookCellLanguageChangeEvent = { document: this.notebookDocument, cell: cell.cell, language };
this._emitter.emitCellLanguageChange(event);
}
private _changeCellMetadata(index: number, newMetadata: NotebookCellMetadata | undefined): void {
const cell = this._cells[index];
cell.setMetadata(newMetadata || {});
const event: vscode.NotebookCellMetadataChangeEvent = { document: this.notebookDocument, cell: cell.cell };
this._emitter.emitCellMetadataChange(event);
}
async eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice<IProcessedOutput>[]) {
const outputDtos: NotebookCellOutputsSplice[] = diffs.map(diff => {
const outputs = diff.toInsert;
return [diff.start, diff.deleteCount, outputs];
});
if (!outputDtos.length) {
return;
}
await this._proxy.$spliceNotebookCellOutputs(this._viewType, this.uri, cell.handle, outputDtos);
this._emitter.emitCellOutputsChange({
document: this.notebookDocument,
cells: [cell.cell]
});
}
getCell(cellHandle: number): ExtHostCell | undefined {
return this._cells.find(cell => cell.handle === cellHandle);
}
addEdit(item: vscode.NotebookDocumentEditEvent): number {
return this._edits.add([item]);
}
async undo(editId: number, isDirty: boolean): Promise<void> {
await this.getEdit(editId).undo();
// if (!isDirty) {
// this.disposeBackup();
// }
}
async redo(editId: number, isDirty: boolean): Promise<void> {
await this.getEdit(editId).redo();
// if (!isDirty) {
// this.disposeBackup();
// }
}
private getEdit(editId: number): vscode.NotebookDocumentEditEvent {
const edit = this._edits.get(editId, 0);
if (!edit) {
throw new Error('No edit found');
}
return edit;
}
disposeEdits(editIds: number[]): void {
for (const id of editIds) {
this._edits.delete(id);
}
}
}
export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellEdit {
private readonly _documentVersionId: number;
private readonly _collectedEdits: ICellEditOperation[] = [];
private _finalized: boolean = false;
constructor(documentVersionId: number) {
this._documentVersionId = documentVersionId;
}
finalize(): INotebookEditData {
this._finalized = true;
return {
documentVersionId: this._documentVersionId,
edits: this._collectedEdits,
};
}
private _throwIfFinalized() {
if (this._finalized) {
throw new Error('Edit is only valid while callback runs');
}
}
replaceMetadata(index: number, metadata: vscode.NotebookCellMetadata): void {
this._throwIfFinalized();
this._collectedEdits.push({
editType: CellEditType.Metadata,
index,
metadata
});
}
replaceOutput(index: number, outputs: vscode.CellOutput[]): void {
this._throwIfFinalized();
this._collectedEdits.push({
editType: CellEditType.Output,
index,
outputs: outputs.map(output => addIdToOutput(output))
});
}
replaceCells(from: number, to: number, cells: vscode.NotebookCellData[]): void {
this._throwIfFinalized();
this._collectedEdits.push({
editType: CellEditType.Replace,
index: from,
count: to - from,
cells: cells.map(data => {
return {
...data,
outputs: data.outputs.map(output => addIdToOutput(output)),
};
})
});
}
insert(index: number, content: string | string[], language: string, type: CellKind, outputs: vscode.CellOutput[], metadata: vscode.NotebookCellMetadata | undefined): void {
this._throwIfFinalized();
this.replaceCells(index, index, [{
language,
outputs,
metadata,
cellKind: type,
source: Array.isArray(content) ? content.join('\n') : content,
}]);
}
delete(index: number): void {
this._throwIfFinalized();
this.replaceCells(index, 1, []);
}
}
import { ExtHostCell, ExtHostNotebookDocument } from './extHostNotebookDocument';
import { ExtHostNotebookEditor } from './extHostNotebookEditor';
class ExtHostWebviewCommWrapper extends Disposable {
private readonly _onDidReceiveDocumentMessage = new Emitter<any>();
@@ -606,152 +68,7 @@ class ExtHostWebviewCommWrapper extends Disposable {
}
}
export class ExtHostNotebookEditor extends Disposable implements vscode.NotebookEditor {
private _viewColumn: vscode.ViewColumn | undefined;
selection?: vscode.NotebookCell;
private _visibleRanges: vscode.NotebookCellRange[] = [];
get visibleRanges() {
return this._visibleRanges;
}
set visibleRanges(_range: vscode.NotebookCellRange[]) {
throw readonly('visibleRanges');
}
_acceptVisibleRanges(value: vscode.NotebookCellRange[]): void {
this._visibleRanges = value;
}
private _active: boolean = false;
get active(): boolean {
return this._active;
}
set active(_state: boolean) {
throw readonly('active');
}
private _visible: boolean = false;
get visible(): boolean {
return this._visible;
}
set visible(_state: boolean) {
throw readonly('visible');
}
_acceptVisibility(value: boolean) {
this._visible = value;
}
_acceptActive(value: boolean) {
this._active = value;
}
private _kernel?: vscode.NotebookKernel;
get kernel() {
return this._kernel;
}
set kernel(_kernel: vscode.NotebookKernel | undefined) {
throw readonly('kernel');
}
private _onDidDispose = new Emitter<void>();
readonly onDidDispose: Event<void> = this._onDidDispose.event;
private _onDidReceiveMessage = new Emitter<any>();
onDidReceiveMessage: vscode.Event<any> = this._onDidReceiveMessage.event;
constructor(
private readonly viewType: string,
readonly id: string,
public uri: URI,
private _proxy: MainThreadNotebookShape,
private _webComm: vscode.NotebookCommunication,
public readonly notebookData: ExtHostNotebookDocument,
) {
super();
this._register(this._webComm.onDidReceiveMessage(e => {
this._onDidReceiveMessage.fire(e);
}));
}
get document(): vscode.NotebookDocument {
return this.notebookData.notebookDocument;
}
edit(callback: (editBuilder: NotebookEditorCellEditBuilder) => void): Thenable<boolean> {
const edit = new NotebookEditorCellEditBuilder(this.document.version);
callback(edit);
return this._applyEdit(edit.finalize());
}
private _applyEdit(editData: INotebookEditData): Promise<boolean> {
// return when there is nothing to do
if (editData.edits.length === 0) {
return Promise.resolve(true);
}
const compressedEdits: ICellEditOperation[] = [];
let compressedEditsIndex = -1;
for (let i = 0; i < editData.edits.length; i++) {
if (compressedEditsIndex < 0) {
compressedEdits.push(editData.edits[i]);
compressedEditsIndex++;
continue;
}
const prevIndex = compressedEditsIndex;
const prev = compressedEdits[prevIndex];
if (prev.editType === CellEditType.Replace && editData.edits[i].editType === CellEditType.Replace) {
if (prev.index === editData.edits[i].index) {
prev.cells.push(...(editData.edits[i] as ICellReplaceEdit).cells);
prev.count += (editData.edits[i] as ICellReplaceEdit).count;
continue;
}
}
compressedEdits.push(editData.edits[i]);
compressedEditsIndex++;
}
return this._proxy.$tryApplyEdits(this.viewType, this.uri, editData.documentVersionId, compressedEdits);
}
revealRange(range: vscode.NotebookCellRange, revealType?: extHostTypes.NotebookEditorRevealType) {
this._proxy.$tryRevealRange(this.id, range, revealType || extHostTypes.NotebookEditorRevealType.Default);
}
get viewColumn(): vscode.ViewColumn | undefined {
return this._viewColumn;
}
set viewColumn(value) {
throw readonly('viewColumn');
}
updateActiveKernel(kernel?: vscode.NotebookKernel) {
this._kernel = kernel;
}
async postMessage(message: any): Promise<boolean> {
return this._webComm.postMessage(message);
}
asWebviewUri(localResource: vscode.Uri): vscode.Uri {
return this._webComm.asWebviewUri(localResource);
}
dispose() {
this._onDidDispose.fire();
super.dispose();
}
}
export interface ExtHostNotebookOutputRenderingHandler {
outputDisplayOrder: INotebookDisplayOrder | undefined;
@@ -874,6 +191,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
private static _notebookKernelProviderHandlePool: number = 0;
private readonly _proxy: MainThreadNotebookShape;
private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape;
private readonly _notebookContentProviders = new Map<string, { readonly provider: vscode.NotebookContentProvider, readonly extension: IExtensionDescription; }>();
private readonly _notebookKernels = new Map<string, { readonly kernel: vscode.NotebookKernel, readonly extension: IExtensionDescription; }>();
private readonly _notebookKernelProviders = new Map<number, ExtHostNotebookKernelProviderAdapter>();
@@ -885,6 +203,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
readonly onDidChangeNotebookEditorSelection = this._onDidChangeNotebookEditorSelection.event;
private readonly _onDidChangeNotebookEditorVisibleRanges = new Emitter<vscode.NotebookEditorVisibleRangesChangeEvent>();
readonly onDidChangeNotebookEditorVisibleRanges = this._onDidChangeNotebookEditorVisibleRanges.event;
private readonly _onDidChangeNotebookDocumentMetadata = new Emitter<vscode.NotebookDocumentMetadataChangeEvent>();
readonly onDidChangeNotebookDocumentMetadata = this._onDidChangeNotebookDocumentMetadata.event;
private readonly _onDidChangeNotebookCells = new Emitter<vscode.NotebookCellsChangeEvent>();
readonly onDidChangeNotebookCells = this._onDidChangeNotebookCells.event;
private readonly _onDidChangeCellOutputs = new Emitter<vscode.NotebookCellOutputsChangeEvent>();
@@ -929,6 +249,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
private readonly _extensionStoragePaths: IExtensionStoragePaths,
) {
this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook);
this._mainThreadBulkEdits = mainContext.getProxy(MainContext.MainThreadBulkEdits);
this._commandsConverter = commands.converter;
commands.registerArgumentProcessor({
@@ -1199,7 +520,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
const kernel = event.kernelId ? adapter.getKernel(event.kernelId) : undefined;
this._editors.forEach(editor => {
if (editor.editor.notebookData === document) {
editor.editor.updateActiveKernel(kernel);
editor.editor._acceptKernel(kernel);
}
});
this._onDidChangeActiveNotebookKernel.fire({ document: document.notebookDocument, kernel });
@@ -1212,7 +533,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
const uriStr = URI.revive(uriComponents).toString();
let editor: { editor: ExtHostNotebookEditor; } | undefined;
this._editors.forEach(e => {
if (e.editor.uri.toString() === uriStr) {
if (e.editor.document.uri.toString() === uriStr) {
editor = e;
}
});
@@ -1285,10 +606,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
}
if (data.metadata) {
editor.editor.notebookData.notebookDocument.metadata = {
...notebookDocumentMetadataDefaults,
...data.metadata
};
editor.editor.notebookData.acceptDocumentPropertiesChanged(data);
}
}
@@ -1302,9 +620,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
}
const editor = new ExtHostNotebookEditor(
document.notebookDocument.viewType,
editorId,
revivedUri,
document.notebookDocument.viewType,
this._proxy,
webComm.contentProviderComm,
document
@@ -1339,7 +656,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
}
for (const e of this._editors.values()) {
if (e.editor.uri.toString() === revivedUri.toString()) {
if (e.editor.document.uri.toString() === revivedUri.toString()) {
e.editor.dispose();
this._editors.delete(e.editor.id);
editorChanged = true;
@@ -1363,7 +680,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
}
const that = this;
const document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, {
const document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, this._mainThreadBulkEdits, {
emitModelChange(event: vscode.NotebookCellsChangeEvent): void {
that._onDidChangeNotebookCells.fire(event);
},
@@ -1375,8 +692,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
},
emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void {
that._onDidChangeCellMetadata.fire(event);
},
emitDocumentMetadataChange(event: vscode.NotebookDocumentMetadataChangeEvent): void {
that._onDidChangeNotebookDocumentMetadata.fire(event);
}
}, viewType, { ...notebookDocumentMetadataDefaults, ...modelData.metadata }, uri, this, storageRoot);
}, viewType, { ...notebookDocumentMetadataDefaults, ...modelData.metadata }, uri, storageRoot);
document.acceptModelChanged({
kind: NotebookCellsChangeType.Initialize,
@@ -1501,11 +821,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
}
}
function hashPath(resource: URI): string {
const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString();
return hash(str) + '';
}
function isEditEvent(e: vscode.NotebookDocumentEditEvent | vscode.NotebookDocumentContentChangeEvent): e is vscode.NotebookDocumentEditEvent {
return typeof (e as vscode.NotebookDocumentEditEvent).undo === 'function'
&& typeof (e as vscode.NotebookDocumentEditEvent).redo === 'function';
@@ -0,0 +1,521 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { hash } from 'vs/base/common/hash';
import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { joinPath } from 'vs/base/common/resources';
import { ISplice } from 'vs/base/common/sequence';
import { URI } from 'vs/base/common/uri';
import * as UUID from 'vs/base/common/uuid';
import { CellKind, INotebookDocumentPropertiesChangeData, IWorkspaceCellEditDto, MainThreadBulkEditsShape, MainThreadNotebookShape, NotebookCellOutputsSplice, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { CellEditType, CellOutputKind, diff, IMainCellDto, IProcessedOutput, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookCellsSplice2, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import * as vscode from 'vscode';
import { Cache } from './cache';
interface IObservable<T> {
proxy: T;
onDidChange: Event<void>;
}
function getObservable<T extends Object>(obj: T): IObservable<T> {
const onDidChange = new Emitter<void>();
const proxy = new Proxy(obj, {
set(target: T, p: PropertyKey, value: any, _receiver: any): boolean {
target[p as keyof T] = value;
onDidChange.fire();
return true;
}
});
return {
proxy,
onDidChange: onDidChange.event
};
}
class RawContentChangeEvent {
constructor(readonly start: number, readonly deletedCount: number, readonly deletedItems: ExtHostCell[], readonly items: ExtHostCell[]) { }
static asApiEvent(event: RawContentChangeEvent): vscode.NotebookCellsChangeData {
return Object.freeze({
start: event.start,
deletedCount: event.deletedCount,
deletedItems: event.deletedItems.map(data => data.cell),
items: event.items.map(data => data.cell)
});
}
}
export class ExtHostCell extends Disposable {
static asModelAddData(notebook: vscode.NotebookDocument, cell: IMainCellDto): IExtHostModelAddedData {
return {
EOL: cell.eol,
lines: cell.source,
modeId: cell.language,
uri: cell.uri,
isDirty: false,
versionId: 1,
notebook
};
}
private _onDidDispose = new Emitter<void>();
readonly onDidDispose: Event<void> = this._onDidDispose.event;
private _onDidChangeOutputs = new Emitter<ISplice<IProcessedOutput>[]>();
readonly onDidChangeOutputs: Event<ISplice<IProcessedOutput>[]> = this._onDidChangeOutputs.event;
private _outputs: any[];
private _outputMapping = new WeakMap<vscode.CellOutput, string | undefined /* output ID */>();
private _metadata: vscode.NotebookCellMetadata;
private _metadataChangeListener: IDisposable;
readonly handle: number;
readonly uri: URI;
readonly cellKind: CellKind;
private _cell: vscode.NotebookCell | undefined;
constructor(
private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape,
private readonly _notebook: ExtHostNotebookDocument,
private readonly _extHostDocument: ExtHostDocumentsAndEditors,
private readonly _cellData: IMainCellDto,
) {
super();
this.handle = _cellData.handle;
this.uri = URI.revive(_cellData.uri);
this.cellKind = _cellData.cellKind;
this._outputs = _cellData.outputs;
for (const output of this._outputs) {
this._outputMapping.set(output, output.outputId);
delete output.outputId;
}
const observableMetadata = getObservable(_cellData.metadata ?? {});
this._metadata = observableMetadata.proxy;
this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => {
this._updateMetadata();
}));
}
get cell(): vscode.NotebookCell {
if (!this._cell) {
const that = this;
const document = this._extHostDocument.getDocument(this.uri)!.document;
this._cell = Object.freeze({
notebook: that._notebook.notebookDocument,
uri: that.uri,
cellKind: this._cellData.cellKind,
document,
language: document.languageId,
get outputs() { return that._outputs; },
set outputs(value) { that._updateOutputs(value); },
get metadata() { return that._metadata; },
set metadata(value) {
that.setMetadata(value);
that._updateMetadata();
},
});
}
return this._cell;
}
dispose() {
super.dispose();
this._onDidDispose.fire();
}
setOutputs(newOutputs: vscode.CellOutput[]): void {
this._outputs = newOutputs;
}
private _updateOutputs(newOutputs: vscode.CellOutput[]) {
const rawDiffs = diff<vscode.CellOutput>(this._outputs || [], newOutputs || [], (a) => {
return this._outputMapping.has(a);
});
const transformedDiffs: ISplice<IProcessedOutput>[] = rawDiffs.map(diff => {
for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
this._outputMapping.delete(this._outputs[i]);
}
return {
deleteCount: diff.deleteCount,
start: diff.start,
toInsert: diff.toInsert.map((output): IProcessedOutput => {
if (output.outputKind === CellOutputKind.Rich) {
const uuid = UUID.generateUuid();
this._outputMapping.set(output, uuid);
return { ...output, outputId: uuid };
}
this._outputMapping.set(output, undefined);
return output;
})
};
});
this._outputs = newOutputs;
this._onDidChangeOutputs.fire(transformedDiffs);
}
setMetadata(newMetadata: vscode.NotebookCellMetadata): void {
// Don't apply metadata defaults here, 'undefined' means 'inherit from document metadata'
this._metadataChangeListener.dispose();
const observableMetadata = getObservable(newMetadata);
this._metadata = observableMetadata.proxy;
this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => {
this._updateMetadata();
}));
}
private _updateMetadata(): Promise<boolean> {
const index = this._notebook.notebookDocument.cells.indexOf(this.cell);
const edit: IWorkspaceCellEditDto = {
_type: WorkspaceEditType.Cell,
metadata: undefined,
resource: this._notebook.uri,
notebookVersionId: this._notebook.notebookDocument.version,
edit: { editType: CellEditType.Metadata, index, metadata: this._metadata }
};
return this._mainThreadBulkEdits.$tryApplyWorkspaceEdit({ edits: [edit] });
}
}
export interface INotebookEventEmitter {
emitModelChange(events: vscode.NotebookCellsChangeEvent): void;
emitDocumentMetadataChange(event: vscode.NotebookDocumentMetadataChangeEvent): void;
emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void;
emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void;
emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void;
}
function hashPath(resource: URI): string {
const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString();
return hash(str) + '';
}
export class ExtHostNotebookDocument extends Disposable {
private static _handlePool: number = 0;
readonly handle = ExtHostNotebookDocument._handlePool++;
private _cells: ExtHostCell[] = [];
private _cellDisposableMapping = new Map<number, DisposableStore>();
private _notebook: vscode.NotebookDocument | undefined;
private _metadata: Required<vscode.NotebookDocumentMetadata>;
private _metadataChangeListener: IDisposable;
private _versionId = 0;
private _isDirty: boolean = false;
private _backupCounter = 1;
private _backup?: vscode.NotebookDocumentBackup;
private _disposed = false;
private _languages: string[] = [];
private readonly _edits = new Cache<vscode.NotebookDocumentEditEvent>('notebook documents');
constructor(
private readonly _proxy: MainThreadNotebookShape,
private readonly _documentsAndEditors: ExtHostDocumentsAndEditors,
private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape,
private readonly _emitter: INotebookEventEmitter,
private readonly _viewType: string,
metadata: Required<vscode.NotebookDocumentMetadata>,
public readonly uri: URI,
private readonly _storagePath: URI | undefined
) {
super();
const observableMetadata = getObservable(metadata);
this._metadata = observableMetadata.proxy;
this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => {
this._tryUpdateMetadata();
}));
}
dispose() {
this._disposed = true;
super.dispose();
dispose(this._cellDisposableMapping.values());
}
private _updateMetadata(newMetadata: Required<vscode.NotebookDocumentMetadata>) {
this._metadataChangeListener.dispose();
newMetadata = {
...notebookDocumentMetadataDefaults,
...newMetadata
};
if (this._metadataChangeListener) {
this._metadataChangeListener.dispose();
}
const observableMetadata = getObservable(newMetadata);
this._metadata = observableMetadata.proxy;
this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => {
this._tryUpdateMetadata();
}));
this._tryUpdateMetadata();
}
private _tryUpdateMetadata() {
const edit: IWorkspaceCellEditDto = {
_type: WorkspaceEditType.Cell,
metadata: undefined,
notebookMetadata: this._metadata,
resource: this.uri,
notebookVersionId: this.notebookDocument.version,
};
return this._mainThreadBulkEdits.$tryApplyWorkspaceEdit({ edits: [edit] });
}
get notebookDocument(): vscode.NotebookDocument {
if (!this._notebook) {
const that = this;
this._notebook = Object.freeze({
get uri() { return that.uri; },
get version() { return that._versionId; },
get fileName() { return that.uri.fsPath; },
get viewType() { return that._viewType; },
get isDirty() { return that._isDirty; },
get isUntitled() { return that.uri.scheme === Schemas.untitled; },
get cells(): ReadonlyArray<vscode.NotebookCell> { return that._cells.map(cell => cell.cell); },
get languages() { return that._languages; },
set languages(value: string[]) { that._trySetLanguages(value); },
get metadata() { return that._metadata; },
set metadata(value: Required<vscode.NotebookDocumentMetadata>) { that._updateMetadata(value); },
});
}
return this._notebook;
}
private _trySetLanguages(newLanguages: string[]) {
this._languages = newLanguages;
this._proxy.$updateNotebookLanguages(this._viewType, this.uri, this._languages);
}
getNewBackupUri(): URI {
if (!this._storagePath) {
throw new Error('Backup requires a valid storage path');
}
const fileName = hashPath(this.uri) + (this._backupCounter++);
return joinPath(this._storagePath, fileName);
}
updateBackup(backup: vscode.NotebookDocumentBackup): void {
this._backup?.delete();
this._backup = backup;
}
disposeBackup(): void {
this._backup?.delete();
this._backup = undefined;
}
acceptDocumentPropertiesChanged(data: INotebookDocumentPropertiesChangeData) {
const newMetadata = {
...notebookDocumentMetadataDefaults,
...data.metadata
};
if (this._metadataChangeListener) {
this._metadataChangeListener.dispose();
}
const observableMetadata = getObservable(newMetadata);
this._metadata = observableMetadata.proxy;
this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => {
this._tryUpdateMetadata();
}));
this._emitter.emitDocumentMetadataChange({ document: this.notebookDocument });
}
acceptModelChanged(event: NotebookCellsChangedEventDto, isDirty: boolean): void {
this._versionId = event.versionId;
this._isDirty = isDirty;
if (event.kind === NotebookCellsChangeType.Initialize) {
this._spliceNotebookCells(event.changes, true);
} if (event.kind === NotebookCellsChangeType.ModelChange) {
this._spliceNotebookCells(event.changes, false);
} else if (event.kind === NotebookCellsChangeType.Move) {
this._moveCell(event.index, event.newIdx);
} else if (event.kind === NotebookCellsChangeType.Output) {
this._setCellOutputs(event.index, event.outputs);
} else if (event.kind === NotebookCellsChangeType.ChangeLanguage) {
this._changeCellLanguage(event.index, event.language);
} else if (event.kind === NotebookCellsChangeType.ChangeCellMetadata) {
this._changeCellMetadata(event.index, event.metadata);
}
}
private _spliceNotebookCells(splices: NotebookCellsSplice2[], initialization: boolean): void {
if (this._disposed) {
return;
}
const contentChangeEvents: RawContentChangeEvent[] = [];
const addedCellDocuments: IExtHostModelAddedData[] = [];
const removedCellDocuments: URI[] = [];
splices.reverse().forEach(splice => {
const cellDtos = splice[2];
const newCells = cellDtos.map(cell => {
const extCell = new ExtHostCell(this._mainThreadBulkEdits, this, this._documentsAndEditors, cell);
if (!initialization) {
addedCellDocuments.push(ExtHostCell.asModelAddData(this.notebookDocument, cell));
}
if (!this._cellDisposableMapping.has(extCell.handle)) {
const store = new DisposableStore();
store.add(extCell);
this._cellDisposableMapping.set(extCell.handle, store);
}
const store = this._cellDisposableMapping.get(extCell.handle)!;
store.add(extCell.onDidChangeOutputs((diffs) => {
this.eventuallyUpdateCellOutputs(extCell, diffs);
}));
return extCell;
});
for (let j = splice[0]; j < splice[0] + splice[1]; j++) {
this._cellDisposableMapping.get(this._cells[j].handle)?.dispose();
this._cellDisposableMapping.delete(this._cells[j].handle);
}
const deletedItems = this._cells.splice(splice[0], splice[1], ...newCells);
for (let cell of deletedItems) {
removedCellDocuments.push(cell.uri);
}
contentChangeEvents.push(new RawContentChangeEvent(splice[0], splice[1], deletedItems, newCells));
});
this._documentsAndEditors.acceptDocumentsAndEditorsDelta({
addedDocuments: addedCellDocuments,
removedDocuments: removedCellDocuments
});
if (!initialization) {
this._emitter.emitModelChange({
document: this.notebookDocument,
changes: contentChangeEvents.map(RawContentChangeEvent.asApiEvent)
});
}
}
private _moveCell(index: number, newIdx: number): void {
const cells = this._cells.splice(index, 1);
this._cells.splice(newIdx, 0, ...cells);
const changes: vscode.NotebookCellsChangeData[] = [{
start: index,
deletedCount: 1,
deletedItems: cells.map(data => data.cell),
items: []
}, {
start: newIdx,
deletedCount: 0,
deletedItems: [],
items: cells.map(data => data.cell)
}];
this._emitter.emitModelChange({
document: this.notebookDocument,
changes
});
}
private _setCellOutputs(index: number, outputs: IProcessedOutput[]): void {
const cell = this._cells[index];
cell.setOutputs(outputs);
this._emitter.emitCellOutputsChange({ document: this.notebookDocument, cells: [cell.cell] });
}
private _changeCellLanguage(index: number, language: string): void {
const cell = this._cells[index];
const event: vscode.NotebookCellLanguageChangeEvent = { document: this.notebookDocument, cell: cell.cell, language };
this._emitter.emitCellLanguageChange(event);
}
private _changeCellMetadata(index: number, newMetadata: NotebookCellMetadata | undefined): void {
const cell = this._cells[index];
cell.setMetadata(newMetadata || {});
const event: vscode.NotebookCellMetadataChangeEvent = { document: this.notebookDocument, cell: cell.cell };
this._emitter.emitCellMetadataChange(event);
}
async eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice<IProcessedOutput>[]) {
const outputDtos: NotebookCellOutputsSplice[] = diffs.map(diff => {
const outputs = diff.toInsert;
return [diff.start, diff.deleteCount, outputs];
});
if (!outputDtos.length) {
return;
}
await this._proxy.$spliceNotebookCellOutputs(this._viewType, this.uri, cell.handle, outputDtos);
this._emitter.emitCellOutputsChange({
document: this.notebookDocument,
cells: [cell.cell]
});
}
getCell(cellHandle: number): ExtHostCell | undefined {
return this._cells.find(cell => cell.handle === cellHandle);
}
addEdit(item: vscode.NotebookDocumentEditEvent): number {
return this._edits.add([item]);
}
async undo(editId: number, isDirty: boolean): Promise<void> {
await this.getEdit(editId).undo();
// if (!isDirty) {
// this.disposeBackup();
// }
}
async redo(editId: number, isDirty: boolean): Promise<void> {
await this.getEdit(editId).redo();
// if (!isDirty) {
// this.disposeBackup();
// }
}
private getEdit(editId: number): vscode.NotebookDocumentEditEvent {
const edit = this._edits.get(editId, 0);
if (!edit) {
throw new Error('No edit found');
}
return edit;
}
disposeEdits(editIds: number[]): void {
for (const id of editIds) {
this._edits.delete(id);
}
}
}
@@ -0,0 +1,257 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { readonly } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { CellKind, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
import { addIdToOutput, CellEditType, ICellEditOperation, ICellReplaceEdit, INotebookEditData, NotebookDocumentMetadata, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import * as vscode from 'vscode';
import { ExtHostNotebookDocument } from './extHostNotebookDocument';
class NotebookEditorCellEditBuilder implements vscode.NotebookEditorEdit {
private readonly _documentVersionId: number;
private _finalized: boolean = false;
private _collectedEdits: ICellEditOperation[] = [];
private _newNotebookDocumentMetadata?: NotebookDocumentMetadata;
constructor(documentVersionId: number) {
this._documentVersionId = documentVersionId;
}
finalize(): INotebookEditData {
this._finalized = true;
return {
documentVersionId: this._documentVersionId,
cellEdits: this._collectedEdits,
newMetadata: this._newNotebookDocumentMetadata
};
}
private _throwIfFinalized() {
if (this._finalized) {
throw new Error('Edit is only valid while callback runs');
}
}
replaceNotebookMetadata(value: vscode.NotebookDocumentMetadata): void {
this._throwIfFinalized();
this._newNotebookDocumentMetadata = { ...notebookDocumentMetadataDefaults, ...value };
}
replaceCellMetadata(index: number, metadata: vscode.NotebookCellMetadata): void {
this._throwIfFinalized();
this._collectedEdits.push({
editType: CellEditType.Metadata,
index,
metadata
});
}
replaceMetadata(index: number, metadata: vscode.NotebookCellMetadata): void {
console.warn('DEPRECATED use "replaceCellMetadata" instead');
this.replaceCellMetadata(index, metadata);
}
replaceCellOutput(index: number, outputs: vscode.CellOutput[]): void {
this._throwIfFinalized();
this._collectedEdits.push({
editType: CellEditType.Output,
index,
outputs: outputs.map(output => addIdToOutput(output))
});
}
replaceOutput(index: number, outputs: vscode.CellOutput[]): void {
console.warn('DEPRECATED use "replaceCellOutput" instead');
this.replaceCellOutput(index, outputs);
}
replaceCells(from: number, to: number, cells: vscode.NotebookCellData[]): void {
this._throwIfFinalized();
this._collectedEdits.push({
editType: CellEditType.Replace,
index: from,
count: to - from,
cells: cells.map(data => {
return {
...data,
outputs: data.outputs.map(output => addIdToOutput(output)),
};
})
});
}
insert(index: number, content: string | string[], language: string, type: CellKind, outputs: vscode.CellOutput[], metadata: vscode.NotebookCellMetadata | undefined): void {
this._throwIfFinalized();
this.replaceCells(index, index, [{
language,
outputs,
metadata,
cellKind: type,
source: Array.isArray(content) ? content.join('\n') : content,
}]);
}
delete(index: number): void {
this._throwIfFinalized();
this.replaceCells(index, 1, []);
}
}
export class ExtHostNotebookEditor extends Disposable implements vscode.NotebookEditor {
//TODO@rebornix noop setter?
selection?: vscode.NotebookCell;
private _visibleRanges: vscode.NotebookCellRange[] = [];
private _viewColumn?: vscode.ViewColumn;
private _active: boolean = false;
private _visible: boolean = false;
private _kernel?: vscode.NotebookKernel;
private _onDidDispose = new Emitter<void>();
private _onDidReceiveMessage = new Emitter<any>();
readonly onDidDispose: Event<void> = this._onDidDispose.event;
readonly onDidReceiveMessage: vscode.Event<any> = this._onDidReceiveMessage.event;
constructor(
readonly id: string,
private readonly _viewType: string,
private readonly _proxy: MainThreadNotebookShape,
private readonly _webComm: vscode.NotebookCommunication,
readonly notebookData: ExtHostNotebookDocument,
) {
super();
this._register(this._webComm.onDidReceiveMessage(e => {
this._onDidReceiveMessage.fire(e);
}));
}
get viewColumn(): vscode.ViewColumn | undefined {
return this._viewColumn;
}
set viewColumn(_value) {
throw readonly('viewColumn');
}
get kernel() {
return this._kernel;
}
set kernel(_kernel: vscode.NotebookKernel | undefined) {
throw readonly('kernel');
}
_acceptKernel(kernel?: vscode.NotebookKernel) {
this._kernel = kernel;
}
get visible(): boolean {
return this._visible;
}
set visible(_state: boolean) {
throw readonly('visible');
}
_acceptVisibility(value: boolean) {
this._visible = value;
}
get visibleRanges() {
return this._visibleRanges;
}
set visibleRanges(_range: vscode.NotebookCellRange[]) {
throw readonly('visibleRanges');
}
_acceptVisibleRanges(value: vscode.NotebookCellRange[]): void {
this._visibleRanges = value;
}
get active(): boolean {
return this._active;
}
set active(_state: boolean) {
throw readonly('active');
}
_acceptActive(value: boolean) {
this._active = value;
}
get document(): vscode.NotebookDocument {
return this.notebookData.notebookDocument;
}
edit(callback: (editBuilder: NotebookEditorCellEditBuilder) => void): Thenable<boolean> {
const edit = new NotebookEditorCellEditBuilder(this.document.version);
callback(edit);
return this._applyEdit(edit.finalize());
}
private _applyEdit(editData: INotebookEditData): Promise<boolean> {
// return when there is nothing to do
if (editData.cellEdits.length === 0) {
return Promise.resolve(true);
}
const compressedEdits: ICellEditOperation[] = [];
let compressedEditsIndex = -1;
for (let i = 0; i < editData.cellEdits.length; i++) {
if (compressedEditsIndex < 0) {
compressedEdits.push(editData.cellEdits[i]);
compressedEditsIndex++;
continue;
}
const prevIndex = compressedEditsIndex;
const prev = compressedEdits[prevIndex];
if (prev.editType === CellEditType.Replace && editData.cellEdits[i].editType === CellEditType.Replace) {
const edit = editData.cellEdits[i];
if (edit.editType !== CellEditType.DocumentMetadata && prev.index === edit.index) {
prev.cells.push(...(editData.cellEdits[i] as ICellReplaceEdit).cells);
prev.count += (editData.cellEdits[i] as ICellReplaceEdit).count;
continue;
}
}
compressedEdits.push(editData.cellEdits[i]);
compressedEditsIndex++;
}
return this._proxy.$tryApplyEdits(this._viewType, this.document.uri, editData.documentVersionId, compressedEdits, editData.newMetadata);
}
revealRange(range: vscode.NotebookCellRange, revealType?: extHostTypes.NotebookEditorRevealType) {
this._proxy.$tryRevealRange(this.id, range, revealType || extHostTypes.NotebookEditorRevealType.Default);
}
async postMessage(message: any): Promise<boolean> {
return this._webComm.postMessage(message);
}
asWebviewUri(localResource: vscode.Uri): vscode.Uri {
return this._webComm.asWebviewUri(localResource);
}
dispose() {
this._onDidDispose.fire();
super.dispose();
}
}
+3 -3
View File
@@ -456,7 +456,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask
});
}
public abstract async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise<vscode.TaskExecution>;
public abstract executeTask(extension: IExtensionDescription, task: vscode.Task): Promise<vscode.TaskExecution>;
public get taskExecutions(): vscode.TaskExecution[] {
const result: vscode.TaskExecution[] = [];
@@ -561,7 +561,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask
});
}
protected abstract async resolveTaskInternal(resolvedTaskDTO: tasks.TaskDTO): Promise<tasks.TaskDTO | undefined>;
protected abstract resolveTaskInternal(resolvedTaskDTO: tasks.TaskDTO): Promise<tasks.TaskDTO | undefined>;
public async $resolveTask(handle: number, taskDTO: tasks.TaskDTO): Promise<tasks.TaskDTO | undefined> {
const handler = this._handlers.get(handle);
@@ -601,7 +601,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask
return await this.resolveTaskInternal(resolvedTaskDTO);
}
public abstract async $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }>;
public abstract $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }>;
public abstract $getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }>;
@@ -120,7 +120,7 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
) {
super(proxy, id);
this._creationOptions = Object.freeze(this._creationOptions);
this._pidPromise = new Promise<number>(c => this._pidPromiseComplete = c);
this._pidPromise = new Promise<number | undefined>(c => this._pidPromiseComplete = c);
}
public async create(
@@ -316,7 +316,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
protected _terminalProcesses: { [id: number]: ITerminalChildProcess } = {};
protected _terminalProcessDisposables: { [id: number]: IDisposable } = {};
protected _extensionTerminalAwaitingStart: { [id: number]: { initialDimensions: ITerminalDimensionsDto | undefined } | undefined } = {};
protected _getTerminalPromises: { [id: number]: Promise<ExtHostTerminal> } = {};
protected _getTerminalPromises: { [id: number]: Promise<ExtHostTerminal | undefined> } = {};
protected _environmentVariableCollections: Map<string, EnvironmentVariableCollection> = new Map();
private readonly _bufferer: TerminalDataBufferer;
@@ -670,7 +670,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
return this._getTerminalPromises[id];
}
private _createGetTerminalPromise(id: number, retries: number = 5): Promise<ExtHostTerminal> {
private _createGetTerminalPromise(id: number, retries: number = 5): Promise<ExtHostTerminal | undefined> {
return new Promise(c => {
if (retries === 0) {
c(undefined);
@@ -7,7 +7,6 @@ import { Emitter, Event } from 'vs/base/common/event';
import * as arrays from 'vs/base/common/arrays';
import { ExtHostEditorsShape, IEditorPropertiesChangeData, IMainContext, ITextDocumentShowOptions, ITextEditorPositionData, MainContext, MainThreadTextEditorsShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
import { ExtHostTextEditor, TextEditorDecorationType } from 'vs/workbench/api/common/extHostTextEditor';
import * as TypeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import { TextEditorSelectionChangeKind } from 'vs/workbench/api/common/extHostTypes';
@@ -34,7 +33,6 @@ export class ExtHostEditors implements ExtHostEditorsShape {
constructor(
mainContext: IMainContext,
private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
private readonly _extHostNotebooks: ExtHostNotebookController,
) {
this._proxy = mainContext.getProxy(MainContext.MainThreadTextEditors);
@@ -92,11 +90,6 @@ export class ExtHostEditors implements ExtHostEditorsShape {
return new TextEditorDecorationType(this._proxy, options);
}
applyWorkspaceEdit(edit: vscode.WorkspaceEdit): Promise<boolean> {
const dto = TypeConverters.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors, this._extHostNotebooks);
return this._proxy.$tryApplyWorkspaceEdit(dto);
}
// --- called from main thread
$acceptEditorPropertiesChanged(id: string, data: IEditorPropertiesChangeData): void {
@@ -537,10 +537,11 @@ export namespace WorkspaceEdit {
} else if (entry._type === types.FileEditType.Cell) {
result.edits.push(<extHostProtocol.IWorkspaceCellEditDto>{
_type: extHostProtocol.WorkspaceEditType.Cell,
metadata: entry.metadata,
resource: entry.uri,
edit: entry.edit,
metadata: entry.metadata,
modelVersionId: notebooks?.lookupNotebookDocument(entry.uri)?.notebookDocument.version
notebookMetadata: entry.notebookMetadata,
notebookVersionId: notebooks?.lookupNotebookDocument(entry.uri)?.notebookDocument.version
});
}
}
+7 -2
View File
@@ -601,7 +601,8 @@ export interface IFileTextEdit {
export interface IFileCellEdit {
_type: FileEditType.Cell;
uri: URI;
edit: ICellEditOperation;
edit?: ICellEditOperation;
notebookMetadata?: vscode.NotebookDocumentMetadata;
metadata?: vscode.WorkspaceEditEntryMetadata;
}
@@ -629,7 +630,11 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
this._edits.push({ _type: FileEditType.File, from: uri, to: undefined, options, metadata });
}
// --- cell
// --- notebook
replaceNotebookMetadata(uri: URI, value: vscode.NotebookDocumentMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void {
this._edits.push({ _type: FileEditType.Cell, metadata, uri, notebookMetadata: value });
}
replaceCells(uri: URI, start: number, end: number, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void {
this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.Replace, index: start, count: end - start, cells: cells.map(cell => ({ ...cell, outputs: cell.outputs.map(output => addIdToOutput(output)) })) } });
@@ -29,12 +29,14 @@ class ExtHostWebviewView extends Disposable implements vscode.WebviewView {
handle: extHostProtocol.WebviewHandle,
proxy: extHostProtocol.MainThreadWebviewViewsShape,
viewType: string,
title: string | undefined,
webview: ExtHostWebview,
isVisible: boolean,
) {
super();
this.#viewType = viewType;
this.#title = title;
this.#handle = handle;
this.#proxy = proxy;
this.#webview = webview;
@@ -153,6 +155,7 @@ export class ExtHostWebviewViews implements extHostProtocol.ExtHostWebviewViewsS
async $resolveWebviewView(
webviewHandle: string,
viewType: string,
title: string | undefined,
state: any,
cancellation: CancellationToken,
): Promise<void> {
@@ -164,7 +167,7 @@ export class ExtHostWebviewViews implements extHostProtocol.ExtHostWebviewViewsS
const { provider, extension } = entry;
const webview = this._extHostWebview.createNewWebview(webviewHandle, { /* todo */ }, extension);
const revivedView = new ExtHostWebviewView(webviewHandle, this._proxy, viewType, webview, true);
const revivedView = new ExtHostWebviewView(webviewHandle, this._proxy, viewType, title, webview, true);
this._webviewViews.set(webviewHandle, revivedView);
@@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri';
import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService';
import { WorkspaceEditMetadata } from 'vs/editor/common/modes';
import { IProgress } from 'vs/platform/progress/common/progress';
import { ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellEditType, ICellEditOperation, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
@@ -17,7 +17,8 @@ export class ResourceNotebookCellEdit extends ResourceEdit {
constructor(
readonly resource: URI,
readonly cellEdit: ICellEditOperation,
readonly cellEdit?: ICellEditOperation,
readonly notebookMetadata?: NotebookDocumentMetadata,
readonly versionId?: number,
readonly metadata?: WorkspaceEditMetadata
) {
@@ -48,10 +49,25 @@ export class BulkCellEdits {
// throw new Error(`Notebook '${first.resource}' has changed in the meantime`);
// }
const edits: ICellEditOperation[] = [];
let newMetadata: NotebookDocumentMetadata | undefined;
for (let edit of group) {
if (edit.cellEdit) {
edits.push(edit.cellEdit);
}
newMetadata = edit.notebookMetadata ?? newMetadata;
}
// set metadata
if (newMetadata) {
edits.push({
editType: CellEditType.DocumentMetadata,
metadata: newMetadata
});
}
// apply edits
const cellEdits = group.map(edit => edit.cellEdit);
this._notebookService.transformEditsOutputs(ref.object.notebook, cellEdits);
ref.object.notebook.applyEdit(ref.object.notebook.versionId, cellEdits, true);
this._notebookService.transformEditsOutputs(ref.object.notebook, edits);
ref.object.notebook.applyEdit(ref.object.notebook.versionId, edits, true);
ref.dispose();
this._progress.report(undefined);
@@ -317,7 +317,7 @@ export class ConfigurationManager implements IConfigurationManager {
await (launch as Launch).writeConfiguration(config);
await this.selectConfiguration(launch, config.name);
}));
disposables.add(input.onDidHide(() => { chosenDidCancel = true; resolve(); }));
disposables.add(input.onDidHide(() => { chosenDidCancel = true; resolve(undefined); }));
});
const token = new CancellationTokenSource();
@@ -198,7 +198,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer {
if (state === State.Initializing) {
this.progressService.withProgress({ location: VIEWLET_ID, }, _progress => {
return new Promise(resolve => this.progressResolve = resolve);
return new Promise<void>(resolve => this.progressResolve = resolve);
});
}
@@ -469,7 +469,7 @@ suite('ExtensionsActions', () => {
testObject.extension = extensions[0];
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: local.identifier, version: '1.0.1' })));
assert.ok(!testObject.enabled);
return new Promise(c => {
return new Promise<void>(c => {
testObject.onDidChange(() => {
if (testObject.enabled) {
c();
@@ -1025,7 +1025,7 @@ suite('ExtensionsActions', () => {
.then(async () => {
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: local[0].identifier, version: '1.0.2' }), aGalleryExtension('b', { identifier: local[1].identifier, version: '1.0.2' }), aGalleryExtension('c', local[2].manifest)));
assert.ok(!testObject.enabled);
return new Promise(c => {
return new Promise<void>(c => {
testObject.onDidChange(() => {
if (testObject.enabled) {
c();
@@ -1046,7 +1046,7 @@ suite('ExtensionsActions', () => {
.then(async () => {
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...gallery));
assert.ok(!testObject.enabled);
return new Promise(c => {
return new Promise<void>(c => {
installEvent.fire({ identifier: local[0].identifier, gallery: gallery[0] });
testObject.onDidChange(() => {
if (testObject.enabled) {
@@ -1246,7 +1246,7 @@ suite('ReloadAction', () => {
const extensions = await workbenchService.queryLocal();
testObject.extension = extensions[0];
return new Promise(c => {
return new Promise<void>(c => {
testObject.onDidChange(() => {
if (testObject.enabled && testObject.tooltip === 'Please reload Visual Studio Code to enable the updated extension.') {
c();
@@ -371,9 +371,14 @@ async function saveSelectedEditors(accessor: ServicesAccessor, options?: ISaveEd
// Special treatment for side by side editors: if the active editor
// has 2 sides, we consider both, to support saving both sides.
// We only allow this when saving, not for "Save As".
// We only allow this when saving, not for "Save As" and not if any
// editor is untitled which would bring up a "Save As" dialog too.
// See also https://github.com/microsoft/vscode/issues/4180
if (activeGroup.activeEditor instanceof SideBySideEditorInput && !options?.saveAs) {
// See also https://github.com/microsoft/vscode/issues/106330
if (
activeGroup.activeEditor instanceof SideBySideEditorInput &&
!options?.saveAs && !(activeGroup.activeEditor.primary.isUntitled() || activeGroup.activeEditor.secondary.isUntitled())
) {
editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor.primary });
editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor.secondary });
} else {
@@ -145,7 +145,7 @@ abstract class NotebookAction extends Action2 {
this.runWithContext(accessor, context);
}
abstract async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void>;
abstract runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void>;
private isNotebookActionContext(context?: unknown): context is INotebookActionContext {
return !!context && !!(context as INotebookActionContext).notebookEditor;
@@ -185,7 +185,7 @@ abstract class NotebookCellAction extends NotebookAction {
this.runWithContext(accessor, context);
}
abstract async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void>;
abstract runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void>;
}
registerAction2(class extends NotebookCellAction {
@@ -1387,7 +1387,12 @@ export class ChangeCellLanguageAction extends NotebookCellAction {
} else if (selection.languageId !== 'markdown' && context.cell?.language === 'markdown') {
await changeCellToKind(CellKind.Code, { cell: context.cell, notebookEditor: context.notebookEditor }, selection.languageId);
} else {
context.notebookEditor.viewModel!.notebookDocument.changeCellLanguage(context.cell.handle, selection.languageId);
const index = context.notebookEditor.viewModel!.notebookDocument.cells.indexOf(context.cell.model);
context.notebookEditor.viewModel!.notebookDocument.applyEdit(
context.notebookEditor.viewModel!.notebookDocument.versionId,
[{ editType: CellEditType.CellLanguage, index, language: selection.languageId }],
true
);
}
}
}
@@ -283,6 +283,14 @@ 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'));
const checkIfModified = (cell: CellDiffViewModel) => {
return cell.type !== 'delete' && cell.type !== 'insert' && hash(this._getFormatedMetadataJSON(cell.original?.metadata || {}, cell.original?.language)) !== hash(this._getFormatedMetadataJSON(cell.modified?.metadata ?? {}, cell.modified?.language));
};
if (checkIfModified(this.cell)) {
this.cell.metadataFoldingState = PropertyFoldingState.Expanded;
}
this._metadataHeader = this.instantiationService.createInstance(
PropertyHeader,
this.cell,
@@ -291,7 +299,7 @@ abstract class AbstractCellRenderer extends Disposable {
{
updateInfoRendering: this.updateMetadataRendering.bind(this),
checkIfModified: (cell) => {
return cell.type !== 'delete' && cell.type !== 'insert' && hash(this._getFormatedMetadataJSON(cell.original?.metadata || {}, cell.original?.language)) !== hash(this._getFormatedMetadataJSON(cell.modified?.metadata ?? {}, cell.modified?.language));
return checkIfModified(cell);
},
getFoldingState: (cell) => {
return cell.metadataFoldingState;
@@ -311,6 +319,14 @@ 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'));
const checkIfOutputsModified = (cell: CellDiffViewModel) => {
return cell.type !== 'delete' && cell.type !== 'insert' && !this.notebookEditor.textModel!.transientOptions.transientOutputs && cell.type === 'modified' && hash(cell.original?.outputs ?? []) !== hash(cell.modified?.outputs ?? []);
};
if (checkIfOutputsModified(this.cell)) {
this.cell.outputFoldingState = PropertyFoldingState.Expanded;
}
this._outputHeader = this.instantiationService.createInstance(
PropertyHeader,
this.cell,
@@ -319,10 +335,10 @@ abstract class AbstractCellRenderer extends Disposable {
{
updateInfoRendering: this.updateOutputRendering.bind(this),
checkIfModified: (cell) => {
return cell.type !== 'delete' && cell.type !== 'insert' && !this.notebookEditor.textModel!.transientOptions.transientOutputs && cell.type === 'modified' && hash(cell.original?.outputs ?? []) !== hash(cell.modified?.outputs ?? []);
return checkIfOutputsModified(cell);
},
getFoldingState: (cell) => {
return this.cell.outputFoldingState;
return cell.outputFoldingState;
},
updateFoldingState: (cell, state) => {
cell.outputFoldingState = state;
@@ -449,8 +465,14 @@ abstract class AbstractCellRenderer extends Disposable {
}
if (newLangauge !== undefined && newLangauge !== this.cell.modified!.language) {
this.notebookEditor.textModel!.changeCellLanguage(this.cell.modified!.handle, newLangauge);
const index = this.notebookEditor.textModel!.cells.indexOf(this.cell.modified!);
this.notebookEditor.textModel!.applyEdit(
this.notebookEditor.textModel!.versionId,
[{ editType: CellEditType.CellLanguage, index, language: newLangauge }],
true
);
}
const index = this.notebookEditor.textModel!.cells.indexOf(this.cell.modified!);
if (index < 0) {
@@ -321,7 +321,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
r();
}));
return new Promise(resolve => { r = resolve; });
return new Promise<void>(resolve => { r = resolve; });
}
getDomNode() {
@@ -221,8 +221,8 @@ export class NotebookTextDiffList extends WorkbenchList<CellDiffViewModel> imple
}
const newStyles = content.join('\n');
if (newStyles !== this.styleElement.innerHTML) {
this.styleElement.innerHTML = newStyles;
if (newStyles !== this.styleElement.textContent) {
this.styleElement.textContent = newStyles;
}
}
}
@@ -564,9 +564,9 @@ class NotebookFileTracker implements IWorkbenchContribution {
constructor(
@INotebookService private readonly _notebookService: INotebookService,
@IEditorService private readonly _editorService: IEditorService,
@IWorkingCopyService workingCopyService: IWorkingCopyService,
@IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService,
) {
this._dirtyListener = Event.debounce(workingCopyService.onDidChangeDirty, () => { }, 100)(() => {
this._dirtyListener = Event.debounce(_workingCopyService.onDidChangeDirty, () => { }, 100)(() => {
const inputs = this._createMissingNotebookEditors();
this._editorService.openEditors(inputs);
});
@@ -580,7 +580,7 @@ class NotebookFileTracker implements IWorkbenchContribution {
const result: IResourceEditorInput[] = [];
for (const notebook of this._notebookService.getNotebookTextModels()) {
if (notebook.isDirty && !this._editorService.isOpen({ resource: notebook.uri })) {
if (this._workingCopyService.isDirty(notebook.uri.with({ scheme: Schemas.vscodeNotebook })) && !this._editorService.isOpen({ resource: notebook.uri })) {
result.push({
resource: notebook.uri,
options: { inactive: true, preserveFocus: true, pinned: true }
@@ -193,14 +193,14 @@ ${patterns}
return;
}
async resolve(editorId?: string): Promise<INotebookDiffEditorModel | null> {
async resolve(): Promise<INotebookDiffEditorModel | null> {
if (!await this._notebookService.canResolve(this.viewType!)) {
return null;
}
if (!this._textModel) {
this._textModel = await this._notebookModelResolverService.resolve(this.resource, this.viewType!, editorId);
this._originalTextModel = await this._notebookModelResolverService.resolve(this.originalResource, this.viewType!, editorId);
this._textModel = await this._notebookModelResolverService.resolve(this.resource, this.viewType!);
this._originalTextModel = await this._notebookModelResolverService.resolve(this.originalResource, this.viewType!);
}
return new NotebookDiffEditorModel(this._originalTextModel!.object as NotebookEditorModel, this._textModel.object as NotebookEditorModel);
@@ -25,6 +25,7 @@ import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorD
import { IEditorGroup, IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState';
@@ -54,6 +55,7 @@ export class NotebookEditor extends EditorPane {
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IEditorDropService private readonly _editorDropService: IEditorDropService,
@INotificationService private readonly _notificationService: INotificationService,
@INotebookService private readonly _notebookService: INotebookService,
@INotebookEditorWidgetService private readonly _notebookWidgetService: INotebookEditorWidgetService,
) {
super(NotebookEditor.ID, telemetryService, themeService, storageService);
@@ -152,7 +154,7 @@ export class NotebookEditor extends EditorPane {
this._widget.value!.layout(this._dimension, this._rootElement);
}
const model = await input.resolve(this._widget.value!.getId());
const model = await input.resolve();
// Check for cancellation
if (token.isCancellationRequested) {
return undefined;
@@ -174,6 +176,8 @@ export class NotebookEditor extends EditorPane {
return;
}
await this._notebookService.resolveNotebookEditor(model.viewType, model.resource, this._widget.value!.getId());
const viewState = this._loadNotebookEditorViewState(input);
await this._widget.value!.setModel(model.notebook, viewState);
@@ -168,13 +168,13 @@ ${patterns}
return;
}
async resolve(editorId?: string): Promise<INotebookEditorModel | null> {
async resolve(): Promise<INotebookEditorModel | null> {
if (!await this._notebookService.canResolve(this.viewType!)) {
return null;
}
if (!this._textModel) {
this._textModel = await this._notebookModelResolverService.resolve(this.resource, this.viewType!, editorId);
this._textModel = await this._notebookModelResolverService.resolve(this.resource, this.viewType!);
this._register(this._textModel.object.onDidChangeDirty(() => {
this._onDidChangeDirty.fire();
@@ -9,6 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import { Iterable } from 'vs/base/common/iterator';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ResourceMap } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions';
import { CopyAction, CutAction, PasteAction } from 'vs/editor/contrib/clipboard/clipboard';
@@ -26,7 +27,7 @@ import { NotebookKernelProviderAssociationRegistry, NotebookViewTypesExtensionRe
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellEditType, CellOutputKind, CellUri, DisplayOrderKey, ICellEditOperation, IDisplayOutput, INotebookKernelInfo2, INotebookKernelProvider, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellOutputsSplice, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellEditType, CellKind, CellOutputKind, CellUri, DisplayOrderKey, ICellEditOperation, IDisplayOutput, INotebookKernelInfo2, INotebookKernelProvider, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellOutputsSplice, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer';
import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
@@ -605,7 +606,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu
return this.notebookRenderersInfoStore.get(id);
}
async resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string, backupId?: string): Promise<NotebookTextModel> {
async resolveNotebook(viewType: string, uri: URI, forceReload: boolean, backupId?: string): Promise<NotebookTextModel> {
if (!await this.canResolve(viewType)) {
throw new Error(`CANNOT load notebook, no provider for '${viewType}'`);
@@ -622,8 +623,16 @@ export class NotebookService extends Disposable implements INotebookService, ICu
return notebookModel;
} else {
notebookModel = this._instantiationService.createInstance(NotebookTextModel, viewType, provider.controller.supportBackup, uri);
await provider.controller.createNotebook(notebookModel, backupId);
const dataDto = await provider.controller.resolveNotebookDocument(viewType, uri, backupId);
let cells = dataDto.data.cells.length ? dataDto.data.cells : (uri.scheme === Schemas.untitled ? [{
cellKind: CellKind.Code,
language: dataDto.data.languages.length ? dataDto.data.languages[0] : '',
outputs: [],
metadata: undefined,
source: ''
}] : []);
notebookModel = this._instantiationService.createInstance(NotebookTextModel, viewType, provider.controller.supportBackup, uri, cells, dataDto.data.languages, dataDto.data.metadata, dataDto.transientOptions);
}
// new notebook model created
@@ -637,10 +646,6 @@ export class NotebookService extends Disposable implements INotebookService, ICu
// after the document is added to the store and sent to ext host, we transform the ouputs
await this.transformTextModelOutputs(notebookModel);
if (editorId) {
await provider.controller.resolveNotebookEditor(viewType, uri, editorId);
}
return modelData.model;
}
@@ -786,6 +791,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu
return ret;
}
async resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise<void> {
const entry = this._notebookProviders.get(viewType);
if (entry) {
entry.controller.resolveNotebookEditor(viewType, uri, editorId);
}
}
removeNotebookEditor(editor: INotebookEditor) {
const editorCache = this._notebookEditors.get(editor.getId());
@@ -919,19 +931,12 @@ export class NotebookService extends Disposable implements INotebookService, ICu
}
});
modelData.model.dispose();
modelData.dispose();
willRemovedEditors.forEach(e => this._notebookEditors.delete(e.getId()));
const provider = this._notebookProviders.get(modelData!.model.viewType);
if (provider) {
provider.controller.removeNotebookDocument(modelData!.model.uri);
modelData!.model.dispose();
}
this._onNotebookEditorsRemove.fire(willRemovedEditors.map(e => e));
this._onDidRemoveNotebookDocument.fire(modelData.model.uri);
modelData.dispose();
}
}
}

Some files were not shown because too many files have changed in this diff Show More