mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-24 08:15:51 +01:00
Merge branch 'master' into ben/pinned-tabs-setting
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"label-to-subscribe-to": [
|
||||
"list of usernames to subscribe",
|
||||
"such as:",
|
||||
"JacksonKearl"
|
||||
]
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Vendored
+15
-15
@@ -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 {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(/&&/g, '&');
|
||||
|
||||
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(/&&/g, '&');
|
||||
titleElement.innerHTML = innerHtml;
|
||||
} else {
|
||||
titleElement.innerHTML = cleanMenuLabel.replace(/&&/g, '&');
|
||||
titleElement.innerText = cleanMenuLabel.replace(/&&/g, '&');
|
||||
}
|
||||
|
||||
let mnemonicMatches = MENU_MNEMONIC_REGEX.exec(label);
|
||||
|
||||
@@ -266,7 +266,7 @@ export class Sash extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
style.innerHTML = `* { cursor: ${cursor} !important; }`;
|
||||
style.textContent = `* { cursor: ${cursor} !important; }`;
|
||||
};
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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...');
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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,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 };
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+11
@@ -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;
|
||||
|
||||
|
||||
Vendored
+16
-4
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user