Archived
Merge from vscode 61d5f2b82f17bf9f99f56405204caab88a7e8747
This commit is contained in:
@@ -1,16 +1,3 @@
|
|||||||
# {
|
|
||||||
# perform: true,
|
|
||||||
# commands: [
|
|
||||||
# {
|
|
||||||
# type: 'comment',
|
|
||||||
# name: 'findDuplicates',
|
|
||||||
# allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'],
|
|
||||||
# action: 'comment',
|
|
||||||
# comment: "Potential duplicates:\n${potentialDuplicates}"
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# }
|
|
||||||
|
|
||||||
{
|
{
|
||||||
perform: true,
|
perform: true,
|
||||||
commands: [
|
commands: [
|
||||||
|
|||||||
@@ -122,6 +122,10 @@
|
|||||||
"name": "vs/workbench/contrib/preferences",
|
"name": "vs/workbench/contrib/preferences",
|
||||||
"project": "vscode-workbench"
|
"project": "vscode-workbench"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "vs/workbench/contrib/notebook",
|
||||||
|
"project": "vscode-workbench"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "vs/workbench/contrib/quickaccess",
|
"name": "vs/workbench/contrib/quickaccess",
|
||||||
"project": "vscode-workbench"
|
"project": "vscode-workbench"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
|
|||||||
import { Range } from 'vs/editor/common/core/range';
|
import { Range } from 'vs/editor/common/core/range';
|
||||||
import { IStandardKernelWithProvider } from 'sql/workbench/services/notebook/browser/models/notebookUtils';
|
import { IStandardKernelWithProvider } from 'sql/workbench/services/notebook/browser/models/notebookUtils';
|
||||||
|
|
||||||
export const SERVICE_ID = 'notebookService';
|
export const SERVICE_ID = 'sqlNotebookService';
|
||||||
export const INotebookService = createDecorator<INotebookService>(SERVICE_ID);
|
export const INotebookService = createDecorator<INotebookService>(SERVICE_ID);
|
||||||
|
|
||||||
export const DEFAULT_NOTEBOOK_PROVIDER = 'builtin';
|
export const DEFAULT_NOTEBOOK_PROVIDER = 'builtin';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "codicon";
|
font-family: "codicon";
|
||||||
src: url("./codicon.ttf?5490083fcec741c6a0a08a366d2f9c98") format("truetype");
|
src: url("./codicon.ttf?fb4c14f317e1decb0289895ecc9356f0") format("truetype");
|
||||||
}
|
}
|
||||||
|
|
||||||
.codicon[class*='codicon-'] {
|
.codicon[class*='codicon-'] {
|
||||||
@@ -416,7 +416,8 @@
|
|||||||
.codicon-feedback:before { content: "\eb96" }
|
.codicon-feedback:before { content: "\eb96" }
|
||||||
.codicon-group-by-ref-type:before { content: "\eb97" }
|
.codicon-group-by-ref-type:before { content: "\eb97" }
|
||||||
.codicon-ungroup-by-ref-type:before { content: "\eb98" }
|
.codicon-ungroup-by-ref-type:before { content: "\eb98" }
|
||||||
.codicon-bell-dot:before { content: "\f101" }
|
.codicon-account:before { content: "\f101" }
|
||||||
.codicon-debug-alt-2:before { content: "\f102" }
|
.codicon-bell-dot:before { content: "\f102" }
|
||||||
.codicon-debug-alt:before { content: "\f103" }
|
.codicon-debug-alt-2:before { content: "\f103" }
|
||||||
.codicon-run-all:before { content: "\f104" }
|
.codicon-debug-alt:before { content: "\f104" }
|
||||||
|
.codicon-run-all:before { content: "\f105" }
|
||||||
|
|||||||
Binary file not shown.
@@ -279,18 +279,32 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
|||||||
this.scrollableElement.triggerScrollFromMouseWheelEvent(browserEvent);
|
this.scrollableElement.triggerScrollFromMouseWheelEvent(browserEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateElementHeight(index: number, size: number): void {
|
updateElementHeight(index: number, size: number, anchorIndex: number | null): void {
|
||||||
if (this.items[index].size === size) {
|
if (this.items[index].size === size) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
|
const lastRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
|
||||||
|
|
||||||
const heightDiff = index < lastRenderRange.start ? size - this.items[index].size : 0;
|
let heightDiff = 0;
|
||||||
|
|
||||||
|
if (index < lastRenderRange.start) {
|
||||||
|
// do not scroll the viewport if resized element is out of viewport
|
||||||
|
heightDiff = size - this.items[index].size;
|
||||||
|
} else {
|
||||||
|
if (anchorIndex !== null && anchorIndex > index && anchorIndex <= lastRenderRange.end) {
|
||||||
|
// anchor in viewport
|
||||||
|
// resized elemnet in viewport and above the anchor
|
||||||
|
heightDiff = size - this.items[index].size;
|
||||||
|
} else {
|
||||||
|
heightDiff = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.rangeMap.splice(index, 1, [{ size: size }]);
|
this.rangeMap.splice(index, 1, [{ size: size }]);
|
||||||
this.items[index].size = size;
|
this.items[index].size = size;
|
||||||
|
|
||||||
this.render(lastRenderRange, this.lastRenderTop + heightDiff, this.lastRenderHeight, undefined, undefined, true);
|
this.render(lastRenderRange, Math.max(0, this.lastRenderTop + heightDiff), this.lastRenderHeight, undefined, undefined, true);
|
||||||
|
|
||||||
this.eventuallyUpdateScrollDimensions();
|
this.eventuallyUpdateScrollDimensions();
|
||||||
|
|
||||||
@@ -1134,6 +1148,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!!this.virtualDelegate.hasDynamicHeight && !this.virtualDelegate.hasDynamicHeight(item.element)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
const size = item.size;
|
const size = item.size;
|
||||||
|
|
||||||
if (!this.setRowHeight && item.row && item.row.domNode) {
|
if (!this.setRowHeight && item.row && item.row.domNode) {
|
||||||
|
|||||||
@@ -1314,7 +1314,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateElementHeight(index: number, size: number): void {
|
updateElementHeight(index: number, size: number): void {
|
||||||
this.view.updateElementHeight(index, size);
|
this.view.updateElementHeight(index, size, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
rerender(): void {
|
rerender(): void {
|
||||||
|
|||||||
@@ -99,3 +99,26 @@
|
|||||||
.monaco-pane-view.animated.horizontal .split-view-view {
|
.monaco-pane-view.animated.horizontal .split-view-view {
|
||||||
transition-property: width;
|
transition-property: width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#monaco-workbench-pane-drop-overlay {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10000;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#monaco-workbench-pane-drop-overlay > .pane-overlay-indicator {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 22px;
|
||||||
|
|
||||||
|
pointer-events: none; /* very important to not take events away from the parent */
|
||||||
|
transition: opacity 150ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#monaco-workbench-pane-drop-overlay > .pane-overlay-indicator.overlay-move-transition {
|
||||||
|
transition: top 70ms ease-out, left 70ms ease-out, width 70ms ease-out, height 70ms ease-out, opacity 150ms ease-out;
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const hasOwnProperty = Object.prototype.hasOwnProperty;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array which contains all values that reside
|
* Returns an array which contains all values that reside
|
||||||
* in the given set.
|
* in the given dictionary.
|
||||||
*/
|
*/
|
||||||
export function values<T>(from: IStringDictionary<T> | INumberDictionary<T>): T[] {
|
export function values<T>(from: IStringDictionary<T> | INumberDictionary<T>): T[] {
|
||||||
const result: T[] = [];
|
const result: T[] = [];
|
||||||
@@ -52,7 +52,7 @@ export function first<T>(from: IStringDictionary<T> | INumberDictionary<T>): T |
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterates over each entry in the provided set. The iterator allows
|
* Iterates over each entry in the provided dictionary. The iterator allows
|
||||||
* to remove elements and will stop when the callback returns {{false}}.
|
* to remove elements and will stop when the callback returns {{false}}.
|
||||||
*/
|
*/
|
||||||
export function forEach<T>(from: IStringDictionary<T>, callback: (entry: { key: string; value: T; }, remove: () => void) => any): void; // {{SQL CARBON EDIT}} @anthonydresser add hard typings
|
export function forEach<T>(from: IStringDictionary<T>, callback: (entry: { key: string; value: T; }, remove: () => void) => any): void; // {{SQL CARBON EDIT}} @anthonydresser add hard typings
|
||||||
|
|||||||
@@ -49,7 +49,15 @@ export namespace Iterable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* map<T, R>(iterable: Iterable<T>, fn: (t: T) => R): IterableIterator<R> {
|
export function* filter<T>(iterable: Iterable<T>, predicate: (t: T) => boolean): Iterable<T> {
|
||||||
|
for (const element of iterable) {
|
||||||
|
if (predicate(element)) {
|
||||||
|
return yield element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* map<T, R>(iterable: Iterable<T>, fn: (t: T) => R): Iterable<R> {
|
||||||
for (const element of iterable) {
|
for (const element of iterable) {
|
||||||
return yield fn(element);
|
return yield fn(element);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import { IDragAndDropData } from 'vs/base/browser/dnd';
|
|
||||||
|
|
||||||
export class CompositeDragAndDropData implements IDragAndDropData {
|
|
||||||
constructor(private type: 'view' | 'composite', private id: string) { }
|
|
||||||
update(dataTransfer: DataTransfer): void {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
getData(): {
|
|
||||||
type: 'view' | 'composite';
|
|
||||||
id: string;
|
|
||||||
} {
|
|
||||||
return { type: this.type, id: this.id };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICompositeDragAndDrop {
|
|
||||||
drop(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): void;
|
|
||||||
onDragOver(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean;
|
|
||||||
onDragEnter(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean;
|
|
||||||
}
|
|
||||||
@@ -220,11 +220,20 @@
|
|||||||
|
|
||||||
.quick-input-list .quick-input-list-entry-action-bar {
|
.quick-input-list .quick-input-list-entry-action-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
visibility: hidden; /* not using display: none here to not flicker too much */
|
|
||||||
flex: 0;
|
flex: 0;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quick-input-list .quick-input-list-entry-action-bar .action-label {
|
||||||
|
/*
|
||||||
|
* By default, actions in the quick input action bar are hidden
|
||||||
|
* until hovered over them or selected. We do not use display:none
|
||||||
|
* so that the amount of visual flickering is little by reserving the
|
||||||
|
* space the button needs still.
|
||||||
|
*/
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.quick-input-list .quick-input-list-entry-action-bar .action-label.codicon {
|
.quick-input-list .quick-input-list-entry-action-bar .action-label.codicon {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 19px;
|
width: 19px;
|
||||||
@@ -244,8 +253,8 @@
|
|||||||
margin-right: 4px; /* separate actions */
|
margin-right: 4px; /* separate actions */
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-input-list .quick-input-list-entry.always-visible-actions .quick-input-list-entry-action-bar,
|
.quick-input-list .quick-input-list-entry .quick-input-list-entry-action-bar .action-label.always-visible,
|
||||||
.quick-input-list .quick-input-list-entry:hover .quick-input-list-entry-action-bar,
|
.quick-input-list .quick-input-list-entry:hover .quick-input-list-entry-action-bar .action-label,
|
||||||
.quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar {
|
.quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar .action-label {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,7 +181,11 @@ class ListElementRenderer implements IListRenderer<ListElement, IListElementTemp
|
|||||||
const buttons = element.item.buttons;
|
const buttons = element.item.buttons;
|
||||||
if (buttons && buttons.length) {
|
if (buttons && buttons.length) {
|
||||||
data.actionBar.push(buttons.map((button, index) => {
|
data.actionBar.push(buttons.map((button, index) => {
|
||||||
const action = new Action(`id-${index}`, '', button.iconClass || (button.iconPath ? getIconClass(button.iconPath) : undefined), true, () => {
|
let cssClasses = button.iconClass || (button.iconPath ? getIconClass(button.iconPath) : undefined);
|
||||||
|
if (button.alwaysVisible) {
|
||||||
|
cssClasses = cssClasses ? `${cssClasses} always-visible` : 'always-visible';
|
||||||
|
}
|
||||||
|
const action = new Action(`id-${index}`, '', cssClasses, true, () => {
|
||||||
element.fireButtonTriggered({
|
element.fireButtonTriggered({
|
||||||
button,
|
button,
|
||||||
item: element.item
|
item: element.item
|
||||||
@@ -195,12 +199,6 @@ class ListElementRenderer implements IListRenderer<ListElement, IListElementTemp
|
|||||||
} else {
|
} else {
|
||||||
dom.removeClass(data.entry, 'has-actions');
|
dom.removeClass(data.entry, 'has-actions');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.item.buttonsAlwaysVisible) {
|
|
||||||
dom.addClass(data.entry, 'always-visible-actions');
|
|
||||||
} else {
|
|
||||||
dom.removeClass(data.entry, 'always-visible-actions');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disposeElement(element: ListElement, index: number, data: IListElementTemplateData): void {
|
disposeElement(element: ListElement, index: number, data: IListElementTemplateData): void {
|
||||||
|
|||||||
@@ -35,11 +35,6 @@ export interface IQuickPickItem {
|
|||||||
strikethrough?: boolean;
|
strikethrough?: boolean;
|
||||||
highlights?: IQuickPickItemHighlights;
|
highlights?: IQuickPickItemHighlights;
|
||||||
buttons?: IQuickInputButton[];
|
buttons?: IQuickInputButton[];
|
||||||
/**
|
|
||||||
* Wether to always show the buttons. By default buttons
|
|
||||||
* are only visible when hovering over them with the mouse
|
|
||||||
*/
|
|
||||||
buttonsAlwaysVisible?: boolean;
|
|
||||||
picked?: boolean;
|
picked?: boolean;
|
||||||
alwaysShow?: boolean;
|
alwaysShow?: boolean;
|
||||||
}
|
}
|
||||||
@@ -290,6 +285,11 @@ export interface IQuickInputButton {
|
|||||||
/** iconPath or iconClass required */
|
/** iconPath or iconClass required */
|
||||||
iconClass?: string;
|
iconClass?: string;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
|
/**
|
||||||
|
* Wether to always show the button. By default buttons
|
||||||
|
* are only visible when hovering over them with the mouse
|
||||||
|
*/
|
||||||
|
alwaysVisible?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IQuickPickItemButtonEvent<T extends IQuickPickItem> {
|
export interface IQuickPickItemButtonEvent<T extends IQuickPickItem> {
|
||||||
@@ -308,22 +308,35 @@ export type QuickPickInput<T = IQuickPickItem> = T | IQuickPickSeparator;
|
|||||||
|
|
||||||
export type IQuickPickItemWithResource = IQuickPickItem & { resource: URI | undefined };
|
export type IQuickPickItemWithResource = IQuickPickItem & { resource: URI | undefined };
|
||||||
|
|
||||||
export const quickPickItemScorerAccessor = new class implements IItemAccessor<IQuickPickItemWithResource> {
|
export class QuickPickItemScorerAccessor implements IItemAccessor<IQuickPickItemWithResource> {
|
||||||
|
|
||||||
|
constructor(private options?: { skipDescription?: boolean, skipPath?: boolean }) { }
|
||||||
|
|
||||||
getItemLabel(entry: IQuickPickItemWithResource): string {
|
getItemLabel(entry: IQuickPickItemWithResource): string {
|
||||||
return entry.label;
|
return entry.label;
|
||||||
}
|
}
|
||||||
|
|
||||||
getItemDescription(entry: IQuickPickItemWithResource): string | undefined {
|
getItemDescription(entry: IQuickPickItemWithResource): string | undefined {
|
||||||
|
if (this.options?.skipDescription) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
return entry.description;
|
return entry.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
getItemPath(entry: IQuickPickItemWithResource): string | undefined {
|
getItemPath(entry: IQuickPickItemWithResource): string | undefined {
|
||||||
|
if (this.options?.skipPath) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (entry.resource?.scheme === Schemas.file) {
|
if (entry.resource?.scheme === Schemas.file) {
|
||||||
return entry.resource.fsPath;
|
return entry.resource.fsPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry.resource?.path;
|
return entry.resource?.path;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export const quickPickItemScorerAccessor = new QuickPickItemScorerAccessor();
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { IWorkbenchConstructionOptions, create, URI, Event, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IApplicationLink } from 'vs/workbench/workbench.web.api';
|
import { IWorkbenchConstructionOptions, create, URI, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IApplicationLink } from 'vs/workbench/workbench.web.api';
|
||||||
import { generateUuid } from 'vs/base/common/uuid';
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||||
import { streamToBuffer } from 'vs/base/common/buffer';
|
import { streamToBuffer } from 'vs/base/common/buffer';
|
||||||
@@ -120,8 +120,8 @@ class PollingURLCallbackProvider extends Disposable implements IURLCallbackProvi
|
|||||||
FRAGMENT: 'vscode-fragment'
|
FRAGMENT: 'vscode-fragment'
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _onCallback: Emitter<URI> = this._register(new Emitter<URI>());
|
private readonly _onCallback = this._register(new Emitter<URI>());
|
||||||
readonly onCallback: Event<URI> = this._onCallback.event;
|
readonly onCallback = this._onCallback.event;
|
||||||
|
|
||||||
create(options?: Partial<UriComponents>): URI {
|
create(options?: Partial<UriComponents>): URI {
|
||||||
const queryValues: Map<string, string> = new Map();
|
const queryValues: Map<string, string> = new Map();
|
||||||
|
|||||||
@@ -1013,6 +1013,16 @@ export function isDiffEditor(thing: any): thing is IDiffEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@internal
|
||||||
|
*/
|
||||||
|
export function isCompositeEditor(thing: any): thing is editorCommon.ICompositeCodeEditor {
|
||||||
|
return thing
|
||||||
|
&& typeof thing === 'object'
|
||||||
|
&& typeof (<editorCommon.ICompositeCodeEditor>thing).onDidChangeActiveEditor === 'function';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*@internal
|
*@internal
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -135,6 +135,11 @@ export interface IEditorOptions {
|
|||||||
* Defaults to false.
|
* Defaults to false.
|
||||||
*/
|
*/
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
/**
|
||||||
|
* Rename matching regions on type.
|
||||||
|
* Defaults to false.
|
||||||
|
*/
|
||||||
|
renameOnType?: boolean;
|
||||||
/**
|
/**
|
||||||
* Should the editor render validation decorations.
|
* Should the editor render validation decorations.
|
||||||
* Defaults to editable.
|
* Defaults to editable.
|
||||||
@@ -3382,6 +3387,7 @@ export const enum EditorOption {
|
|||||||
quickSuggestions,
|
quickSuggestions,
|
||||||
quickSuggestionsDelay,
|
quickSuggestionsDelay,
|
||||||
readOnly,
|
readOnly,
|
||||||
|
renameOnType,
|
||||||
renderControlCharacters,
|
renderControlCharacters,
|
||||||
renderIndentGuides,
|
renderIndentGuides,
|
||||||
renderFinalNewline,
|
renderFinalNewline,
|
||||||
@@ -3798,6 +3804,10 @@ export const EditorOptions = {
|
|||||||
readOnly: register(new EditorBooleanOption(
|
readOnly: register(new EditorBooleanOption(
|
||||||
EditorOption.readOnly, 'readOnly', false,
|
EditorOption.readOnly, 'readOnly', false,
|
||||||
)),
|
)),
|
||||||
|
renameOnType: register(new EditorBooleanOption(
|
||||||
|
EditorOption.renameOnType, 'renameOnType', false,
|
||||||
|
{ description: nls.localize('renameOnType', "Controls whether the editor auto renames on type.") }
|
||||||
|
)),
|
||||||
renderControlCharacters: register(new EditorBooleanOption(
|
renderControlCharacters: register(new EditorBooleanOption(
|
||||||
EditorOption.renderControlCharacters, 'renderControlCharacters', false,
|
EditorOption.renderControlCharacters, 'renderControlCharacters', false,
|
||||||
{ description: nls.localize('renderControlCharacters', "Controls whether the editor should render control characters.") }
|
{ description: nls.localize('renderControlCharacters', "Controls whether the editor should render control characters.") }
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { Event } from 'vs/base/common/event';
|
||||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||||
import { ConfigurationChangedEvent, IComputedEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
import { ConfigurationChangedEvent, IComputedEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||||
@@ -529,6 +530,24 @@ export interface IDiffEditor extends IEditor {
|
|||||||
getModifiedEditor(): IEditor;
|
getModifiedEditor(): IEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export interface ICompositeCodeEditor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that signals that the active editor has changed
|
||||||
|
*/
|
||||||
|
readonly onDidChangeActiveEditor: Event<ICompositeCodeEditor>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The active code editor iff any
|
||||||
|
*/
|
||||||
|
readonly activeCodeEditor: IEditor | undefined;
|
||||||
|
// readonly editors: readonly ICodeEditor[] maybe supported with uris
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An editor contribution that gets created every time a new editor gets created and gets disposed when the editor gets disposed.
|
* An editor contribution that gets created every time a new editor gets created and gets disposed when the editor gets disposed.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -627,6 +627,12 @@ export interface ITextModel {
|
|||||||
*/
|
*/
|
||||||
equalsTextBuffer(other: ITextBuffer): boolean;
|
equalsTextBuffer(other: ITextBuffer): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the underling text buffer.
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
getTextBuffer(): ITextBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the text in a certain range.
|
* Get the text in a certain range.
|
||||||
* @param range The range describing what text to get.
|
* @param range The range describing what text to get.
|
||||||
|
|||||||
@@ -384,6 +384,11 @@ export class TextModel extends Disposable implements model.ITextModel {
|
|||||||
return this._buffer.equals(other);
|
return this._buffer.equals(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getTextBuffer(): model.ITextBuffer {
|
||||||
|
this._assertNotDisposed();
|
||||||
|
return this._buffer;
|
||||||
|
}
|
||||||
|
|
||||||
private _emitContentChangedEvent(rawChange: ModelRawContentChangedEvent, change: IModelContentChangedEvent): void {
|
private _emitContentChangedEvent(rawChange: ModelRawContentChangedEvent, change: IModelContentChangedEvent): void {
|
||||||
if (this._isDisposing) {
|
if (this._isDisposing) {
|
||||||
// Do not confuse listeners by emitting any event after disposing
|
// Do not confuse listeners by emitting any event after disposing
|
||||||
|
|||||||
@@ -789,6 +789,20 @@ export interface DocumentHighlightProvider {
|
|||||||
provideDocumentHighlights(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<DocumentHighlight[]>;
|
provideDocumentHighlights(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<DocumentHighlight[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rename provider interface defines the contract between extensions and
|
||||||
|
* the live-rename feature.
|
||||||
|
*/
|
||||||
|
export interface OnTypeRenameProvider {
|
||||||
|
|
||||||
|
stopPattern?: RegExp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a list of ranges that can be live-renamed together.
|
||||||
|
*/
|
||||||
|
provideOnTypeRenameRanges(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<IRange[]>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value-object that contains additional information when
|
* Value-object that contains additional information when
|
||||||
* requesting references.
|
* requesting references.
|
||||||
@@ -1642,6 +1656,11 @@ export const DocumentSymbolProviderRegistry = new LanguageFeatureRegistry<Docume
|
|||||||
*/
|
*/
|
||||||
export const DocumentHighlightProviderRegistry = new LanguageFeatureRegistry<DocumentHighlightProvider>();
|
export const DocumentHighlightProviderRegistry = new LanguageFeatureRegistry<DocumentHighlightProvider>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export const OnTypeRenameProviderRegistry = new LanguageFeatureRegistry<OnTypeRenameProvider>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -238,47 +238,48 @@ export enum EditorOption {
|
|||||||
quickSuggestions = 70,
|
quickSuggestions = 70,
|
||||||
quickSuggestionsDelay = 71,
|
quickSuggestionsDelay = 71,
|
||||||
readOnly = 72,
|
readOnly = 72,
|
||||||
renderControlCharacters = 73,
|
renameOnType = 73,
|
||||||
renderIndentGuides = 74,
|
renderControlCharacters = 74,
|
||||||
renderFinalNewline = 75,
|
renderIndentGuides = 75,
|
||||||
renderLineHighlight = 76,
|
renderFinalNewline = 76,
|
||||||
renderValidationDecorations = 77,
|
renderLineHighlight = 77,
|
||||||
renderWhitespace = 78,
|
renderValidationDecorations = 78,
|
||||||
revealHorizontalRightPadding = 79,
|
renderWhitespace = 79,
|
||||||
roundedSelection = 80,
|
revealHorizontalRightPadding = 80,
|
||||||
rulers = 81,
|
roundedSelection = 81,
|
||||||
scrollbar = 82,
|
rulers = 82,
|
||||||
scrollBeyondLastColumn = 83,
|
scrollbar = 83,
|
||||||
scrollBeyondLastLine = 84,
|
scrollBeyondLastColumn = 84,
|
||||||
scrollPredominantAxis = 85,
|
scrollBeyondLastLine = 85,
|
||||||
selectionClipboard = 86,
|
scrollPredominantAxis = 86,
|
||||||
selectionHighlight = 87,
|
selectionClipboard = 87,
|
||||||
selectOnLineNumbers = 88,
|
selectionHighlight = 88,
|
||||||
showFoldingControls = 89,
|
selectOnLineNumbers = 89,
|
||||||
showUnused = 90,
|
showFoldingControls = 90,
|
||||||
snippetSuggestions = 91,
|
showUnused = 91,
|
||||||
smoothScrolling = 92,
|
snippetSuggestions = 92,
|
||||||
stopRenderingLineAfter = 93,
|
smoothScrolling = 93,
|
||||||
suggest = 94,
|
stopRenderingLineAfter = 94,
|
||||||
suggestFontSize = 95,
|
suggest = 95,
|
||||||
suggestLineHeight = 96,
|
suggestFontSize = 96,
|
||||||
suggestOnTriggerCharacters = 97,
|
suggestLineHeight = 97,
|
||||||
suggestSelection = 98,
|
suggestOnTriggerCharacters = 98,
|
||||||
tabCompletion = 99,
|
suggestSelection = 99,
|
||||||
useTabStops = 100,
|
tabCompletion = 100,
|
||||||
wordSeparators = 101,
|
useTabStops = 101,
|
||||||
wordWrap = 102,
|
wordSeparators = 102,
|
||||||
wordWrapBreakAfterCharacters = 103,
|
wordWrap = 103,
|
||||||
wordWrapBreakBeforeCharacters = 104,
|
wordWrapBreakAfterCharacters = 104,
|
||||||
wordWrapColumn = 105,
|
wordWrapBreakBeforeCharacters = 105,
|
||||||
wordWrapMinified = 106,
|
wordWrapColumn = 106,
|
||||||
wrappingIndent = 107,
|
wordWrapMinified = 107,
|
||||||
wrappingStrategy = 108,
|
wrappingIndent = 108,
|
||||||
editorClassName = 109,
|
wrappingStrategy = 109,
|
||||||
pixelRatio = 110,
|
editorClassName = 110,
|
||||||
tabFocusMode = 111,
|
pixelRatio = 111,
|
||||||
layoutInfo = 112,
|
tabFocusMode = 112,
|
||||||
wrappingInfo = 113
|
layoutInfo = 113,
|
||||||
|
wrappingInfo = 114
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ export class FindDecorations implements IDisposable {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly _CURRENT_FIND_MATCH_DECORATION = ModelDecorationOptions.register({
|
public static readonly _CURRENT_FIND_MATCH_DECORATION = ModelDecorationOptions.register({
|
||||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||||
zIndex: 13,
|
zIndex: 13,
|
||||||
className: 'currentFindMatch',
|
className: 'currentFindMatch',
|
||||||
@@ -276,7 +276,7 @@ export class FindDecorations implements IDisposable {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
private static readonly _FIND_MATCH_DECORATION = ModelDecorationOptions.register({
|
public static readonly _FIND_MATCH_DECORATION = ModelDecorationOptions.register({
|
||||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||||
className: 'findMatch',
|
className: 'findMatch',
|
||||||
showIfCollapsed: true,
|
showIfCollapsed: true,
|
||||||
@@ -290,7 +290,7 @@ export class FindDecorations implements IDisposable {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
private static readonly _FIND_MATCH_NO_OVERVIEW_DECORATION = ModelDecorationOptions.register({
|
public static readonly _FIND_MATCH_NO_OVERVIEW_DECORATION = ModelDecorationOptions.register({
|
||||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||||
className: 'findMatch',
|
className: 'findMatch',
|
||||||
showIfCollapsed: true
|
showIfCollapsed: true
|
||||||
|
|||||||
+5
-2
@@ -3,6 +3,9 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
.open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-close::before {
|
.monaco-editor .on-type-rename-decoration {
|
||||||
content: "\ea71";
|
background: rgba(255, 0, 0, 0.3);
|
||||||
|
border-left: 1px solid rgba(255, 0, 0, 0.3);
|
||||||
|
/* So border can be transparent */
|
||||||
|
background-clip: padding-box;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,367 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import 'vs/css!./media/onTypeRename';
|
||||||
|
import * as nls from 'vs/nls';
|
||||||
|
import { registerEditorContribution, registerModelAndPositionCommand, EditorAction, EditorCommand, ServicesAccessor, registerEditorAction, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
|
||||||
|
import * as arrays from 'vs/base/common/arrays';
|
||||||
|
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||||
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||||
|
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||||
|
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||||
|
import { ITextModel, IModelDeltaDecoration, TrackedRangeStickiness, IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
|
||||||
|
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||||
|
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||||
|
import { OnTypeRenameProviderRegistry } from 'vs/editor/common/modes';
|
||||||
|
import { first, createCancelablePromise, CancelablePromise, RunOnceScheduler } from 'vs/base/common/async';
|
||||||
|
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||||
|
import { ContextKeyExpr, RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||||
|
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||||
|
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||||
|
import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||||
|
import * as strings from 'vs/base/common/strings';
|
||||||
|
|
||||||
|
export const CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE = new RawContextKey<boolean>('onTypeRenameInputVisible', false);
|
||||||
|
|
||||||
|
export class OnTypeRenameContribution extends Disposable implements IEditorContribution {
|
||||||
|
|
||||||
|
public static readonly ID = 'editor.contrib.onTypeRename';
|
||||||
|
|
||||||
|
private static readonly DECORATION = ModelDecorationOptions.register({
|
||||||
|
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||||
|
className: 'on-type-rename-decoration'
|
||||||
|
});
|
||||||
|
|
||||||
|
static get(editor: ICodeEditor): OnTypeRenameContribution {
|
||||||
|
return editor.getContribution<OnTypeRenameContribution>(OnTypeRenameContribution.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly _editor: ICodeEditor;
|
||||||
|
private _enabled: boolean;
|
||||||
|
|
||||||
|
private readonly _visibleContextKey: IContextKey<boolean>;
|
||||||
|
|
||||||
|
private _currentRequest: CancelablePromise<{
|
||||||
|
ranges: IRange[],
|
||||||
|
stopPattern?: RegExp
|
||||||
|
} | null | undefined> | null;
|
||||||
|
private _currentDecorations: string[]; // The one at index 0 is the reference one
|
||||||
|
private _stopPattern: RegExp;
|
||||||
|
private _ignoreChangeEvent: boolean;
|
||||||
|
private _updateMirrors: RunOnceScheduler;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
editor: ICodeEditor,
|
||||||
|
@IContextKeyService contextKeyService: IContextKeyService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this._editor = editor;
|
||||||
|
this._enabled = this._editor.getOption(EditorOption.renameOnType);
|
||||||
|
this._visibleContextKey = CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE.bindTo(contextKeyService);
|
||||||
|
this._currentRequest = null;
|
||||||
|
this._currentDecorations = [];
|
||||||
|
this._stopPattern = /^\s/;
|
||||||
|
this._ignoreChangeEvent = false;
|
||||||
|
this._updateMirrors = this._register(new RunOnceScheduler(() => this._doUpdateMirrors(), 0));
|
||||||
|
|
||||||
|
this._register(this._editor.onDidChangeModel((e) => {
|
||||||
|
this.stopAll();
|
||||||
|
this.run();
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(this._editor.onDidChangeConfiguration((e) => {
|
||||||
|
if (e.hasChanged(EditorOption.renameOnType)) {
|
||||||
|
this._enabled = this._editor.getOption(EditorOption.renameOnType);
|
||||||
|
this.stopAll();
|
||||||
|
this.run();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(this._editor.onDidChangeCursorPosition((e) => {
|
||||||
|
// no regions, run
|
||||||
|
if (this._currentDecorations.length === 0) {
|
||||||
|
this.run(e.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
// has cached regions, don't run
|
||||||
|
if (!this._editor.hasModel()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._currentDecorations.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const model = this._editor.getModel();
|
||||||
|
const currentRanges = this._currentDecorations.map(decId => model.getDecorationRange(decId)!);
|
||||||
|
|
||||||
|
// just moving cursor around, don't run again
|
||||||
|
if (Range.containsPosition(currentRanges[0], e.position)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// moving cursor out of primary region, run
|
||||||
|
this.run(e.position);
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(OnTypeRenameProviderRegistry.onDidChange(() => {
|
||||||
|
this.run();
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(this._editor.onDidChangeModelContent((e) => {
|
||||||
|
if (this._ignoreChangeEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this._editor.hasModel()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._currentDecorations.length === 0) {
|
||||||
|
// nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.isUndoing || e.isRedoing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.changes[0] && this._stopPattern.test(e.changes[0].text)) {
|
||||||
|
this.stopAll();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._updateMirrors.schedule();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _doUpdateMirrors(): void {
|
||||||
|
if (!this._editor.hasModel()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._currentDecorations.length === 0) {
|
||||||
|
// nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const model = this._editor.getModel();
|
||||||
|
const currentRanges = this._currentDecorations.map(decId => model.getDecorationRange(decId)!);
|
||||||
|
|
||||||
|
const referenceRange = currentRanges[0];
|
||||||
|
if (referenceRange.startLineNumber !== referenceRange.endLineNumber) {
|
||||||
|
return this.stopAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
const referenceValue = model.getValueInRange(referenceRange);
|
||||||
|
if (this._stopPattern.test(referenceValue)) {
|
||||||
|
return this.stopAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
let edits: IIdentifiedSingleEditOperation[] = [];
|
||||||
|
for (let i = 1, len = currentRanges.length; i < len; i++) {
|
||||||
|
const mirrorRange = currentRanges[i];
|
||||||
|
if (mirrorRange.startLineNumber !== mirrorRange.endLineNumber) {
|
||||||
|
edits.push({
|
||||||
|
range: mirrorRange,
|
||||||
|
text: referenceValue
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let oldValue = model.getValueInRange(mirrorRange);
|
||||||
|
let newValue = referenceValue;
|
||||||
|
let rangeStartColumn = mirrorRange.startColumn;
|
||||||
|
let rangeEndColumn = mirrorRange.endColumn;
|
||||||
|
|
||||||
|
const commonPrefixLength = strings.commonPrefixLength(oldValue, newValue);
|
||||||
|
rangeStartColumn += commonPrefixLength;
|
||||||
|
oldValue = oldValue.substr(commonPrefixLength);
|
||||||
|
newValue = newValue.substr(commonPrefixLength);
|
||||||
|
|
||||||
|
const commonSuffixLength = strings.commonSuffixLength(oldValue, newValue);
|
||||||
|
rangeEndColumn -= commonSuffixLength;
|
||||||
|
oldValue = oldValue.substr(0, oldValue.length - commonSuffixLength);
|
||||||
|
newValue = newValue.substr(0, newValue.length - commonSuffixLength);
|
||||||
|
|
||||||
|
if (rangeStartColumn !== rangeEndColumn || newValue.length !== 0) {
|
||||||
|
edits.push({
|
||||||
|
range: new Range(mirrorRange.startLineNumber, rangeStartColumn, mirrorRange.endLineNumber, rangeEndColumn),
|
||||||
|
text: newValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (edits.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._ignoreChangeEvent = true;
|
||||||
|
const prevEditOperationType = this._editor._getCursors().getPrevEditOperationType();
|
||||||
|
this._editor.executeEdits('onTypeRename', edits);
|
||||||
|
this._editor._getCursors().setPrevEditOperationType(prevEditOperationType);
|
||||||
|
} finally {
|
||||||
|
this._ignoreChangeEvent = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
super.dispose();
|
||||||
|
this.stopAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
stopAll(): void {
|
||||||
|
this._visibleContextKey.set(false);
|
||||||
|
this._currentDecorations = this._editor.deltaDecorations(this._currentDecorations, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(position: Position | null = this._editor.getPosition(), force = false): Promise<void> {
|
||||||
|
if (!position) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this._enabled && !force) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this._editor.hasModel()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._currentRequest) {
|
||||||
|
this._currentRequest.cancel();
|
||||||
|
this._currentRequest = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const model = this._editor.getModel();
|
||||||
|
|
||||||
|
this._currentRequest = createCancelablePromise(token => getOnTypeRenameRanges(model, position, token));
|
||||||
|
try {
|
||||||
|
const response = await this._currentRequest;
|
||||||
|
|
||||||
|
let ranges: IRange[] = [];
|
||||||
|
if (response?.ranges) {
|
||||||
|
ranges = response.ranges;
|
||||||
|
}
|
||||||
|
if (response?.stopPattern) {
|
||||||
|
this._stopPattern = response.stopPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
let foundReferenceRange = false;
|
||||||
|
for (let i = 0, len = ranges.length; i < len; i++) {
|
||||||
|
if (Range.containsPosition(ranges[i], position)) {
|
||||||
|
foundReferenceRange = true;
|
||||||
|
if (i !== 0) {
|
||||||
|
const referenceRange = ranges[i];
|
||||||
|
ranges.splice(i, 1);
|
||||||
|
ranges.unshift(referenceRange);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundReferenceRange) {
|
||||||
|
// Cannot do on type rename if the ranges are not where the cursor is...
|
||||||
|
this.stopAll();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const decorations: IModelDeltaDecoration[] = ranges.map(range => ({ range: range, options: OnTypeRenameContribution.DECORATION }));
|
||||||
|
this._visibleContextKey.set(true);
|
||||||
|
this._currentDecorations = this._editor.deltaDecorations(this._currentDecorations, decorations);
|
||||||
|
} catch (err) {
|
||||||
|
onUnexpectedError(err);
|
||||||
|
this.stopAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OnTypeRenameAction extends EditorAction {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'editor.action.onTypeRename',
|
||||||
|
label: nls.localize('onTypeRename.label', "On Type Rename Symbol"),
|
||||||
|
alias: 'On Type Rename Symbol',
|
||||||
|
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasRenameProvider),
|
||||||
|
kbOpts: {
|
||||||
|
kbExpr: EditorContextKeys.editorTextFocus,
|
||||||
|
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F2,
|
||||||
|
weight: KeybindingWeight.EditorContrib
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
runCommand(accessor: ServicesAccessor, args: [URI, IPosition]): void | Promise<void> {
|
||||||
|
const editorService = accessor.get(ICodeEditorService);
|
||||||
|
const [uri, pos] = Array.isArray(args) && args || [undefined, undefined];
|
||||||
|
|
||||||
|
if (URI.isUri(uri) && Position.isIPosition(pos)) {
|
||||||
|
return editorService.openCodeEditor({ resource: uri }, editorService.getActiveCodeEditor()).then(editor => {
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editor.setPosition(pos);
|
||||||
|
editor.invokeWithinContext(accessor => {
|
||||||
|
this.reportTelemetry(accessor, editor);
|
||||||
|
return this.run(accessor, editor);
|
||||||
|
});
|
||||||
|
}, onUnexpectedError);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.runCommand(accessor, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||||
|
const controller = OnTypeRenameContribution.get(editor);
|
||||||
|
if (controller) {
|
||||||
|
return Promise.resolve(controller.run(editor.getPosition(), true));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const OnTypeRenameCommand = EditorCommand.bindToContribution<OnTypeRenameContribution>(OnTypeRenameContribution.get);
|
||||||
|
registerEditorCommand(new OnTypeRenameCommand({
|
||||||
|
id: 'cancelOnTypeRenameInput',
|
||||||
|
precondition: CONTEXT_ONTYPE_RENAME_INPUT_VISIBLE,
|
||||||
|
handler: x => x.stopAll(),
|
||||||
|
kbOpts: {
|
||||||
|
kbExpr: EditorContextKeys.editorTextFocus,
|
||||||
|
weight: KeybindingWeight.EditorContrib + 99,
|
||||||
|
primary: KeyCode.Escape,
|
||||||
|
secondary: [KeyMod.Shift | KeyCode.Escape]
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
export function getOnTypeRenameRanges(model: ITextModel, position: Position, token: CancellationToken): Promise<{
|
||||||
|
ranges: IRange[],
|
||||||
|
stopPattern?: RegExp
|
||||||
|
} | undefined | null> {
|
||||||
|
const orderedByScore = OnTypeRenameProviderRegistry.ordered(model);
|
||||||
|
|
||||||
|
// in order of score ask the occurrences provider
|
||||||
|
// until someone response with a good result
|
||||||
|
// (good = none empty array)
|
||||||
|
return first<{
|
||||||
|
ranges: IRange[],
|
||||||
|
stopPattern?: RegExp
|
||||||
|
} | undefined>(orderedByScore.map(provider => () => {
|
||||||
|
return Promise.resolve(provider.provideOnTypeRenameRanges(model, position, token)).then((ranges) => {
|
||||||
|
if (!ranges) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ranges,
|
||||||
|
stopPattern: provider.stopPattern
|
||||||
|
};
|
||||||
|
}, (err) => {
|
||||||
|
onUnexpectedExternalError(err);
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
}), result => !!result && arrays.isNonEmptyArray(result?.ranges));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
registerModelAndPositionCommand('_executeRenameOnTypeProvider', (model, position) => getOnTypeRenameRanges(model, position, CancellationToken.None));
|
||||||
|
|
||||||
|
registerEditorContribution(OnTypeRenameContribution.ID, OnTypeRenameContribution);
|
||||||
|
registerEditorAction(OnTypeRenameAction);
|
||||||
@@ -0,0 +1,451 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as assert from 'assert';
|
||||||
|
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import { Position } from 'vs/editor/common/core/position';
|
||||||
|
import { Range } from 'vs/editor/common/core/range';
|
||||||
|
import { Handler } from 'vs/editor/common/editorCommon';
|
||||||
|
import * as modes from 'vs/editor/common/modes';
|
||||||
|
import { OnTypeRenameContribution } from 'vs/editor/contrib/rename/onTypeRename';
|
||||||
|
import { createTestCodeEditor, TestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||||
|
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
|
||||||
|
import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
|
||||||
|
|
||||||
|
const mockFile = URI.parse('test:somefile.ttt');
|
||||||
|
const mockFileSelector = { scheme: 'test' };
|
||||||
|
const timeout = 30;
|
||||||
|
|
||||||
|
suite('On type rename', () => {
|
||||||
|
const disposables = new DisposableStore();
|
||||||
|
|
||||||
|
setup(() => {
|
||||||
|
disposables.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
teardown(() => {
|
||||||
|
disposables.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
function createMockEditor(text: string | string[]) {
|
||||||
|
const model = typeof text === 'string'
|
||||||
|
? createTextModel(text, undefined, undefined, mockFile)
|
||||||
|
: createTextModel(text.join('\n'), undefined, undefined, mockFile);
|
||||||
|
|
||||||
|
const editor = createTestCodeEditor({ model });
|
||||||
|
disposables.add(model);
|
||||||
|
disposables.add(editor);
|
||||||
|
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function testCase(
|
||||||
|
name: string,
|
||||||
|
initialState: { text: string | string[], ranges: Range[], stopPattern?: RegExp },
|
||||||
|
operations: (editor: TestCodeEditor, contrib: OnTypeRenameContribution) => Promise<void>,
|
||||||
|
expectedEndText: string | string[]
|
||||||
|
) {
|
||||||
|
test(name, async () => {
|
||||||
|
disposables.add(modes.OnTypeRenameProviderRegistry.register(mockFileSelector, {
|
||||||
|
stopPattern: initialState.stopPattern || /^\s/,
|
||||||
|
|
||||||
|
provideOnTypeRenameRanges() {
|
||||||
|
return initialState.ranges;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const editor = createMockEditor(initialState.text);
|
||||||
|
const ontypeRenameContribution = editor.registerAndInstantiateContribution(
|
||||||
|
OnTypeRenameContribution.ID,
|
||||||
|
OnTypeRenameContribution
|
||||||
|
);
|
||||||
|
|
||||||
|
await operations(editor, ontypeRenameContribution);
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (typeof expectedEndText === 'string') {
|
||||||
|
assert.equal(editor.getModel()!.getValue(), expectedEndText);
|
||||||
|
} else {
|
||||||
|
assert.equal(editor.getModel()!.getValue(), expectedEndText.join('\n'));
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
}, timeout);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
text: '<ooo></ooo>',
|
||||||
|
ranges: [
|
||||||
|
new Range(1, 2, 1, 5),
|
||||||
|
new Range(1, 8, 1, 11),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple insertion
|
||||||
|
*/
|
||||||
|
testCase('Simple insert - initial', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 2);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, '<iooo></iooo>');
|
||||||
|
|
||||||
|
testCase('Simple insert - middle', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 3);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, '<oioo></oioo>');
|
||||||
|
|
||||||
|
testCase('Simple insert - end', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 5);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, '<oooi></oooi>');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple insertion - end
|
||||||
|
*/
|
||||||
|
testCase('Simple insert end - initial', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 8);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, '<iooo></iooo>');
|
||||||
|
|
||||||
|
testCase('Simple insert end - middle', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 9);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, '<oioo></oioo>');
|
||||||
|
|
||||||
|
testCase('Simple insert end - end', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 11);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, '<oooi></oooi>');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boundary insertion
|
||||||
|
*/
|
||||||
|
testCase('Simple insert - out of boundary', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 1);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, 'i<ooo></ooo>');
|
||||||
|
|
||||||
|
testCase('Simple insert - out of boundary 2', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 6);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, '<ooo>i</ooo>');
|
||||||
|
|
||||||
|
testCase('Simple insert - out of boundary 3', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 7);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, '<ooo><i/ooo>');
|
||||||
|
|
||||||
|
testCase('Simple insert - out of boundary 4', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 12);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, '<ooo></ooo>i');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert + Move
|
||||||
|
*/
|
||||||
|
testCase('Continuous insert', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 2);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, '<iiooo></iiooo>');
|
||||||
|
|
||||||
|
testCase('Insert - move - insert', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 2);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
editor.setPosition(new Position(1, 4));
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, '<ioioo></ioioo>');
|
||||||
|
|
||||||
|
testCase('Insert - move - insert outside region', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 2);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
editor.setPosition(new Position(1, 7));
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, '<iooo>i</iooo>');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selection insert
|
||||||
|
*/
|
||||||
|
testCase('Selection insert - simple', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 2);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.setSelection(new Range(1, 2, 1, 3));
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, '<ioo></ioo>');
|
||||||
|
|
||||||
|
testCase('Selection insert - whole', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 2);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.setSelection(new Range(1, 2, 1, 5));
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, '<i></i>');
|
||||||
|
|
||||||
|
testCase('Selection insert - across boundary', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 2);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.setSelection(new Range(1, 1, 1, 3));
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, 'ioo></oo>');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo
|
||||||
|
* Undefined behavior
|
||||||
|
*/
|
||||||
|
// testCase('Selection insert - across two boundary', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
// const pos = new Position(1, 2);
|
||||||
|
// editor.setPosition(pos);
|
||||||
|
// await ontypeRenameContribution.run(pos, true);
|
||||||
|
// editor.setSelection(new Range(1, 4, 1, 9));
|
||||||
|
// editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
// }, '<ooioo>');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Break out behavior
|
||||||
|
*/
|
||||||
|
testCase('Breakout - type space', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 5);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: ' ' });
|
||||||
|
}, '<ooo ></ooo>');
|
||||||
|
|
||||||
|
testCase('Breakout - type space then undo', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 5);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: ' ' });
|
||||||
|
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||||
|
}, '<ooo></ooo>');
|
||||||
|
|
||||||
|
testCase('Breakout - type space in middle', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 4);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: ' ' });
|
||||||
|
}, '<oo o></ooo>');
|
||||||
|
|
||||||
|
testCase('Breakout - paste content starting with space', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 5);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Paste, { text: ' i="i"' });
|
||||||
|
}, '<ooo i="i"></ooo>');
|
||||||
|
|
||||||
|
testCase('Breakout - paste content starting with space then undo', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 5);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Paste, { text: ' i="i"' });
|
||||||
|
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||||
|
}, '<ooo></ooo>');
|
||||||
|
|
||||||
|
testCase('Breakout - paste content starting with space in middle', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 4);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Paste, { text: ' i' });
|
||||||
|
}, '<oo io></ooo>');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Break out with custom stopPattern
|
||||||
|
*/
|
||||||
|
|
||||||
|
const state3 = {
|
||||||
|
...state,
|
||||||
|
stopPattern: /^s/
|
||||||
|
};
|
||||||
|
|
||||||
|
testCase('Breakout with stop pattern - insert', state3, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 2);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, '<iooo></iooo>');
|
||||||
|
|
||||||
|
testCase('Breakout with stop pattern - insert stop char', state3, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 2);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 's' });
|
||||||
|
}, '<sooo></ooo>');
|
||||||
|
|
||||||
|
testCase('Breakout with stop pattern - paste char', state3, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 2);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Paste, { text: 's' });
|
||||||
|
}, '<sooo></ooo>');
|
||||||
|
|
||||||
|
testCase('Breakout with stop pattern - paste string', state3, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 2);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Paste, { text: 'so' });
|
||||||
|
}, '<soooo></ooo>');
|
||||||
|
|
||||||
|
testCase('Breakout with stop pattern - insert at end', state3, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 5);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 's' });
|
||||||
|
}, '<ooos></ooo>');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete
|
||||||
|
*/
|
||||||
|
testCase('Delete - left char', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 5);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', 'deleteLeft', {});
|
||||||
|
}, '<oo></oo>');
|
||||||
|
|
||||||
|
testCase('Delete - left char then undo', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 5);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', 'deleteLeft', {});
|
||||||
|
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||||
|
}, '<ooo></ooo>');
|
||||||
|
|
||||||
|
testCase('Delete - left word', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 5);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', 'deleteWordLeft', {});
|
||||||
|
}, '<></>');
|
||||||
|
|
||||||
|
testCase('Delete - left word then undo', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 5);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', 'deleteWordLeft', {});
|
||||||
|
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||||
|
}, '<ooo></ooo>');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Todo: Fix test
|
||||||
|
*/
|
||||||
|
// testCase('Delete - left all', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
// const pos = new Position(1, 3);
|
||||||
|
// editor.setPosition(pos);
|
||||||
|
// await ontypeRenameContribution.run(pos, true);
|
||||||
|
// editor.trigger('keyboard', 'deleteAllLeft', {});
|
||||||
|
// }, '></>');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Todo: Fix test
|
||||||
|
*/
|
||||||
|
// testCase('Delete - left all then undo', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
// const pos = new Position(1, 5);
|
||||||
|
// editor.setPosition(pos);
|
||||||
|
// await ontypeRenameContribution.run(pos, true);
|
||||||
|
// editor.trigger('keyboard', 'deleteAllLeft', {});
|
||||||
|
// CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||||
|
// }, '></ooo>');
|
||||||
|
|
||||||
|
testCase('Delete - left all then undo twice', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 5);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', 'deleteAllLeft', {});
|
||||||
|
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||||
|
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||||
|
}, '<ooo></ooo>');
|
||||||
|
|
||||||
|
testCase('Delete - selection', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 5);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.setSelection(new Range(1, 2, 1, 3));
|
||||||
|
editor.trigger('keyboard', 'deleteLeft', {});
|
||||||
|
}, '<oo></oo>');
|
||||||
|
|
||||||
|
testCase('Delete - selection across boundary', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 3);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.setSelection(new Range(1, 1, 1, 3));
|
||||||
|
editor.trigger('keyboard', 'deleteLeft', {});
|
||||||
|
}, 'oo></oo>');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undo / redo
|
||||||
|
*/
|
||||||
|
testCase('Undo/redo - simple undo', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 2);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||||
|
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||||
|
}, '<ooo></ooo>');
|
||||||
|
|
||||||
|
testCase('Undo/redo - simple undo/redo', state, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 2);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
|
||||||
|
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
|
||||||
|
}, '<iooo></iooo>');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multi line
|
||||||
|
*/
|
||||||
|
const state2 = {
|
||||||
|
text: [
|
||||||
|
'<ooo>',
|
||||||
|
'</ooo>'
|
||||||
|
],
|
||||||
|
ranges: [
|
||||||
|
new Range(1, 2, 1, 5),
|
||||||
|
new Range(2, 3, 2, 6),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
testCase('Multiline insert', state2, async (editor, ontypeRenameContribution) => {
|
||||||
|
const pos = new Position(1, 2);
|
||||||
|
editor.setPosition(pos);
|
||||||
|
await ontypeRenameContribution.run(pos, true);
|
||||||
|
editor.trigger('keyboard', Handler.Type, { text: 'i' });
|
||||||
|
}, [
|
||||||
|
'<iooo>',
|
||||||
|
'</iooo>'
|
||||||
|
]);
|
||||||
|
});
|
||||||
@@ -32,6 +32,7 @@ import 'vs/editor/contrib/linesOperations/linesOperations';
|
|||||||
import 'vs/editor/contrib/links/links';
|
import 'vs/editor/contrib/links/links';
|
||||||
import 'vs/editor/contrib/multicursor/multicursor';
|
import 'vs/editor/contrib/multicursor/multicursor';
|
||||||
import 'vs/editor/contrib/parameterHints/parameterHints';
|
import 'vs/editor/contrib/parameterHints/parameterHints';
|
||||||
|
import 'vs/editor/contrib/rename/onTypeRename';
|
||||||
import 'vs/editor/contrib/rename/rename';
|
import 'vs/editor/contrib/rename/rename';
|
||||||
import 'vs/editor/contrib/smartSelect/smartSelect';
|
import 'vs/editor/contrib/smartSelect/smartSelect';
|
||||||
import 'vs/editor/contrib/snippet/snippetController2';
|
import 'vs/editor/contrib/snippet/snippetController2';
|
||||||
|
|||||||
@@ -391,6 +391,13 @@ export function registerDocumentHighlightProvider(languageId: string, provider:
|
|||||||
return modes.DocumentHighlightProviderRegistry.register(languageId, provider);
|
return modes.DocumentHighlightProviderRegistry.register(languageId, provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an on type rename provider.
|
||||||
|
*/
|
||||||
|
export function registerOnTypeRenameProvider(languageId: string, provider: modes.OnTypeRenameProvider): IDisposable {
|
||||||
|
return modes.OnTypeRenameProviderRegistry.register(languageId, provider);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a definition provider (used by e.g. go to definition).
|
* Register a definition provider (used by e.g. go to definition).
|
||||||
*/
|
*/
|
||||||
@@ -559,6 +566,7 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages {
|
|||||||
registerHoverProvider: <any>registerHoverProvider,
|
registerHoverProvider: <any>registerHoverProvider,
|
||||||
registerDocumentSymbolProvider: <any>registerDocumentSymbolProvider,
|
registerDocumentSymbolProvider: <any>registerDocumentSymbolProvider,
|
||||||
registerDocumentHighlightProvider: <any>registerDocumentHighlightProvider,
|
registerDocumentHighlightProvider: <any>registerDocumentHighlightProvider,
|
||||||
|
registerOnTypeRenameProvider: <any>registerOnTypeRenameProvider,
|
||||||
registerDefinitionProvider: <any>registerDefinitionProvider,
|
registerDefinitionProvider: <any>registerDefinitionProvider,
|
||||||
registerImplementationProvider: <any>registerImplementationProvider,
|
registerImplementationProvider: <any>registerImplementationProvider,
|
||||||
registerTypeDefinitionProvider: <any>registerTypeDefinitionProvider,
|
registerTypeDefinitionProvider: <any>registerTypeDefinitionProvider,
|
||||||
|
|||||||
Vendored
+65
-41
@@ -2673,6 +2673,11 @@ declare namespace monaco.editor {
|
|||||||
* Defaults to false.
|
* Defaults to false.
|
||||||
*/
|
*/
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
|
/**
|
||||||
|
* Rename matching regions on type.
|
||||||
|
* Defaults to false.
|
||||||
|
*/
|
||||||
|
renameOnType?: boolean;
|
||||||
/**
|
/**
|
||||||
* Should the editor render validation decorations.
|
* Should the editor render validation decorations.
|
||||||
* Defaults to editable.
|
* Defaults to editable.
|
||||||
@@ -3886,47 +3891,48 @@ declare namespace monaco.editor {
|
|||||||
quickSuggestions = 70,
|
quickSuggestions = 70,
|
||||||
quickSuggestionsDelay = 71,
|
quickSuggestionsDelay = 71,
|
||||||
readOnly = 72,
|
readOnly = 72,
|
||||||
renderControlCharacters = 73,
|
renameOnType = 73,
|
||||||
renderIndentGuides = 74,
|
renderControlCharacters = 74,
|
||||||
renderFinalNewline = 75,
|
renderIndentGuides = 75,
|
||||||
renderLineHighlight = 76,
|
renderFinalNewline = 76,
|
||||||
renderValidationDecorations = 77,
|
renderLineHighlight = 77,
|
||||||
renderWhitespace = 78,
|
renderValidationDecorations = 78,
|
||||||
revealHorizontalRightPadding = 79,
|
renderWhitespace = 79,
|
||||||
roundedSelection = 80,
|
revealHorizontalRightPadding = 80,
|
||||||
rulers = 81,
|
roundedSelection = 81,
|
||||||
scrollbar = 82,
|
rulers = 82,
|
||||||
scrollBeyondLastColumn = 83,
|
scrollbar = 83,
|
||||||
scrollBeyondLastLine = 84,
|
scrollBeyondLastColumn = 84,
|
||||||
scrollPredominantAxis = 85,
|
scrollBeyondLastLine = 85,
|
||||||
selectionClipboard = 86,
|
scrollPredominantAxis = 86,
|
||||||
selectionHighlight = 87,
|
selectionClipboard = 87,
|
||||||
selectOnLineNumbers = 88,
|
selectionHighlight = 88,
|
||||||
showFoldingControls = 89,
|
selectOnLineNumbers = 89,
|
||||||
showUnused = 90,
|
showFoldingControls = 90,
|
||||||
snippetSuggestions = 91,
|
showUnused = 91,
|
||||||
smoothScrolling = 92,
|
snippetSuggestions = 92,
|
||||||
stopRenderingLineAfter = 93,
|
smoothScrolling = 93,
|
||||||
suggest = 94,
|
stopRenderingLineAfter = 94,
|
||||||
suggestFontSize = 95,
|
suggest = 95,
|
||||||
suggestLineHeight = 96,
|
suggestFontSize = 96,
|
||||||
suggestOnTriggerCharacters = 97,
|
suggestLineHeight = 97,
|
||||||
suggestSelection = 98,
|
suggestOnTriggerCharacters = 98,
|
||||||
tabCompletion = 99,
|
suggestSelection = 99,
|
||||||
useTabStops = 100,
|
tabCompletion = 100,
|
||||||
wordSeparators = 101,
|
useTabStops = 101,
|
||||||
wordWrap = 102,
|
wordSeparators = 102,
|
||||||
wordWrapBreakAfterCharacters = 103,
|
wordWrap = 103,
|
||||||
wordWrapBreakBeforeCharacters = 104,
|
wordWrapBreakAfterCharacters = 104,
|
||||||
wordWrapColumn = 105,
|
wordWrapBreakBeforeCharacters = 105,
|
||||||
wordWrapMinified = 106,
|
wordWrapColumn = 106,
|
||||||
wrappingIndent = 107,
|
wordWrapMinified = 107,
|
||||||
wrappingStrategy = 108,
|
wrappingIndent = 108,
|
||||||
editorClassName = 109,
|
wrappingStrategy = 109,
|
||||||
pixelRatio = 110,
|
editorClassName = 110,
|
||||||
tabFocusMode = 111,
|
pixelRatio = 111,
|
||||||
layoutInfo = 112,
|
tabFocusMode = 112,
|
||||||
wrappingInfo = 113
|
layoutInfo = 113,
|
||||||
|
wrappingInfo = 114
|
||||||
}
|
}
|
||||||
export const EditorOptions: {
|
export const EditorOptions: {
|
||||||
acceptSuggestionOnCommitCharacter: IEditorOption<EditorOption.acceptSuggestionOnCommitCharacter, boolean>;
|
acceptSuggestionOnCommitCharacter: IEditorOption<EditorOption.acceptSuggestionOnCommitCharacter, boolean>;
|
||||||
@@ -4002,6 +4008,7 @@ declare namespace monaco.editor {
|
|||||||
quickSuggestions: IEditorOption<EditorOption.quickSuggestions, ValidQuickSuggestionsOptions>;
|
quickSuggestions: IEditorOption<EditorOption.quickSuggestions, ValidQuickSuggestionsOptions>;
|
||||||
quickSuggestionsDelay: IEditorOption<EditorOption.quickSuggestionsDelay, number>;
|
quickSuggestionsDelay: IEditorOption<EditorOption.quickSuggestionsDelay, number>;
|
||||||
readOnly: IEditorOption<EditorOption.readOnly, boolean>;
|
readOnly: IEditorOption<EditorOption.readOnly, boolean>;
|
||||||
|
renameOnType: IEditorOption<EditorOption.renameOnType, boolean>;
|
||||||
renderControlCharacters: IEditorOption<EditorOption.renderControlCharacters, boolean>;
|
renderControlCharacters: IEditorOption<EditorOption.renderControlCharacters, boolean>;
|
||||||
renderIndentGuides: IEditorOption<EditorOption.renderIndentGuides, boolean>;
|
renderIndentGuides: IEditorOption<EditorOption.renderIndentGuides, boolean>;
|
||||||
renderFinalNewline: IEditorOption<EditorOption.renderFinalNewline, boolean>;
|
renderFinalNewline: IEditorOption<EditorOption.renderFinalNewline, boolean>;
|
||||||
@@ -4958,6 +4965,11 @@ declare namespace monaco.languages {
|
|||||||
*/
|
*/
|
||||||
export function registerDocumentHighlightProvider(languageId: string, provider: DocumentHighlightProvider): IDisposable;
|
export function registerDocumentHighlightProvider(languageId: string, provider: DocumentHighlightProvider): IDisposable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an on type rename provider.
|
||||||
|
*/
|
||||||
|
export function registerOnTypeRenameProvider(languageId: string, provider: OnTypeRenameProvider): IDisposable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a definition provider (used by e.g. go to definition).
|
* Register a definition provider (used by e.g. go to definition).
|
||||||
*/
|
*/
|
||||||
@@ -5716,6 +5728,18 @@ declare namespace monaco.languages {
|
|||||||
provideDocumentHighlights(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<DocumentHighlight[]>;
|
provideDocumentHighlights(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<DocumentHighlight[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rename provider interface defines the contract between extensions and
|
||||||
|
* the live-rename feature.
|
||||||
|
*/
|
||||||
|
export interface OnTypeRenameProvider {
|
||||||
|
stopPattern?: RegExp;
|
||||||
|
/**
|
||||||
|
* Provide a list of ranges that can be live-renamed together.
|
||||||
|
*/
|
||||||
|
provideOnTypeRenameRanges(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<IRange[]>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value-object that contains additional information when
|
* Value-object that contains additional information when
|
||||||
* requesting references.
|
* requesting references.
|
||||||
|
|||||||
@@ -442,14 +442,14 @@ export function registerAction2(ctor: { new(): Action2 }): IDisposable {
|
|||||||
KeybindingsRegistry.registerKeybindingRule({
|
KeybindingsRegistry.registerKeybindingRule({
|
||||||
...item,
|
...item,
|
||||||
id: command.id,
|
id: command.id,
|
||||||
when: ContextKeyExpr.and(command.precondition, item.when)
|
when: command.precondition ? ContextKeyExpr.and(command.precondition, item.when) : item.when
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (keybinding) {
|
} else if (keybinding) {
|
||||||
KeybindingsRegistry.registerKeybindingRule({
|
KeybindingsRegistry.registerKeybindingRule({
|
||||||
...keybinding,
|
...keybinding,
|
||||||
id: command.id,
|
id: command.id,
|
||||||
when: ContextKeyExpr.and(command.precondition, keybinding.when)
|
when: command.precondition ? ContextKeyExpr.and(command.precondition, keybinding.when) : keybinding.when
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,8 +136,6 @@ export interface IEnvironmentService extends IUserHomeProvider {
|
|||||||
// sync resources
|
// sync resources
|
||||||
userDataSyncLogResource: URI;
|
userDataSyncLogResource: URI;
|
||||||
userDataSyncHome: URI;
|
userDataSyncHome: URI;
|
||||||
settingsSyncPreviewResource: URI;
|
|
||||||
keybindingsSyncPreviewResource: URI;
|
|
||||||
|
|
||||||
machineSettingsResource: URI;
|
machineSettingsResource: URI;
|
||||||
|
|
||||||
|
|||||||
@@ -114,12 +114,6 @@ export class EnvironmentService implements IEnvironmentService {
|
|||||||
@memoize
|
@memoize
|
||||||
get userDataSyncHome(): URI { return resources.joinPath(this.userRoamingDataHome, 'sync'); }
|
get userDataSyncHome(): URI { return resources.joinPath(this.userRoamingDataHome, 'sync'); }
|
||||||
|
|
||||||
@memoize
|
|
||||||
get settingsSyncPreviewResource(): URI { return resources.joinPath(this.userDataSyncHome, 'settings.json'); }
|
|
||||||
|
|
||||||
@memoize
|
|
||||||
get keybindingsSyncPreviewResource(): URI { return resources.joinPath(this.userDataSyncHome, 'keybindings.json'); }
|
|
||||||
|
|
||||||
@memoize
|
@memoize
|
||||||
get userDataSyncLogResource(): URI { return URI.file(path.join(this.logsPath, 'userDataSync.log')); }
|
get userDataSyncLogResource(): URI { return URI.file(path.join(this.logsPath, 'userDataSync.log')); }
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export interface IProgressNotificationOptions extends IProgressOptions {
|
|||||||
readonly primaryActions?: ReadonlyArray<IAction>;
|
readonly primaryActions?: ReadonlyArray<IAction>;
|
||||||
readonly secondaryActions?: ReadonlyArray<IAction>;
|
readonly secondaryActions?: ReadonlyArray<IAction>;
|
||||||
readonly delay?: number;
|
readonly delay?: number;
|
||||||
|
readonly silent?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProgressWindowOptions extends IProgressOptions {
|
export interface IProgressWindowOptions extends IProgressOptions {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||||
import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess';
|
import { PickerQuickAccessProvider, IPickerQuickAccessItem, IPickerQuickAccessProviderOptions } from 'vs/platform/quickinput/browser/pickerQuickAccess';
|
||||||
import { distinct } from 'vs/base/common/arrays';
|
import { distinct } from 'vs/base/common/arrays';
|
||||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||||
import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||||
@@ -30,7 +30,7 @@ export interface ICommandQuickPick extends IPickerQuickAccessItem {
|
|||||||
commandAlias: string | undefined;
|
commandAlias: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICommandsQuickAccessOptions {
|
export interface ICommandsQuickAccessOptions extends IPickerQuickAccessProviderOptions {
|
||||||
showAlias: boolean;
|
showAlias: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,14 +43,14 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc
|
|||||||
private readonly commandsHistory = this._register(this.instantiationService.createInstance(CommandsHistory));
|
private readonly commandsHistory = this._register(this.instantiationService.createInstance(CommandsHistory));
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private options: ICommandsQuickAccessOptions,
|
protected options: ICommandsQuickAccessOptions,
|
||||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||||
@ICommandService private readonly commandService: ICommandService,
|
@ICommandService private readonly commandService: ICommandService,
|
||||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||||
@INotificationService private readonly notificationService: INotificationService
|
@INotificationService private readonly notificationService: INotificationService
|
||||||
) {
|
) {
|
||||||
super(AbstractCommandsQuickAccessProvider.PREFIX);
|
super(AbstractCommandsQuickAccessProvider.PREFIX, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise<Array<ICommandQuickPick | IQuickPickSeparator>> {
|
protected async getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise<Array<ICommandQuickPick | IQuickPickSeparator>> {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance
|
|||||||
import { IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent } from 'vs/base/parts/quickinput/common/quickInput';
|
import { IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent } from 'vs/base/parts/quickinput/common/quickInput';
|
||||||
import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
|
import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
|
||||||
import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
|
import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { timeout } from 'vs/base/common/async';
|
||||||
|
|
||||||
export enum TriggerAction {
|
export enum TriggerAction {
|
||||||
|
|
||||||
@@ -53,17 +54,31 @@ export interface IPickerQuickAccessItem extends IQuickPickItem {
|
|||||||
trigger?(buttonIndex: number, keyMods: IKeyMods): TriggerAction | Promise<TriggerAction>;
|
trigger?(buttonIndex: number, keyMods: IKeyMods): TriggerAction | Promise<TriggerAction>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IPickerQuickAccessProviderOptions {
|
||||||
|
canAcceptInBackground?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FastAndSlowPicksType<T> = { picks: Array<T | IQuickPickSeparator>, additionalPicks: Promise<Array<T | IQuickPickSeparator>> };
|
||||||
|
|
||||||
|
function isFastAndSlowPicksType<T>(obj: unknown): obj is FastAndSlowPicksType<T> {
|
||||||
|
const candidate = obj as FastAndSlowPicksType<T>;
|
||||||
|
|
||||||
|
return Array.isArray(candidate.picks) && candidate.additionalPicks instanceof Promise;
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem> extends Disposable implements IQuickAccessProvider {
|
export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem> extends Disposable implements IQuickAccessProvider {
|
||||||
|
|
||||||
constructor(private prefix: string) {
|
private static FAST_PICKS_RACE_DELAY = 200; // timeout before we accept fast results before slow results are present
|
||||||
|
|
||||||
|
constructor(private prefix: string, protected options?: IPickerQuickAccessProviderOptions) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
provide(picker: IQuickPick<T>, token: CancellationToken): IDisposable {
|
provide(picker: IQuickPick<T>, token: CancellationToken): IDisposable {
|
||||||
const disposables = new DisposableStore();
|
const disposables = new DisposableStore();
|
||||||
|
|
||||||
// Allow subclasses to configure picker
|
// Apply options if any
|
||||||
this.configure(picker);
|
picker.canAcceptInBackground = !!this.options?.canAcceptInBackground;
|
||||||
|
|
||||||
// Disable filtering & sorting, we control the results
|
// Disable filtering & sorting, we control the results
|
||||||
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
|
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
|
||||||
@@ -79,21 +94,77 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
|
|||||||
// Create new cancellation source for this run
|
// Create new cancellation source for this run
|
||||||
picksCts = new CancellationTokenSource(token);
|
picksCts = new CancellationTokenSource(token);
|
||||||
|
|
||||||
// Collect picks and support both long running and short
|
// Collect picks and support both long running and short or combined
|
||||||
const res = this.getPicks(picker.value.substr(this.prefix.length).trim(), disposables.add(new DisposableStore()), picksCts.token);
|
const picksToken = picksCts.token;
|
||||||
if (Array.isArray(res)) {
|
const res = this.getPicks(picker.value.substr(this.prefix.length).trim(), disposables.add(new DisposableStore()), picksToken);
|
||||||
|
if (isFastAndSlowPicksType(res)) {
|
||||||
|
let fastPicksHandlerDone = false;
|
||||||
|
let slowPicksHandlerDone = false;
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
|
||||||
|
// Fast Picks: to reduce amount of flicker, we race against
|
||||||
|
// the slow picks over 500ms and then set the fast picks.
|
||||||
|
// If the slow picks are faster, we reduce the flicker by
|
||||||
|
// only setting the items once.
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
await timeout(PickerQuickAccessProvider.FAST_PICKS_RACE_DELAY);
|
||||||
|
if (picksToken.isCancellationRequested) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!slowPicksHandlerDone) {
|
||||||
|
picker.items = res.picks;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fastPicksHandlerDone = true;
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
|
||||||
|
|
||||||
|
// Slow Picks: we await the slow picks and then set them at
|
||||||
|
// once together with the fast picks, but only if we actually
|
||||||
|
// have additional results.
|
||||||
|
(async () => {
|
||||||
|
picker.busy = true;
|
||||||
|
try {
|
||||||
|
const additionalPicks = await res.additionalPicks;
|
||||||
|
if (picksToken.isCancellationRequested) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalPicks.length > 0 || !fastPicksHandlerDone) {
|
||||||
|
picker.items = [...res.picks, ...additionalPicks];
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (!picksToken.isCancellationRequested) {
|
||||||
|
picker.busy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
slowPicksHandlerDone = true;
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast Picks
|
||||||
|
else if (Array.isArray(res)) {
|
||||||
picker.items = res;
|
picker.items = res;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
// Slow Picks
|
||||||
|
else {
|
||||||
picker.busy = true;
|
picker.busy = true;
|
||||||
try {
|
try {
|
||||||
const items = await res;
|
const items = await res;
|
||||||
if (token.isCancellationRequested) {
|
if (picksToken.isCancellationRequested) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
picker.items = items;
|
picker.items = items;
|
||||||
} finally {
|
} finally {
|
||||||
if (!token.isCancellationRequested) {
|
if (!picksToken.isCancellationRequested) {
|
||||||
picker.busy = false;
|
picker.busy = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,13 +213,6 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
|
|||||||
return disposables;
|
return disposables;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Subclasses can override this method to configure the picker before showing it.
|
|
||||||
*
|
|
||||||
* @param picker the picker instance used for the quick access before it opens.
|
|
||||||
*/
|
|
||||||
protected configure(picker: IQuickPick<T>): void { }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of picks and separators as needed. If the picks are resolved
|
* Returns an array of picks and separators as needed. If the picks are resolved
|
||||||
* long running, the provided cancellation token should be used to cancel the
|
* long running, the provided cancellation token should be used to cancel the
|
||||||
@@ -162,6 +226,7 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
|
|||||||
* up when the picker closes.
|
* up when the picker closes.
|
||||||
* @param token for long running tasks, implementors need to check on cancellation
|
* @param token for long running tasks, implementors need to check on cancellation
|
||||||
* through this token.
|
* through this token.
|
||||||
|
* @returns the picks either directly, as promise or combined fast and slow results.
|
||||||
*/
|
*/
|
||||||
protected abstract getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array<T | IQuickPickSeparator> | Promise<Array<T | IQuickPickSeparator>>;
|
protected abstract getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array<T | IQuickPickSeparator> | Promise<Array<T | IQuickPickSeparator>> | FastAndSlowPicksType<T>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -288,8 +288,8 @@ export class UndoRedoService implements IUndoRedoService {
|
|||||||
Severity.Info,
|
Severity.Info,
|
||||||
nls.localize('confirmWorkspace', "Would you like to undo '{0}' across all files?", element.label),
|
nls.localize('confirmWorkspace', "Would you like to undo '{0}' across all files?", element.label),
|
||||||
[
|
[
|
||||||
nls.localize('ok', "Undo In {0} Files", affectedEditStacks.length),
|
nls.localize('ok', "Undo in {0} Files", affectedEditStacks.length),
|
||||||
nls.localize('nok', "Undo This File"),
|
nls.localize('nok', "Undo this File"),
|
||||||
nls.localize('cancel', "Cancel"),
|
nls.localize('cancel', "Cancel"),
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
|||||||
import { IFileService, IFileContent, FileChangesEvent, FileSystemProviderError, FileSystemProviderErrorCode, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
|
import { IFileService, IFileContent, FileChangesEvent, FileSystemProviderError, FileSystemProviderErrorCode, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
|
||||||
import { VSBuffer } from 'vs/base/common/buffer';
|
import { VSBuffer } from 'vs/base/common/buffer';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
|
import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict } from 'vs/platform/userDataSync/common/userDataSync';
|
||||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||||
import { joinPath, dirname } from 'vs/base/common/resources';
|
import { joinPath, dirname, isEqual } from 'vs/base/common/resources';
|
||||||
import { CancelablePromise } from 'vs/base/common/async';
|
import { CancelablePromise } from 'vs/base/common/async';
|
||||||
import { Emitter, Event } from 'vs/base/common/event';
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
@@ -20,6 +20,7 @@ import { localize } from 'vs/nls';
|
|||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
import { isString } from 'vs/base/common/types';
|
import { isString } from 'vs/base/common/types';
|
||||||
import { uppercaseFirstLetter } from 'vs/base/common/strings';
|
import { uppercaseFirstLetter } from 'vs/base/common/strings';
|
||||||
|
import { equals } from 'vs/base/common/arrays';
|
||||||
|
|
||||||
type SyncSourceClassification = {
|
type SyncSourceClassification = {
|
||||||
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||||
@@ -51,6 +52,11 @@ export abstract class AbstractSynchroniser extends Disposable {
|
|||||||
private _onDidChangStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
|
private _onDidChangStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
|
||||||
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangStatus.event;
|
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangStatus.event;
|
||||||
|
|
||||||
|
private _conflicts: Conflict[] = [];
|
||||||
|
get conflicts(): Conflict[] { return this._conflicts; }
|
||||||
|
private _onDidChangeConflicts: Emitter<Conflict[]> = this._register(new Emitter<Conflict[]>());
|
||||||
|
readonly onDidChangeConflicts: Event<Conflict[]> = this._onDidChangeConflicts.event;
|
||||||
|
|
||||||
protected readonly _onDidChangeLocal: Emitter<void> = this._register(new Emitter<void>());
|
protected readonly _onDidChangeLocal: Emitter<void> = this._register(new Emitter<void>());
|
||||||
readonly onDidChangeLocal: Event<void> = this._onDidChangeLocal.event;
|
readonly onDidChangeLocal: Event<void> = this._onDidChangeLocal.event;
|
||||||
|
|
||||||
@@ -87,6 +93,16 @@ export abstract class AbstractSynchroniser extends Disposable {
|
|||||||
// Log to telemetry when conflicts are resolved
|
// Log to telemetry when conflicts are resolved
|
||||||
this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsResolved', { source: this.resource });
|
this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsResolved', { source: this.resource });
|
||||||
}
|
}
|
||||||
|
if (this.status !== SyncStatus.HasConflicts) {
|
||||||
|
this.setConflicts([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setConflicts(conflicts: Conflict[]) {
|
||||||
|
if (!equals(this._conflicts, conflicts, (a, b) => isEqual(a.local, b.local) && isEqual(a.remote, b.remote))) {
|
||||||
|
this._conflicts = conflicts;
|
||||||
|
this._onDidChangeConflicts.fire(this._conflicts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +170,7 @@ export abstract class AbstractSynchroniser extends Disposable {
|
|||||||
return !!lastSyncData;
|
return !!lastSyncData;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRemoteContentFromPreview(): Promise<string | null> {
|
async getConflictContent(conflictResource: URI): Promise<string | null> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,16 +301,23 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
|
|||||||
this.cancel();
|
this.cancel();
|
||||||
this.logService.trace(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.resource.toLowerCase()}.`);
|
this.logService.trace(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.resource.toLowerCase()}.`);
|
||||||
try {
|
try {
|
||||||
await this.fileService.del(this.conflictsPreviewResource);
|
await this.fileService.del(this.localPreviewResource);
|
||||||
} catch (e) { /* ignore */ }
|
} catch (e) { /* ignore */ }
|
||||||
this.setStatus(SyncStatus.Idle);
|
this.setStatus(SyncStatus.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRemoteContentFromPreview(): Promise<string | null> {
|
async getConflictContent(conflictResource: URI): Promise<string | null> {
|
||||||
|
if (isEqual(this.remotePreviewResource, conflictResource) || isEqual(this.localPreviewResource, conflictResource)) {
|
||||||
if (this.syncPreviewResultPromise) {
|
if (this.syncPreviewResultPromise) {
|
||||||
const result = await this.syncPreviewResultPromise;
|
const result = await this.syncPreviewResultPromise;
|
||||||
|
if (isEqual(this.remotePreviewResource, conflictResource)) {
|
||||||
return result.remoteUserData && result.remoteUserData.syncData ? result.remoteUserData.syncData.content : null;
|
return result.remoteUserData && result.remoteUserData.syncData ? result.remoteUserData.syncData.content : null;
|
||||||
}
|
}
|
||||||
|
if (isEqual(this.localPreviewResource, conflictResource)) {
|
||||||
|
return result.fileContent ? result.fileContent.value.toString() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,7 +379,8 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract readonly conflictsPreviewResource: URI;
|
protected abstract readonly localPreviewResource: URI;
|
||||||
|
protected abstract readonly remotePreviewResource: URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroniser {
|
export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroniser {
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/comm
|
|||||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||||
import { IFileService } from 'vs/platform/files/common/files';
|
import { IFileService } from 'vs/platform/files/common/files';
|
||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
import { localize } from 'vs/nls';
|
|
||||||
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
|
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
|
||||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||||
import { AbstractSynchroniser, IRemoteUserData, ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
import { AbstractSynchroniser, IRemoteUserData, ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
|
||||||
interface ISyncPreviewResult {
|
interface ISyncPreviewResult {
|
||||||
readonly localExtensions: ISyncExtension[];
|
readonly localExtensions: ISyncExtension[];
|
||||||
@@ -147,7 +147,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
accept(content: string): Promise<void> {
|
async acceptConflict(conflict: URI, content: string): Promise<void> {
|
||||||
throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`);
|
throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,9 +230,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||||||
const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User);
|
const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User);
|
||||||
const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r)));
|
const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r)));
|
||||||
await Promise.all(extensionsToRemove.map(async extensionToRemove => {
|
await Promise.all(extensionsToRemove.map(async extensionToRemove => {
|
||||||
this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...', extensionToRemove.identifier.i`);
|
this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...`, extensionToRemove.identifier.id);
|
||||||
await this.extensionManagementService.uninstall(extensionToRemove);
|
await this.extensionManagementService.uninstall(extensionToRemove);
|
||||||
this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.', extensionToRemove.identifier.i`);
|
this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.`, extensionToRemove.identifier.id);
|
||||||
removeFromSkipped.push(extensionToRemove.identifier);
|
removeFromSkipped.push(extensionToRemove.identifier);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -245,13 +245,13 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||||||
// Builtin Extension: Sync only enablement state
|
// Builtin Extension: Sync only enablement state
|
||||||
if (installedExtension && installedExtension.type === ExtensionType.System) {
|
if (installedExtension && installedExtension.type === ExtensionType.System) {
|
||||||
if (e.disabled) {
|
if (e.disabled) {
|
||||||
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...', e.identifier.i`);
|
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id);
|
||||||
await this.extensionEnablementService.disableExtension(e.identifier);
|
await this.extensionEnablementService.disableExtension(e.identifier);
|
||||||
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension', e.identifier.i`);
|
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id);
|
||||||
} else {
|
} else {
|
||||||
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...', e.identifier.i`);
|
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id);
|
||||||
await this.extensionEnablementService.enableExtension(e.identifier);
|
await this.extensionEnablementService.enableExtension(e.identifier);
|
||||||
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension', e.identifier.i`);
|
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id);
|
||||||
}
|
}
|
||||||
removeFromSkipped.push(e.identifier);
|
removeFromSkipped.push(e.identifier);
|
||||||
return;
|
return;
|
||||||
@@ -261,25 +261,25 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||||||
if (extension) {
|
if (extension) {
|
||||||
try {
|
try {
|
||||||
if (e.disabled) {
|
if (e.disabled) {
|
||||||
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...', e.identifier.id, extension.versio`);
|
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id, extension.version);
|
||||||
await this.extensionEnablementService.disableExtension(extension.identifier);
|
await this.extensionEnablementService.disableExtension(extension.identifier);
|
||||||
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension', e.identifier.id, extension.versio`);
|
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id, extension.version);
|
||||||
} else {
|
} else {
|
||||||
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...', e.identifier.id, extension.versio`);
|
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id, extension.version);
|
||||||
await this.extensionEnablementService.enableExtension(extension.identifier);
|
await this.extensionEnablementService.enableExtension(extension.identifier);
|
||||||
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension', e.identifier.id, extension.versio`);
|
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id, extension.version);
|
||||||
}
|
}
|
||||||
// Install only if the extension does not exist
|
// Install only if the extension does not exist
|
||||||
if (!installedExtension || installedExtension.manifest.version !== extension.version) {
|
if (!installedExtension || installedExtension.manifest.version !== extension.version) {
|
||||||
this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...', e.identifier.id, extension.versio`);
|
this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version);
|
||||||
await this.extensionManagementService.installFromGallery(extension);
|
await this.extensionManagementService.installFromGallery(extension);
|
||||||
this.logService.info(`${this.syncResourceLogLabel}: Installed extension.', e.identifier.id, extension.versio`);
|
this.logService.info(`${this.syncResourceLogLabel}: Installed extension.`, e.identifier.id, extension.version);
|
||||||
removeFromSkipped.push(extension.identifier);
|
removeFromSkipped.push(extension.identifier);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addToSkipped.push(e);
|
addToSkipped.push(e);
|
||||||
this.logService.error(error);
|
this.logService.error(error);
|
||||||
this.logService.info(localize('skip extension', "Skipped synchronizing extension {0}", extension.displayName || extension.identifier.id));
|
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension`, extension.displayName || extension.identifier.id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addToSkipped.push(e);
|
addToSkipped.push(e);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { parse } from 'vs/base/common/json';
|
|||||||
import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
|
||||||
const argvProperties: string[] = ['locale'];
|
const argvProperties: string[] = ['locale'];
|
||||||
|
|
||||||
@@ -131,7 +132,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
accept(content: string): Promise<void> {
|
async acceptConflict(conflict: URI, content: string): Promise<void> {
|
||||||
throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`);
|
throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||||
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
|
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME } from 'vs/platform/userDataSync/common/userDataSync';
|
||||||
import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge';
|
import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge';
|
||||||
import { VSBuffer } from 'vs/base/common/buffer';
|
import { VSBuffer } from 'vs/base/common/buffer';
|
||||||
import { parse } from 'vs/base/common/json';
|
import { parse } from 'vs/base/common/json';
|
||||||
@@ -19,6 +19,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays';
|
|||||||
import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import { joinPath, isEqual } from 'vs/base/common/resources';
|
||||||
|
|
||||||
interface ISyncContent {
|
interface ISyncContent {
|
||||||
mac?: string;
|
mac?: string;
|
||||||
@@ -29,8 +30,9 @@ interface ISyncContent {
|
|||||||
|
|
||||||
export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
|
export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
|
||||||
|
|
||||||
protected get conflictsPreviewResource(): URI { return this.environmentService.keybindingsSyncPreviewResource; }
|
|
||||||
protected readonly version: number = 1;
|
protected readonly version: number = 1;
|
||||||
|
protected readonly localPreviewResource: URI = joinPath(this.syncFolder, PREVIEW_DIR_NAME, 'keybindings.json');
|
||||||
|
protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME });
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
|
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
|
||||||
@@ -39,7 +41,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
|||||||
@IConfigurationService configurationService: IConfigurationService,
|
@IConfigurationService configurationService: IConfigurationService,
|
||||||
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
|
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||||
@IFileService fileService: IFileService,
|
@IFileService fileService: IFileService,
|
||||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
@IEnvironmentService environmentService: IEnvironmentService,
|
||||||
@IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService,
|
@IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService,
|
||||||
@ITelemetryService telemetryService: ITelemetryService,
|
@ITelemetryService telemetryService: ITelemetryService,
|
||||||
) {
|
) {
|
||||||
@@ -129,8 +131,10 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async accept(content: string): Promise<void> {
|
async acceptConflict(conflict: URI, content: string): Promise<void> {
|
||||||
if (this.status === SyncStatus.HasConflicts) {
|
if (this.status === SyncStatus.HasConflicts
|
||||||
|
&& (isEqual(this.localPreviewResource, conflict) || isEqual(this.remotePreviewResource, conflict))
|
||||||
|
) {
|
||||||
const preview = await this.syncPreviewResultPromise!;
|
const preview = await this.syncPreviewResultPromise!;
|
||||||
this.cancel();
|
this.cancel();
|
||||||
this.syncPreviewResultPromise = createCancelablePromise(async () => ({ ...preview, content }));
|
this.syncPreviewResultPromise = createCancelablePromise(async () => ({ ...preview, content }));
|
||||||
@@ -156,8 +160,8 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRemoteContentFromPreview(): Promise<string | null> {
|
async getConflictContent(conflictResource: URI): Promise<string | null> {
|
||||||
const content = await super.getRemoteContentFromPreview();
|
const content = await super.getConflictContent(conflictResource);
|
||||||
return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null;
|
return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +228,9 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
|||||||
|
|
||||||
if (hasLocalChanged) {
|
if (hasLocalChanged) {
|
||||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating local keybindings...`);
|
this.logService.trace(`${this.syncResourceLogLabel}: Updating local keybindings...`);
|
||||||
await this.backupLocal(this.toSyncContent(content, null));
|
if (fileContent) {
|
||||||
|
await this.backupLocal(this.toSyncContent(fileContent.value.toString(), null));
|
||||||
|
}
|
||||||
await this.updateLocalFileContent(content, fileContent);
|
await this.updateLocalFileContent(content, fileContent);
|
||||||
this.logService.info(`${this.syncResourceLogLabel}: Updated local keybindings`);
|
this.logService.info(`${this.syncResourceLogLabel}: Updated local keybindings`);
|
||||||
}
|
}
|
||||||
@@ -238,7 +244,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
|||||||
|
|
||||||
// Delete the preview
|
// Delete the preview
|
||||||
try {
|
try {
|
||||||
await this.fileService.del(this.conflictsPreviewResource);
|
await this.fileService.del(this.localPreviewResource);
|
||||||
} catch (e) { /* ignore */ }
|
} catch (e) { /* ignore */ }
|
||||||
} else {
|
} else {
|
||||||
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing keybindings.`);
|
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing keybindings.`);
|
||||||
@@ -303,9 +309,11 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (content && !token.isCancellationRequested) {
|
if (content && !token.isCancellationRequested) {
|
||||||
await this.fileService.writeFile(this.environmentService.keybindingsSyncPreviewResource, VSBuffer.fromString(content));
|
await this.fileService.writeFile(this.localPreviewResource, VSBuffer.fromString(content));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setConflicts(hasConflicts && !token.isCancellationRequested ? [{ local: this.localPreviewResource, remote: this.remotePreviewResource }] : []);
|
||||||
|
|
||||||
return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,24 +4,23 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||||
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, CONFIGURATION_SYNC_STORE_KEY, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
|
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, CONFIGURATION_SYNC_STORE_KEY, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME } from 'vs/platform/userDataSync/common/userDataSync';
|
||||||
import { VSBuffer } from 'vs/base/common/buffer';
|
import { VSBuffer } from 'vs/base/common/buffer';
|
||||||
import { parse } from 'vs/base/common/json';
|
import { parse } from 'vs/base/common/json';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { Emitter, Event } from 'vs/base/common/event';
|
import { Event } from 'vs/base/common/event';
|
||||||
import { createCancelablePromise } from 'vs/base/common/async';
|
import { createCancelablePromise } from 'vs/base/common/async';
|
||||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||||
import { updateIgnoredSettings, merge, getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge';
|
import { updateIgnoredSettings, merge, getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||||
import * as arrays from 'vs/base/common/arrays';
|
|
||||||
import * as objects from 'vs/base/common/objects';
|
|
||||||
import { isEmptyObject } from 'vs/base/common/types';
|
import { isEmptyObject } from 'vs/base/common/types';
|
||||||
import { edit } from 'vs/platform/userDataSync/common/content';
|
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||||
import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||||
|
import { joinPath, isEqual } from 'vs/base/common/resources';
|
||||||
|
|
||||||
export interface ISettingsSyncContent {
|
export interface ISettingsSyncContent {
|
||||||
settings: string;
|
settings: string;
|
||||||
@@ -38,16 +37,12 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser {
|
|||||||
_serviceBrand: any;
|
_serviceBrand: any;
|
||||||
|
|
||||||
protected readonly version: number = 1;
|
protected readonly version: number = 1;
|
||||||
protected get conflictsPreviewResource(): URI { return this.environmentService.settingsSyncPreviewResource; }
|
protected readonly localPreviewResource: URI = joinPath(this.syncFolder, PREVIEW_DIR_NAME, 'settings.json');
|
||||||
|
protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME });
|
||||||
private _conflicts: IConflictSetting[] = [];
|
|
||||||
get conflicts(): IConflictSetting[] { return this._conflicts; }
|
|
||||||
private _onDidChangeConflicts: Emitter<IConflictSetting[]> = this._register(new Emitter<IConflictSetting[]>());
|
|
||||||
readonly onDidChangeConflicts: Event<IConflictSetting[]> = this._onDidChangeConflicts.event;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@IFileService fileService: IFileService,
|
@IFileService fileService: IFileService,
|
||||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
@IEnvironmentService environmentService: IEnvironmentService,
|
||||||
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
|
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
|
||||||
@IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
|
@IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
|
||||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||||
@@ -67,15 +62,6 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setConflicts(conflicts: IConflictSetting[]): void {
|
|
||||||
if (!arrays.equals(this.conflicts, conflicts,
|
|
||||||
(a, b) => a.key === b.key && objects.equals(a.localValue, b.localValue) && objects.equals(a.remoteValue, b.remoteValue))
|
|
||||||
) {
|
|
||||||
this._conflicts = conflicts;
|
|
||||||
this._onDidChangeConflicts.fire(conflicts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async pull(): Promise<void> {
|
async pull(): Promise<void> {
|
||||||
if (!this.isEnabled()) {
|
if (!this.isEnabled()) {
|
||||||
this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling settings as it is disabled.`);
|
this.logService.info(`${this.syncResourceLogLabel}: Skipped pulling settings as it is disabled.`);
|
||||||
@@ -187,8 +173,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRemoteContentFromPreview(): Promise<string | null> {
|
async getConflictContent(conflictResource: URI): Promise<string | null> {
|
||||||
let content = await super.getRemoteContentFromPreview();
|
let content = await super.getConflictContent(conflictResource);
|
||||||
if (content !== null) {
|
if (content !== null) {
|
||||||
const settingsSyncContent = this.parseSettingsSyncContent(content);
|
const settingsSyncContent = this.parseSettingsSyncContent(content);
|
||||||
content = settingsSyncContent ? settingsSyncContent.settings : null;
|
content = settingsSyncContent ? settingsSyncContent.settings : null;
|
||||||
@@ -232,8 +218,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async accept(content: string): Promise<void> {
|
async acceptConflict(conflict: URI, content: string): Promise<void> {
|
||||||
if (this.status === SyncStatus.HasConflicts) {
|
if (this.status === SyncStatus.HasConflicts
|
||||||
|
&& (isEqual(this.localPreviewResource, conflict) || isEqual(this.remotePreviewResource, conflict))
|
||||||
|
) {
|
||||||
const preview = await this.syncPreviewResultPromise!;
|
const preview = await this.syncPreviewResultPromise!;
|
||||||
this.cancel();
|
this.cancel();
|
||||||
const formatUtils = await this.getFormattingOptions();
|
const formatUtils = await this.getFormattingOptions();
|
||||||
@@ -289,7 +277,9 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser {
|
|||||||
|
|
||||||
if (hasLocalChanged) {
|
if (hasLocalChanged) {
|
||||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating local settings...`);
|
this.logService.trace(`${this.syncResourceLogLabel}: Updating local settings...`);
|
||||||
await this.backupLocal(JSON.stringify(this.toSettingsSyncContent(content)));
|
if (fileContent) {
|
||||||
|
await this.backupLocal(JSON.stringify(this.toSettingsSyncContent(fileContent.value.toString())));
|
||||||
|
}
|
||||||
await this.updateLocalFileContent(content, fileContent);
|
await this.updateLocalFileContent(content, fileContent);
|
||||||
this.logService.info(`${this.syncResourceLogLabel}: Updated local settings`);
|
this.logService.info(`${this.syncResourceLogLabel}: Updated local settings`);
|
||||||
}
|
}
|
||||||
@@ -306,7 +296,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser {
|
|||||||
|
|
||||||
// Delete the preview
|
// Delete the preview
|
||||||
try {
|
try {
|
||||||
await this.fileService.del(this.conflictsPreviewResource);
|
await this.fileService.del(this.localPreviewResource);
|
||||||
} catch (e) { /* ignore */ }
|
} catch (e) { /* ignore */ }
|
||||||
} else {
|
} else {
|
||||||
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing settings.`);
|
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing settings.`);
|
||||||
@@ -338,7 +328,6 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser {
|
|||||||
let hasLocalChanged: boolean = false;
|
let hasLocalChanged: boolean = false;
|
||||||
let hasRemoteChanged: boolean = false;
|
let hasRemoteChanged: boolean = false;
|
||||||
let hasConflicts: boolean = false;
|
let hasConflicts: boolean = false;
|
||||||
let conflictSettings: IConflictSetting[] = [];
|
|
||||||
|
|
||||||
if (remoteSettingsSyncContent) {
|
if (remoteSettingsSyncContent) {
|
||||||
const localContent: string = fileContent ? fileContent.value.toString() : '{}';
|
const localContent: string = fileContent ? fileContent.value.toString() : '{}';
|
||||||
@@ -350,7 +339,6 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser {
|
|||||||
hasLocalChanged = result.localContent !== null;
|
hasLocalChanged = result.localContent !== null;
|
||||||
hasRemoteChanged = result.remoteContent !== null;
|
hasRemoteChanged = result.remoteContent !== null;
|
||||||
hasConflicts = result.hasConflicts;
|
hasConflicts = result.hasConflicts;
|
||||||
conflictSettings = result.conflictsSettings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// First time syncing to remote
|
// First time syncing to remote
|
||||||
@@ -364,10 +352,11 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser {
|
|||||||
// Remove the ignored settings from the preview.
|
// Remove the ignored settings from the preview.
|
||||||
const ignoredSettings = await this.getIgnoredSettings();
|
const ignoredSettings = await this.getIgnoredSettings();
|
||||||
const previewContent = updateIgnoredSettings(content, '{}', ignoredSettings, formattingOptions);
|
const previewContent = updateIgnoredSettings(content, '{}', ignoredSettings, formattingOptions);
|
||||||
await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(previewContent));
|
await this.fileService.writeFile(this.localPreviewResource, VSBuffer.fromString(previewContent));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setConflicts(conflictSettings);
|
this.setConflicts(hasConflicts && !token.isCancellationRequested ? [{ local: this.localPreviewResource, remote: this.remotePreviewResource }] : []);
|
||||||
|
|
||||||
return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
|||||||
import { IStringDictionary } from 'vs/base/common/collections';
|
import { IStringDictionary } from 'vs/base/common/collections';
|
||||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { isEqual, joinPath, dirname, basename } from 'vs/base/common/resources';
|
import { joinPath, dirname, basename, isEqualOrParent } from 'vs/base/common/resources';
|
||||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||||
import { IProductService } from 'vs/platform/product/common/productService';
|
import { IProductService } from 'vs/platform/product/common/productService';
|
||||||
import { distinct } from 'vs/base/common/arrays';
|
import { distinct } from 'vs/base/common/arrays';
|
||||||
@@ -242,11 +242,15 @@ export const enum SyncStatus {
|
|||||||
HasConflicts = 'hasConflicts',
|
HasConflicts = 'hasConflicts',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Conflict = { remote: URI, local: URI };
|
||||||
|
|
||||||
export interface IUserDataSynchroniser {
|
export interface IUserDataSynchroniser {
|
||||||
|
|
||||||
readonly resource: SyncResource;
|
readonly resource: SyncResource;
|
||||||
readonly status: SyncStatus;
|
readonly status: SyncStatus;
|
||||||
readonly onDidChangeStatus: Event<SyncStatus>;
|
readonly onDidChangeStatus: Event<SyncStatus>;
|
||||||
|
readonly conflicts: Conflict[];
|
||||||
|
readonly onDidChangeConflicts: Event<Conflict[]>;
|
||||||
readonly onDidChangeLocal: Event<void>;
|
readonly onDidChangeLocal: Event<void>;
|
||||||
|
|
||||||
pull(): Promise<void>;
|
pull(): Promise<void>;
|
||||||
@@ -258,10 +262,11 @@ export interface IUserDataSynchroniser {
|
|||||||
hasLocalData(): Promise<boolean>;
|
hasLocalData(): Promise<boolean>;
|
||||||
resetLocal(): Promise<void>;
|
resetLocal(): Promise<void>;
|
||||||
|
|
||||||
getRemoteContentFromPreview(): Promise<string | null>;
|
getConflictContent(conflictResource: URI): Promise<string | null>;
|
||||||
|
acceptConflict(conflictResource: URI, content: string): Promise<void>;
|
||||||
|
|
||||||
getRemoteContent(ref?: string, fragment?: string): Promise<string | null>;
|
getRemoteContent(ref?: string, fragment?: string): Promise<string | null>;
|
||||||
getLocalBackupContent(ref?: string, fragment?: string): Promise<string | null>;
|
getLocalBackupContent(ref?: string, fragment?: string): Promise<string | null>;
|
||||||
accept(content: string): Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
@@ -282,6 +287,8 @@ export interface IUserDataSyncEnablementService {
|
|||||||
setResourceEnablement(resource: SyncResource, enabled: boolean): void;
|
setResourceEnablement(resource: SyncResource, enabled: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SyncResourceConflicts = { syncResource: SyncResource, conflicts: Conflict[] };
|
||||||
|
|
||||||
export const IUserDataSyncService = createDecorator<IUserDataSyncService>('IUserDataSyncService');
|
export const IUserDataSyncService = createDecorator<IUserDataSyncService>('IUserDataSyncService');
|
||||||
export interface IUserDataSyncService {
|
export interface IUserDataSyncService {
|
||||||
_serviceBrand: any;
|
_serviceBrand: any;
|
||||||
@@ -289,8 +296,8 @@ export interface IUserDataSyncService {
|
|||||||
readonly status: SyncStatus;
|
readonly status: SyncStatus;
|
||||||
readonly onDidChangeStatus: Event<SyncStatus>;
|
readonly onDidChangeStatus: Event<SyncStatus>;
|
||||||
|
|
||||||
readonly conflictsSources: SyncResource[];
|
readonly conflicts: SyncResourceConflicts[];
|
||||||
readonly onDidChangeConflicts: Event<SyncResource[]>;
|
readonly onDidChangeConflicts: Event<SyncResourceConflicts[]>;
|
||||||
|
|
||||||
readonly onDidChangeLocal: Event<SyncResource>;
|
readonly onDidChangeLocal: Event<SyncResource>;
|
||||||
readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]>;
|
readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]>;
|
||||||
@@ -306,7 +313,7 @@ export interface IUserDataSyncService {
|
|||||||
|
|
||||||
isFirstTimeSyncWithMerge(): Promise<boolean>;
|
isFirstTimeSyncWithMerge(): Promise<boolean>;
|
||||||
resolveContent(resource: URI): Promise<string | null>;
|
resolveContent(resource: URI): Promise<string | null>;
|
||||||
accept(source: SyncResource, content: string): Promise<void>;
|
acceptConflict(conflictResource: URI, content: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IUserDataAutoSyncService = createDecorator<IUserDataAutoSyncService>('IUserDataAutoSyncService');
|
export const IUserDataAutoSyncService = createDecorator<IUserDataAutoSyncService>('IUserDataAutoSyncService');
|
||||||
@@ -335,36 +342,41 @@ export interface IConflictSetting {
|
|||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync';
|
||||||
export const CONTEXT_SYNC_STATE = new RawContextKey<string>('syncStatus', SyncStatus.Uninitialized);
|
export const CONTEXT_SYNC_STATE = new RawContextKey<string>('syncStatus', SyncStatus.Uninitialized);
|
||||||
export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey<boolean>('syncEnabled', false);
|
export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey<boolean>('syncEnabled', false);
|
||||||
|
|
||||||
export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync';
|
export function toRemoteBackupSyncResource(resource: SyncResource, ref?: string): URI {
|
||||||
export const PREVIEW_QUERY = 'preview=true';
|
return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote-backup', path: `/${resource}/${ref ? ref : 'latest'}` });
|
||||||
export function toRemoteSyncResource(resource: SyncResource, ref?: string): URI {
|
|
||||||
return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote', path: `/${resource}/${ref ? ref : 'latest'}` });
|
|
||||||
}
|
}
|
||||||
export function toLocalBackupSyncResource(resource: SyncResource, ref?: string): URI {
|
export function toLocalBackupSyncResource(resource: SyncResource, ref?: string): URI {
|
||||||
return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local-backup', path: `/${resource}/${ref ? ref : 'latest'}` });
|
return URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local-backup', path: `/${resource}/${ref ? ref : 'latest'}` });
|
||||||
}
|
}
|
||||||
|
export function resolveBackupSyncResource(resource: URI): { remote: boolean, resource: SyncResource, path: string } | null {
|
||||||
export function resolveSyncResource(resource: URI): { remote: boolean, resource: SyncResource, ref?: string } | null {
|
if (resource.scheme === USER_DATA_SYNC_SCHEME
|
||||||
if (resource.scheme === USER_DATA_SYNC_SCHEME) {
|
&& resource.authority === 'remote-backup' || resource.authority === 'local-backup') {
|
||||||
const remote = resource.authority === 'remote';
|
|
||||||
const resourceKey: SyncResource = basename(dirname(resource)) as SyncResource;
|
const resourceKey: SyncResource = basename(dirname(resource)) as SyncResource;
|
||||||
const ref = basename(resource);
|
const path = resource.path.substring(resourceKey.length + 1);
|
||||||
if (resourceKey && ref) {
|
if (resourceKey && path) {
|
||||||
return { remote, resource: resourceKey, ref: ref !== 'latest' ? ref : undefined };
|
const remote = resource.authority === 'remote-backup';
|
||||||
|
return { remote, resource: resourceKey, path };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSyncSourceFromPreviewResource(uri: URI, environmentService: IEnvironmentService): SyncResource | undefined {
|
export const PREVIEW_DIR_NAME = 'preview';
|
||||||
if (isEqual(uri, environmentService.settingsSyncPreviewResource)) {
|
export function getSyncResourceFromLocalPreview(localPreview: URI, environmentService: IEnvironmentService): SyncResource | undefined {
|
||||||
return SyncResource.Settings;
|
if (localPreview.scheme === USER_DATA_SYNC_SCHEME) {
|
||||||
}
|
|
||||||
if (isEqual(uri, environmentService.keybindingsSyncPreviewResource)) {
|
|
||||||
return SyncResource.Keybindings;
|
|
||||||
}
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
}
|
||||||
|
localPreview = localPreview.with({ scheme: environmentService.userDataSyncHome.scheme });
|
||||||
|
return ALL_SYNC_RESOURCES.filter(syncResource => isEqualOrParent(localPreview, joinPath(environmentService.userDataSyncHome, syncResource, PREVIEW_DIR_NAME)))[0];
|
||||||
|
}
|
||||||
|
export function getSyncResourceFromRemotePreview(remotePreview: URI, environmentService: IEnvironmentService): SyncResource | undefined {
|
||||||
|
if (remotePreview.scheme !== USER_DATA_SYNC_SCHEME) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
remotePreview = remotePreview.with({ scheme: environmentService.userDataSyncHome.scheme });
|
||||||
|
return ALL_SYNC_RESOURCES.filter(syncResource => isEqualOrParent(remotePreview, joinPath(environmentService.userDataSyncHome, syncResource, PREVIEW_DIR_NAME)))[0];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ export class UserDataSyncChannel implements IServerChannel {
|
|||||||
|
|
||||||
call(context: any, command: string, args?: any): Promise<any> {
|
call(context: any, command: string, args?: any): Promise<any> {
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case '_getInitialData': return Promise.resolve([this.service.status, this.service.conflictsSources, this.service.lastSyncTime]);
|
case '_getInitialData': return Promise.resolve([this.service.status, this.service.conflicts, this.service.lastSyncTime]);
|
||||||
case 'sync': return this.service.sync();
|
case 'sync': return this.service.sync();
|
||||||
case 'accept': return this.service.accept(args[0], args[1]);
|
case 'acceptConflict': return this.service.acceptConflict(URI.revive(args[0]), args[1]);
|
||||||
case 'pull': return this.service.pull();
|
case 'pull': return this.service.pull();
|
||||||
case 'stop': this.service.stop(); return Promise.resolve();
|
case 'stop': this.service.stop(); return Promise.resolve();
|
||||||
case 'reset': return this.service.reset();
|
case 'reset': return this.service.reset();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, resolveSyncResource, PREVIEW_QUERY } from 'vs/platform/userDataSync/common/userDataSync';
|
import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, resolveBackupSyncResource, SyncResourceConflicts } from 'vs/platform/userDataSync/common/userDataSync';
|
||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { Emitter, Event } from 'vs/base/common/event';
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
@@ -17,6 +17,7 @@ import { localize } from 'vs/nls';
|
|||||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
|
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
|
||||||
|
import { isEqual } from 'vs/base/common/resources';
|
||||||
|
|
||||||
type SyncErrorClassification = {
|
type SyncErrorClassification = {
|
||||||
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||||
@@ -38,10 +39,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
|||||||
|
|
||||||
readonly onDidChangeLocal: Event<SyncResource>;
|
readonly onDidChangeLocal: Event<SyncResource>;
|
||||||
|
|
||||||
private _conflictsSources: SyncResource[] = [];
|
private _conflicts: SyncResourceConflicts[] = [];
|
||||||
get conflictsSources(): SyncResource[] { return this._conflictsSources; }
|
get conflicts(): SyncResourceConflicts[] { return this._conflicts; }
|
||||||
private _onDidChangeConflicts: Emitter<SyncResource[]> = this._register(new Emitter<SyncResource[]>());
|
private _onDidChangeConflicts: Emitter<SyncResourceConflicts[]> = this._register(new Emitter<SyncResourceConflicts[]>());
|
||||||
readonly onDidChangeConflicts: Event<SyncResource[]> = this._onDidChangeConflicts.event;
|
readonly onDidChangeConflicts: Event<SyncResourceConflicts[]> = this._onDidChangeConflicts.event;
|
||||||
|
|
||||||
private _syncErrors: [SyncResource, UserDataSyncError][] = [];
|
private _syncErrors: [SyncResource, UserDataSyncError][] = [];
|
||||||
private _onSyncErrors: Emitter<[SyncResource, UserDataSyncError][]> = this._register(new Emitter<[SyncResource, UserDataSyncError][]>());
|
private _onSyncErrors: Emitter<[SyncResource, UserDataSyncError][]> = this._register(new Emitter<[SyncResource, UserDataSyncError][]>());
|
||||||
@@ -62,7 +63,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
|||||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||||
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
|
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
|
||||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||||
@IStorageService private readonly storageService: IStorageService,
|
@IStorageService private readonly storageService: IStorageService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser));
|
this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser));
|
||||||
@@ -74,6 +75,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
|||||||
|
|
||||||
if (this.userDataSyncStoreService.userDataSyncStore) {
|
if (this.userDataSyncStoreService.userDataSyncStore) {
|
||||||
this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus()));
|
this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus()));
|
||||||
|
this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeConflicts, () => undefined)))(() => this.updateConflicts()));
|
||||||
}
|
}
|
||||||
|
|
||||||
this._lastSyncTime = this.storageService.getNumber(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL, undefined);
|
this._lastSyncTime = this.storageService.getNumber(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL, undefined);
|
||||||
@@ -173,23 +175,32 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async accept(source: SyncResource, content: string): Promise<void> {
|
async acceptConflict(conflict: URI, content: string): Promise<void> {
|
||||||
await this.checkEnablement();
|
await this.checkEnablement();
|
||||||
const synchroniser = this.getSynchroniser(source);
|
const syncResourceConflict = this.conflicts.filter(({ conflicts }) => conflicts.some(({ local, remote }) => isEqual(conflict, local) || isEqual(conflict, remote)))[0];
|
||||||
await synchroniser.accept(content);
|
if (syncResourceConflict) {
|
||||||
|
const synchroniser = this.getSynchroniser(syncResourceConflict.syncResource);
|
||||||
|
await synchroniser.acceptConflict(conflict, content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolveContent(resource: URI): Promise<string | null> {
|
async resolveContent(resource: URI): Promise<string | null> {
|
||||||
const result = resolveSyncResource(resource);
|
const result = resolveBackupSyncResource(resource);
|
||||||
if (result) {
|
if (result) {
|
||||||
const synchronizer = this.synchronisers.filter(s => s.resource === result.resource)[0];
|
const synchronizer = this.synchronisers.filter(s => s.resource === result.resource)[0];
|
||||||
if (synchronizer) {
|
if (synchronizer) {
|
||||||
if (PREVIEW_QUERY === resource.query) {
|
const ref = result.path !== 'latest' ? result.path : undefined;
|
||||||
return result.remote ? synchronizer.getRemoteContentFromPreview() : null;
|
return result.remote ? synchronizer.getRemoteContent(ref, resource.fragment) : synchronizer.getLocalBackupContent(ref, resource.fragment);
|
||||||
}
|
|
||||||
return result.remote ? synchronizer.getRemoteContent(result.ref, resource.fragment) : synchronizer.getLocalBackupContent(result.ref, resource.fragment);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const synchronizer of this.synchronisers) {
|
||||||
|
const content = await synchronizer.getConflictContent(resource);
|
||||||
|
if (content !== null) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,15 +274,19 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateStatus(): void {
|
private updateStatus(): void {
|
||||||
const conflictsSources = this.computeConflictsSources();
|
this.updateConflicts();
|
||||||
if (!equals(this._conflictsSources, conflictsSources)) {
|
|
||||||
this._conflictsSources = this.computeConflictsSources();
|
|
||||||
this._onDidChangeConflicts.fire(conflictsSources);
|
|
||||||
}
|
|
||||||
const status = this.computeStatus();
|
const status = this.computeStatus();
|
||||||
this.setStatus(status);
|
this.setStatus(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateConflicts(): void {
|
||||||
|
const conflicts = this.computeConflicts();
|
||||||
|
if (!equals(this._conflicts, conflicts, (a, b) => a.syncResource === b.syncResource && equals(a.conflicts, b.conflicts, (a, b) => isEqual(a.local, b.local) && isEqual(a.remote, b.remote)))) {
|
||||||
|
this._conflicts = this.computeConflicts();
|
||||||
|
this._onDidChangeConflicts.fire(conflicts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private computeStatus(): SyncStatus {
|
private computeStatus(): SyncStatus {
|
||||||
if (!this.userDataSyncStoreService.userDataSyncStore) {
|
if (!this.userDataSyncStoreService.userDataSyncStore) {
|
||||||
return SyncStatus.Uninitialized;
|
return SyncStatus.Uninitialized;
|
||||||
@@ -305,8 +320,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
|||||||
this.logService.error(`${source}: ${toErrorMessage(e)}`);
|
this.logService.error(`${source}: ${toErrorMessage(e)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private computeConflictsSources(): SyncResource[] {
|
private computeConflicts(): SyncResourceConflicts[] {
|
||||||
return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts).map(s => s.resource);
|
return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts)
|
||||||
|
.map(s => ({ syncResource: s.resource, conflicts: s.conflicts }));
|
||||||
}
|
}
|
||||||
|
|
||||||
getSynchroniser(source: SyncResource): IUserDataSynchroniser {
|
getSynchroniser(source: SyncResource): IUserDataSynchroniser {
|
||||||
|
|||||||
@@ -52,9 +52,7 @@ export class UserDataSyncClient extends Disposable {
|
|||||||
const environmentService = this.instantiationService.stub(IEnvironmentService, <Partial<IEnvironmentService>>{
|
const environmentService = this.instantiationService.stub(IEnvironmentService, <Partial<IEnvironmentService>>{
|
||||||
userDataSyncHome,
|
userDataSyncHome,
|
||||||
settingsResource: joinPath(userDataDirectory, 'settings.json'),
|
settingsResource: joinPath(userDataDirectory, 'settings.json'),
|
||||||
settingsSyncPreviewResource: joinPath(userDataSyncHome, 'settings.json'),
|
|
||||||
keybindingsResource: joinPath(userDataDirectory, 'keybindings.json'),
|
keybindingsResource: joinPath(userDataDirectory, 'keybindings.json'),
|
||||||
keybindingsSyncPreviewResource: joinPath(userDataSyncHome, 'keybindings.json'),
|
|
||||||
argvResource: joinPath(userDataDirectory, 'argv.json'),
|
argvResource: joinPath(userDataDirectory, 'argv.json'),
|
||||||
args: {}
|
args: {}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -480,7 +480,7 @@ suite.skip('UserDataSyncService', () => { // {{SQL CARBON EDIT}} skip failing te
|
|||||||
await testObject.sync();
|
await testObject.sync();
|
||||||
|
|
||||||
assert.deepEqual(testObject.status, SyncStatus.HasConflicts);
|
assert.deepEqual(testObject.status, SyncStatus.HasConflicts);
|
||||||
assert.deepEqual(testObject.conflictsSources, [SyncResource.Settings]);
|
assert.deepEqual(testObject.conflicts.map(({ syncResource }) => syncResource), [SyncResource.Settings]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test sync will sync other non conflicted areas', async () => {
|
test('test sync will sync other non conflicted areas', async () => {
|
||||||
@@ -549,7 +549,7 @@ suite.skip('UserDataSyncService', () => { // {{SQL CARBON EDIT}} skip failing te
|
|||||||
await testObject.stop();
|
await testObject.stop();
|
||||||
|
|
||||||
assert.deepEqual(testObject.status, SyncStatus.Idle);
|
assert.deepEqual(testObject.status, SyncStatus.Idle);
|
||||||
assert.deepEqual(testObject.conflictsSources, []);
|
assert.deepEqual(testObject.conflicts, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Vendored
+156
@@ -1230,6 +1230,43 @@ declare module 'vscode' {
|
|||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
//#region OnTypeRename: https://github.com/microsoft/vscode/issues/88424
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rename provider interface defines the contract between extensions and
|
||||||
|
* the live-rename feature.
|
||||||
|
*/
|
||||||
|
export interface OnTypeRenameProvider {
|
||||||
|
/**
|
||||||
|
* Provide a list of ranges that can be live renamed together.
|
||||||
|
*
|
||||||
|
* @param document The document in which the command was invoked.
|
||||||
|
* @param position The position at which the command was invoked.
|
||||||
|
* @param token A cancellation token.
|
||||||
|
* @return A list of ranges that can be live-renamed togehter. The ranges must have
|
||||||
|
* identical length and contain identical text content. The ranges cannot overlap.
|
||||||
|
*/
|
||||||
|
provideOnTypeRenameRanges(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<Range[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace languages {
|
||||||
|
/**
|
||||||
|
* Register a rename provider that works on type.
|
||||||
|
*
|
||||||
|
* Multiple providers can be registered for a language. In that case providers are sorted
|
||||||
|
* by their [score](#languages.match) and the best-matching provider is used. Failure
|
||||||
|
* of the selected provider will cause a failure of the whole operation.
|
||||||
|
*
|
||||||
|
* @param selector A selector that defines the documents this provider is applicable to.
|
||||||
|
* @param provider An on type rename provider.
|
||||||
|
* @param stopPattern Stop on type renaming when input text matches the regular expression. Defaults to `^\s`.
|
||||||
|
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
|
||||||
|
*/
|
||||||
|
export function registerOnTypeRenameProvider(selector: DocumentSelector, provider: OnTypeRenameProvider, stopPattern?: RegExp): Disposable;
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
//#region Custom editors: https://github.com/microsoft/vscode/issues/77131
|
//#region Custom editors: https://github.com/microsoft/vscode/issues/77131
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
@@ -1540,6 +1577,125 @@ declare module 'vscode' {
|
|||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
//#region Peng: Notebook
|
||||||
|
|
||||||
|
export enum CellKind {
|
||||||
|
Markdown = 1,
|
||||||
|
Code = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CellOutputKind {
|
||||||
|
Text = 1,
|
||||||
|
Error = 2,
|
||||||
|
Rich = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CellStreamOutput {
|
||||||
|
outputKind: CellOutputKind.Text;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CellErrorOutput {
|
||||||
|
outputKind: CellOutputKind.Error;
|
||||||
|
/**
|
||||||
|
* Exception Name
|
||||||
|
*/
|
||||||
|
ename: string;
|
||||||
|
/**
|
||||||
|
* Exception Value
|
||||||
|
*/
|
||||||
|
evalue: string;
|
||||||
|
/**
|
||||||
|
* Exception call stack
|
||||||
|
*/
|
||||||
|
traceback: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CellDisplayOutput {
|
||||||
|
outputKind: CellOutputKind.Rich;
|
||||||
|
/**
|
||||||
|
* { mime_type: value }
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```json
|
||||||
|
* {
|
||||||
|
* "outputKind": vscode.CellOutputKind.Rich,
|
||||||
|
* "data": {
|
||||||
|
* "text/html": [
|
||||||
|
* "<h1>Hello</h1>"
|
||||||
|
* ],
|
||||||
|
* "text/plain": [
|
||||||
|
* "<IPython.lib.display.IFrame at 0x11dee3e80>"
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
data: { [key: string]: any };
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CellOutput = CellStreamOutput | CellErrorOutput | CellDisplayOutput;
|
||||||
|
|
||||||
|
export interface NotebookCell {
|
||||||
|
readonly uri: Uri;
|
||||||
|
handle: number;
|
||||||
|
language: string;
|
||||||
|
cellKind: CellKind;
|
||||||
|
outputs: CellOutput[];
|
||||||
|
getContent(): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotebookDocument {
|
||||||
|
readonly uri: Uri;
|
||||||
|
readonly fileName: string;
|
||||||
|
readonly isDirty: boolean;
|
||||||
|
languages: string[];
|
||||||
|
cells: NotebookCell[];
|
||||||
|
displayOrder?: GlobPattern[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotebookEditor {
|
||||||
|
readonly document: NotebookDocument;
|
||||||
|
viewColumn?: ViewColumn;
|
||||||
|
/**
|
||||||
|
* Create a notebook cell. The cell is not inserted into current document when created. Extensions should insert the cell into the document by [TextDocument.cells](#TextDocument.cells)
|
||||||
|
*/
|
||||||
|
createCell(content: string, language: string, type: CellKind, outputs: CellOutput[]): NotebookCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotebookProvider {
|
||||||
|
resolveNotebook(editor: NotebookEditor): Promise<void>;
|
||||||
|
executeCell(document: NotebookDocument, cell: NotebookCell | undefined): Promise<void>;
|
||||||
|
save(document: NotebookDocument): Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotebookOutputSelector {
|
||||||
|
type: string;
|
||||||
|
subTypes?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotebookOutputRenderer {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns HTML fragment. We can probably return `CellOutput` instead of string ?
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
render(document: NotebookDocument, cell: NotebookCell, output: CellOutput, mimeType: string): string;
|
||||||
|
preloads?: Uri[];
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace window {
|
||||||
|
export function registerNotebookProvider(
|
||||||
|
notebookType: string,
|
||||||
|
provider: NotebookProvider
|
||||||
|
): Disposable;
|
||||||
|
|
||||||
|
export function registerNotebookOutputRenderer(type: string, outputSelector: NotebookOutputSelector, renderer: NotebookOutputRenderer): Disposable;
|
||||||
|
|
||||||
|
export let activeNotebookDocument: NotebookDocument | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
//#region color theme access
|
//#region color theme access
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import './mainThreadWindow';
|
|||||||
import './mainThreadWebview';
|
import './mainThreadWebview';
|
||||||
import './mainThreadWorkspace';
|
import './mainThreadWorkspace';
|
||||||
import './mainThreadComments';
|
import './mainThreadComments';
|
||||||
|
import './mainThreadNotebook';
|
||||||
// import './mainThreadTask'; {{SQL CARBON EDIT}} @anthonydresser comment out task
|
// import './mainThreadTask'; {{SQL CARBON EDIT}} @anthonydresser comment out task
|
||||||
import './mainThreadLabelService';
|
import './mainThreadLabelService';
|
||||||
import './mainThreadTunnelService';
|
import './mainThreadTunnelService';
|
||||||
|
|||||||
@@ -261,6 +261,18 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- on type rename
|
||||||
|
|
||||||
|
$registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[], stopPattern?: IRegExpDto): void {
|
||||||
|
const revivedStopPattern = stopPattern ? MainThreadLanguageFeatures._reviveRegExp(stopPattern) : undefined;
|
||||||
|
this._registrations.set(handle, modes.OnTypeRenameProviderRegistry.register(selector, <modes.OnTypeRenameProvider>{
|
||||||
|
stopPattern: revivedStopPattern,
|
||||||
|
provideOnTypeRenameRanges: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<IRange[] | undefined> => {
|
||||||
|
return this._proxy.$provideOnTypeRenameRanges(handle, model.uri, position, token);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// --- references
|
// --- references
|
||||||
|
|
||||||
$registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void {
|
$registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void {
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||||
|
import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext } from '../common/extHost.protocol';
|
||||||
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||||
|
import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/browser/notebookService';
|
||||||
|
import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellsSplice, NotebookCellOutputsSplice, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
|
||||||
|
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
|
||||||
|
|
||||||
|
export class MainThreadNotebookDocument extends Disposable {
|
||||||
|
private _textModel: NotebookTextModel;
|
||||||
|
|
||||||
|
get textModel() {
|
||||||
|
return this._textModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _proxy: ExtHostNotebookShape,
|
||||||
|
public handle: number,
|
||||||
|
public viewType: string,
|
||||||
|
public uri: URI
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this._textModel = new NotebookTextModel(handle, viewType, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteCell(uri: URI, index: number): Promise<boolean> {
|
||||||
|
let deleteExtHostCell = await this._proxy.$deleteCell(this.viewType, uri, index);
|
||||||
|
if (deleteExtHostCell) {
|
||||||
|
this._textModel.removeCell(index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._textModel.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@extHostNamedCustomer(MainContext.MainThreadNotebook)
|
||||||
|
export class MainThreadNotebooks extends Disposable implements MainThreadNotebookShape {
|
||||||
|
private readonly _notebookProviders = new Map<string, MainThreadNotebookController>();
|
||||||
|
private readonly _proxy: ExtHostNotebookShape;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
extHostContext: IExtHostContext,
|
||||||
|
@INotebookService private _notebookService: INotebookService,
|
||||||
|
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook);
|
||||||
|
this.registerListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
registerListeners() {
|
||||||
|
this._register(this._notebookService.onDidChangeActiveEditor(e => {
|
||||||
|
this._proxy.$updateActiveEditor(e.viewType, e.uri);
|
||||||
|
}));
|
||||||
|
|
||||||
|
let userOrder = this.configurationService.getValue<string[]>('notebook.displayOrder');
|
||||||
|
this._proxy.$acceptDisplayOrder({
|
||||||
|
defaultOrder: NOTEBOOK_DISPLAY_ORDER,
|
||||||
|
userOrder: userOrder
|
||||||
|
});
|
||||||
|
|
||||||
|
this.configurationService.onDidChangeConfiguration(e => {
|
||||||
|
if (e.affectedKeys.indexOf('notebook.displayOrder') >= 0) {
|
||||||
|
let userOrder = this.configurationService.getValue<string[]>('notebook.displayOrder');
|
||||||
|
|
||||||
|
this._proxy.$acceptDisplayOrder({
|
||||||
|
defaultOrder: NOTEBOOK_DISPLAY_ORDER,
|
||||||
|
userOrder: userOrder
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise<void> {
|
||||||
|
this._notebookService.registerNotebookRenderer(handle, extension, type, selectors, preloads.map(uri => URI.revive(uri)));
|
||||||
|
}
|
||||||
|
|
||||||
|
async $unregisterNotebookRenderer(handle: number): Promise<void> {
|
||||||
|
this._notebookService.unregisterNotebookRenderer(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise<void> {
|
||||||
|
let controller = new MainThreadNotebookController(this._proxy, this, viewType);
|
||||||
|
this._notebookProviders.set(viewType, controller);
|
||||||
|
this._notebookService.registerNotebookController(viewType, extension, controller);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async $unregisterNotebookProvider(viewType: string): Promise<void> {
|
||||||
|
this._notebookProviders.delete(viewType);
|
||||||
|
this._notebookService.unregisterNotebookProvider(viewType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async $createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise<void> {
|
||||||
|
let controller = this._notebookProviders.get(viewType);
|
||||||
|
|
||||||
|
if (controller) {
|
||||||
|
controller.createNotebookDocument(handle, viewType, resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void> {
|
||||||
|
let controller = this._notebookProviders.get(viewType);
|
||||||
|
|
||||||
|
if (controller) {
|
||||||
|
controller.updateLanguages(resource, languages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolveNotebook(viewType: string, uri: URI): Promise<number | undefined> {
|
||||||
|
let handle = await this._proxy.$resolveNotebook(viewType, uri);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
async $spliceNotebookCells(viewType: string, resource: UriComponents, splices: NotebookCellsSplice[], renderers: number[]): Promise<void> {
|
||||||
|
let controller = this._notebookProviders.get(viewType);
|
||||||
|
controller?.spliceNotebookCells(resource, splices, renderers);
|
||||||
|
}
|
||||||
|
|
||||||
|
async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise<void> {
|
||||||
|
let controller = this._notebookProviders.get(viewType);
|
||||||
|
controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers);
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeNotebook(viewType: string, uri: URI): Promise<void> {
|
||||||
|
return this._proxy.$executeNotebook(viewType, uri, undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MainThreadNotebookController implements IMainNotebookController {
|
||||||
|
private _mapping: Map<string, MainThreadNotebookDocument> = new Map();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _proxy: ExtHostNotebookShape,
|
||||||
|
private _mainThreadNotebook: MainThreadNotebooks,
|
||||||
|
private _viewType: string
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolveNotebook(viewType: string, uri: URI): Promise<NotebookTextModel | undefined> {
|
||||||
|
// TODO: resolve notebook should wait for all notebook document destory operations to finish.
|
||||||
|
let mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
|
||||||
|
|
||||||
|
if (mainthreadNotebook) {
|
||||||
|
return mainthreadNotebook.textModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
let notebookHandle = await this._mainThreadNotebook.resolveNotebook(viewType, uri);
|
||||||
|
if (notebookHandle !== undefined) {
|
||||||
|
mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
|
||||||
|
return mainthreadNotebook?.textModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
spliceNotebookCells(resource: UriComponents, splices: NotebookCellsSplice[], renderers: number[]): void {
|
||||||
|
let mainthreadNotebook = this._mapping.get(URI.from(resource).toString());
|
||||||
|
mainthreadNotebook?.textModel.updateRenderers(renderers);
|
||||||
|
mainthreadNotebook?.textModel.$spliceNotebookCells(splices);
|
||||||
|
}
|
||||||
|
|
||||||
|
spliceNotebookCellOutputs(resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): void {
|
||||||
|
let mainthreadNotebook = this._mapping.get(URI.from(resource).toString());
|
||||||
|
mainthreadNotebook?.textModel.updateRenderers(renderers);
|
||||||
|
mainthreadNotebook?.textModel.$spliceNotebookCellOutputs(cellHandle, splices);
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeNotebook(viewType: string, uri: URI): Promise<void> {
|
||||||
|
this._mainThreadNotebook.executeNotebook(viewType, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods for ExtHost
|
||||||
|
async createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise<void> {
|
||||||
|
let document = new MainThreadNotebookDocument(this._proxy, handle, viewType, URI.revive(resource));
|
||||||
|
this._mapping.set(URI.revive(resource).toString(), document);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLanguages(resource: UriComponents, languages: string[]) {
|
||||||
|
let document = this._mapping.get(URI.from(resource).toString());
|
||||||
|
document?.textModel.updateLanguages(languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateNotebookRenderers(resource: UriComponents, renderers: number[]): void {
|
||||||
|
let document = this._mapping.get(URI.from(resource).toString());
|
||||||
|
document?.textModel.updateRenderers(renderers);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateNotebookActiveCell(uri: URI, cellHandle: number): void {
|
||||||
|
let mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
|
||||||
|
mainthreadNotebook?.textModel.updateActiveCell(cellHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createRawCell(uri: URI, index: number, language: string, type: CellKind): Promise<NotebookCellTextModel | undefined> {
|
||||||
|
let cell = await this._proxy.$createEmptyCell(this._viewType, uri, index, language, type);
|
||||||
|
if (cell) {
|
||||||
|
let mainCell = new NotebookCellTextModel(URI.revive(cell.uri), cell.handle, cell.source, cell.language, cell.cellKind, cell.outputs);
|
||||||
|
return mainCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteCell(uri: URI, index: number): Promise<boolean> {
|
||||||
|
let mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
|
||||||
|
|
||||||
|
if (mainthreadNotebook) {
|
||||||
|
return mainthreadNotebook.deleteCell(uri, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
executeNotebookActiveCell(uri: URI): void {
|
||||||
|
let mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
|
||||||
|
|
||||||
|
if (mainthreadNotebook && mainthreadNotebook.textModel.activeCell) {
|
||||||
|
this._proxy.$executeNotebook(this._viewType, uri, mainthreadNotebook.textModel.activeCell.handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async destoryNotebookDocument(notebook: INotebookTextModel): Promise<void> {
|
||||||
|
let document = this._mapping.get(URI.from(notebook.uri).toString());
|
||||||
|
|
||||||
|
if (!document) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let removeFromExtHost = await this._proxy.$destoryNotebookDocument(this._viewType, notebook.uri);
|
||||||
|
if (removeFromExtHost) {
|
||||||
|
document.dispose();
|
||||||
|
this._mapping.delete(URI.from(notebook.uri).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(uri: URI): Promise<boolean> {
|
||||||
|
return this._proxy.$saveNotebook(this._viewType, uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,6 +68,7 @@ import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransf
|
|||||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||||
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
||||||
import { find } from 'vs/base/common/arrays';
|
import { find } from 'vs/base/common/arrays';
|
||||||
|
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
|
||||||
import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming';
|
import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming';
|
||||||
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
|
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
|
||||||
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
|
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
|
||||||
@@ -130,7 +131,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||||||
const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments));
|
const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments));
|
||||||
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
|
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
|
||||||
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
|
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
|
||||||
const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHostLabelService, new ExtHostLabelService(rpcProtocol));
|
const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol));
|
||||||
|
const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostDocumentsAndEditors));
|
||||||
const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol));
|
const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol));
|
||||||
const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol));
|
const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol));
|
||||||
const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands));
|
const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands));
|
||||||
@@ -363,6 +365,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||||||
registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable {
|
registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable {
|
||||||
return extHostLanguageFeatures.registerDocumentHighlightProvider(extension, checkSelector(selector), provider);
|
return extHostLanguageFeatures.registerDocumentHighlightProvider(extension, checkSelector(selector), provider);
|
||||||
},
|
},
|
||||||
|
registerOnTypeRenameProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeRenameProvider, stopPattern?: RegExp): vscode.Disposable {
|
||||||
|
checkProposedApiEnabled(extension);
|
||||||
|
return extHostLanguageFeatures.registerOnTypeRenameProvider(extension, checkSelector(selector), provider, stopPattern);
|
||||||
|
},
|
||||||
registerReferenceProvider(selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable {
|
registerReferenceProvider(selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable {
|
||||||
return extHostLanguageFeatures.registerReferenceProvider(extension, checkSelector(selector), provider);
|
return extHostLanguageFeatures.registerReferenceProvider(extension, checkSelector(selector), provider);
|
||||||
},
|
},
|
||||||
@@ -597,6 +603,15 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||||||
createInputBox(): vscode.InputBox {
|
createInputBox(): vscode.InputBox {
|
||||||
return extHostQuickOpen.createInputBox(extension.identifier);
|
return extHostQuickOpen.createInputBox(extension.identifier);
|
||||||
},
|
},
|
||||||
|
registerNotebookProvider: (viewType: string, provider: vscode.NotebookProvider) => {
|
||||||
|
return extHostNotebook.registerNotebookProvider(extension, viewType, provider);
|
||||||
|
},
|
||||||
|
registerNotebookOutputRenderer: (type: string, outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => {
|
||||||
|
return extHostNotebook.registerNotebookOutputRenderer(type, extension, outputFilter, renderer);
|
||||||
|
},
|
||||||
|
get activeNotebookDocument(): vscode.NotebookDocument | undefined {
|
||||||
|
return extHostNotebook.activeNotebookDocument;
|
||||||
|
},
|
||||||
get activeColorTheme(): vscode.ColorTheme {
|
get activeColorTheme(): vscode.ColorTheme {
|
||||||
checkProposedApiEnabled(extension);
|
checkProposedApiEnabled(extension);
|
||||||
return extHostTheming.activeColorTheme;
|
return extHostTheming.activeColorTheme;
|
||||||
@@ -1036,7 +1051,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||||||
WebviewContentState: extHostTypes.WebviewContentState,
|
WebviewContentState: extHostTypes.WebviewContentState,
|
||||||
UIKind: UIKind,
|
UIKind: UIKind,
|
||||||
ColorThemeKind: extHostTypes.ColorThemeKind,
|
ColorThemeKind: extHostTypes.ColorThemeKind,
|
||||||
TimelineItem: extHostTypes.TimelineItem
|
TimelineItem: extHostTypes.TimelineItem,
|
||||||
|
CellKind: extHostTypes.CellKind,
|
||||||
|
CellOutputKind: extHostTypes.CellOutputKind
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
|||||||
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
|
||||||
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
|
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
|
||||||
import { revive } from 'vs/base/common/marshalling';
|
import { revive } from 'vs/base/common/marshalling';
|
||||||
|
import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
|
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
|
||||||
import { Dto } from 'vs/base/common/types';
|
import { Dto } from 'vs/base/common/types';
|
||||||
|
|
||||||
@@ -365,6 +366,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
|
|||||||
$registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
$registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||||
$registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
$registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||||
$registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
$registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void;
|
||||||
|
$registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[], stopPattern: IRegExpDto | undefined): void;
|
||||||
$registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void;
|
$registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void;
|
||||||
$registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string): void;
|
$registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string): void;
|
||||||
$registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void;
|
$registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void;
|
||||||
@@ -580,6 +582,16 @@ export interface WebviewExtensionDescription {
|
|||||||
readonly location: UriComponents;
|
readonly location: UriComponents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NotebookExtensionDescription {
|
||||||
|
readonly id: ExtensionIdentifier;
|
||||||
|
readonly location: UriComponents;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WebviewEditorCapabilities {
|
||||||
|
Editable,
|
||||||
|
SupportsHotExit,
|
||||||
|
}
|
||||||
|
|
||||||
export interface CustomTextEditorCapabilities {
|
export interface CustomTextEditorCapabilities {
|
||||||
readonly supportsMove?: boolean;
|
readonly supportsMove?: boolean;
|
||||||
}
|
}
|
||||||
@@ -639,6 +651,49 @@ export interface ExtHostWebviewsShape {
|
|||||||
$onMoveCustomEditor(handle: WebviewPanelHandle, newResource: UriComponents, viewType: string): Promise<void>;
|
$onMoveCustomEditor(handle: WebviewPanelHandle, newResource: UriComponents, viewType: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CellKind {
|
||||||
|
Markdown = 1,
|
||||||
|
Code = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CellOutputKind {
|
||||||
|
Text = 1,
|
||||||
|
Error = 2,
|
||||||
|
Rich = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICellDto {
|
||||||
|
handle: number;
|
||||||
|
uri: UriComponents,
|
||||||
|
source: string[];
|
||||||
|
language: string;
|
||||||
|
cellKind: CellKind;
|
||||||
|
outputs: IOutput[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NotebookCellsSplice = [
|
||||||
|
number /* start */,
|
||||||
|
number /* delete count */,
|
||||||
|
ICellDto[]
|
||||||
|
];
|
||||||
|
|
||||||
|
export type NotebookCellOutputsSplice = [
|
||||||
|
number /* start */,
|
||||||
|
number /* delete count */,
|
||||||
|
IOutput[]
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface MainThreadNotebookShape extends IDisposable {
|
||||||
|
$registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise<void>;
|
||||||
|
$unregisterNotebookProvider(viewType: string): Promise<void>;
|
||||||
|
$registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise<void>;
|
||||||
|
$unregisterNotebookRenderer(handle: number): Promise<void>;
|
||||||
|
$createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise<void>;
|
||||||
|
$updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise<void>;
|
||||||
|
$spliceNotebookCells(viewType: string, resource: UriComponents, splices: NotebookCellsSplice[], renderers: number[]): Promise<void>;
|
||||||
|
$spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface MainThreadUrlsShape extends IDisposable {
|
export interface MainThreadUrlsShape extends IDisposable {
|
||||||
$registerUriHandler(handle: number, extensionId: ExtensionIdentifier): Promise<void>;
|
$registerUriHandler(handle: number, extensionId: ExtensionIdentifier): Promise<void>;
|
||||||
$unregisterUriHandler(handle: number): Promise<void>;
|
$unregisterUriHandler(handle: number): Promise<void>;
|
||||||
@@ -1237,6 +1292,7 @@ export interface ExtHostLanguageFeaturesShape {
|
|||||||
$provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.Hover | undefined>;
|
$provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.Hover | undefined>;
|
||||||
$provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined>;
|
$provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined>;
|
||||||
$provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.DocumentHighlight[] | undefined>;
|
$provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.DocumentHighlight[] | undefined>;
|
||||||
|
$provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<IRange[] | undefined>;
|
||||||
$provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise<ILocationDto[] | undefined>;
|
$provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise<ILocationDto[] | undefined>;
|
||||||
$provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise<ICodeActionListDto | undefined>;
|
$provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise<ICodeActionListDto | undefined>;
|
||||||
$releaseCodeActions(handle: number, cacheId: number): void;
|
$releaseCodeActions(handle: number, cacheId: number): void;
|
||||||
@@ -1466,6 +1522,17 @@ export interface ExtHostCommentsShape {
|
|||||||
$toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise<void>;
|
$toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExtHostNotebookShape {
|
||||||
|
$resolveNotebook(viewType: string, uri: UriComponents): Promise<number | undefined>;
|
||||||
|
$executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void>;
|
||||||
|
$createEmptyCell(viewType: string, uri: UriComponents, index: number, language: string, type: CellKind): Promise<ICellDto | undefined>;
|
||||||
|
$deleteCell(viewType: string, uri: UriComponents, index: number): Promise<boolean>;
|
||||||
|
$saveNotebook(viewType: string, uri: UriComponents): Promise<boolean>;
|
||||||
|
$updateActiveEditor(viewType: string, uri: UriComponents): Promise<void>;
|
||||||
|
$destoryNotebookDocument(viewType: string, uri: UriComponents): Promise<boolean>;
|
||||||
|
$acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ExtHostStorageShape {
|
export interface ExtHostStorageShape {
|
||||||
$acceptValue(shared: boolean, key: string, value: object | undefined): void;
|
$acceptValue(shared: boolean, key: string, value: object | undefined): void;
|
||||||
}
|
}
|
||||||
@@ -1531,6 +1598,7 @@ export const MainContext = {
|
|||||||
MainThreadTask: createMainId<MainThreadTaskShape>('MainThreadTask'),
|
MainThreadTask: createMainId<MainThreadTaskShape>('MainThreadTask'),
|
||||||
MainThreadWindow: createMainId<MainThreadWindowShape>('MainThreadWindow'),
|
MainThreadWindow: createMainId<MainThreadWindowShape>('MainThreadWindow'),
|
||||||
MainThreadLabelService: createMainId<MainThreadLabelServiceShape>('MainThreadLabelService'),
|
MainThreadLabelService: createMainId<MainThreadLabelServiceShape>('MainThreadLabelService'),
|
||||||
|
MainThreadNotebook: createMainId<MainThreadNotebookShape>('MainThreadNotebook'),
|
||||||
MainThreadTheming: createMainId<MainThreadThemingShape>('MainThreadTheming'),
|
MainThreadTheming: createMainId<MainThreadThemingShape>('MainThreadTheming'),
|
||||||
MainThreadTunnelService: createMainId<MainThreadTunnelServiceShape>('MainThreadTunnelService'),
|
MainThreadTunnelService: createMainId<MainThreadTunnelServiceShape>('MainThreadTunnelService'),
|
||||||
MainThreadTimeline: createMainId<MainThreadTimelineShape>('MainThreadTimeline')
|
MainThreadTimeline: createMainId<MainThreadTimelineShape>('MainThreadTimeline')
|
||||||
@@ -1567,7 +1635,8 @@ export const ExtHostContext = {
|
|||||||
ExtHostStorage: createMainId<ExtHostStorageShape>('ExtHostStorage'),
|
ExtHostStorage: createMainId<ExtHostStorageShape>('ExtHostStorage'),
|
||||||
ExtHostUrls: createExtId<ExtHostUrlsShape>('ExtHostUrls'),
|
ExtHostUrls: createExtId<ExtHostUrlsShape>('ExtHostUrls'),
|
||||||
ExtHostOutputService: createMainId<ExtHostOutputServiceShape>('ExtHostOutputService'),
|
ExtHostOutputService: createMainId<ExtHostOutputServiceShape>('ExtHostOutputService'),
|
||||||
ExtHostLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService'),
|
ExtHosLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService'),
|
||||||
|
ExtHostNotebook: createMainId<ExtHostNotebookShape>('ExtHostNotebook'),
|
||||||
ExtHostTheming: createMainId<ExtHostThemingShape>('ExtHostTheming'),
|
ExtHostTheming: createMainId<ExtHostThemingShape>('ExtHostTheming'),
|
||||||
ExtHostTunnelService: createMainId<ExtHostTunnelServiceShape>('ExtHostTunnelService'),
|
ExtHostTunnelService: createMainId<ExtHostTunnelServiceShape>('ExtHostTunnelService'),
|
||||||
ExtHostAuthentication: createMainId<ExtHostAuthenticationShape>('ExtHostAuthentication'),
|
ExtHostAuthentication: createMainId<ExtHostAuthenticationShape>('ExtHostAuthentication'),
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ export class ExtHostDocumentData extends MirrorTextModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExtHostDocumentLine implements vscode.TextLine {
|
export class ExtHostDocumentLine implements vscode.TextLine {
|
||||||
|
|
||||||
private readonly _line: number;
|
private readonly _line: number;
|
||||||
private readonly _text: string;
|
private readonly _text: string;
|
||||||
|
|||||||
@@ -318,6 +318,26 @@ class DocumentHighlightAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class OnTypeRenameAdapter {
|
||||||
|
constructor(
|
||||||
|
private readonly _documents: ExtHostDocuments,
|
||||||
|
private readonly _provider: vscode.OnTypeRenameProvider
|
||||||
|
) { }
|
||||||
|
|
||||||
|
provideOnTypeRenameRanges(resource: URI, position: IPosition, token: CancellationToken): Promise<IRange[] | undefined> {
|
||||||
|
|
||||||
|
const doc = this._documents.getDocument(resource);
|
||||||
|
const pos = typeConvert.Position.to(position);
|
||||||
|
|
||||||
|
return asPromise(() => this._provider.provideOnTypeRenameRanges(doc, pos, token)).then(value => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return coalesce(value.map(typeConvert.Range.from));
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ReferenceAdapter {
|
class ReferenceAdapter {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -1350,7 +1370,8 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov
|
|||||||
| RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter
|
| RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter
|
||||||
| SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter
|
| SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter
|
||||||
| TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter
|
| TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter
|
||||||
| SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter;
|
| SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter
|
||||||
|
| OnTypeRenameAdapter;
|
||||||
|
|
||||||
class AdapterData {
|
class AdapterData {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -1594,6 +1615,19 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
|||||||
return this._withAdapter(handle, DocumentHighlightAdapter, adapter => adapter.provideDocumentHighlights(URI.revive(resource), position, token), undefined);
|
return this._withAdapter(handle, DocumentHighlightAdapter, adapter => adapter.provideDocumentHighlights(URI.revive(resource), position, token), undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- on type rename
|
||||||
|
|
||||||
|
registerOnTypeRenameProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.OnTypeRenameProvider, stopPattern?: RegExp): vscode.Disposable {
|
||||||
|
const handle = this._addNewAdapter(new OnTypeRenameAdapter(this._documents, provider), extension);
|
||||||
|
const serializedStopPattern = stopPattern ? ExtHostLanguageFeatures._serializeRegExp(stopPattern) : undefined;
|
||||||
|
this._proxy.$registerOnTypeRenameProvider(handle, this._transformDocumentSelector(selector), serializedStopPattern);
|
||||||
|
return this._createDisposable(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
$provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<IRange[] | undefined> {
|
||||||
|
return this._withAdapter(handle, OnTypeRenameAdapter, adapter => adapter.provideOnTypeRenameRanges(URI.revive(resource), position, token), undefined);
|
||||||
|
}
|
||||||
|
|
||||||
// --- references
|
// --- references
|
||||||
|
|
||||||
registerReferenceProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable {
|
registerReferenceProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable {
|
||||||
|
|||||||
@@ -0,0 +1,621 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { ExtHostNotebookShape, IMainContext, MainThreadNotebookShape, MainContext, ICellDto, NotebookCellsSplice, NotebookCellOutputsSplice, CellKind, CellOutputKind } from 'vs/workbench/api/common/extHost.protocol';
|
||||||
|
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||||
|
import { Disposable as VSCodeDisposable } from './extHostTypes';
|
||||||
|
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||||
|
import { DisposableStore, Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { readonly } from 'vs/base/common/errors';
|
||||||
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
|
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
|
||||||
|
import { INotebookDisplayOrder, ITransformedDisplayOutputDto, IOrderedMimeType, IStreamOutput, IErrorOutput, mimeTypeSupportedByCore, IOutput, sortMimeTypes, diff, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
|
import { ISplice } from 'vs/base/common/sequence';
|
||||||
|
|
||||||
|
export class ExtHostCell implements vscode.NotebookCell {
|
||||||
|
|
||||||
|
public source: string[];
|
||||||
|
private _outputs: any[];
|
||||||
|
private _onDidChangeOutputs = new Emitter<ISplice<vscode.CellOutput>[]>();
|
||||||
|
onDidChangeOutputs: Event<ISplice<vscode.CellOutput>[]> = this._onDidChangeOutputs.event;
|
||||||
|
private _textDocument: vscode.TextDocument | undefined;
|
||||||
|
private _initalVersion: number = -1;
|
||||||
|
private _outputMapping = new Set<vscode.CellOutput>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly handle: number,
|
||||||
|
readonly uri: URI,
|
||||||
|
private _content: string,
|
||||||
|
public cellKind: CellKind,
|
||||||
|
public language: string,
|
||||||
|
outputs: any[]
|
||||||
|
) {
|
||||||
|
this.source = this._content.split(/\r|\n|\r\n/g);
|
||||||
|
this._outputs = outputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
get outputs() {
|
||||||
|
return this._outputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
set outputs(newOutputs: vscode.CellOutput[]) {
|
||||||
|
let diffs = diff<vscode.CellOutput>(this._outputs || [], newOutputs || [], (a) => {
|
||||||
|
return this._outputMapping.has(a);
|
||||||
|
});
|
||||||
|
|
||||||
|
diffs.forEach(diff => {
|
||||||
|
for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
|
||||||
|
this._outputMapping.delete(this._outputs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
diff.toInsert.forEach(output => {
|
||||||
|
this._outputMapping.add(output);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this._outputs = newOutputs;
|
||||||
|
this._onDidChangeOutputs.fire(diffs);
|
||||||
|
}
|
||||||
|
|
||||||
|
getContent(): string {
|
||||||
|
if (this._textDocument && this._initalVersion !== this._textDocument?.version) {
|
||||||
|
return this._textDocument.getText();
|
||||||
|
} else {
|
||||||
|
return this.source.join('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attachTextDocument(document: vscode.TextDocument) {
|
||||||
|
this._textDocument = document;
|
||||||
|
this._initalVersion = this._textDocument.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
detachTextDocument(document: vscode.TextDocument) {
|
||||||
|
if (this._textDocument && this._textDocument.version !== this._initalVersion) {
|
||||||
|
this.source = this._textDocument.getText().split(/\r|\n|\r\n/g);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._textDocument = undefined;
|
||||||
|
this._initalVersion = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExtHostNotebookDocument extends Disposable implements vscode.NotebookDocument {
|
||||||
|
private static _handlePool: number = 0;
|
||||||
|
readonly handle = ExtHostNotebookDocument._handlePool++;
|
||||||
|
|
||||||
|
private _cells: ExtHostCell[] = [];
|
||||||
|
|
||||||
|
private _cellDisposableMapping = new Map<number, DisposableStore>();
|
||||||
|
|
||||||
|
get cells() {
|
||||||
|
return this._cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
set cells(newCells: ExtHostCell[]) {
|
||||||
|
let diffs = diff<ExtHostCell>(this._cells, newCells, (a) => {
|
||||||
|
return this._cellDisposableMapping.has(a.handle);
|
||||||
|
});
|
||||||
|
|
||||||
|
diffs.forEach(diff => {
|
||||||
|
for (let i = diff.start; i < diff.start + diff.deleteCount; i++) {
|
||||||
|
this._cellDisposableMapping.get(this._cells[i].handle)?.clear();
|
||||||
|
this._cellDisposableMapping.delete(this._cells[i].handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
diff.toInsert.forEach(cell => {
|
||||||
|
this._cellDisposableMapping.set(cell.handle, new DisposableStore());
|
||||||
|
this._cellDisposableMapping.get(cell.handle)?.add(cell.onDidChangeOutputs((outputDiffs) => {
|
||||||
|
this.eventuallyUpdateCellOutputs(cell, outputDiffs);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this._cells = newCells;
|
||||||
|
this.eventuallyUpdateCells(diffs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _languages: string[] = [];
|
||||||
|
|
||||||
|
get languages() {
|
||||||
|
return this._languages = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
set languages(newLanguages: string[]) {
|
||||||
|
this._languages = newLanguages;
|
||||||
|
this._proxy.$updateNotebookLanguages(this.viewType, this.uri, this._languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _displayOrder: string[] = [];
|
||||||
|
|
||||||
|
get displayOrder() {
|
||||||
|
return this._displayOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
set displayOrder(newOrder: string[]) {
|
||||||
|
this._displayOrder = newOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _proxy: MainThreadNotebookShape,
|
||||||
|
public viewType: string,
|
||||||
|
public uri: URI,
|
||||||
|
public renderingHandler: ExtHostNotebookOutputRenderingHandler
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
super.dispose();
|
||||||
|
this._cellDisposableMapping.forEach(cell => cell.dispose());
|
||||||
|
}
|
||||||
|
|
||||||
|
get fileName() { return this.uri.fsPath; }
|
||||||
|
|
||||||
|
get isDirty() { return false; }
|
||||||
|
|
||||||
|
eventuallyUpdateCells(diffs: ISplice<ExtHostCell>[]) {
|
||||||
|
let renderers = new Set<number>();
|
||||||
|
let diffDtos: NotebookCellsSplice[] = [];
|
||||||
|
|
||||||
|
diffDtos = diffs.map(diff => {
|
||||||
|
let inserts = diff.toInsert;
|
||||||
|
|
||||||
|
let cellDtos = inserts.map(cell => {
|
||||||
|
let outputs: IOutput[] = [];
|
||||||
|
if (cell.outputs.length) {
|
||||||
|
outputs = cell.outputs.map(output => {
|
||||||
|
if (output.outputKind === CellOutputKind.Rich) {
|
||||||
|
const ret = this.transformMimeTypes(cell, output);
|
||||||
|
|
||||||
|
if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) {
|
||||||
|
renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
return output as IStreamOutput | IErrorOutput;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
uri: cell.uri,
|
||||||
|
handle: cell.handle,
|
||||||
|
source: cell.source,
|
||||||
|
language: cell.language,
|
||||||
|
cellKind: cell.cellKind,
|
||||||
|
outputs: outputs,
|
||||||
|
isDirty: false
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return [diff.start, diff.deleteCount, cellDtos];
|
||||||
|
});
|
||||||
|
|
||||||
|
this._proxy.$spliceNotebookCells(
|
||||||
|
this.viewType,
|
||||||
|
this.uri,
|
||||||
|
diffDtos,
|
||||||
|
Array.from(renderers)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice<vscode.CellOutput>[]) {
|
||||||
|
let renderers = new Set<number>();
|
||||||
|
let outputDtos: NotebookCellOutputsSplice[] = diffs.map(diff => {
|
||||||
|
let outputs = diff.toInsert;
|
||||||
|
|
||||||
|
let transformedOutputs = outputs.map(output => {
|
||||||
|
if (output.outputKind === CellOutputKind.Rich) {
|
||||||
|
const ret = this.transformMimeTypes(cell, output);
|
||||||
|
|
||||||
|
if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) {
|
||||||
|
renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
return output as IStreamOutput | IErrorOutput;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return [diff.start, diff.deleteCount, transformedOutputs];
|
||||||
|
});
|
||||||
|
|
||||||
|
this._proxy.$spliceNotebookCellOutputs(this.viewType, this.uri, cell.handle, outputDtos, Array.from(renderers));
|
||||||
|
}
|
||||||
|
|
||||||
|
insertCell(index: number, cell: ExtHostCell) {
|
||||||
|
this.cells.splice(index, 0, cell);
|
||||||
|
|
||||||
|
if (!this._cellDisposableMapping.has(cell.handle)) {
|
||||||
|
this._cellDisposableMapping.set(cell.handle, new DisposableStore());
|
||||||
|
}
|
||||||
|
|
||||||
|
let store = this._cellDisposableMapping.get(cell.handle)!;
|
||||||
|
|
||||||
|
store.add(cell.onDidChangeOutputs((diffs) => {
|
||||||
|
this.eventuallyUpdateCellOutputs(cell, diffs);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCell(index: number): boolean {
|
||||||
|
if (index >= this.cells.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cell = this.cells[index];
|
||||||
|
this._cellDisposableMapping.get(cell.handle)?.dispose();
|
||||||
|
this._cellDisposableMapping.delete(cell.handle);
|
||||||
|
|
||||||
|
this.cells.splice(index, 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
transformMimeTypes(cell: ExtHostCell, output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto {
|
||||||
|
let mimeTypes = Object.keys(output.data);
|
||||||
|
|
||||||
|
// TODO@rebornix, the document display order might be assigned a bit later. We need to postpone sending the outputs to the core side.
|
||||||
|
let coreDisplayOrder = this.renderingHandler.outputDisplayOrder;
|
||||||
|
const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], this._displayOrder, coreDisplayOrder?.defaultOrder || []);
|
||||||
|
|
||||||
|
let orderMimeTypes: IOrderedMimeType[] = [];
|
||||||
|
|
||||||
|
sorted.forEach(mimeType => {
|
||||||
|
let handlers = this.renderingHandler.findBestMatchedRenderer(mimeType);
|
||||||
|
|
||||||
|
if (handlers.length) {
|
||||||
|
let renderedOutput = handlers[0].render(this, cell, output, mimeType);
|
||||||
|
|
||||||
|
orderMimeTypes.push({
|
||||||
|
mimeType: mimeType,
|
||||||
|
isResolved: true,
|
||||||
|
rendererId: handlers[0].handle,
|
||||||
|
output: renderedOutput
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 1; i < handlers.length; i++) {
|
||||||
|
orderMimeTypes.push({
|
||||||
|
mimeType: mimeType,
|
||||||
|
isResolved: false,
|
||||||
|
rendererId: handlers[i].handle
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mimeTypeSupportedByCore(mimeType)) {
|
||||||
|
orderMimeTypes.push({
|
||||||
|
mimeType: mimeType,
|
||||||
|
isResolved: false,
|
||||||
|
rendererId: -1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
orderMimeTypes.push({
|
||||||
|
mimeType: mimeType,
|
||||||
|
isResolved: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
outputKind: output.outputKind,
|
||||||
|
data: output.data,
|
||||||
|
orderedMimeTypes: orderMimeTypes,
|
||||||
|
pickedMimeTypeIndex: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getCell(cellHandle: number) {
|
||||||
|
return this.cells.find(cell => cell.handle === cellHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
attachCellTextDocument(textDocument: vscode.TextDocument) {
|
||||||
|
let cell = this.cells.find(cell => cell.uri.toString() === textDocument.uri.toString());
|
||||||
|
if (cell) {
|
||||||
|
cell.attachTextDocument(textDocument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
detachCellTextDocument(textDocument: vscode.TextDocument) {
|
||||||
|
let cell = this.cells.find(cell => cell.uri.toString() === textDocument.uri.toString());
|
||||||
|
if (cell) {
|
||||||
|
cell.detachTextDocument(textDocument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExtHostNotebookEditor extends Disposable implements vscode.NotebookEditor {
|
||||||
|
private _viewColumn: vscode.ViewColumn | undefined;
|
||||||
|
private static _cellhandlePool: number = 0;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
viewType: string,
|
||||||
|
readonly id: string,
|
||||||
|
public uri: URI,
|
||||||
|
public document: ExtHostNotebookDocument,
|
||||||
|
private _documentsAndEditors: ExtHostDocumentsAndEditors
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this._register(this._documentsAndEditors.onDidAddDocuments(documents => {
|
||||||
|
for (const { document: textDocument } of documents) {
|
||||||
|
let data = CellUri.parse(textDocument.uri);
|
||||||
|
if (data) {
|
||||||
|
if (this.document.uri.toString() === data.notebook.toString()) {
|
||||||
|
document.attachCellTextDocument(textDocument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(this._documentsAndEditors.onDidRemoveDocuments(documents => {
|
||||||
|
for (const { document: textDocument } of documents) {
|
||||||
|
let data = CellUri.parse(textDocument.uri);
|
||||||
|
if (data) {
|
||||||
|
if (this.document.uri.toString() === data.notebook.toString()) {
|
||||||
|
document.detachCellTextDocument(textDocument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
createCell(content: string, language: string, type: CellKind, outputs: vscode.CellOutput[]): vscode.NotebookCell {
|
||||||
|
const handle = ExtHostNotebookEditor._cellhandlePool++;
|
||||||
|
const uri = CellUri.generate(this.document.uri, handle);
|
||||||
|
const cell = new ExtHostCell(handle, uri, content, type, language, outputs);
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
get viewColumn(): vscode.ViewColumn | undefined {
|
||||||
|
return this._viewColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
set viewColumn(value) {
|
||||||
|
throw readonly('viewColumn');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExtHostNotebookOutputRenderer {
|
||||||
|
private static _handlePool: number = 0;
|
||||||
|
readonly handle = ExtHostNotebookOutputRenderer._handlePool++;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public type: string,
|
||||||
|
public filter: vscode.NotebookOutputSelector,
|
||||||
|
public renderer: vscode.NotebookOutputRenderer
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(mimeType: string): boolean {
|
||||||
|
if (this.filter.subTypes) {
|
||||||
|
if (this.filter.subTypes.indexOf(mimeType) >= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(document: ExtHostNotebookDocument, cell: ExtHostCell, output: vscode.CellOutput, mimeType: string): string {
|
||||||
|
let html = this.renderer.render(document, cell, output, mimeType);
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtHostNotebookOutputRenderingHandler {
|
||||||
|
outputDisplayOrder: INotebookDisplayOrder | undefined;
|
||||||
|
findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostNotebookOutputRenderingHandler {
|
||||||
|
private static _handlePool: number = 0;
|
||||||
|
|
||||||
|
private readonly _proxy: MainThreadNotebookShape;
|
||||||
|
private readonly _notebookProviders = new Map<string, { readonly provider: vscode.NotebookProvider, readonly extension: IExtensionDescription }>();
|
||||||
|
private readonly _documents = new Map<string, ExtHostNotebookDocument>();
|
||||||
|
private readonly _editors = new Map<string, ExtHostNotebookEditor>();
|
||||||
|
private readonly _notebookOutputRenderers = new Map<number, ExtHostNotebookOutputRenderer>();
|
||||||
|
private _outputDisplayOrder: INotebookDisplayOrder | undefined;
|
||||||
|
|
||||||
|
get outputDisplayOrder(): INotebookDisplayOrder | undefined {
|
||||||
|
return this._outputDisplayOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _activeNotebookDocument: ExtHostNotebookDocument | undefined;
|
||||||
|
|
||||||
|
get activeNotebookDocument() {
|
||||||
|
return this._activeNotebookDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(mainContext: IMainContext, private _documentsAndEditors: ExtHostDocumentsAndEditors) {
|
||||||
|
this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerNotebookOutputRenderer(
|
||||||
|
type: string,
|
||||||
|
extension: IExtensionDescription,
|
||||||
|
filter: vscode.NotebookOutputSelector,
|
||||||
|
renderer: vscode.NotebookOutputRenderer
|
||||||
|
): vscode.Disposable {
|
||||||
|
let extHostRenderer = new ExtHostNotebookOutputRenderer(type, filter, renderer);
|
||||||
|
this._notebookOutputRenderers.set(extHostRenderer.handle, extHostRenderer);
|
||||||
|
this._proxy.$registerNotebookRenderer({ id: extension.identifier, location: extension.extensionLocation }, type, filter, extHostRenderer.handle, renderer.preloads || []);
|
||||||
|
return new VSCodeDisposable(() => {
|
||||||
|
this._notebookOutputRenderers.delete(extHostRenderer.handle);
|
||||||
|
this._proxy.$unregisterNotebookRenderer(extHostRenderer.handle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[] {
|
||||||
|
let matches: ExtHostNotebookOutputRenderer[] = [];
|
||||||
|
for (let renderer of this._notebookOutputRenderers) {
|
||||||
|
if (renderer[1].matches(mimeType)) {
|
||||||
|
matches.push(renderer[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerNotebookProvider(
|
||||||
|
extension: IExtensionDescription,
|
||||||
|
viewType: string,
|
||||||
|
provider: vscode.NotebookProvider,
|
||||||
|
): vscode.Disposable {
|
||||||
|
|
||||||
|
if (this._notebookProviders.has(viewType)) {
|
||||||
|
throw new Error(`Notebook provider for '${viewType}' already registered`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._notebookProviders.set(viewType, { extension, provider });
|
||||||
|
this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType);
|
||||||
|
return new VSCodeDisposable(() => {
|
||||||
|
this._notebookProviders.delete(viewType);
|
||||||
|
this._proxy.$unregisterNotebookProvider(viewType);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async $resolveNotebook(viewType: string, uri: UriComponents): Promise<number | undefined> {
|
||||||
|
let provider = this._notebookProviders.get(viewType);
|
||||||
|
|
||||||
|
if (provider) {
|
||||||
|
if (!this._documents.has(URI.revive(uri).toString())) {
|
||||||
|
let document = new ExtHostNotebookDocument(this._proxy, viewType, URI.revive(uri), this);
|
||||||
|
await this._proxy.$createNotebookDocument(
|
||||||
|
document.handle,
|
||||||
|
viewType,
|
||||||
|
uri
|
||||||
|
);
|
||||||
|
|
||||||
|
this._documents.set(URI.revive(uri).toString(), document);
|
||||||
|
}
|
||||||
|
|
||||||
|
let editor = new ExtHostNotebookEditor(
|
||||||
|
viewType,
|
||||||
|
`${ExtHostNotebookController._handlePool++}`,
|
||||||
|
URI.revive(uri),
|
||||||
|
this._documents.get(URI.revive(uri).toString())!,
|
||||||
|
this._documentsAndEditors
|
||||||
|
);
|
||||||
|
|
||||||
|
this._editors.set(URI.revive(uri).toString(), editor);
|
||||||
|
await provider.provider.resolveNotebook(editor);
|
||||||
|
// await editor.document.$updateCells();
|
||||||
|
return editor.document.handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
async $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise<void> {
|
||||||
|
let provider = this._notebookProviders.get(viewType);
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let document = this._documents.get(URI.revive(uri).toString());
|
||||||
|
|
||||||
|
if (!document) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
|
||||||
|
return provider.provider.executeCell(document!, cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
async $createEmptyCell(viewType: string, uri: URI, index: number, language: string, type: CellKind): Promise<ICellDto | undefined> {
|
||||||
|
let provider = this._notebookProviders.get(viewType);
|
||||||
|
|
||||||
|
if (provider) {
|
||||||
|
let editor = this._editors.get(URI.revive(uri).toString());
|
||||||
|
let document = this._documents.get(URI.revive(uri).toString());
|
||||||
|
|
||||||
|
let rawCell = editor?.createCell('', language, type, []) as ExtHostCell;
|
||||||
|
document?.insertCell(index, rawCell!);
|
||||||
|
|
||||||
|
let allDocuments = this._documentsAndEditors.allDocuments();
|
||||||
|
for (let { document: textDocument } of allDocuments) {
|
||||||
|
let data = CellUri.parse(textDocument.uri);
|
||||||
|
if (data) {
|
||||||
|
if (uri.toString() === data.notebook.toString() && textDocument.uri.toString() === rawCell.uri.toString()) {
|
||||||
|
rawCell.attachTextDocument(textDocument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
uri: rawCell.uri,
|
||||||
|
handle: rawCell.handle,
|
||||||
|
source: rawCell.source,
|
||||||
|
language: rawCell.language,
|
||||||
|
cellKind: rawCell.cellKind,
|
||||||
|
outputs: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||||
|
}
|
||||||
|
|
||||||
|
async $deleteCell(viewType: string, uri: UriComponents, index: number): Promise<boolean> {
|
||||||
|
let provider = this._notebookProviders.get(viewType);
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let document = this._documents.get(URI.revive(uri).toString());
|
||||||
|
|
||||||
|
if (document) {
|
||||||
|
return document.deleteCell(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async $saveNotebook(viewType: string, uri: UriComponents): Promise<boolean> {
|
||||||
|
let provider = this._notebookProviders.get(viewType);
|
||||||
|
let document = this._documents.get(URI.revive(uri).toString());
|
||||||
|
|
||||||
|
if (provider && document) {
|
||||||
|
return await provider.provider.save(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async $updateActiveEditor(viewType: string, uri: UriComponents): Promise<void> {
|
||||||
|
this._activeNotebookDocument = this._documents.get(URI.revive(uri).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
async $destoryNotebookDocument(viewType: string, uri: UriComponents): Promise<boolean> {
|
||||||
|
let provider = this._notebookProviders.get(viewType);
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let document = this._documents.get(URI.revive(uri).toString());
|
||||||
|
|
||||||
|
if (document) {
|
||||||
|
document.dispose();
|
||||||
|
this._documents.delete(URI.revive(uri).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
let editor = this._editors.get(URI.revive(uri).toString());
|
||||||
|
|
||||||
|
if (editor) {
|
||||||
|
editor.dispose();
|
||||||
|
this._editors.delete(URI.revive(uri).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void {
|
||||||
|
this._outputDisplayOrder = displayOrder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2559,6 +2559,21 @@ export enum ColorThemeKind {
|
|||||||
|
|
||||||
//#endregion Theming
|
//#endregion Theming
|
||||||
|
|
||||||
|
//#region Notebook
|
||||||
|
|
||||||
|
export enum CellKind {
|
||||||
|
Markdown = 1,
|
||||||
|
Code = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CellOutputKind {
|
||||||
|
Text = 1,
|
||||||
|
Error = 2,
|
||||||
|
Rich = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
//#region Timeline
|
//#region Timeline
|
||||||
|
|
||||||
@es5ClassCompat
|
@es5ClassCompat
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri';
|
|||||||
import { ITextFileService, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles';
|
import { ITextFileService, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles';
|
||||||
import { Schemas } from 'vs/base/common/network';
|
import { Schemas } from 'vs/base/common/network';
|
||||||
import { IEditorViewState } from 'vs/editor/common/editorCommon';
|
import { IEditorViewState } from 'vs/editor/common/editorCommon';
|
||||||
import { DataTransfers } from 'vs/base/browser/dnd';
|
import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd';
|
||||||
import { DragMouseEvent } from 'vs/base/browser/mouseEvent';
|
import { DragMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||||
import { normalizeDriveLetter } from 'vs/base/common/labels';
|
import { normalizeDriveLetter } from 'vs/base/common/labels';
|
||||||
import { MIME_BINARY } from 'vs/base/common/mime';
|
import { MIME_BINARY } from 'vs/base/common/mime';
|
||||||
@@ -21,7 +21,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
|
|||||||
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||||
import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor';
|
import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor';
|
||||||
import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService';
|
import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService';
|
||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
import { addDisposableListener, EventType, asDomUri } from 'vs/base/browser/dom';
|
import { addDisposableListener, EventType, asDomUri } from 'vs/base/browser/dom';
|
||||||
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
|
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||||
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
|
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
|
||||||
@@ -29,6 +29,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
|
|||||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||||
import { isStandalone } from 'vs/base/browser/browser';
|
import { isStandalone } from 'vs/base/browser/browser';
|
||||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||||
|
import { Emitter } from 'vs/base/common/event';
|
||||||
|
|
||||||
export interface IDraggedResource {
|
export interface IDraggedResource {
|
||||||
resource: URI;
|
resource: URI;
|
||||||
@@ -507,3 +508,219 @@ export function containsDragType(event: DragEvent, ...dragTypesToFind: string[])
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ICompositeDragAndDrop {
|
||||||
|
drop(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent, before?: boolean): void;
|
||||||
|
onDragOver(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean;
|
||||||
|
onDragEnter(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICompositeDragAndDropObserverCallbacks {
|
||||||
|
onDragEnter?: (e: IDraggedCompositeData) => void;
|
||||||
|
onDragLeave?: (e: IDraggedCompositeData) => void;
|
||||||
|
onDrop?: (e: IDraggedCompositeData) => void;
|
||||||
|
onDragOver?: (e: IDraggedCompositeData) => void;
|
||||||
|
onDragStart?: (e: IDraggedCompositeData) => void;
|
||||||
|
onDragEnd?: (e: IDraggedCompositeData) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CompositeDragAndDropData implements IDragAndDropData {
|
||||||
|
constructor(private type: 'view' | 'composite', private id: string) { }
|
||||||
|
update(dataTransfer: DataTransfer): void {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
getData(): {
|
||||||
|
type: 'view' | 'composite';
|
||||||
|
id: string;
|
||||||
|
} {
|
||||||
|
return { type: this.type, id: this.id };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDraggedCompositeData {
|
||||||
|
eventData: DragEvent;
|
||||||
|
dragAndDropData: CompositeDragAndDropData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DraggedCompositeIdentifier {
|
||||||
|
constructor(private _compositeId: string) { }
|
||||||
|
|
||||||
|
get id(): string {
|
||||||
|
return this._compositeId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DraggedViewIdentifier {
|
||||||
|
constructor(private _viewId: string) { }
|
||||||
|
|
||||||
|
get id(): string {
|
||||||
|
return this._viewId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ViewType = 'composite' | 'view';
|
||||||
|
|
||||||
|
export class CompositeDragAndDropObserver extends Disposable {
|
||||||
|
private transferData: LocalSelectionTransfer<DraggedCompositeIdentifier | DraggedViewIdentifier>;
|
||||||
|
private _onDragStart = this._register(new Emitter<IDraggedCompositeData>());
|
||||||
|
private _onDragEnd = this._register(new Emitter<IDraggedCompositeData>());
|
||||||
|
private static _instance: CompositeDragAndDropObserver | undefined;
|
||||||
|
static get INSTANCE(): CompositeDragAndDropObserver {
|
||||||
|
if (!CompositeDragAndDropObserver._instance) {
|
||||||
|
CompositeDragAndDropObserver._instance = new CompositeDragAndDropObserver();
|
||||||
|
}
|
||||||
|
return CompositeDragAndDropObserver._instance;
|
||||||
|
}
|
||||||
|
private constructor() {
|
||||||
|
super();
|
||||||
|
this.transferData = LocalSelectionTransfer.getInstance<DraggedCompositeIdentifier | DraggedViewIdentifier>();
|
||||||
|
}
|
||||||
|
private readDragData(type: ViewType): CompositeDragAndDropData | undefined {
|
||||||
|
if (this.transferData.hasData(type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype)) {
|
||||||
|
const data = this.transferData.getData(type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype);
|
||||||
|
if (data && data[0]) {
|
||||||
|
return new CompositeDragAndDropData(type, data[0].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
private writeDragData(id: string, type: ViewType): void {
|
||||||
|
this.transferData.setData([type === 'view' ? new DraggedViewIdentifier(id) : new DraggedCompositeIdentifier(id)], type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype);
|
||||||
|
}
|
||||||
|
registerTarget(element: HTMLElement, callbacks: ICompositeDragAndDropObserverCallbacks): IDisposable {
|
||||||
|
const disposableStore = new DisposableStore();
|
||||||
|
disposableStore.add(new DragAndDropObserver(element, {
|
||||||
|
onDragEnd: e => {
|
||||||
|
// no-op
|
||||||
|
},
|
||||||
|
onDragEnter: e => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (callbacks.onDragEnter) {
|
||||||
|
const data = this.readDragData('composite') || this.readDragData('view');
|
||||||
|
if (data) {
|
||||||
|
callbacks.onDragEnter({ eventData: e, dragAndDropData: data! });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragLeave: e => {
|
||||||
|
const data = this.readDragData('composite') || this.readDragData('view');
|
||||||
|
if (callbacks.onDragLeave && data) {
|
||||||
|
callbacks.onDragLeave({ eventData: e, dragAndDropData: data! });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDrop: e => {
|
||||||
|
if (callbacks.onDrop) {
|
||||||
|
const data = this.readDragData('composite') || this.readDragData('view');
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks.onDrop({ eventData: e, dragAndDropData: data! });
|
||||||
|
|
||||||
|
// Fire drag event in case drop handler destroys the dragged element
|
||||||
|
this._onDragEnd.fire({ eventData: e, dragAndDropData: data! });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragOver: e => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (callbacks.onDragOver) {
|
||||||
|
const data = this.readDragData('composite') || this.readDragData('view');
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks.onDragOver({ eventData: e, dragAndDropData: data! });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
if (callbacks.onDragStart) {
|
||||||
|
this._onDragStart.event(e => {
|
||||||
|
callbacks.onDragStart!(e);
|
||||||
|
}, this, disposableStore);
|
||||||
|
}
|
||||||
|
if (callbacks.onDragEnd) {
|
||||||
|
this._onDragEnd.event(e => {
|
||||||
|
callbacks.onDragEnd!(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this._register(disposableStore);
|
||||||
|
}
|
||||||
|
registerDraggable(element: HTMLElement, type: ViewType, id: string, callbacks: ICompositeDragAndDropObserverCallbacks): IDisposable {
|
||||||
|
element.draggable = true;
|
||||||
|
const disposableStore = new DisposableStore();
|
||||||
|
disposableStore.add(addDisposableListener(element, EventType.DRAG_START, e => {
|
||||||
|
this.writeDragData(id, type);
|
||||||
|
this._onDragStart.fire({ eventData: e, dragAndDropData: this.readDragData(type)! });
|
||||||
|
}));
|
||||||
|
disposableStore.add(new DragAndDropObserver(element, {
|
||||||
|
onDragEnd: e => {
|
||||||
|
const data = this.readDragData(type);
|
||||||
|
if (data && data.getData().id === id) {
|
||||||
|
this.transferData.clearData(type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._onDragEnd.fire({ eventData: e, dragAndDropData: data! });
|
||||||
|
},
|
||||||
|
onDragEnter: e => {
|
||||||
|
if (callbacks.onDragEnter) {
|
||||||
|
const data = this.readDragData('composite') || this.readDragData('view');
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
callbacks.onDragEnter({ eventData: e, dragAndDropData: data! });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragLeave: e => {
|
||||||
|
const data = this.readDragData('composite') || this.readDragData('view');
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callbacks.onDragLeave) {
|
||||||
|
callbacks.onDragLeave({ eventData: e, dragAndDropData: data! });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDrop: e => {
|
||||||
|
if (callbacks.onDrop) {
|
||||||
|
const data = this.readDragData('composite') || this.readDragData('view');
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callbacks.onDrop({ eventData: e, dragAndDropData: data! });
|
||||||
|
|
||||||
|
// Fire drag event in case drop handler destroys the dragged element
|
||||||
|
this._onDragEnd.fire({ eventData: e, dragAndDropData: data! });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragOver: e => {
|
||||||
|
if (callbacks.onDragOver) {
|
||||||
|
const data = this.readDragData('composite') || this.readDragData('view');
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks.onDragOver({ eventData: e, dragAndDropData: data! });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
if (callbacks.onDragStart) {
|
||||||
|
this._onDragStart.event(e => {
|
||||||
|
callbacks.onDragStart!(e);
|
||||||
|
}, this, disposableStore);
|
||||||
|
}
|
||||||
|
if (callbacks.onDragEnd) {
|
||||||
|
this._onDragEnd.event(e => {
|
||||||
|
callbacks.onDragEnd!(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this._register(disposableStore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,19 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part > .drop-block-overlay {
|
||||||
|
visibility: hidden; /* use visibility to ensure transitions */
|
||||||
|
transition-property: opacity;
|
||||||
|
transition-timing-function: linear;
|
||||||
|
transition-duration: 250ms;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.monaco-workbench .part > .title {
|
.monaco-workbench .part > .title {
|
||||||
display: none; /* Parts have to opt in to show title area */
|
display: none; /* Parts have to opt in to show title area */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,8 +130,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
|||||||
hidePart: () => this.layoutService.setSideBarHidden(true),
|
hidePart: () => this.layoutService.setSideBarHidden(true),
|
||||||
dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Sidebar,
|
dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Sidebar,
|
||||||
(id: string, focus?: boolean) => this.viewletService.openViewlet(id, focus),
|
(id: string, focus?: boolean) => this.viewletService.openViewlet(id, focus),
|
||||||
(from: string, to: string) => this.compositeBar.move(from, to),
|
(from: string, to: string, before?: boolean) => this.compositeBar.move(from, to, before)
|
||||||
() => this.getPinnedViewletIds()
|
|
||||||
),
|
),
|
||||||
compositeSize: 50,
|
compositeSize: 50,
|
||||||
colors: (theme: IColorTheme) => this.getActivitybarItemColors(theme),
|
colors: (theme: IColorTheme) => this.getActivitybarItemColors(theme),
|
||||||
@@ -337,6 +336,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
|||||||
container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : '';
|
container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : '';
|
||||||
container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : '';
|
container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : '';
|
||||||
container.style.borderLeftColor = !isPositionLeft ? borderColor : '';
|
container.style.borderLeftColor = !isPositionLeft ? borderColor : '';
|
||||||
|
// container.style.outlineColor = this.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
private getActivitybarItemColors(theme: IColorTheme): ICompositeBarColors {
|
private getActivitybarItemColors(theme: IColorTheme): ICompositeBarColors {
|
||||||
|
|||||||
@@ -9,6 +9,42 @@
|
|||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item::before,
|
||||||
|
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item::after {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
width: 48px;
|
||||||
|
height: 2px;
|
||||||
|
display: block;
|
||||||
|
background-color: var(--insert-border-color);
|
||||||
|
opacity: 0;
|
||||||
|
transition-property: opacity;
|
||||||
|
transition-duration: 0ms;
|
||||||
|
transition-delay: 100ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item::before {
|
||||||
|
margin-top: -3px;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item::after {
|
||||||
|
margin-top: 1px;
|
||||||
|
margin-bottom: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.top::before,
|
||||||
|
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.top::after,
|
||||||
|
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.bottom::before,
|
||||||
|
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.bottom::after {
|
||||||
|
transition-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.top::before,
|
||||||
|
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.bottom::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label {
|
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|||||||
@@ -7,6 +7,12 @@
|
|||||||
width: 48px;
|
width: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part > .drop-block-overlay.visible {
|
||||||
|
visibility: visible;
|
||||||
|
backdrop-filter: brightness(97%) blur(2px);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.monaco-workbench .activitybar > .content {
|
.monaco-workbench .activitybar > .content {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -11,21 +11,19 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
|||||||
import { IBadge } from 'vs/workbench/services/activity/common/activity';
|
import { IBadge } from 'vs/workbench/services/activity/common/activity';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { ActionBar, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
import { ActionBar, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||||
import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors, DraggedCompositeIdentifier } from 'vs/workbench/browser/parts/compositeBarActions';
|
import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions';
|
||||||
import { Dimension, $, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom';
|
import { Dimension, $, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom';
|
||||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
import { Widget } from 'vs/base/browser/ui/widget';
|
import { Widget } from 'vs/base/browser/ui/widget';
|
||||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||||
import { LocalSelectionTransfer, DragAndDropObserver } from 'vs/workbench/browser/dnd';
|
|
||||||
import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService';
|
import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
import { Emitter } from 'vs/base/common/event';
|
import { Emitter } from 'vs/base/common/event';
|
||||||
import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
|
||||||
import { Registry } from 'vs/platform/registry/common/platform';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views';
|
import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views';
|
||||||
import { ICompositeDragAndDrop, CompositeDragAndDropData } from 'vs/base/parts/composite/browser/compositeDnd';
|
|
||||||
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||||
import { IComposite } from 'vs/workbench/common/composite';
|
import { IComposite } from 'vs/workbench/common/composite';
|
||||||
|
import { CompositeDragAndDropData, CompositeDragAndDropObserver, IDraggedCompositeData, ICompositeDragAndDrop } from 'vs/workbench/browser/dnd';
|
||||||
|
|
||||||
export interface ICompositeBarItem {
|
export interface ICompositeBarItem {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -41,30 +39,38 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
|||||||
private viewDescriptorService: IViewDescriptorService,
|
private viewDescriptorService: IViewDescriptorService,
|
||||||
private targetContainerLocation: ViewContainerLocation,
|
private targetContainerLocation: ViewContainerLocation,
|
||||||
private openComposite: (id: string, focus?: boolean) => Promise<IPaneComposite | undefined>,
|
private openComposite: (id: string, focus?: boolean) => Promise<IPaneComposite | undefined>,
|
||||||
private moveComposite: (from: string, to: string) => void,
|
private moveComposite: (from: string, to: string, before?: boolean) => void,
|
||||||
private getVisibleCompositeIds: () => string[]
|
|
||||||
) { }
|
) { }
|
||||||
drop(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent): void {
|
drop(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent, before?: boolean): void {
|
||||||
const dragData = data.getData();
|
const dragData = data.getData();
|
||||||
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||||
|
|
||||||
if (dragData.type === 'composite') {
|
if (dragData.type === 'composite') {
|
||||||
const currentContainer = viewContainerRegistry.get(dragData.id)!;
|
const currentContainer = viewContainerRegistry.get(dragData.id)!;
|
||||||
const currentLocation = viewContainerRegistry.getViewContainerLocation(currentContainer);
|
const currentLocation = viewContainerRegistry.getViewContainerLocation(currentContainer);
|
||||||
|
|
||||||
|
// Inserting a composite between composites
|
||||||
if (targetCompositeId) {
|
if (targetCompositeId) {
|
||||||
if (currentLocation !== this.targetContainerLocation && this.targetContainerLocation !== ViewContainerLocation.Panel) {
|
// ... on the same composite bar
|
||||||
const destinationContainer = viewContainerRegistry.get(targetCompositeId);
|
if (currentLocation === this.targetContainerLocation) {
|
||||||
if (destinationContainer && !destinationContainer.rejectAddedViews) {
|
this.moveComposite(dragData.id, targetCompositeId, before);
|
||||||
|
}
|
||||||
|
// ... on a different composite bar
|
||||||
|
else {
|
||||||
const viewsToMove = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.filter(vd => vd.canMoveView);
|
const viewsToMove = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.filter(vd => vd.canMoveView);
|
||||||
this.viewDescriptorService.moveViewsToContainer(viewsToMove, destinationContainer);
|
if (viewsToMove.length === 1) {
|
||||||
this.openComposite(targetCompositeId, true).then(composite => {
|
this.viewDescriptorService.moveViewToLocation(viewsToMove[0], this.targetContainerLocation);
|
||||||
|
|
||||||
|
const newContainer = this.viewDescriptorService.getViewContainer(viewsToMove[0].id)!;
|
||||||
|
|
||||||
|
this.moveComposite(newContainer.id, targetCompositeId, before);
|
||||||
|
|
||||||
|
this.openComposite(newContainer.id, true).then(composite => {
|
||||||
if (composite && viewsToMove.length === 1) {
|
if (composite && viewsToMove.length === 1) {
|
||||||
composite.openView(viewsToMove[0].id, true);
|
composite.openView(viewsToMove[0].id, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.moveComposite(dragData.id, targetCompositeId);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const draggedViews = this.viewDescriptorService.getViewDescriptors(currentContainer).allViewDescriptors;
|
const draggedViews = this.viewDescriptorService.getViewDescriptors(currentContainer).allViewDescriptors;
|
||||||
@@ -76,38 +82,24 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dragData.type === 'view') {
|
if (dragData.type === 'view') {
|
||||||
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dragData.id);
|
|
||||||
if (viewDescriptor && viewDescriptor.canMoveView) {
|
|
||||||
if (targetCompositeId) {
|
if (targetCompositeId) {
|
||||||
const destinationContainer = viewContainerRegistry.get(targetCompositeId);
|
const viewToMove = this.viewDescriptorService.getViewDescriptor(dragData.id)!;
|
||||||
if (destinationContainer && !destinationContainer.rejectAddedViews) {
|
|
||||||
if (this.targetContainerLocation === ViewContainerLocation.Sidebar || this.targetContainerLocation === ViewContainerLocation.Panel) {
|
|
||||||
this.viewDescriptorService.moveViewsToContainer([viewDescriptor], destinationContainer);
|
|
||||||
this.openComposite(targetCompositeId, true).then(composite => {
|
|
||||||
if (composite) {
|
|
||||||
composite.openView(viewDescriptor.id, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.viewDescriptorService.moveViewToLocation(viewDescriptor, this.targetContainerLocation);
|
|
||||||
this.moveComposite(this.viewDescriptorService.getViewContainer(viewDescriptor.id)!.id, targetCompositeId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.viewDescriptorService.moveViewToLocation(viewDescriptor, this.targetContainerLocation);
|
|
||||||
const newCompositeId = this.viewDescriptorService.getViewContainer(dragData.id)!.id;
|
|
||||||
const visibleItems = this.getVisibleCompositeIds();
|
|
||||||
const targetId = visibleItems.length ? visibleItems[visibleItems.length - 1] : undefined;
|
|
||||||
if (targetId && targetId !== newCompositeId) {
|
|
||||||
this.moveComposite(newCompositeId, targetId);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.openComposite(newCompositeId, true).then(composite => {
|
if (viewToMove && viewToMove.canMoveView) {
|
||||||
|
this.viewDescriptorService.moveViewToLocation(viewToMove, this.targetContainerLocation);
|
||||||
|
|
||||||
|
const newContainer = this.viewDescriptorService.getViewContainer(viewToMove.id)!;
|
||||||
|
|
||||||
|
this.moveComposite(newContainer.id, targetCompositeId, before);
|
||||||
|
|
||||||
|
this.openComposite(newContainer.id, true).then(composite => {
|
||||||
if (composite) {
|
if (composite) {
|
||||||
composite.openView(viewDescriptor.id, true);
|
composite.openView(viewToMove.id, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,41 +121,21 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
|||||||
const currentContainer = viewContainerRegistry.get(dragData.id)!;
|
const currentContainer = viewContainerRegistry.get(dragData.id)!;
|
||||||
const currentLocation = viewContainerRegistry.getViewContainerLocation(currentContainer);
|
const currentLocation = viewContainerRegistry.getViewContainerLocation(currentContainer);
|
||||||
|
|
||||||
// ... to the same location
|
// ... to the same composite location
|
||||||
if (currentLocation === this.targetContainerLocation) {
|
if (currentLocation === this.targetContainerLocation) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... across view containers but without a destination composite
|
// ... to another composite location
|
||||||
if (!targetCompositeId) {
|
|
||||||
const draggedViews = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors;
|
const draggedViews = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors;
|
||||||
if (draggedViews.some(vd => !vd.canMoveView)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (draggedViews.length !== 1) {
|
if (draggedViews.length !== 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultLocation = viewContainerRegistry.getViewContainerLocation(this.viewDescriptorService.getDefaultContainer(draggedViews[0].id)!);
|
// ... single view
|
||||||
if (this.targetContainerLocation === ViewContainerLocation.Sidebar && this.targetContainerLocation !== defaultLocation) {
|
const defaultContainer = this.viewDescriptorService.getDefaultContainer(draggedViews[0].id);
|
||||||
return false;
|
const canMoveToDefault = !!defaultContainer && this.viewDescriptorService.getViewContainerLocation(defaultContainer) === this.targetContainerLocation;
|
||||||
}
|
return !!draggedViews[0].canMoveView && (!!draggedViews[0].containerIcon || canMoveToDefault || this.targetContainerLocation === ViewContainerLocation.Panel);
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... from panel to the sidebar
|
|
||||||
if (this.targetContainerLocation === ViewContainerLocation.Sidebar) {
|
|
||||||
const destinationContainer = viewContainerRegistry.get(targetCompositeId);
|
|
||||||
return !!destinationContainer &&
|
|
||||||
!destinationContainer.rejectAddedViews &&
|
|
||||||
this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.some(vd => vd.canMoveView);
|
|
||||||
}
|
|
||||||
// ... from sidebar to the panel
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Dragging an individual view
|
// Dragging an individual view
|
||||||
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dragData.id);
|
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dragData.id);
|
||||||
@@ -174,13 +146,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ... to create a view container
|
// ... to create a view container
|
||||||
if (!targetCompositeId) {
|
return this.targetContainerLocation === ViewContainerLocation.Panel || !!viewDescriptor.containerIcon;
|
||||||
return this.targetContainerLocation === ViewContainerLocation.Panel;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ... into a destination
|
|
||||||
const destinationContainer = viewContainerRegistry.get(targetCompositeId);
|
|
||||||
return !!destinationContainer && !destinationContainer.rejectAddedViews;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,8 +181,6 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
|||||||
private visibleComposites: string[];
|
private visibleComposites: string[];
|
||||||
private compositeSizeInBar: Map<string, number>;
|
private compositeSizeInBar: Map<string, number>;
|
||||||
|
|
||||||
private compositeTransfer: LocalSelectionTransfer<DraggedCompositeIdentifier | DraggedViewIdentifier>;
|
|
||||||
|
|
||||||
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
|
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
|
||||||
readonly onDidChange = this._onDidChange.event;
|
readonly onDidChange = this._onDidChange.event;
|
||||||
|
|
||||||
@@ -232,7 +196,6 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
|||||||
this.model = new CompositeBarModel(items, options);
|
this.model = new CompositeBarModel(items, options);
|
||||||
this.visibleComposites = [];
|
this.visibleComposites = [];
|
||||||
this.compositeSizeInBar = new Map<string, number>();
|
this.compositeSizeInBar = new Map<string, number>();
|
||||||
this.compositeTransfer = LocalSelectionTransfer.getInstance<DraggedCompositeIdentifier>();
|
|
||||||
this.computeSizes(this.model.visibleItems);
|
this.computeSizes(this.model.visibleItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,100 +241,25 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
|||||||
// Contextmenu for composites
|
// Contextmenu for composites
|
||||||
this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e)));
|
this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e)));
|
||||||
|
|
||||||
|
// Register a drop target on the whole bar to prevent forbidden feedback
|
||||||
|
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(parent, {}));
|
||||||
|
|
||||||
// Allow to drop at the end to move composites to the end
|
// Allow to drop at the end to move composites to the end
|
||||||
this._register(new DragAndDropObserver(excessDiv, {
|
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(excessDiv, {
|
||||||
onDragOver: (e: DragEvent) => {
|
onDragEnter: (e: IDraggedCompositeData) => {
|
||||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
const pinnedItems = this.getPinnedComposites();
|
||||||
EventHelper.stop(e, true);
|
const validDropTarget = this.options.dndHandler.onDragEnter(e.dragAndDropData, pinnedItems[pinnedItems.length - 1].id, e.eventData);
|
||||||
|
|
||||||
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
const draggedCompositeId = data[0].id;
|
|
||||||
|
|
||||||
// Check if drop is allowed
|
|
||||||
if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e)) {
|
|
||||||
e.dataTransfer.dropEffect = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
|
||||||
EventHelper.stop(e, true);
|
|
||||||
|
|
||||||
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
const draggedViewId = data[0].id;
|
|
||||||
|
|
||||||
// Check if drop is allowed
|
|
||||||
if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('view', draggedViewId), undefined, e)) {
|
|
||||||
e.dataTransfer.dropEffect = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onDragEnter: (e: DragEvent) => {
|
|
||||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
|
||||||
EventHelper.stop(e, true);
|
|
||||||
|
|
||||||
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
const draggedCompositeId = data[0].id;
|
|
||||||
|
|
||||||
// Check if drop is allowed
|
|
||||||
const validDropTarget = this.options.dndHandler.onDragEnter(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e);
|
|
||||||
this.updateFromDragging(excessDiv, validDropTarget);
|
this.updateFromDragging(excessDiv, validDropTarget);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
|
||||||
EventHelper.stop(e, true);
|
|
||||||
|
|
||||||
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
const draggedViewId = data[0].id;
|
|
||||||
|
|
||||||
// Check if drop is allowed
|
|
||||||
const validDropTarget = this.options.dndHandler.onDragEnter(new CompositeDragAndDropData('view', draggedViewId), undefined, e);
|
|
||||||
this.updateFromDragging(excessDiv, validDropTarget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onDragLeave: (e: DragEvent) => {
|
onDragLeave: (e: IDraggedCompositeData) => {
|
||||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) ||
|
this.updateFromDragging(excessDiv, false);
|
||||||
this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
},
|
||||||
|
onDrop: (e: IDraggedCompositeData) => {
|
||||||
|
const pinnedItems = this.getPinnedComposites();
|
||||||
|
this.options.dndHandler.drop(e.dragAndDropData, pinnedItems[pinnedItems.length - 1].id, e.eventData, false);
|
||||||
this.updateFromDragging(excessDiv, false);
|
this.updateFromDragging(excessDiv, false);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
onDragEnd: (e: DragEvent) => {
|
|
||||||
// no-op, will not be called
|
|
||||||
},
|
|
||||||
onDrop: (e: DragEvent) => {
|
|
||||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
|
||||||
EventHelper.stop(e, true);
|
|
||||||
|
|
||||||
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
const draggedCompositeId = data[0].id;
|
|
||||||
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
|
|
||||||
|
|
||||||
this.options.dndHandler.drop(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e);
|
|
||||||
this.updateFromDragging(excessDiv, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
|
||||||
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
const draggedViewId = data[0].id;
|
|
||||||
this.compositeTransfer.clearData(DraggedViewIdentifier.prototype);
|
|
||||||
|
|
||||||
this.options.dndHandler.drop(new CompositeDragAndDropData('view', draggedViewId), undefined, e);
|
|
||||||
this.updateFromDragging(excessDiv, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return actionBarDiv;
|
return actionBarDiv;
|
||||||
@@ -519,12 +407,36 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
|||||||
return item?.pinned;
|
return item?.pinned;
|
||||||
}
|
}
|
||||||
|
|
||||||
move(compositeId: string, toCompositeId: string): void {
|
move(compositeId: string, toCompositeId: string, before?: boolean): void {
|
||||||
|
|
||||||
|
if (before !== undefined) {
|
||||||
|
const fromIndex = this.model.items.findIndex(c => c.id === compositeId);
|
||||||
|
let toIndex = this.model.items.findIndex(c => c.id === toCompositeId);
|
||||||
|
|
||||||
|
if (fromIndex >= 0 && toIndex >= 0) {
|
||||||
|
if (!before && fromIndex > toIndex) {
|
||||||
|
toIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (before && fromIndex < toIndex) {
|
||||||
|
toIndex--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toIndex < this.model.items.length && toIndex >= 0 && toIndex !== fromIndex) {
|
||||||
|
if (this.model.move(this.model.items[fromIndex].id, this.model.items[toIndex].id)) {
|
||||||
|
// timeout helps to prevent artifacts from showing up
|
||||||
|
setTimeout(() => this.updateCompositeSwitcher(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
if (this.model.move(compositeId, toCompositeId)) {
|
if (this.model.move(compositeId, toCompositeId)) {
|
||||||
// timeout helps to prevent artifacts from showing up
|
// timeout helps to prevent artifacts from showing up
|
||||||
setTimeout(() => this.updateCompositeSwitcher(), 0);
|
setTimeout(() => this.updateCompositeSwitcher(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getAction(compositeId: string): ActivityAction {
|
getAction(compositeId: string): ActivityAction {
|
||||||
const item = this.model.findItem(compositeId);
|
const item = this.model.findItem(compositeId);
|
||||||
|
|||||||
@@ -18,10 +18,8 @@ import { DelayedDragHandler } from 'vs/base/browser/dnd';
|
|||||||
import { IActivity } from 'vs/workbench/common/activity';
|
import { IActivity } from 'vs/workbench/common/activity';
|
||||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
import { Emitter } from 'vs/base/common/event';
|
import { Emitter } from 'vs/base/common/event';
|
||||||
import { DragAndDropObserver, LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
|
import { LocalSelectionTransfer, DraggedCompositeIdentifier, DraggedViewIdentifier, CompositeDragAndDropObserver, ICompositeDragAndDrop } from 'vs/workbench/browser/dnd';
|
||||||
import { Color } from 'vs/base/common/color';
|
import { Color } from 'vs/base/common/color';
|
||||||
import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
|
||||||
import { ICompositeDragAndDrop, CompositeDragAndDropData } from 'vs/base/parts/composite/browser/compositeDnd';
|
|
||||||
|
|
||||||
export interface ICompositeActivity {
|
export interface ICompositeActivity {
|
||||||
badge: IBadge;
|
badge: IBadge;
|
||||||
@@ -167,11 +165,15 @@ export class ActivityActionViewItem extends BaseActionViewItem {
|
|||||||
// Apply foreground color to activity bar items provided with codicons
|
// Apply foreground color to activity bar items provided with codicons
|
||||||
this.label.style.color = foreground ? foreground.toString() : '';
|
this.label.style.color = foreground ? foreground.toString() : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dragColor = colors.activeBackgroundColor || colors.activeForegroundColor;
|
||||||
|
this.container.style.setProperty('--insert-border-color', dragColor ? dragColor.toString() : '');
|
||||||
} else {
|
} else {
|
||||||
const foreground = this._action.checked ? colors.activeForegroundColor : colors.inactiveForegroundColor;
|
const foreground = this._action.checked ? colors.activeForegroundColor : colors.inactiveForegroundColor;
|
||||||
const borderBottomColor = this._action.checked ? colors.activeBorderBottomColor : null;
|
const borderBottomColor = this._action.checked ? colors.activeBorderBottomColor : null;
|
||||||
this.label.style.color = foreground ? foreground.toString() : '';
|
this.label.style.color = foreground ? foreground.toString() : '';
|
||||||
this.label.style.borderBottomColor = borderBottomColor ? borderBottomColor.toString() : '';
|
this.label.style.borderBottomColor = borderBottomColor ? borderBottomColor.toString() : '';
|
||||||
|
this.container.style.setProperty('--insert-border-color', colors.activeForegroundColor ? colors.activeForegroundColor.toString() : '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,14 +447,6 @@ class ManageExtensionAction extends Action {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DraggedCompositeIdentifier {
|
|
||||||
constructor(private _compositeId: string) { }
|
|
||||||
|
|
||||||
get id(): string {
|
|
||||||
return this._compositeId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CompositeActionViewItem extends ActivityActionViewItem {
|
export class CompositeActionViewItem extends ActivityActionViewItem {
|
||||||
|
|
||||||
private static manageExtensionAction: ManageExtensionAction;
|
private static manageExtensionAction: ManageExtensionAction;
|
||||||
@@ -522,106 +516,39 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
|||||||
this.showContextMenu(container);
|
this.showContextMenu(container);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let insertDropBefore: boolean | undefined = undefined;
|
||||||
// Allow to drag
|
// Allow to drag
|
||||||
this._register(dom.addDisposableListener(this.container, dom.EventType.DRAG_START, (e: DragEvent) => {
|
this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(this.container, 'composite', this.activity.id, {
|
||||||
if (e.dataTransfer) {
|
onDragOver: e => {
|
||||||
e.dataTransfer.effectAllowed = 'move';
|
const isValidMove = e.dragAndDropData.getData().id !== this.activity.id && this.dndHandler.onDragOver(e.dragAndDropData, this.activity.id, e.eventData);
|
||||||
|
insertDropBefore = this.updateFromDragging(container, isValidMove, e.eventData);
|
||||||
|
},
|
||||||
|
|
||||||
|
onDragLeave: e => {
|
||||||
|
insertDropBefore = this.updateFromDragging(container, false, e.eventData);
|
||||||
|
},
|
||||||
|
|
||||||
|
onDragEnd: e => {
|
||||||
|
insertDropBefore = this.updateFromDragging(container, false, e.eventData);
|
||||||
|
},
|
||||||
|
|
||||||
|
onDrop: e => {
|
||||||
|
this.dndHandler.drop(e.dragAndDropData, this.activity.id, e.eventData, !!insertDropBefore);
|
||||||
|
insertDropBefore = this.updateFromDragging(container, false, e.eventData);
|
||||||
|
},
|
||||||
|
onDragStart: e => {
|
||||||
|
if (e.dragAndDropData.getData().id !== this.activity.id) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registe as dragged to local transfer
|
if (e.eventData.dataTransfer) {
|
||||||
this.compositeTransfer.setData([new DraggedCompositeIdentifier(this.activity.id)], DraggedCompositeIdentifier.prototype);
|
e.eventData.dataTransfer.effectAllowed = 'move';
|
||||||
|
}
|
||||||
|
|
||||||
// Trigger the action even on drag start to prevent clicks from failing that started a drag
|
// Trigger the action even on drag start to prevent clicks from failing that started a drag
|
||||||
if (!this.getAction().checked) {
|
if (!this.getAction().checked) {
|
||||||
this.getAction().run();
|
this.getAction().run();
|
||||||
}
|
}
|
||||||
}));
|
|
||||||
|
|
||||||
this._register(new DragAndDropObserver(this.container, {
|
|
||||||
onDragEnter: e => {
|
|
||||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
|
||||||
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
|
|
||||||
if (Array.isArray(data) && data[0].id !== this.activity.id) {
|
|
||||||
const validDropTarget = this.dndHandler.onDragEnter(new CompositeDragAndDropData('composite', data[0].id), this.activity.id, e);
|
|
||||||
this.updateFromDragging(container, validDropTarget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
|
||||||
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
|
|
||||||
if (Array.isArray(data) && data[0].id !== this.activity.id) {
|
|
||||||
const validDropTarget = this.dndHandler.onDragEnter(new CompositeDragAndDropData('view', data[0].id), this.activity.id, e);
|
|
||||||
this.updateFromDragging(container, validDropTarget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onDragOver: e => {
|
|
||||||
dom.EventHelper.stop(e, true);
|
|
||||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
|
||||||
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
const draggedCompositeId = data[0].id;
|
|
||||||
if (draggedCompositeId !== this.activity.id) {
|
|
||||||
if (e.dataTransfer && !this.dndHandler.onDragOver(new CompositeDragAndDropData('composite', draggedCompositeId), this.activity.id, e)) {
|
|
||||||
e.dataTransfer.dropEffect = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
|
||||||
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
const draggedViewId = data[0].id;
|
|
||||||
if (e.dataTransfer && !this.dndHandler.onDragOver(new CompositeDragAndDropData('view', draggedViewId), this.activity.id, e)) {
|
|
||||||
e.dataTransfer.dropEffect = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onDragLeave: e => {
|
|
||||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) ||
|
|
||||||
this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
|
||||||
this.updateFromDragging(container, false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onDragEnd: e => {
|
|
||||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
|
||||||
this.updateFromDragging(container, false);
|
|
||||||
|
|
||||||
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onDrop: e => {
|
|
||||||
dom.EventHelper.stop(e, true);
|
|
||||||
|
|
||||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
|
||||||
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
const draggedCompositeId = data[0].id;
|
|
||||||
if (draggedCompositeId !== this.activity.id) {
|
|
||||||
this.updateFromDragging(container, false);
|
|
||||||
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
|
|
||||||
|
|
||||||
this.dndHandler.drop(new CompositeDragAndDropData('composite', draggedCompositeId), this.activity.id, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
|
||||||
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
const draggedViewId = data[0].id;
|
|
||||||
this.updateFromDragging(container, false);
|
|
||||||
this.compositeTransfer.clearData(DraggedViewIdentifier.prototype);
|
|
||||||
|
|
||||||
this.dndHandler.drop(new CompositeDragAndDropData('view', draggedViewId), this.activity.id, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -637,11 +564,42 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
|
|||||||
this.updateStyles();
|
this.updateStyles();
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateFromDragging(element: HTMLElement, isDragging: boolean): void {
|
private updateFromDragging(element: HTMLElement, showFeedback: boolean, event: DragEvent): boolean | undefined {
|
||||||
const theme = this.themeService.getColorTheme();
|
const rect = element.getBoundingClientRect();
|
||||||
const dragBackground = this.options.colors(theme).dragAndDropBackground;
|
const posX = event.clientX;
|
||||||
|
const posY = event.clientY;
|
||||||
|
const height = rect.bottom - rect.top;
|
||||||
|
const width = rect.right - rect.left;
|
||||||
|
|
||||||
element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : '';
|
const forceTop = posY <= rect.top + height * 0.4;
|
||||||
|
const forceBottom = posY > rect.bottom - height * 0.4;
|
||||||
|
const preferTop = posY <= rect.top + height * 0.5;
|
||||||
|
|
||||||
|
const forceLeft = posX <= rect.left + width * 0.4;
|
||||||
|
const forceRight = posX > rect.right - width * 0.4;
|
||||||
|
const preferLeft = posX <= rect.left + width * 0.5;
|
||||||
|
|
||||||
|
const classes = element.classList;
|
||||||
|
const lastClasses = {
|
||||||
|
vertical: classes.contains('top') ? 'top' : (classes.contains('bottom') ? 'bottom' : undefined),
|
||||||
|
horizontal: classes.contains('left') ? 'left' : (classes.contains('right') ? 'right' : undefined)
|
||||||
|
};
|
||||||
|
|
||||||
|
const top = forceTop || (preferTop && !lastClasses.vertical) || (!forceBottom && lastClasses.vertical === 'top');
|
||||||
|
const bottom = forceBottom || (!preferTop && !lastClasses.vertical) || (!forceTop && lastClasses.vertical === 'bottom');
|
||||||
|
const left = forceLeft || (preferLeft && !lastClasses.horizontal) || (!forceRight && lastClasses.horizontal === 'left');
|
||||||
|
const right = forceRight || (!preferLeft && !lastClasses.horizontal) || (!forceLeft && lastClasses.horizontal === 'right');
|
||||||
|
|
||||||
|
dom.toggleClass(element, 'top', showFeedback && top);
|
||||||
|
dom.toggleClass(element, 'bottom', showFeedback && bottom);
|
||||||
|
dom.toggleClass(element, 'left', showFeedback && left);
|
||||||
|
dom.toggleClass(element, 'right', showFeedback && right);
|
||||||
|
|
||||||
|
if (!showFeedback) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return top || left;
|
||||||
}
|
}
|
||||||
|
|
||||||
private showContextMenu(container: HTMLElement): void {
|
private showContextMenu(container: HTMLElement): void {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
|||||||
import { MementoObject } from 'vs/workbench/common/memento';
|
import { MementoObject } from 'vs/workbench/common/memento';
|
||||||
import { assertIsDefined } from 'vs/base/common/types';
|
import { assertIsDefined } from 'vs/base/common/types';
|
||||||
import { IBoundarySashes } from 'vs/base/browser/ui/grid/gridview';
|
import { IBoundarySashes } from 'vs/base/browser/ui/grid/gridview';
|
||||||
|
import { CompositeDragAndDropObserver } from 'vs/workbench/browser/dnd';
|
||||||
|
|
||||||
interface IEditorPartUIState {
|
interface IEditorPartUIState {
|
||||||
serializedGrid: ISerializedGrid;
|
serializedGrid: ISerializedGrid;
|
||||||
@@ -826,6 +827,20 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
|
|||||||
// Drop support
|
// Drop support
|
||||||
this._register(this.createEditorDropTarget(this.container, {}));
|
this._register(this.createEditorDropTarget(this.container, {}));
|
||||||
|
|
||||||
|
// No drop in the editor
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
addClass(overlay, 'drop-block-overlay');
|
||||||
|
parent.appendChild(overlay);
|
||||||
|
|
||||||
|
CompositeDragAndDropObserver.INSTANCE.registerTarget(this.element, {
|
||||||
|
onDragStart: e => {
|
||||||
|
toggleClass(overlay, 'visible', true);
|
||||||
|
},
|
||||||
|
onDragEnd: e => {
|
||||||
|
toggleClass(overlay, 'visible', false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return this.container;
|
return this.container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import 'vs/css!./media/editorquickaccess';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource, IQuickPick } from 'vs/platform/quickinput/common/quickInput';
|
import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource } from 'vs/platform/quickinput/common/quickInput';
|
||||||
import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess';
|
import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess';
|
||||||
import { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
|
import { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||||
import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor } from 'vs/workbench/common/editor';
|
import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor } from 'vs/workbench/common/editor';
|
||||||
@@ -25,18 +26,14 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro
|
|||||||
@IModelService private readonly modelService: IModelService,
|
@IModelService private readonly modelService: IModelService,
|
||||||
@IModeService private readonly modeService: IModeService
|
@IModeService private readonly modeService: IModeService
|
||||||
) {
|
) {
|
||||||
super(prefix);
|
super(prefix, { canAcceptInBackground: true });
|
||||||
}
|
|
||||||
|
|
||||||
protected configure(picker: IQuickPick<IEditorQuickPickItem>): void {
|
|
||||||
|
|
||||||
// Allow to open editors in background without closing picker
|
|
||||||
picker.canAcceptInBackground = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getPicks(filter: string): Array<IEditorQuickPickItem | IQuickPickSeparator> {
|
protected getPicks(filter: string): Array<IEditorQuickPickItem | IQuickPickSeparator> {
|
||||||
const query = prepareQuery(filter);
|
const query = prepareQuery(filter);
|
||||||
const scorerCache = Object.create(null);
|
const scorerCache = Object.create(null);
|
||||||
|
|
||||||
|
// Filtering
|
||||||
const filteredEditorEntries = this.doGetEditorPickItems().filter(entry => {
|
const filteredEditorEntries = this.doGetEditorPickItems().filter(entry => {
|
||||||
if (!query.value) {
|
if (!query.value) {
|
||||||
return true;
|
return true;
|
||||||
@@ -102,11 +99,11 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro
|
|||||||
description: editor.getDescription(),
|
description: editor.getDescription(),
|
||||||
iconClasses: getIconClasses(this.modelService, this.modeService, resource),
|
iconClasses: getIconClasses(this.modelService, this.modeService, resource),
|
||||||
italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor),
|
italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor),
|
||||||
buttonsAlwaysVisible: isDirty,
|
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
iconClass: isDirty ? 'codicon-circle-filled' : 'codicon-close',
|
iconClass: isDirty ? 'dirty-editor codicon-circle-filled' : 'codicon-close',
|
||||||
tooltip: localize('closeEditor', "Close Editor")
|
tooltip: localize('closeEditor', "Close Editor"),
|
||||||
|
alwaysVisible: isDirty
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
trigger: async () => {
|
trigger: async () => {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
opacity: 0; /* hidden initially */
|
opacity: 0; /* hidden initially */
|
||||||
transition: opacity 150ms ease-out;
|
transition: opacity 150ms ease-out;
|
||||||
|
/* color: red; */
|
||||||
}
|
}
|
||||||
|
|
||||||
#monaco-workbench-editor-drop-overlay > .editor-group-overlay-indicator.overlay-move-transition {
|
#monaco-workbench-editor-drop-overlay > .editor-group-overlay-indicator.overlay-move-transition {
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.quick-input-list .quick-input-list-entry.has-actions:hover .quick-input-list-entry-action-bar .action-label.dirty-editor::before {
|
||||||
|
content: "\ea76"; /* Close icon flips between black dot and "X" for dirty open editors */
|
||||||
|
}
|
||||||
@@ -85,6 +85,40 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item::before,
|
||||||
|
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item::after {
|
||||||
|
content: '';
|
||||||
|
width: 2px;
|
||||||
|
display: block;
|
||||||
|
background-color: var(--insert-border-color);
|
||||||
|
opacity: 0;
|
||||||
|
transition-property: opacity;
|
||||||
|
transition-duration: 0ms;
|
||||||
|
transition-delay: 100ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item::before {
|
||||||
|
margin-left: -11px;
|
||||||
|
margin-right: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item::after {
|
||||||
|
margin-right: -11px;
|
||||||
|
margin-left: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.right::before,
|
||||||
|
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.left::after,
|
||||||
|
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.left::before,
|
||||||
|
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.right::after {
|
||||||
|
transition-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.left::before,
|
||||||
|
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.right::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item .action-label{
|
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item .action-label{
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,9 +144,8 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
|||||||
getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(),
|
getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(),
|
||||||
hidePart: () => this.layoutService.setPanelHidden(true),
|
hidePart: () => this.layoutService.setPanelHidden(true),
|
||||||
dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Panel,
|
dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Panel,
|
||||||
(id: string, focus?: boolean) => (<unknown>this.openPanel(id, focus)) as Promise<IPaneComposite | undefined>, // {{SQL CARBON EDIT}} strict-null-check
|
(id: string, focus?: boolean) => <unknown>this.openPanel(id, focus) as Promise<IPaneComposite | undefined>, // {{SQL CARBON EDIT}} strict-null-checks
|
||||||
(from: string, to: string) => this.compositeBar.move(from, to),
|
(from: string, to: string, before?: boolean) => this.compositeBar.move(from, to, before)
|
||||||
() => this.getPinnedPanels().map(p => p.id)
|
|
||||||
),
|
),
|
||||||
compositeSize: 0,
|
compositeSize: 0,
|
||||||
overflowActionSize: 44,
|
overflowActionSize: 44,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
|||||||
import { Event, Emitter } from 'vs/base/common/event';
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||||
import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER } from 'vs/workbench/common/theme';
|
import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER, SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
|
||||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
import { EventType, addDisposableListener, trackFocus } from 'vs/base/browser/dom';
|
import { EventType, addDisposableListener, trackFocus } from 'vs/base/browser/dom';
|
||||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||||
@@ -33,9 +33,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
|
|||||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||||
import { LayoutPriority } from 'vs/base/browser/ui/grid/grid';
|
import { LayoutPriority } from 'vs/base/browser/ui/grid/grid';
|
||||||
import { assertIsDefined } from 'vs/base/common/types';
|
import { assertIsDefined } from 'vs/base/common/types';
|
||||||
import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
|
import { LocalSelectionTransfer, DraggedViewIdentifier, DraggedCompositeIdentifier } from 'vs/workbench/browser/dnd';
|
||||||
import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
|
||||||
import { DraggedCompositeIdentifier } from 'vs/workbench/browser/parts/compositeBarActions';
|
|
||||||
|
|
||||||
export class SidebarPart extends CompositePart<Viewlet> implements IViewletService {
|
export class SidebarPart extends CompositePart<Viewlet> implements IViewletService {
|
||||||
|
|
||||||
@@ -209,6 +207,7 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
|
|||||||
container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : '';
|
container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : '';
|
||||||
container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : '';
|
container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : '';
|
||||||
container.style.borderLeftColor = !isPositionLeft ? borderColor || '' : '';
|
container.style.borderLeftColor = !isPositionLeft ? borderColor || '' : '';
|
||||||
|
container.style.outlineColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
layout(width: number, height: number): void {
|
layout(width: number, height: number): void {
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
import 'vs/css!./media/paneviewlet';
|
import 'vs/css!./media/paneviewlet';
|
||||||
import * as nls from 'vs/nls';
|
import * as nls from 'vs/nls';
|
||||||
import { Event, Emitter } from 'vs/base/common/event';
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry';
|
import { ColorIdentifier, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||||
import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler';
|
import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler';
|
||||||
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND, EDITOR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
|
||||||
import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass } from 'vs/base/browser/dom';
|
import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass } from 'vs/base/browser/dom';
|
||||||
import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
import { firstIndex } from 'vs/base/common/arrays';
|
import { firstIndex } from 'vs/base/common/arrays';
|
||||||
@@ -20,8 +20,8 @@ import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
|||||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
|
||||||
import { PaneView, IPaneViewOptions, IPaneOptions, Pane, DefaultPaneDndController } from 'vs/base/browser/ui/splitview/paneview';
|
import { PaneView, IPaneViewOptions, IPaneOptions, Pane } from 'vs/base/browser/ui/splitview/paneview';
|
||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||||
@@ -42,11 +42,12 @@ import { parseLinkedText } from 'vs/base/common/linkedText';
|
|||||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||||
import { Button } from 'vs/base/browser/ui/button/button';
|
import { Button } from 'vs/base/browser/ui/button/button';
|
||||||
import { Link } from 'vs/platform/opener/browser/link';
|
import { Link } from 'vs/platform/opener/browser/link';
|
||||||
import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
|
import { CompositeDragAndDropObserver, DragAndDropObserver } from 'vs/workbench/browser/dnd';
|
||||||
import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||||
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
||||||
import { CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator';
|
import { CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator';
|
||||||
import { IProgressIndicator } from 'vs/platform/progress/common/progress';
|
import { IProgressIndicator } from 'vs/platform/progress/common/progress';
|
||||||
|
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||||
|
|
||||||
export interface IPaneColors extends IColorMapping {
|
export interface IPaneColors extends IColorMapping {
|
||||||
dropBackground?: ColorIdentifier;
|
dropBackground?: ColorIdentifier;
|
||||||
@@ -61,14 +62,6 @@ export interface IViewPaneOptions extends IPaneOptions {
|
|||||||
titleMenuId?: MenuId;
|
titleMenuId?: MenuId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DraggedViewIdentifier {
|
|
||||||
constructor(private _viewId: string) { }
|
|
||||||
|
|
||||||
get id(): string {
|
|
||||||
return this._viewId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type WelcomeActionClassification = {
|
type WelcomeActionClassification = {
|
||||||
viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||||
uri: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
uri: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||||
@@ -508,6 +501,210 @@ interface IViewPaneItem {
|
|||||||
disposable: IDisposable;
|
disposable: IDisposable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enum DropDirection {
|
||||||
|
UP,
|
||||||
|
DOWN,
|
||||||
|
LEFT,
|
||||||
|
RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewPaneDropOverlay extends Themable {
|
||||||
|
|
||||||
|
private static readonly OVERLAY_ID = 'monaco-workbench-pane-drop-overlay';
|
||||||
|
|
||||||
|
private container!: HTMLElement;
|
||||||
|
private overlay!: HTMLElement;
|
||||||
|
|
||||||
|
private _currentDropOperation: DropDirection | undefined;
|
||||||
|
|
||||||
|
// private currentDropOperation: IDropOperation | undefined;
|
||||||
|
private _disposed: boolean | undefined;
|
||||||
|
|
||||||
|
private cleanupOverlayScheduler: RunOnceScheduler;
|
||||||
|
|
||||||
|
get currentDropOperation(): DropDirection | undefined {
|
||||||
|
return this._currentDropOperation;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private paneElement: HTMLElement,
|
||||||
|
private orientation: Orientation,
|
||||||
|
protected themeService: IThemeService
|
||||||
|
) {
|
||||||
|
super(themeService);
|
||||||
|
this.cleanupOverlayScheduler = this._register(new RunOnceScheduler(() => this.dispose(), 300));
|
||||||
|
|
||||||
|
this.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
get disposed(): boolean {
|
||||||
|
return !!this._disposed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private create(): void {
|
||||||
|
// Container
|
||||||
|
this.container = document.createElement('div');
|
||||||
|
this.container.id = ViewPaneDropOverlay.OVERLAY_ID;
|
||||||
|
|
||||||
|
// Parent
|
||||||
|
this.paneElement.appendChild(this.container);
|
||||||
|
addClass(this.paneElement, 'dragged-over');
|
||||||
|
this._register(toDisposable(() => {
|
||||||
|
this.paneElement.removeChild(this.container);
|
||||||
|
removeClass(this.paneElement, 'dragged-over');
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Overlay
|
||||||
|
this.overlay = document.createElement('div');
|
||||||
|
addClass(this.overlay, 'pane-overlay-indicator');
|
||||||
|
this.container.appendChild(this.overlay);
|
||||||
|
|
||||||
|
// Overlay Event Handling
|
||||||
|
this.registerListeners();
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
this.updateStyles();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updateStyles(): void {
|
||||||
|
|
||||||
|
// Overlay drop background
|
||||||
|
this.overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) || '';
|
||||||
|
|
||||||
|
// Overlay contrast border (if any)
|
||||||
|
const activeContrastBorderColor = this.getColor(activeContrastBorder);
|
||||||
|
this.overlay.style.outlineColor = activeContrastBorderColor || '';
|
||||||
|
this.overlay.style.outlineOffset = activeContrastBorderColor ? '-2px' : '';
|
||||||
|
this.overlay.style.outlineStyle = activeContrastBorderColor ? 'dashed' : '';
|
||||||
|
this.overlay.style.outlineWidth = activeContrastBorderColor ? '2px' : '';
|
||||||
|
|
||||||
|
this.overlay.style.borderColor = activeContrastBorderColor || '';
|
||||||
|
this.overlay.style.borderStyle = 'solid' || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerListeners(): void {
|
||||||
|
this._register(new DragAndDropObserver(this.container, {
|
||||||
|
onDragEnter: e => undefined,
|
||||||
|
onDragOver: e => {
|
||||||
|
|
||||||
|
// Position overlay
|
||||||
|
this.positionOverlay(e.offsetX, e.offsetY);
|
||||||
|
|
||||||
|
// Make sure to stop any running cleanup scheduler to remove the overlay
|
||||||
|
if (this.cleanupOverlayScheduler.isScheduled()) {
|
||||||
|
this.cleanupOverlayScheduler.cancel();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDragLeave: e => this.dispose(),
|
||||||
|
onDragEnd: e => this.dispose(),
|
||||||
|
|
||||||
|
onDrop: e => {
|
||||||
|
// Dispose overlay
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(addDisposableListener(this.container, EventType.MOUSE_OVER, () => {
|
||||||
|
// Under some circumstances we have seen reports where the drop overlay is not being
|
||||||
|
// cleaned up and as such the editor area remains under the overlay so that you cannot
|
||||||
|
// type into the editor anymore. This seems related to using VMs and DND via host and
|
||||||
|
// guest OS, though some users also saw it without VMs.
|
||||||
|
// To protect against this issue we always destroy the overlay as soon as we detect a
|
||||||
|
// mouse event over it. The delay is used to guarantee we are not interfering with the
|
||||||
|
// actual DROP event that can also trigger a mouse over event.
|
||||||
|
if (!this.cleanupOverlayScheduler.isScheduled()) {
|
||||||
|
this.cleanupOverlayScheduler.schedule();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private positionOverlay(mousePosX: number, mousePosY: number): void {
|
||||||
|
const paneWidth = this.paneElement.clientWidth;
|
||||||
|
const paneHeight = this.paneElement.clientHeight;
|
||||||
|
|
||||||
|
const splitWidthThreshold = paneWidth / 2;
|
||||||
|
const splitHeightThreshold = paneHeight / 2;
|
||||||
|
|
||||||
|
let dropDirection: DropDirection | undefined;
|
||||||
|
|
||||||
|
if (this.orientation === Orientation.VERTICAL) {
|
||||||
|
if (mousePosY < splitHeightThreshold) {
|
||||||
|
dropDirection = DropDirection.UP;
|
||||||
|
} else if (mousePosY >= splitHeightThreshold) {
|
||||||
|
dropDirection = DropDirection.DOWN;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mousePosX < splitWidthThreshold) {
|
||||||
|
dropDirection = DropDirection.LEFT;
|
||||||
|
} else if (mousePosX >= splitWidthThreshold) {
|
||||||
|
dropDirection = DropDirection.RIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw overlay based on split direction
|
||||||
|
switch (dropDirection) {
|
||||||
|
case DropDirection.UP:
|
||||||
|
this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '50%' });
|
||||||
|
break;
|
||||||
|
case DropDirection.DOWN:
|
||||||
|
this.doPositionOverlay({ bottom: '0', left: '0', width: '100%', height: '50%' });
|
||||||
|
break;
|
||||||
|
case DropDirection.LEFT:
|
||||||
|
this.doPositionOverlay({ top: '0', left: '0', width: '50%', height: '100%' });
|
||||||
|
break;
|
||||||
|
case DropDirection.RIGHT:
|
||||||
|
this.doPositionOverlay({ top: '0', right: '0', width: '50%', height: '100%' });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '100%' });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.doUpdateOverlayBorder(dropDirection);
|
||||||
|
|
||||||
|
// Make sure the overlay is visible now
|
||||||
|
this.overlay.style.opacity = '1';
|
||||||
|
|
||||||
|
// Enable transition after a timeout to prevent initial animation
|
||||||
|
setTimeout(() => addClass(this.overlay, 'overlay-move-transition'), 0);
|
||||||
|
|
||||||
|
// Remember as current split direction
|
||||||
|
this._currentDropOperation = dropDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private doUpdateOverlayBorder(direction: DropDirection | undefined): void {
|
||||||
|
this.overlay.style.borderTopWidth = direction === DropDirection.UP ? '2px' : '0px';
|
||||||
|
this.overlay.style.borderLeftWidth = direction === DropDirection.LEFT ? '2px' : '0px';
|
||||||
|
this.overlay.style.borderBottomWidth = direction === DropDirection.DOWN ? '2px' : '0px';
|
||||||
|
this.overlay.style.borderRightWidth = direction === DropDirection.RIGHT ? '2px' : '0px';
|
||||||
|
}
|
||||||
|
|
||||||
|
private doPositionOverlay(options: { top?: string, bottom?: string, left?: string, right?: string, width: string, height: string }): void {
|
||||||
|
|
||||||
|
// Container
|
||||||
|
this.container.style.height = '100%';
|
||||||
|
|
||||||
|
// Overlay
|
||||||
|
this.overlay.style.top = options.top || '';
|
||||||
|
this.overlay.style.left = options.left || '';
|
||||||
|
this.overlay.style.bottom = options.bottom || '';
|
||||||
|
this.overlay.style.right = options.right || '';
|
||||||
|
this.overlay.style.width = options.width;
|
||||||
|
this.overlay.style.height = options.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
contains(element: HTMLElement): boolean {
|
||||||
|
return element === this.container || element === this.overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
super.dispose();
|
||||||
|
|
||||||
|
this._disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
||||||
|
|
||||||
readonly viewContainer: ViewContainer;
|
readonly viewContainer: ViewContainer;
|
||||||
@@ -515,8 +712,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
|||||||
private paneItems: IViewPaneItem[] = [];
|
private paneItems: IViewPaneItem[] = [];
|
||||||
private paneview?: PaneView;
|
private paneview?: PaneView;
|
||||||
|
|
||||||
private static viewTransfer = LocalSelectionTransfer.getInstance<DraggedViewIdentifier>();
|
|
||||||
|
|
||||||
private visible: boolean = false;
|
private visible: boolean = false;
|
||||||
|
|
||||||
private areExtensionsReady: boolean = false;
|
private areExtensionsReady: boolean = false;
|
||||||
@@ -583,10 +778,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
|||||||
throw new Error('Could not find container');
|
throw new Error('Could not find container');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use default pane dnd controller if not specified
|
|
||||||
if (!this.options.dnd) {
|
|
||||||
this.options.dnd = new DefaultPaneDndController();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.viewContainer = container;
|
this.viewContainer = container;
|
||||||
this.visibleViewsStorageId = `${id}.numberOfVisibleViews`;
|
this.visibleViewsStorageId = `${id}.numberOfVisibleViews`;
|
||||||
@@ -949,19 +1140,104 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
|
|||||||
this.paneItems.splice(index, 0, paneItem);
|
this.paneItems.splice(index, 0, paneItem);
|
||||||
assertIsDefined(this.paneview).addPane(pane, size, index);
|
assertIsDefined(this.paneview).addPane(pane, size, index);
|
||||||
|
|
||||||
this._register(addDisposableListener(pane.draggableElement, EventType.DRAG_START, (e: DragEvent) => {
|
let overlay: ViewPaneDropOverlay | undefined;
|
||||||
if (e.dataTransfer) {
|
|
||||||
e.dataTransfer.effectAllowed = 'move';
|
this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(pane.draggableElement, 'view', pane.id, {}));
|
||||||
|
|
||||||
|
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(pane.dropTargetElement, {
|
||||||
|
onDragEnter: (e) => {
|
||||||
|
if (!overlay) {
|
||||||
|
const dropData = e.dragAndDropData.getData();
|
||||||
|
if (dropData.type === 'view' && dropData.id !== pane.id) {
|
||||||
|
|
||||||
|
const oldViewContainer = this.viewDescriptorService.getViewContainer(dropData.id);
|
||||||
|
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dropData.id);
|
||||||
|
|
||||||
|
if (oldViewContainer !== this.viewContainer && (!viewDescriptor || !viewDescriptor.canMoveView)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register as dragged to local transfer
|
overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.options.orientation ?? Orientation.VERTICAL, this.themeService);
|
||||||
ViewPaneContainer.viewTransfer.setData([new DraggedViewIdentifier(pane.id)], DraggedViewIdentifier.prototype);
|
}
|
||||||
}));
|
|
||||||
|
|
||||||
|
if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) {
|
||||||
|
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||||
|
|
||||||
this._register(addDisposableListener(pane.draggableElement, EventType.DRAG_END, (e: DragEvent) => {
|
const container = viewContainerRegistry.get(dropData.id)!;
|
||||||
if (ViewPaneContainer.viewTransfer.hasData(DraggedViewIdentifier.prototype)) {
|
const viewsToMove = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors;
|
||||||
ViewPaneContainer.viewTransfer.clearData(DraggedViewIdentifier.prototype);
|
|
||||||
|
if (viewsToMove.length === 1 && viewsToMove[0].canMoveView) {
|
||||||
|
overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.options.orientation ?? Orientation.VERTICAL, this.themeService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragLeave: (e) => {
|
||||||
|
overlay?.dispose();
|
||||||
|
overlay = undefined;
|
||||||
|
},
|
||||||
|
onDrop: (e) => {
|
||||||
|
if (overlay) {
|
||||||
|
const dropData = e.dragAndDropData.getData();
|
||||||
|
|
||||||
|
if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) {
|
||||||
|
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||||
|
|
||||||
|
const container = viewContainerRegistry.get(dropData.id)!;
|
||||||
|
const viewsToMove = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors;
|
||||||
|
|
||||||
|
if (viewsToMove.length === 1 && viewsToMove[0].canMoveView) {
|
||||||
|
dropData.type = 'view';
|
||||||
|
dropData.id = viewsToMove[0].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dropData.type === 'view') {
|
||||||
|
|
||||||
|
const oldViewContainer = this.viewDescriptorService.getViewContainer(dropData.id);
|
||||||
|
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dropData.id);
|
||||||
|
if (oldViewContainer !== this.viewContainer && viewDescriptor && viewDescriptor.canMoveView) {
|
||||||
|
this.viewDescriptorService.moveViewsToContainer([viewDescriptor], this.viewContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overlay.currentDropOperation === DropDirection.DOWN ||
|
||||||
|
overlay.currentDropOperation === DropDirection.RIGHT) {
|
||||||
|
|
||||||
|
const fromIndex = this.panes.findIndex(p => p.id === dropData.id);
|
||||||
|
let toIndex = this.panes.findIndex(p => p.id === pane.id);
|
||||||
|
|
||||||
|
if (fromIndex >= 0 && toIndex >= 0) {
|
||||||
|
if (fromIndex > toIndex) {
|
||||||
|
toIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toIndex < this.panes.length && toIndex !== fromIndex) {
|
||||||
|
this.movePane(this.panes[fromIndex], this.panes[toIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overlay.currentDropOperation === DropDirection.UP ||
|
||||||
|
overlay.currentDropOperation === DropDirection.LEFT) {
|
||||||
|
const fromIndex = this.panes.findIndex(p => p.id === dropData.id);
|
||||||
|
let toIndex = this.panes.findIndex(p => p.id === pane.id);
|
||||||
|
|
||||||
|
if (fromIndex >= 0 && toIndex >= 0) {
|
||||||
|
if (fromIndex < toIndex) {
|
||||||
|
toIndex--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toIndex >= 0 && toIndex !== fromIndex) {
|
||||||
|
this.movePane(this.panes[fromIndex], this.panes[toIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
overlay?.dispose();
|
||||||
|
overlay = undefined;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class BrowserMain extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private restoreBaseTheme(): void {
|
private restoreBaseTheme(): void {
|
||||||
addClass(this.domElement, window.localStorage.getItem('vscode.baseTheme') || getThemeTypeSelector(DARK));
|
addClass(this.domElement, window.localStorage.getItem('vscode.baseTheme') || getThemeTypeSelector(LIGHT) /* Fallback to a light theme by default on web */);
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveBaseTheme(): void {
|
private saveBaseTheme(): void {
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.monaco-workbench .simple-fr-find-part-wrapper {
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 10;
|
||||||
|
position: absolute;
|
||||||
|
top: -45px;
|
||||||
|
right: 18px;
|
||||||
|
width: 318px;
|
||||||
|
max-width: calc(100% - 28px - 28px - 8px);
|
||||||
|
pointer-events: none;
|
||||||
|
transition: top 200ms linear;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .simple-fr-find-part {
|
||||||
|
/* visibility: hidden; Use visibility to maintain flex layout while hidden otherwise interferes with transition */
|
||||||
|
z-index: 10;
|
||||||
|
position: relative;
|
||||||
|
top: 0px;
|
||||||
|
display: flex;
|
||||||
|
padding: 4px;
|
||||||
|
align-items: center;
|
||||||
|
pointer-events: all;
|
||||||
|
margin: 0 0 0 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .simple-fr-replace-part {
|
||||||
|
/* visibility: hidden; Use visibility to maintain flex layout while hidden otherwise interferes with transition */
|
||||||
|
z-index: 10;
|
||||||
|
position: relative;
|
||||||
|
top: 0px;
|
||||||
|
display: flex;
|
||||||
|
padding: 4px;
|
||||||
|
align-items: center;
|
||||||
|
pointer-events: all;
|
||||||
|
margin: 0 0 0 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .simple-fr-find-part-wrapper .find-replace-progress {
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .simple-fr-find-part-wrapper .find-replace-progress .monaco-progress-container {
|
||||||
|
height: 2px;
|
||||||
|
top: 0px !important;
|
||||||
|
z-index: 100 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .simple-fr-find-part-wrapper .find-replace-progress .monaco-progress-container .progress-bit {
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .simple-fr-find-part-wrapper .monaco-findInput {
|
||||||
|
width: 224px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .simple-fr-find-part-wrapper .button {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
flex: initial;
|
||||||
|
margin-left: 3px;
|
||||||
|
background-position: 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .simple-fr-find-part-wrapper.visible .simple-fr-find-part {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .simple-fr-find-part-wrapper .toggle {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 18px;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 0px;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .simple-fr-find-part-wrapper.visible {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .simple-fr-find-part-wrapper.visible-transition {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .simple-fr-find-part .monaco-findInput {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .simple-fr-find-part .button {
|
||||||
|
min-width: 20px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex: initial;
|
||||||
|
margin-left: 3px;
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .simple-fr-find-part .button.disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
@@ -0,0 +1,427 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import 'vs/css!./simpleFindReplaceWidget';
|
||||||
|
import * as nls from 'vs/nls';
|
||||||
|
import * as dom from 'vs/base/browser/dom';
|
||||||
|
import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput';
|
||||||
|
import { Widget } from 'vs/base/browser/ui/widget';
|
||||||
|
import { Delayer } from 'vs/base/common/async';
|
||||||
|
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||||
|
import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState';
|
||||||
|
import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||||
|
import { SimpleButton } from 'vs/editor/contrib/find/findWidget';
|
||||||
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||||
|
import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||||
|
import { IColorTheme, registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget';
|
||||||
|
import { ReplaceInput, IReplaceInputStyles } from 'vs/base/browser/ui/findinput/replaceInput';
|
||||||
|
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
||||||
|
import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
|
||||||
|
|
||||||
|
const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find");
|
||||||
|
const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find");
|
||||||
|
const NLS_PREVIOUS_MATCH_BTN_LABEL = nls.localize('label.previousMatchButton', "Previous match");
|
||||||
|
const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next match");
|
||||||
|
const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");
|
||||||
|
const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace mode");
|
||||||
|
const NLS_REPLACE_INPUT_LABEL = nls.localize('label.replace', "Replace");
|
||||||
|
const NLS_REPLACE_INPUT_PLACEHOLDER = nls.localize('placeholder.replace', "Replace");
|
||||||
|
const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace");
|
||||||
|
const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All");
|
||||||
|
|
||||||
|
export abstract class SimpleFindReplaceWidget extends Widget {
|
||||||
|
protected readonly _findInput: FindInput;
|
||||||
|
private readonly _domNode: HTMLElement;
|
||||||
|
private readonly _innerFindDomNode: HTMLElement;
|
||||||
|
private readonly _focusTracker: dom.IFocusTracker;
|
||||||
|
private readonly _findInputFocusTracker: dom.IFocusTracker;
|
||||||
|
private readonly _updateHistoryDelayer: Delayer<void>;
|
||||||
|
private readonly prevBtn: SimpleButton;
|
||||||
|
private readonly nextBtn: SimpleButton;
|
||||||
|
|
||||||
|
private readonly _replaceInput!: ReplaceInput;
|
||||||
|
private readonly _innerReplaceDomNode!: HTMLElement;
|
||||||
|
private _toggleReplaceBtn!: SimpleButton;
|
||||||
|
private readonly _replaceInputFocusTracker!: dom.IFocusTracker;
|
||||||
|
private _replaceBtn!: SimpleButton;
|
||||||
|
private _replaceAllBtn!: SimpleButton;
|
||||||
|
|
||||||
|
|
||||||
|
private _isVisible: boolean = false;
|
||||||
|
private _isReplaceVisible: boolean = false;
|
||||||
|
private foundMatch: boolean = false;
|
||||||
|
|
||||||
|
protected _progressBar!: ProgressBar;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IContextViewService private readonly _contextViewService: IContextViewService,
|
||||||
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
|
@IThemeService private readonly _themeService: IThemeService,
|
||||||
|
private readonly _state: FindReplaceState = new FindReplaceState(),
|
||||||
|
showOptionButtons?: boolean
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._domNode = document.createElement('div');
|
||||||
|
this._domNode.classList.add('simple-fr-find-part-wrapper');
|
||||||
|
this._register(this._state.onFindReplaceStateChange((e) => this._onStateChanged(e)));
|
||||||
|
|
||||||
|
let progressContainer = dom.$('.find-replace-progress');
|
||||||
|
this._progressBar = new ProgressBar(progressContainer);
|
||||||
|
this._register(attachProgressBarStyler(this._progressBar, this._themeService));
|
||||||
|
this._domNode.appendChild(progressContainer);
|
||||||
|
|
||||||
|
// Toggle replace button
|
||||||
|
this._toggleReplaceBtn = this._register(new SimpleButton({
|
||||||
|
label: NLS_TOGGLE_REPLACE_MODE_BTN_LABEL,
|
||||||
|
className: 'codicon toggle left',
|
||||||
|
onTrigger: () => {
|
||||||
|
this._isReplaceVisible = !this._isReplaceVisible;
|
||||||
|
this._state.change({ isReplaceRevealed: this._isReplaceVisible }, false);
|
||||||
|
if (this._isReplaceVisible) {
|
||||||
|
this._innerReplaceDomNode.style.display = 'flex';
|
||||||
|
} else {
|
||||||
|
this._innerReplaceDomNode.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
this._toggleReplaceBtn.toggleClass('codicon-chevron-down', this._isReplaceVisible);
|
||||||
|
this._toggleReplaceBtn.toggleClass('codicon-chevron-right', !this._isReplaceVisible);
|
||||||
|
this._toggleReplaceBtn.setExpanded(this._isReplaceVisible);
|
||||||
|
this._domNode.appendChild(this._toggleReplaceBtn.domNode);
|
||||||
|
|
||||||
|
|
||||||
|
this._innerFindDomNode = document.createElement('div');
|
||||||
|
this._innerFindDomNode.classList.add('simple-fr-find-part');
|
||||||
|
|
||||||
|
this._findInput = this._register(new ContextScopedFindInput(null, this._contextViewService, {
|
||||||
|
label: NLS_FIND_INPUT_LABEL,
|
||||||
|
placeholder: NLS_FIND_INPUT_PLACEHOLDER,
|
||||||
|
validation: (value: string): InputBoxMessage | null => {
|
||||||
|
if (value.length === 0 || !this._findInput.getRegex()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
new RegExp(value);
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
this.foundMatch = false;
|
||||||
|
this.updateButtons(this.foundMatch);
|
||||||
|
return { content: e.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, contextKeyService, showOptionButtons));
|
||||||
|
|
||||||
|
// Find History with update delayer
|
||||||
|
this._updateHistoryDelayer = new Delayer<void>(500);
|
||||||
|
|
||||||
|
this.oninput(this._findInput.domNode, (e) => {
|
||||||
|
this.foundMatch = this.onInputChanged();
|
||||||
|
this.updateButtons(this.foundMatch);
|
||||||
|
this._delayedUpdateHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._findInput.setRegex(!!this._state.isRegex);
|
||||||
|
this._findInput.setCaseSensitive(!!this._state.matchCase);
|
||||||
|
this._findInput.setWholeWords(!!this._state.wholeWord);
|
||||||
|
|
||||||
|
this._register(this._findInput.onDidOptionChange(() => {
|
||||||
|
this._state.change({
|
||||||
|
isRegex: this._findInput.getRegex(),
|
||||||
|
wholeWord: this._findInput.getWholeWords(),
|
||||||
|
matchCase: this._findInput.getCaseSensitive()
|
||||||
|
}, true);
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(this._state.onFindReplaceStateChange(() => {
|
||||||
|
this._findInput.setRegex(this._state.isRegex);
|
||||||
|
this._findInput.setWholeWords(this._state.wholeWord);
|
||||||
|
this._findInput.setCaseSensitive(this._state.matchCase);
|
||||||
|
this.findFirst();
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.prevBtn = this._register(new SimpleButton({
|
||||||
|
label: NLS_PREVIOUS_MATCH_BTN_LABEL,
|
||||||
|
className: 'codicon codicon-arrow-up',
|
||||||
|
onTrigger: () => {
|
||||||
|
this.find(true);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.nextBtn = this._register(new SimpleButton({
|
||||||
|
label: NLS_NEXT_MATCH_BTN_LABEL,
|
||||||
|
className: 'codicon codicon-arrow-down',
|
||||||
|
onTrigger: () => {
|
||||||
|
this.find(false);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const closeBtn = this._register(new SimpleButton({
|
||||||
|
label: NLS_CLOSE_BTN_LABEL,
|
||||||
|
className: 'codicon codicon-close',
|
||||||
|
onTrigger: () => {
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._innerFindDomNode.appendChild(this._findInput.domNode);
|
||||||
|
this._innerFindDomNode.appendChild(this.prevBtn.domNode);
|
||||||
|
this._innerFindDomNode.appendChild(this.nextBtn.domNode);
|
||||||
|
this._innerFindDomNode.appendChild(closeBtn.domNode);
|
||||||
|
|
||||||
|
// _domNode wraps _innerDomNode, ensuring that
|
||||||
|
this._domNode.appendChild(this._innerFindDomNode);
|
||||||
|
|
||||||
|
this.onkeyup(this._innerFindDomNode, e => {
|
||||||
|
if (e.equals(KeyCode.Escape)) {
|
||||||
|
this.hide();
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._focusTracker = this._register(dom.trackFocus(this._innerFindDomNode));
|
||||||
|
this._register(this._focusTracker.onDidFocus(this.onFocusTrackerFocus.bind(this)));
|
||||||
|
this._register(this._focusTracker.onDidBlur(this.onFocusTrackerBlur.bind(this)));
|
||||||
|
|
||||||
|
this._findInputFocusTracker = this._register(dom.trackFocus(this._findInput.domNode));
|
||||||
|
this._register(this._findInputFocusTracker.onDidFocus(this.onFindInputFocusTrackerFocus.bind(this)));
|
||||||
|
this._register(this._findInputFocusTracker.onDidBlur(this.onFindInputFocusTrackerBlur.bind(this)));
|
||||||
|
|
||||||
|
this._register(dom.addDisposableListener(this._innerFindDomNode, 'click', (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Replace
|
||||||
|
this._innerReplaceDomNode = document.createElement('div');
|
||||||
|
this._innerReplaceDomNode.classList.add('simple-fr-replace-part');
|
||||||
|
|
||||||
|
this._replaceInput = this._register(new ContextScopedReplaceInput(null, undefined, {
|
||||||
|
label: NLS_REPLACE_INPUT_LABEL,
|
||||||
|
placeholder: NLS_REPLACE_INPUT_PLACEHOLDER,
|
||||||
|
history: []
|
||||||
|
}, contextKeyService, false));
|
||||||
|
this._innerReplaceDomNode.appendChild(this._replaceInput.domNode);
|
||||||
|
this._replaceInputFocusTracker = this._register(dom.trackFocus(this._replaceInput.domNode));
|
||||||
|
this._register(this._replaceInputFocusTracker.onDidFocus(this.onReplaceInputFocusTrackerFocus.bind(this)));
|
||||||
|
this._register(this._replaceInputFocusTracker.onDidBlur(this.onReplaceInputFocusTrackerBlur.bind(this)));
|
||||||
|
|
||||||
|
this._domNode.appendChild(this._innerReplaceDomNode);
|
||||||
|
|
||||||
|
if (this._isReplaceVisible) {
|
||||||
|
this._innerReplaceDomNode.style.display = 'flex';
|
||||||
|
} else {
|
||||||
|
this._innerReplaceDomNode.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
this._replaceBtn = this._register(new SimpleButton({
|
||||||
|
label: NLS_REPLACE_BTN_LABEL,
|
||||||
|
className: 'codicon codicon-replace',
|
||||||
|
onTrigger: () => {
|
||||||
|
this.replaceOne();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Replace all button
|
||||||
|
this._replaceAllBtn = this._register(new SimpleButton({
|
||||||
|
label: NLS_REPLACE_ALL_BTN_LABEL,
|
||||||
|
className: 'codicon codicon-replace-all',
|
||||||
|
onTrigger: () => {
|
||||||
|
this.replaceAll();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._innerReplaceDomNode.appendChild(this._replaceBtn.domNode);
|
||||||
|
this._innerReplaceDomNode.appendChild(this._replaceAllBtn.domNode);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract onInputChanged(): boolean;
|
||||||
|
protected abstract find(previous: boolean): void;
|
||||||
|
protected abstract findFirst(): void;
|
||||||
|
protected abstract replaceOne(): void;
|
||||||
|
protected abstract replaceAll(): void;
|
||||||
|
protected abstract onFocusTrackerFocus(): void;
|
||||||
|
protected abstract onFocusTrackerBlur(): void;
|
||||||
|
protected abstract onFindInputFocusTrackerFocus(): void;
|
||||||
|
protected abstract onFindInputFocusTrackerBlur(): void;
|
||||||
|
protected abstract onReplaceInputFocusTrackerFocus(): void;
|
||||||
|
protected abstract onReplaceInputFocusTrackerBlur(): void;
|
||||||
|
|
||||||
|
protected get inputValue() {
|
||||||
|
return this._findInput.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get replaceValue() {
|
||||||
|
return this._replaceInput.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get focusTracker(): dom.IFocusTracker {
|
||||||
|
return this._focusTracker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateTheme(theme: IColorTheme): void {
|
||||||
|
const inputStyles: IFindInputStyles = {
|
||||||
|
inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder),
|
||||||
|
inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground),
|
||||||
|
inputBackground: theme.getColor(inputBackground),
|
||||||
|
inputForeground: theme.getColor(inputForeground),
|
||||||
|
inputBorder: theme.getColor(inputBorder),
|
||||||
|
inputValidationInfoBackground: theme.getColor(inputValidationInfoBackground),
|
||||||
|
inputValidationInfoForeground: theme.getColor(inputValidationInfoForeground),
|
||||||
|
inputValidationInfoBorder: theme.getColor(inputValidationInfoBorder),
|
||||||
|
inputValidationWarningBackground: theme.getColor(inputValidationWarningBackground),
|
||||||
|
inputValidationWarningForeground: theme.getColor(inputValidationWarningForeground),
|
||||||
|
inputValidationWarningBorder: theme.getColor(inputValidationWarningBorder),
|
||||||
|
inputValidationErrorBackground: theme.getColor(inputValidationErrorBackground),
|
||||||
|
inputValidationErrorForeground: theme.getColor(inputValidationErrorForeground),
|
||||||
|
inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder)
|
||||||
|
};
|
||||||
|
this._findInput.style(inputStyles);
|
||||||
|
const replaceStyles: IReplaceInputStyles = {
|
||||||
|
inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder),
|
||||||
|
inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground),
|
||||||
|
inputBackground: theme.getColor(inputBackground),
|
||||||
|
inputForeground: theme.getColor(inputForeground),
|
||||||
|
inputBorder: theme.getColor(inputBorder),
|
||||||
|
inputValidationInfoBackground: theme.getColor(inputValidationInfoBackground),
|
||||||
|
inputValidationInfoForeground: theme.getColor(inputValidationInfoForeground),
|
||||||
|
inputValidationInfoBorder: theme.getColor(inputValidationInfoBorder),
|
||||||
|
inputValidationWarningBackground: theme.getColor(inputValidationWarningBackground),
|
||||||
|
inputValidationWarningForeground: theme.getColor(inputValidationWarningForeground),
|
||||||
|
inputValidationWarningBorder: theme.getColor(inputValidationWarningBorder),
|
||||||
|
inputValidationErrorBackground: theme.getColor(inputValidationErrorBackground),
|
||||||
|
inputValidationErrorForeground: theme.getColor(inputValidationErrorForeground),
|
||||||
|
inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder)
|
||||||
|
};
|
||||||
|
this._replaceInput.style(replaceStyles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onStateChanged(e: FindReplaceStateChangedEvent): void {
|
||||||
|
this._updateButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateButtons(): void {
|
||||||
|
this._findInput.setEnabled(this._isVisible);
|
||||||
|
this._replaceInput.setEnabled(this._isVisible && this._isReplaceVisible);
|
||||||
|
let findInputIsNonEmpty = (this._state.searchString.length > 0);
|
||||||
|
this._replaceBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty);
|
||||||
|
this._replaceAllBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty);
|
||||||
|
|
||||||
|
dom.toggleClass(this._domNode, 'replaceToggled', this._isReplaceVisible);
|
||||||
|
this._toggleReplaceBtn.toggleClass('codicon-chevron-right', !this._isReplaceVisible);
|
||||||
|
this._toggleReplaceBtn.toggleClass('codicon-chevron-down', this._isReplaceVisible);
|
||||||
|
this._toggleReplaceBtn.setExpanded(this._isReplaceVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
super.dispose();
|
||||||
|
|
||||||
|
if (this._domNode && this._domNode.parentElement) {
|
||||||
|
this._domNode.parentElement.removeChild(this._domNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDomNode() {
|
||||||
|
return this._domNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public reveal(initialInput?: string): void {
|
||||||
|
if (initialInput) {
|
||||||
|
this._findInput.setValue(initialInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._isVisible) {
|
||||||
|
this._findInput.select();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._isVisible = true;
|
||||||
|
this.updateButtons(this.foundMatch);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
dom.addClass(this._domNode, 'visible');
|
||||||
|
dom.addClass(this._domNode, 'visible-transition');
|
||||||
|
this._domNode.setAttribute('aria-hidden', 'false');
|
||||||
|
this._findInput.select();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public show(initialInput?: string): void {
|
||||||
|
if (initialInput && !this._isVisible) {
|
||||||
|
this._findInput.setValue(initialInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._isVisible = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
dom.addClass(this._domNode, 'visible');
|
||||||
|
dom.addClass(this._domNode, 'visible-transition');
|
||||||
|
this._domNode.setAttribute('aria-hidden', 'false');
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public hide(): void {
|
||||||
|
if (this._isVisible) {
|
||||||
|
dom.removeClass(this._domNode, 'visible-transition');
|
||||||
|
this._domNode.setAttribute('aria-hidden', 'true');
|
||||||
|
// Need to delay toggling visibility until after Transition, then visibility hidden - removes from tabIndex list
|
||||||
|
setTimeout(() => {
|
||||||
|
this._isVisible = false;
|
||||||
|
this.updateButtons(this.foundMatch);
|
||||||
|
dom.removeClass(this._domNode, 'visible');
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _delayedUpdateHistory() {
|
||||||
|
this._updateHistoryDelayer.trigger(this._updateHistory.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _updateHistory() {
|
||||||
|
this._findInput.inputBox.addToHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _getRegexValue(): boolean {
|
||||||
|
return this._findInput.getRegex();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _getWholeWordValue(): boolean {
|
||||||
|
return this._findInput.getWholeWords();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _getCaseSensitiveValue(): boolean {
|
||||||
|
return this._findInput.getCaseSensitive();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updateButtons(foundMatch: boolean) {
|
||||||
|
const hasInput = this.inputValue.length > 0;
|
||||||
|
this.prevBtn.setEnabled(this._isVisible && hasInput && foundMatch);
|
||||||
|
this.nextBtn.setEnabled(this._isVisible && hasInput && foundMatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// theming
|
||||||
|
registerThemingParticipant((theme, collector) => {
|
||||||
|
const findWidgetBGColor = theme.getColor(editorWidgetBackground);
|
||||||
|
if (findWidgetBGColor) {
|
||||||
|
collector.addRule(`.monaco-workbench .simple-fr-find-part-wrapper { background-color: ${findWidgetBGColor} !important; }`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const widgetForeground = theme.getColor(editorWidgetForeground);
|
||||||
|
if (widgetForeground) {
|
||||||
|
collector.addRule(`.monaco-workbench .simple-fr-find-part-wrapper { color: ${widgetForeground}; }`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const widgetShadowColor = theme.getColor(widgetShadow);
|
||||||
|
if (widgetShadowColor) {
|
||||||
|
collector.addRule(`.monaco-workbench .simple-fr-find-part-wrapper { box-shadow: 0 2px 8px ${widgetShadowColor}; }`);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -54,6 +54,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
|||||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||||
import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess';
|
import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess';
|
||||||
import { StartDebugQuickAccessProvider } from 'vs/workbench/contrib/debug/browser/debugQuickAccess';
|
import { StartDebugQuickAccessProvider } from 'vs/workbench/contrib/debug/browser/debugQuickAccess';
|
||||||
|
import { DebugProgressContribution } from 'vs/workbench/contrib/debug/browser/debugProgress';
|
||||||
|
|
||||||
class OpenDebugViewletAction extends ShowViewletAction {
|
class OpenDebugViewletAction extends ShowViewletAction {
|
||||||
public static readonly ID = VIEWLET_ID;
|
public static readonly ID = VIEWLET_ID;
|
||||||
@@ -298,6 +299,7 @@ configurationRegistry.registerConfiguration({
|
|||||||
|
|
||||||
// Register Debug Status
|
// Register Debug Status
|
||||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugStatusContribution, LifecyclePhase.Eventually);
|
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugStatusContribution, LifecyclePhase.Eventually);
|
||||||
|
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugProgressContribution, LifecyclePhase.Eventually);
|
||||||
|
|
||||||
// Debug toolbar
|
// Debug toolbar
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { Event } from 'vs/base/common/event';
|
||||||
|
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||||
|
import { IDebugService, VIEWLET_ID, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
|
||||||
|
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||||
|
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||||
|
|
||||||
|
export class DebugProgressContribution implements IWorkbenchContribution {
|
||||||
|
|
||||||
|
private toDispose: IDisposable[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IDebugService private readonly debugService: IDebugService,
|
||||||
|
@IProgressService private readonly progressService: IProgressService
|
||||||
|
) {
|
||||||
|
let progressListener: IDisposable;
|
||||||
|
const onFocusSession = (session: IDebugSession | undefined) => {
|
||||||
|
if (progressListener) {
|
||||||
|
progressListener.dispose();
|
||||||
|
}
|
||||||
|
if (session) {
|
||||||
|
progressListener = session.onDidProgressStart(async progressStartEvent => {
|
||||||
|
const promise = new Promise<void>(r => {
|
||||||
|
// Show progress until a progress end event comes or the session ends
|
||||||
|
const listener = Event.any(Event.filter(session.onDidProgressEnd, e => e.body.progressId === progressStartEvent.body.progressId),
|
||||||
|
session.onDidEndAdapter)(() => {
|
||||||
|
listener.dispose();
|
||||||
|
r();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.progressService.withProgress({ location: VIEWLET_ID }, () => promise);
|
||||||
|
this.progressService.withProgress({
|
||||||
|
location: ProgressLocation.Notification,
|
||||||
|
title: progressStartEvent.body.title,
|
||||||
|
cancellable: progressStartEvent.body.cancellable,
|
||||||
|
silent: true
|
||||||
|
}, () => promise, () => session.cancel(progressStartEvent.body.progressId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.toDispose.push(this.debugService.getViewModel().onDidFocusSession(onFocusSession));
|
||||||
|
onFocusSession(this.debugService.getViewModel().focusedSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
dispose(this.toDispose);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -603,6 +603,14 @@ export class DebugSession implements IDebugSession {
|
|||||||
}, token);
|
}, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async cancel(progressId: string): Promise<DebugProtocol.CancelResponse> {
|
||||||
|
if (!this.raw) {
|
||||||
|
return Promise.reject(new Error(localize('noDebugAdapter', "No debug adapter, can not send '{0}'", 'cancel')));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.raw.cancel({ progressId });
|
||||||
|
}
|
||||||
|
|
||||||
//---- threads
|
//---- threads
|
||||||
|
|
||||||
getThread(threadId: number): Thread | undefined {
|
getThread(threadId: number): Thread | undefined {
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer {
|
|||||||
if (CONTEXT_DEBUG_UX.getValue(this.contextKeyService) === 'simple') {
|
if (CONTEXT_DEBUG_UX.getValue(this.contextKeyService) === 'simple') {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.showInitialDebugActions) {
|
if (!this.showInitialDebugActions) {
|
||||||
|
|
||||||
if (!this.debugToolBarMenu) {
|
if (!this.debugToolBarMenu) {
|
||||||
@@ -185,7 +186,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state === State.Initializing) {
|
if (state === State.Initializing) {
|
||||||
this.progressService.withProgress({ location: VIEWLET_ID }, _progress => {
|
this.progressService.withProgress({ location: VIEWLET_ID, }, _progress => {
|
||||||
return new Promise(resolve => this.progressResolve = resolve);
|
return new Promise(resolve => this.progressResolve = resolve);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ export interface IDebugSession extends ITreeElement {
|
|||||||
variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise<DebugProtocol.VariablesResponse>;
|
variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise<DebugProtocol.VariablesResponse>;
|
||||||
evaluate(expression: string, frameId?: number, context?: string): Promise<DebugProtocol.EvaluateResponse>;
|
evaluate(expression: string, frameId?: number, context?: string): Promise<DebugProtocol.EvaluateResponse>;
|
||||||
customRequest(request: string, args: any): Promise<DebugProtocol.Response>;
|
customRequest(request: string, args: any): Promise<DebugProtocol.Response>;
|
||||||
|
cancel(progressId: string): Promise<DebugProtocol.CancelResponse>;
|
||||||
|
|
||||||
restartFrame(frameId: number, threadId: number): Promise<void>;
|
restartFrame(frameId: number, threadId: number): Promise<void>;
|
||||||
next(threadId: number): Promise<void>;
|
next(threadId: number): Promise<void>;
|
||||||
|
|||||||
@@ -72,13 +72,12 @@ declare module DebugProtocol {
|
|||||||
/** Cancel request; value of command field is 'cancel'.
|
/** Cancel request; value of command field is 'cancel'.
|
||||||
The 'cancel' request is used by the frontend in two situations:
|
The 'cancel' request is used by the frontend in two situations:
|
||||||
- to indicate that it is no longer interested in the result produced by a specific request issued earlier
|
- to indicate that it is no longer interested in the result produced by a specific request issued earlier
|
||||||
- to cancel a progress indicator.
|
- to cancel a progress sequence.
|
||||||
This request has a hint characteristic: a debug adapter can only be expected to make a 'best effort' in honouring this request but there are no guarantees.
|
This request has a hint characteristic: a debug adapter can only be expected to make a 'best effort' in honouring this request but there are no guarantees.
|
||||||
The 'cancel' request may return an error if it could not cancel an operation but a frontend should refrain from presenting this error to end users.
|
The 'cancel' request may return an error if it could not cancel an operation but a frontend should refrain from presenting this error to end users.
|
||||||
A frontend client should only call this request if the capability 'supportsCancelRequest' is true.
|
A frontend client should only call this request if the capability 'supportsCancelRequest' is true.
|
||||||
The request that got canceled still needs to send a response back.
|
The request that got canceled still needs to send a response back. This can either be a normal result ('success' attribute true) or an error response ('success' attribute false and the 'message' set to 'cancelled'). Returning partial results from a cancelled request is possible but please note that a frontend client has no generic way for detecting that a response is partial or not.
|
||||||
This can either be a normal result ('success' attribute true) or an error response ('success' attribute false and the 'message' set to 'cancelled').
|
The progress that got cancelled still needs to send a 'progressEnd' event back. A client should not assume that progress just got cancelled after sending the 'cancel' request.
|
||||||
Returning partial results from a cancelled request is possible but please note that a frontend client has no generic way for detecting that a response is partial or not.
|
|
||||||
*/
|
*/
|
||||||
export interface CancelRequest extends Request {
|
export interface CancelRequest extends Request {
|
||||||
// command: 'cancel';
|
// command: 'cancel';
|
||||||
|
|||||||
@@ -134,6 +134,10 @@ export class MockDebugService implements IDebugService {
|
|||||||
|
|
||||||
export class MockSession implements IDebugSession {
|
export class MockSession implements IDebugSession {
|
||||||
|
|
||||||
|
cancel(_progressId: string): Promise<DebugProtocol.CancelResponse> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
breakpointsLocations(uri: uri, lineNumber: number): Promise<IPosition[]> {
|
breakpointsLocations(uri: uri, lineNumber: number): Promise<IPosition[]> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import 'vs/css!./media/fileactions';
|
|
||||||
import * as nls from 'vs/nls';
|
import * as nls from 'vs/nls';
|
||||||
import { isWindows, isWeb } from 'vs/base/common/platform';
|
import { isWindows, isWeb } from 'vs/base/common/platform';
|
||||||
import * as extpath from 'vs/base/common/extpath';
|
import * as extpath from 'vs/base/common/extpath';
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-close::before {
|
||||||
|
content: "\ea71"; /* Close icon flips between black dot and "X" for dirty open editors */
|
||||||
|
}
|
||||||
|
|
||||||
.open-editors .monaco-list .monaco-list-row > .monaco-action-bar .action-close-all-files,
|
.open-editors .monaco-list .monaco-list-row > .monaco-action-bar .action-close-all-files,
|
||||||
.open-editors .monaco-list .monaco-list-row > .monaco-action-bar .save-all {
|
.open-editors .monaco-list .monaco-list-row > .monaco-action-bar .save-all {
|
||||||
width: 23px;
|
width: 23px;
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
export const INSERT_CODE_CELL_ABOVE_COMMAND_ID = 'workbench.notebook.code.insertCellAbove';
|
||||||
|
export const INSERT_CODE_CELL_BELOW_COMMAND_ID = 'workbench.notebook.code.insertCellBelow';
|
||||||
|
export const INSERT_MARKDOWN_CELL_ABOVE_COMMAND_ID = 'workbench.notebook.markdown.insertCellAbove';
|
||||||
|
export const INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID = 'workbench.notebook.markdown.insertCellAbove';
|
||||||
|
|
||||||
|
export const EDIT_CELL_COMMAND_ID = 'workbench.notebook.cell.edit';
|
||||||
|
export const SAVE_CELL_COMMAND_ID = 'workbench.notebook.cell.save';
|
||||||
|
export const DELETE_CELL_COMMAND_ID = 'workbench.notebook.cell.delete';
|
||||||
|
|
||||||
|
export const MOVE_CELL_UP_COMMAND_ID = 'workbench.notebook.cell.moveUp';
|
||||||
|
export const MOVE_CELL_DOWN_COMMAND_ID = 'workbench.notebook.cell.moveDown';
|
||||||
|
export const COPY_CELL_UP_COMMAND_ID = 'workbench.notebook.cell.copyUp';
|
||||||
|
export const COPY_CELL_DOWN_COMMAND_ID = 'workbench.notebook.cell.copyDown';
|
||||||
|
|
||||||
|
export const EXECUTE_CELL_COMMAND_ID = 'workbench.notebook.cell.execute';
|
||||||
|
|
||||||
|
// Cell sizing related
|
||||||
|
export const CELL_MARGIN = 32;
|
||||||
|
export const EDITOR_TOP_PADDING = 8;
|
||||||
|
export const EDITOR_BOTTOM_PADDING = 8;
|
||||||
|
export const EDITOR_TOOLBAR_HEIGHT = 22;
|
||||||
@@ -0,0 +1,956 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import { Action2, IAction2Options, MenuId, MenuItemAction, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||||
|
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||||
|
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { InputFocusedContext, InputFocusedContextKey, IsDevelopmentContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||||
|
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||||
|
import { DELETE_CELL_COMMAND_ID, EDIT_CELL_COMMAND_ID, INSERT_CODE_CELL_ABOVE_COMMAND_ID, INSERT_CODE_CELL_BELOW_COMMAND_ID, INSERT_MARKDOWN_CELL_ABOVE_COMMAND_ID, INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID, MOVE_CELL_DOWN_COMMAND_ID, MOVE_CELL_UP_COMMAND_ID, SAVE_CELL_COMMAND_ID, COPY_CELL_UP_COMMAND_ID, COPY_CELL_DOWN_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/constants';
|
||||||
|
import { INotebookEditor, KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, NOTEBOOK_EDITOR_FOCUSED, ICellViewModel, CellState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||||
|
import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService';
|
||||||
|
import { CellKind, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
|
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.action.executeNotebookCell',
|
||||||
|
title: localize('notebookActions.execute', "Execute Notebook Cell"),
|
||||||
|
keybinding: {
|
||||||
|
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext),
|
||||||
|
primary: KeyMod.WinCtrl | KeyCode.Enter,
|
||||||
|
win: {
|
||||||
|
primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Enter
|
||||||
|
},
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor): Promise<void> {
|
||||||
|
runActiveCell(accessor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.action.executeNotebookCellSelectBelow',
|
||||||
|
title: localize('notebookActions.executeAndSelectBelow', "Execute Notebook Cell and Select Below"),
|
||||||
|
keybinding: {
|
||||||
|
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext),
|
||||||
|
primary: KeyMod.Shift | KeyCode.Enter,
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor): Promise<void> {
|
||||||
|
const editorService = accessor.get(IEditorService);
|
||||||
|
const activeCell = runActiveCell(accessor);
|
||||||
|
if (!activeCell) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const editor = getActiveNotebookEditor(editorService);
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const idx = editor.viewModel?.getViewCellIndex(activeCell);
|
||||||
|
if (typeof idx !== 'number') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to select below, fall back on inserting
|
||||||
|
const nextCell = editor.viewModel?.viewCells[idx + 1];
|
||||||
|
if (nextCell) {
|
||||||
|
editor.focusNotebookCell(nextCell, false);
|
||||||
|
} else {
|
||||||
|
await editor.insertNotebookCell(activeCell, CellKind.Code, 'below');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.action.executeNotebookCellInsertBelow',
|
||||||
|
title: localize('notebookActions.executeAndInsertBelow', "Execute Notebook Cell and Insert Below"),
|
||||||
|
keybinding: {
|
||||||
|
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext),
|
||||||
|
primary: KeyMod.Alt | KeyCode.Enter,
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor): Promise<void> {
|
||||||
|
const editorService = accessor.get(IEditorService);
|
||||||
|
const activeCell = runActiveCell(accessor);
|
||||||
|
if (!activeCell) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const editor = getActiveNotebookEditor(editorService);
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await editor.insertNotebookCell(activeCell, CellKind.Code, 'below');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.action.executeNotebook',
|
||||||
|
title: localize('notebookActions.executeNotebook', "Execute Notebook")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor): Promise<void> {
|
||||||
|
let editorService = accessor.get(IEditorService);
|
||||||
|
let notebookService = accessor.get(INotebookService);
|
||||||
|
|
||||||
|
let resource = editorService.activeEditor?.resource;
|
||||||
|
|
||||||
|
if (!resource) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let notebookProviders = notebookService.getContributedNotebookProviders(resource!);
|
||||||
|
|
||||||
|
if (notebookProviders.length > 0) {
|
||||||
|
let viewType = notebookProviders[0].id;
|
||||||
|
notebookService.executeNotebook(viewType, resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.action.quitNotebookEdit',
|
||||||
|
title: localize('notebookActions.quitEditing', "Quit Notebook Cell Editing"),
|
||||||
|
keybinding: {
|
||||||
|
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext),
|
||||||
|
primary: KeyCode.Escape,
|
||||||
|
weight: KeybindingWeight.EditorContrib - 5
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor): Promise<void> {
|
||||||
|
let editorService = accessor.get(IEditorService);
|
||||||
|
let editor = getActiveNotebookEditor(editorService);
|
||||||
|
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let activeCell = editor.getActiveCell();
|
||||||
|
if (activeCell) {
|
||||||
|
if (activeCell.cellKind === CellKind.Markdown) {
|
||||||
|
activeCell.state = CellState.Preview;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.focusNotebookCell(activeCell, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.action.notebook.hideFind',
|
||||||
|
title: localize('notebookActions.hideFind', "Hide Find in Notebook"),
|
||||||
|
keybinding: {
|
||||||
|
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED),
|
||||||
|
primary: KeyCode.Escape,
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor): Promise<void> {
|
||||||
|
let editorService = accessor.get(IEditorService);
|
||||||
|
let editor = getActiveNotebookEditor(editorService);
|
||||||
|
|
||||||
|
editor?.hideFind();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.action.notebook.find',
|
||||||
|
title: localize('notebookActions.findInNotebook', "Find in Notebook"),
|
||||||
|
keybinding: {
|
||||||
|
when: NOTEBOOK_EDITOR_FOCUSED,
|
||||||
|
primary: KeyCode.KEY_F | KeyMod.CtrlCmd,
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor): Promise<void> {
|
||||||
|
let editorService = accessor.get(IEditorService);
|
||||||
|
let editor = getActiveNotebookEditor(editorService);
|
||||||
|
|
||||||
|
editor?.showFind();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||||
|
command: {
|
||||||
|
id: 'workbench.action.executeNotebook',
|
||||||
|
title: localize('notebookActions.menu.executeNotebook', "Execute Notebook (Run all cells)"),
|
||||||
|
icon: { id: 'codicon/debug-start' }
|
||||||
|
},
|
||||||
|
order: -1,
|
||||||
|
group: 'navigation',
|
||||||
|
when: NOTEBOOK_EDITOR_FOCUSED
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||||
|
command: {
|
||||||
|
id: 'workbench.action.executeNotebookCell',
|
||||||
|
title: localize('notebookActions.menu.execute', "Execute Notebook Cell"),
|
||||||
|
icon: { id: 'codicon/debug-continue' }
|
||||||
|
},
|
||||||
|
order: -1,
|
||||||
|
group: 'navigation',
|
||||||
|
when: NOTEBOOK_EDITOR_FOCUSED
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.action.changeCellToCode',
|
||||||
|
title: localize('notebookActions.changeCellToCode', "Change Cell to Code"),
|
||||||
|
keybinding: {
|
||||||
|
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
|
||||||
|
primary: KeyCode.KEY_Y,
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor): Promise<void> {
|
||||||
|
return changeActiveCellToKind(CellKind.Code, accessor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.action.changeCellToMarkdown',
|
||||||
|
title: localize('notebookActions.changeCellToMarkdown', "Change Cell to Markdown"),
|
||||||
|
keybinding: {
|
||||||
|
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
|
||||||
|
primary: KeyCode.KEY_M,
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor): Promise<void> {
|
||||||
|
return changeActiveCellToKind(CellKind.Markdown, accessor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function getActiveNotebookEditor(editorService: IEditorService): INotebookEditor | undefined {
|
||||||
|
// TODO can `isNotebookEditor` be on INotebookEditor to avoid a circular dependency?
|
||||||
|
const activeEditorPane = editorService.activeEditorPane as any | undefined;
|
||||||
|
return activeEditorPane?.isNotebookEditor ? activeEditorPane : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function runActiveCell(accessor: ServicesAccessor): ICellViewModel | undefined {
|
||||||
|
const editorService = accessor.get(IEditorService);
|
||||||
|
const notebookService = accessor.get(INotebookService);
|
||||||
|
|
||||||
|
const resource = editorService.activeEditor?.resource;
|
||||||
|
if (!resource) {
|
||||||
|
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||||
|
}
|
||||||
|
|
||||||
|
const editor = getActiveNotebookEditor(editorService);
|
||||||
|
if (!editor) {
|
||||||
|
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||||
|
}
|
||||||
|
|
||||||
|
const notebookProviders = notebookService.getContributedNotebookProviders(resource);
|
||||||
|
if (!notebookProviders.length) {
|
||||||
|
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeCell = editor.getActiveCell();
|
||||||
|
if (!activeCell) {
|
||||||
|
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||||
|
}
|
||||||
|
|
||||||
|
const idx = editor.viewModel?.getViewCellIndex(activeCell);
|
||||||
|
if (typeof idx !== 'number') {
|
||||||
|
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewType = notebookProviders[0].id;
|
||||||
|
notebookService.executeNotebookActiveCell(viewType, resource);
|
||||||
|
|
||||||
|
return activeCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function changeActiveCellToKind(kind: CellKind, accessor: ServicesAccessor): Promise<void> {
|
||||||
|
const editorService = accessor.get(IEditorService);
|
||||||
|
const editor = getActiveNotebookEditor(editorService);
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeCell = editor.getActiveCell();
|
||||||
|
if (!activeCell) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeCell.cellKind === kind) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = activeCell.getText();
|
||||||
|
await editor.insertNotebookCell(activeCell, kind, 'below', text);
|
||||||
|
const idx = editor.viewModel?.getViewCellIndex(activeCell);
|
||||||
|
if (typeof idx !== 'number') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCell = editor.viewModel?.viewCells[idx + 1];
|
||||||
|
if (!newCell) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.focusNotebookCell(newCell, false);
|
||||||
|
editor.deleteNotebookCell(activeCell);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INotebookCellActionContext {
|
||||||
|
cell: ICellViewModel;
|
||||||
|
notebookEditor: INotebookEditor;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveCellContext(accessor: ServicesAccessor): INotebookCellActionContext | undefined {
|
||||||
|
const editorService = accessor.get(IEditorService);
|
||||||
|
|
||||||
|
const editor = getActiveNotebookEditor(editorService);
|
||||||
|
if (!editor) {
|
||||||
|
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeCell = editor.getActiveCell();
|
||||||
|
if (!activeCell) {
|
||||||
|
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cell: activeCell,
|
||||||
|
notebookEditor: editor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class InsertCellCommand extends Action2 {
|
||||||
|
constructor(
|
||||||
|
desc: Readonly<IAction2Options>,
|
||||||
|
private kind: CellKind,
|
||||||
|
private direction: 'above' | 'below'
|
||||||
|
) {
|
||||||
|
super(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise<void> {
|
||||||
|
if (!context) {
|
||||||
|
context = getActiveCellContext(accessor);
|
||||||
|
if (!context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.notebookEditor.insertNotebookCell(context.cell, this.kind, this.direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerAction2(class extends InsertCellCommand {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: INSERT_CODE_CELL_ABOVE_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.insertCodeCellAbove', "Insert Code Cell Above")
|
||||||
|
},
|
||||||
|
CellKind.Code,
|
||||||
|
'above');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends InsertCellCommand {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: INSERT_CODE_CELL_BELOW_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.insertCodeCellBelow', "Insert Code Cell Below")
|
||||||
|
},
|
||||||
|
CellKind.Code,
|
||||||
|
'below');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends InsertCellCommand {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.insertMarkdownCellAbove', "Insert Markdown Cell Above"),
|
||||||
|
},
|
||||||
|
CellKind.Markdown,
|
||||||
|
'above');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends InsertCellCommand {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.insertMarkdownCellBelow', "Insert Markdown Cell Below"),
|
||||||
|
},
|
||||||
|
CellKind.Code,
|
||||||
|
'below');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export class InsertCodeCellAboveAction extends MenuItemAction {
|
||||||
|
constructor(
|
||||||
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
|
@ICommandService commandService: ICommandService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: INSERT_CODE_CELL_ABOVE_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.insertCodeCellAbove', "Insert Code Cell Above"),
|
||||||
|
icon: { id: 'codicon/add' }
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
{ shouldForwardArgs: true },
|
||||||
|
contextKeyService,
|
||||||
|
commandService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InsertCodeCellBelowAction extends MenuItemAction {
|
||||||
|
constructor(
|
||||||
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
|
@ICommandService commandService: ICommandService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: INSERT_CODE_CELL_BELOW_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.insertCodeCellBelow', "Insert Code Cell Below"),
|
||||||
|
icon: { id: 'codicon/add' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.insertMarkdownCellBelow', "Insert Markdown Cell Below"),
|
||||||
|
icon: { id: 'codicon/add' }
|
||||||
|
},
|
||||||
|
{ shouldForwardArgs: true },
|
||||||
|
contextKeyService,
|
||||||
|
commandService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InsertMarkdownCellAboveAction extends MenuItemAction {
|
||||||
|
constructor(
|
||||||
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
|
@ICommandService commandService: ICommandService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: INSERT_MARKDOWN_CELL_ABOVE_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.insertMarkdownCellAbove', "Insert Markdown Cell Above"),
|
||||||
|
icon: { id: 'codicon/add' }
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
{ shouldForwardArgs: true },
|
||||||
|
contextKeyService,
|
||||||
|
commandService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InsertMarkdownCellBelowAction extends MenuItemAction {
|
||||||
|
constructor(
|
||||||
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
|
@ICommandService commandService: ICommandService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.insertMarkdownCellBelow', "Insert Markdown Cell Below"),
|
||||||
|
icon: { id: 'codicon/add' }
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
{ shouldForwardArgs: true },
|
||||||
|
contextKeyService,
|
||||||
|
commandService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: EDIT_CELL_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.editCell', "Edit Cell"),
|
||||||
|
keybinding: {
|
||||||
|
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
|
||||||
|
primary: KeyCode.Enter,
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(accessor: ServicesAccessor, context?: INotebookCellActionContext) {
|
||||||
|
if (!context) {
|
||||||
|
context = getActiveCellContext(accessor);
|
||||||
|
if (!context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.notebookEditor.editNotebookCell(context.cell);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export class EditCellAction extends MenuItemAction {
|
||||||
|
constructor(
|
||||||
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
|
@ICommandService commandService: ICommandService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: EDIT_CELL_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.editCell', "Edit Cell"),
|
||||||
|
icon: { id: 'codicon/pencil' }
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
{ shouldForwardArgs: true },
|
||||||
|
contextKeyService,
|
||||||
|
commandService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: SAVE_CELL_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.saveCell', "Save Cell")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(accessor: ServicesAccessor, context?: INotebookCellActionContext) {
|
||||||
|
if (!context) {
|
||||||
|
context = getActiveCellContext(accessor);
|
||||||
|
if (!context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.notebookEditor.saveNotebookCell(context.cell);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export class SaveCellAction extends MenuItemAction {
|
||||||
|
constructor(
|
||||||
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
|
@ICommandService commandService: ICommandService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: SAVE_CELL_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.saveCell', "Save Cell"),
|
||||||
|
icon: { id: 'codicon/save' }
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
{ shouldForwardArgs: true },
|
||||||
|
contextKeyService,
|
||||||
|
commandService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: DELETE_CELL_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.deleteCell', "Delete Cell")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(accessor: ServicesAccessor, context?: INotebookCellActionContext) {
|
||||||
|
if (!context) {
|
||||||
|
context = getActiveCellContext(accessor);
|
||||||
|
if (!context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.notebookEditor.deleteNotebookCell(context.cell);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export class DeleteCellAction extends MenuItemAction {
|
||||||
|
constructor(
|
||||||
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
|
@ICommandService commandService: ICommandService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: DELETE_CELL_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.deleteCell', "Delete Cell"),
|
||||||
|
icon: { id: 'codicon/x' }
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
{ shouldForwardArgs: true },
|
||||||
|
contextKeyService,
|
||||||
|
commandService);
|
||||||
|
|
||||||
|
this.class = 'codicon-x';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function moveCell(context: INotebookCellActionContext, direction: 'up' | 'down'): Promise<void> {
|
||||||
|
direction === 'up' ?
|
||||||
|
context.notebookEditor.moveCellUp(context.cell) :
|
||||||
|
context.notebookEditor.moveCellDown(context.cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyCell(context: INotebookCellActionContext, direction: 'up' | 'down'): Promise<void> {
|
||||||
|
const text = context.cell.getText();
|
||||||
|
const newCellDirection = direction === 'up' ? 'above' : 'below';
|
||||||
|
await context.notebookEditor.insertNotebookCell(context.cell, context.cell.cellKind, newCellDirection, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: MOVE_CELL_UP_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.moveCellUp', "Move Cell Up")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor, context?: INotebookCellActionContext) {
|
||||||
|
if (!context) {
|
||||||
|
context = getActiveCellContext(accessor);
|
||||||
|
if (!context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return moveCell(context, 'up');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export class MoveCellUpAction extends MenuItemAction {
|
||||||
|
constructor(
|
||||||
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
|
@ICommandService commandService: ICommandService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: MOVE_CELL_UP_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.moveCellUp', "Move Cell Up"),
|
||||||
|
icon: { id: 'codicon/arrow-up' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: COPY_CELL_UP_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.copyCellUp', "Copy Cell Up"),
|
||||||
|
icon: { id: 'codicon/arrow-up' }
|
||||||
|
},
|
||||||
|
{ shouldForwardArgs: true },
|
||||||
|
contextKeyService,
|
||||||
|
commandService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: MOVE_CELL_DOWN_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.moveCellDown', "Move Cell Down")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor, context?: INotebookCellActionContext) {
|
||||||
|
if (!context) {
|
||||||
|
context = getActiveCellContext(accessor);
|
||||||
|
if (!context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return moveCell(context, 'down');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export class MoveCellDownAction extends MenuItemAction {
|
||||||
|
constructor(
|
||||||
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
|
@ICommandService commandService: ICommandService
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: MOVE_CELL_DOWN_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.moveCellDown', "Move Cell Down"),
|
||||||
|
icon: { id: 'codicon/arrow-down' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: COPY_CELL_DOWN_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.copyCellDown', "Copy Cell Down"),
|
||||||
|
icon: { id: 'codicon/arrow-down' }
|
||||||
|
},
|
||||||
|
{ shouldForwardArgs: true },
|
||||||
|
contextKeyService,
|
||||||
|
commandService);
|
||||||
|
|
||||||
|
this.class = 'codicon-arrow-down';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: COPY_CELL_UP_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.copyCellUp', "Copy Cell Up")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor, context?: INotebookCellActionContext) {
|
||||||
|
if (!context) {
|
||||||
|
context = getActiveCellContext(accessor);
|
||||||
|
if (!context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyCell(context, 'up');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
id: COPY_CELL_DOWN_COMMAND_ID,
|
||||||
|
title: localize('notebookActions.copyCellDown', "Copy Cell Down")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor, context?: INotebookCellActionContext) {
|
||||||
|
if (!context) {
|
||||||
|
context = getActiveCellContext(accessor);
|
||||||
|
if (!context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyCell(context, 'down');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.action.notebook.cursorDown',
|
||||||
|
title: 'Notebook Cursor Move Down',
|
||||||
|
keybinding: {
|
||||||
|
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')),
|
||||||
|
primary: KeyCode.DownArrow,
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise<void> {
|
||||||
|
if (!context) {
|
||||||
|
context = getActiveCellContext(accessor);
|
||||||
|
if (!context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const editor = context.notebookEditor;
|
||||||
|
const activeCell = context.cell;
|
||||||
|
|
||||||
|
const idx = editor.viewModel?.getViewCellIndex(activeCell);
|
||||||
|
if (typeof idx !== 'number') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCell = editor.viewModel?.viewCells[idx + 1];
|
||||||
|
|
||||||
|
if (!newCell) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.focusNotebookCell(newCell, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.action.notebook.cursorUp',
|
||||||
|
title: 'Notebook Cursor Move Up',
|
||||||
|
keybinding: {
|
||||||
|
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')),
|
||||||
|
primary: KeyCode.UpArrow,
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise<void> {
|
||||||
|
if (!context) {
|
||||||
|
context = getActiveCellContext(accessor);
|
||||||
|
if (!context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const editor = context.notebookEditor;
|
||||||
|
const activeCell = context.cell;
|
||||||
|
|
||||||
|
const idx = editor.viewModel?.getViewCellIndex(activeCell);
|
||||||
|
if (typeof idx !== 'number') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx < 1) {
|
||||||
|
// we don't do loop
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCell = editor.viewModel?.viewCells[idx - 1];
|
||||||
|
|
||||||
|
if (!newCell) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.focusNotebookCell(newCell, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.action.notebook.undo',
|
||||||
|
title: 'Notebook Undo',
|
||||||
|
keybinding: {
|
||||||
|
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
|
||||||
|
primary: KeyMod.CtrlCmd | KeyCode.KEY_Z,
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor): Promise<void> {
|
||||||
|
const editorService = accessor.get(IEditorService);
|
||||||
|
|
||||||
|
const editor = getActiveNotebookEditor(editorService);
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewModel = editor.viewModel;
|
||||||
|
|
||||||
|
if (!viewModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.undo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.action.notebook.redo',
|
||||||
|
title: 'Notebook Redo',
|
||||||
|
keybinding: {
|
||||||
|
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
|
||||||
|
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z,
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor): Promise<void> {
|
||||||
|
const editorService = accessor.get(IEditorService);
|
||||||
|
|
||||||
|
const editor = getActiveNotebookEditor(editorService);
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewModel = editor.viewModel;
|
||||||
|
|
||||||
|
if (!viewModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.redo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAction2(class extends Action2 {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: 'workbench.action.notebook.testResize',
|
||||||
|
title: 'Notebook Test Cell Resize',
|
||||||
|
keybinding: {
|
||||||
|
when: IsDevelopmentContext,
|
||||||
|
primary: undefined,
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib
|
||||||
|
},
|
||||||
|
f1: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor): Promise<void> {
|
||||||
|
const editorService = accessor.get(IEditorService);
|
||||||
|
const resource = editorService.activeEditor?.resource;
|
||||||
|
if (!resource) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const editor = getActiveNotebookEditor(editorService);
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cells = editor.viewModel?.viewCells;
|
||||||
|
|
||||||
|
if (cells && cells.length) {
|
||||||
|
const firstCell = cells[0];
|
||||||
|
editor.layoutNotebookCell(firstCell, 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||||
|
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, INotebookEditor, CellFindMatch, CellState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||||
|
import { FindDecorations } from 'vs/editor/contrib/find/findDecorations';
|
||||||
|
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||||
|
import { IModelDeltaDecoration } from 'vs/editor/common/model';
|
||||||
|
import { ICellModelDeltaDecorations, ICellModelDecorations } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
|
||||||
|
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
|
||||||
|
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||||
|
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||||
|
import { SimpleFindReplaceWidget } from 'vs/workbench/contrib/codeEditor/browser/find/simpleFindReplaceWidget';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
|
||||||
|
export class NotebookFindWidget extends SimpleFindReplaceWidget {
|
||||||
|
protected _findWidgetFocused: IContextKey<boolean>;
|
||||||
|
private _findMatches: CellFindMatch[] = [];
|
||||||
|
protected _findMatchesStarts: PrefixSumComputer | null = null;
|
||||||
|
private _currentMatch: number = -1;
|
||||||
|
private _allMatchesDecorations: ICellModelDecorations[] = [];
|
||||||
|
private _currentMatchDecorations: ICellModelDecorations[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _notebookEditor: INotebookEditor,
|
||||||
|
@IContextViewService contextViewService: IContextViewService,
|
||||||
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
|
@IThemeService themeService: IThemeService,
|
||||||
|
|
||||||
|
) {
|
||||||
|
super(contextViewService, contextKeyService, themeService);
|
||||||
|
this._findWidgetFocused = KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED.bindTo(contextKeyService);
|
||||||
|
this._register(this._findInput.onKeyDown((e) => this._onFindInputKeyDown(e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onFindInputKeyDown(e: IKeyboardEvent): void {
|
||||||
|
if (e.equals(KeyCode.Enter)) {
|
||||||
|
if (this._findMatches.length) {
|
||||||
|
this.find(false);
|
||||||
|
} else {
|
||||||
|
this.set(null);
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
} else if (e.equals(KeyMod.Shift | KeyCode.Enter)) {
|
||||||
|
if (this._findMatches.length) {
|
||||||
|
this.find(true);
|
||||||
|
} else {
|
||||||
|
this.set(null);
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onInputChanged(): boolean {
|
||||||
|
const val = this.inputValue;
|
||||||
|
if (val) {
|
||||||
|
this._findMatches = this._notebookEditor.viewModel!.find(val).filter(match => match.matches.length > 0);
|
||||||
|
if (this._findMatches.length) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected find(previous: boolean): void {
|
||||||
|
if (!this._findMatches.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._findMatchesStarts) {
|
||||||
|
this.set(this._findMatches);
|
||||||
|
} else {
|
||||||
|
const totalVal = this._findMatchesStarts!.getTotalValue();
|
||||||
|
const nextVal = (this._currentMatch + (previous ? -1 : 1) + totalVal) % totalVal;
|
||||||
|
this._currentMatch = nextVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const nextIndex = this._findMatchesStarts!.getIndexOf(this._currentMatch);
|
||||||
|
this.setCurrentFindMatchDecoration(nextIndex.index, nextIndex.remainder);
|
||||||
|
this.revealCellRange(nextIndex.index, nextIndex.remainder);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected replaceOne() {
|
||||||
|
if (!this._findMatches.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._findMatchesStarts) {
|
||||||
|
this.set(this._findMatches);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextIndex = this._findMatchesStarts!.getIndexOf(this._currentMatch);
|
||||||
|
const cell = this._findMatches[nextIndex.index].cell;
|
||||||
|
const match = this._findMatches[nextIndex.index].matches[nextIndex.remainder];
|
||||||
|
|
||||||
|
this._progressBar.infinite().show();
|
||||||
|
|
||||||
|
this._notebookEditor.viewModel!.replaceOne(cell, match.range, this.replaceValue).then(() => {
|
||||||
|
this._progressBar.stop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected replaceAll() {
|
||||||
|
this._progressBar.infinite().show();
|
||||||
|
|
||||||
|
this._notebookEditor.viewModel!.replaceAll(this._findMatches, this.replaceValue).then(() => {
|
||||||
|
this._progressBar.stop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private revealCellRange(cellIndex: number, matchIndex: number) {
|
||||||
|
this._findMatches[cellIndex].cell.state = CellState.Editing;
|
||||||
|
this._notebookEditor.selectElement(this._findMatches[cellIndex].cell);
|
||||||
|
this._notebookEditor.setCellSelection(this._findMatches[cellIndex].cell, this._findMatches[cellIndex].matches[matchIndex].range);
|
||||||
|
this._notebookEditor.revealRangeInCenterIfOutsideViewport(this._findMatches[cellIndex].cell, this._findMatches[cellIndex].matches[matchIndex].range);
|
||||||
|
}
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
super.hide();
|
||||||
|
this.set([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected findFirst(): void { }
|
||||||
|
|
||||||
|
protected onFocusTrackerFocus() {
|
||||||
|
this._findWidgetFocused.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onFocusTrackerBlur() {
|
||||||
|
this._findWidgetFocused.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onReplaceInputFocusTrackerFocus(): void {
|
||||||
|
// throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
protected onReplaceInputFocusTrackerBlur(): void {
|
||||||
|
// throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onFindInputFocusTrackerFocus(): void { }
|
||||||
|
protected onFindInputFocusTrackerBlur(): void { }
|
||||||
|
|
||||||
|
private constructFindMatchesStarts() {
|
||||||
|
if (this._findMatches && this._findMatches.length) {
|
||||||
|
const values = new Uint32Array(this._findMatches.length);
|
||||||
|
for (let i = 0; i < this._findMatches.length; i++) {
|
||||||
|
values[i] = this._findMatches[i].matches.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._findMatchesStarts = new PrefixSumComputer(values);
|
||||||
|
} else {
|
||||||
|
this._findMatchesStarts = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private set(cellFindMatches: CellFindMatch[] | null): void {
|
||||||
|
if (!cellFindMatches || !cellFindMatches.length) {
|
||||||
|
this._findMatches = [];
|
||||||
|
this.setAllFindMatchesDecorations([]);
|
||||||
|
|
||||||
|
this.constructFindMatchesStarts();
|
||||||
|
this._currentMatch = -1;
|
||||||
|
this.clearCurrentFindMatchDecoration();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all matches
|
||||||
|
this._findMatches = cellFindMatches;
|
||||||
|
this.setAllFindMatchesDecorations(cellFindMatches || []);
|
||||||
|
|
||||||
|
// current match
|
||||||
|
this.constructFindMatchesStarts();
|
||||||
|
this._currentMatch = 0;
|
||||||
|
this.setCurrentFindMatchDecoration(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setCurrentFindMatchDecoration(cellIndex: number, matchIndex: number) {
|
||||||
|
this._notebookEditor.changeDecorations(accessor => {
|
||||||
|
const findMatchesOptions: ModelDecorationOptions = FindDecorations._CURRENT_FIND_MATCH_DECORATION;
|
||||||
|
|
||||||
|
const cell = this._findMatches[cellIndex].cell;
|
||||||
|
const match = this._findMatches[cellIndex].matches[matchIndex];
|
||||||
|
const decorations: IModelDeltaDecoration[] = [
|
||||||
|
{ range: match.range, options: findMatchesOptions }
|
||||||
|
];
|
||||||
|
const deltaDecoration: ICellModelDeltaDecorations = {
|
||||||
|
ownerId: cell.handle,
|
||||||
|
decorations: decorations
|
||||||
|
};
|
||||||
|
|
||||||
|
this._currentMatchDecorations = accessor.deltaDecorations(this._currentMatchDecorations, [deltaDecoration]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearCurrentFindMatchDecoration() {
|
||||||
|
this._notebookEditor.changeDecorations(accessor => {
|
||||||
|
this._currentMatchDecorations = accessor.deltaDecorations(this._currentMatchDecorations, []);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private setAllFindMatchesDecorations(cellFindMatches: CellFindMatch[]) {
|
||||||
|
this._notebookEditor.changeDecorations((accessor) => {
|
||||||
|
|
||||||
|
let findMatchesOptions: ModelDecorationOptions = FindDecorations._FIND_MATCH_DECORATION;
|
||||||
|
|
||||||
|
let deltaDecorations: ICellModelDeltaDecorations[] = cellFindMatches.map(cellFindMatch => {
|
||||||
|
const findMatches = cellFindMatch.matches;
|
||||||
|
|
||||||
|
// Find matches
|
||||||
|
let newFindMatchesDecorations: IModelDeltaDecoration[] = new Array<IModelDeltaDecoration>(findMatches.length);
|
||||||
|
for (let i = 0, len = findMatches.length; i < len; i++) {
|
||||||
|
newFindMatchesDecorations[i] = {
|
||||||
|
range: findMatches[i].range,
|
||||||
|
options: findMatchesOptions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ownerId: cellFindMatch.cell.handle, decorations: newFindMatchesDecorations };
|
||||||
|
});
|
||||||
|
|
||||||
|
this._allMatchesDecorations = accessor.deltaDecorations(this._allMatchesDecorations, deltaDecorations);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this._currentMatch = -1;
|
||||||
|
this._findMatches = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||||
|
import * as nls from 'vs/nls';
|
||||||
|
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||||
|
import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookProvider';
|
||||||
|
|
||||||
|
namespace NotebookEditorContribution {
|
||||||
|
export const viewType = 'viewType';
|
||||||
|
export const displayName = 'displayName';
|
||||||
|
export const selector = 'selector';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface INotebookEditorContribution {
|
||||||
|
readonly [NotebookEditorContribution.viewType]: string;
|
||||||
|
readonly [NotebookEditorContribution.displayName]: string;
|
||||||
|
readonly [NotebookEditorContribution.selector]?: readonly NotebookSelector[];
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace NotebookRendererContribution {
|
||||||
|
export const viewType = 'viewType';
|
||||||
|
export const displayName = 'displayName';
|
||||||
|
export const mimeTypes = 'mimeTypes';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface INotebookRendererContribution {
|
||||||
|
readonly [NotebookRendererContribution.viewType]: string;
|
||||||
|
readonly [NotebookRendererContribution.displayName]: string;
|
||||||
|
readonly [NotebookRendererContribution.mimeTypes]?: readonly string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const notebookProviderContribution: IJSONSchema = {
|
||||||
|
description: nls.localize('contributes.notebook.provider', 'Contributes notebook document provider.'),
|
||||||
|
type: 'array',
|
||||||
|
defaultSnippets: [{ body: [{ viewType: '', displayName: '' }] }],
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
required: [
|
||||||
|
NotebookEditorContribution.viewType,
|
||||||
|
NotebookEditorContribution.displayName,
|
||||||
|
NotebookEditorContribution.selector,
|
||||||
|
],
|
||||||
|
properties: {
|
||||||
|
[NotebookEditorContribution.viewType]: {
|
||||||
|
type: 'string',
|
||||||
|
description: nls.localize('contributes.notebook.provider.viewType', 'Unique identifier of the notebook.'),
|
||||||
|
},
|
||||||
|
[NotebookEditorContribution.displayName]: {
|
||||||
|
type: 'string',
|
||||||
|
description: nls.localize('contributes.notebook.provider.displayName', 'Human readable name of the notebook.'),
|
||||||
|
},
|
||||||
|
[NotebookEditorContribution.selector]: {
|
||||||
|
type: 'array',
|
||||||
|
description: nls.localize('contributes.notebook.provider.selector', 'Set of globs that the notebook is for.'),
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
filenamePattern: {
|
||||||
|
type: 'string',
|
||||||
|
description: nls.localize('contributes.notebook.provider.selector.filenamePattern', 'Glob that the notebook is enabled for.'),
|
||||||
|
},
|
||||||
|
excludeFileNamePattern: {
|
||||||
|
type: 'string',
|
||||||
|
description: nls.localize('contributes.notebook.selector.provider.excludeFileNamePattern', 'Glob that the notebook is disabled for.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const notebookRendererContribution: IJSONSchema = {
|
||||||
|
description: nls.localize('contributes.notebook.renderer', 'Contributes notebook output renderer provider.'),
|
||||||
|
type: 'array',
|
||||||
|
defaultSnippets: [{ body: [{ viewType: '', displayName: '', mimeTypes: [''] }] }],
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
required: [
|
||||||
|
NotebookRendererContribution.viewType,
|
||||||
|
NotebookRendererContribution.displayName,
|
||||||
|
NotebookRendererContribution.mimeTypes,
|
||||||
|
],
|
||||||
|
properties: {
|
||||||
|
[NotebookRendererContribution.viewType]: {
|
||||||
|
type: 'string',
|
||||||
|
description: nls.localize('contributes.notebook.renderer.viewType', 'Unique identifier of the notebook output renderer.'),
|
||||||
|
},
|
||||||
|
[NotebookRendererContribution.displayName]: {
|
||||||
|
type: 'string',
|
||||||
|
description: nls.localize('contributes.notebook.renderer.displayName', 'Human readable name of the notebook output renderer.'),
|
||||||
|
},
|
||||||
|
[NotebookRendererContribution.mimeTypes]: {
|
||||||
|
type: 'array',
|
||||||
|
description: nls.localize('contributes.notebook.selector', 'Set of globs that the notebook is for.'),
|
||||||
|
items: {
|
||||||
|
type: 'string'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const notebookProviderExtensionPoint = ExtensionsRegistry.registerExtensionPoint<INotebookEditorContribution[]>(
|
||||||
|
{
|
||||||
|
extensionPoint: 'notebookProvider',
|
||||||
|
jsonSchema: notebookProviderContribution
|
||||||
|
});
|
||||||
|
|
||||||
|
export const notebookRendererExtensionPoint = ExtensionsRegistry.registerExtensionPoint<INotebookRendererContribution[]>(
|
||||||
|
{
|
||||||
|
extensionPoint: 'notebookOutputRenderer',
|
||||||
|
jsonSchema: notebookRendererContribution
|
||||||
|
});
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as nls from 'vs/nls';
|
||||||
|
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||||
|
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||||
|
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||||
|
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||||
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
|
import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor';
|
||||||
|
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||||
|
import { IEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor';
|
||||||
|
import { NotebookEditor, NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookEditor';
|
||||||
|
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
|
||||||
|
import { INotebookService, NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService';
|
||||||
|
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||||
|
import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService';
|
||||||
|
import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||||
|
import { ITextModel } from 'vs/editor/common/model';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||||
|
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||||
|
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { assertType } from 'vs/base/common/types';
|
||||||
|
import { parse } from 'vs/base/common/marshalling';
|
||||||
|
import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
|
import { ResourceMap } from 'vs/base/common/map';
|
||||||
|
|
||||||
|
// Output renderers registration
|
||||||
|
|
||||||
|
import 'vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform';
|
||||||
|
import 'vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform';
|
||||||
|
import 'vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform';
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
import 'vs/workbench/contrib/notebook/browser/contrib/notebookActions';
|
||||||
|
import { basename } from 'vs/base/common/resources';
|
||||||
|
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
|
||||||
|
|
||||||
|
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||||
|
EditorDescriptor.create(
|
||||||
|
NotebookEditor,
|
||||||
|
NotebookEditor.ID,
|
||||||
|
'Notebook Editor'
|
||||||
|
),
|
||||||
|
[
|
||||||
|
new SyncDescriptor(NotebookEditorInput)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(
|
||||||
|
NotebookEditorInput.ID,
|
||||||
|
class implements IEditorInputFactory {
|
||||||
|
canSerialize(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
serialize(input: EditorInput): string {
|
||||||
|
assertType(input instanceof NotebookEditorInput);
|
||||||
|
return JSON.stringify({
|
||||||
|
resource: input.resource,
|
||||||
|
name: input.name,
|
||||||
|
viewType: input.viewType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
deserialize(instantiationService: IInstantiationService, raw: string) {
|
||||||
|
type Data = { resource: URI, name: string, viewType: string };
|
||||||
|
const data = <Data>parse(raw);
|
||||||
|
if (!data) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const { resource, name, viewType } = data;
|
||||||
|
if (!data || !URI.isUri(resource) || typeof name !== 'string' || typeof viewType !== 'string') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
// TODO@joh,peng this is disabled because the note-editor isn't fit for being
|
||||||
|
// restorted (as it seems)
|
||||||
|
if ('true') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return instantiationService.createInstance(NotebookEditorInput, resource, name, viewType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function getFirstNotebookInfo(notebookService: INotebookService, uri: URI): NotebookProviderInfo | undefined {
|
||||||
|
return notebookService.getContributedNotebookProviders(uri)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotebookContribution implements IWorkbenchContribution {
|
||||||
|
private _resourceMapping = new ResourceMap<NotebookEditorInput>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IEditorService private readonly editorService: IEditorService,
|
||||||
|
@INotebookService private readonly notebookService: INotebookService,
|
||||||
|
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||||
|
|
||||||
|
) {
|
||||||
|
this.editorService.overrideOpenEditor((editor, options, group) => this.onEditorOpening(editor, options, group));
|
||||||
|
|
||||||
|
this.editorService.onDidActiveEditorChange(() => {
|
||||||
|
if (this.editorService.activeEditor && this.editorService.activeEditor! instanceof NotebookEditorInput) {
|
||||||
|
let editorInput = this.editorService.activeEditor! as NotebookEditorInput;
|
||||||
|
this.notebookService.updateActiveNotebookDocument(editorInput.viewType!, editorInput.resource!);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onEditorOpening(originalInput: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined {
|
||||||
|
let resource = originalInput.resource;
|
||||||
|
if (!resource) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let info: NotebookProviderInfo | undefined;
|
||||||
|
const data = CellUri.parse(resource);
|
||||||
|
if (data && (info = getFirstNotebookInfo(this.notebookService, data.notebook))) {
|
||||||
|
// cell-uri -> open (container) notebook
|
||||||
|
const name = basename(data.notebook);
|
||||||
|
const input = this.instantiationService.createInstance(NotebookEditorInput, data.notebook, name, info.id);
|
||||||
|
this._resourceMapping.set(resource, input);
|
||||||
|
return { override: this.editorService.openEditor(input, new NotebookEditorOptions({ ...options, forceReload: true, cellOptions: { resource, options } }), group) };
|
||||||
|
}
|
||||||
|
|
||||||
|
info = getFirstNotebookInfo(this.notebookService, resource);
|
||||||
|
if (!info) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._resourceMapping.has(resource)) {
|
||||||
|
const input = this._resourceMapping.get(resource);
|
||||||
|
|
||||||
|
if (!input!.isDisposed()) {
|
||||||
|
return { override: this.editorService.openEditor(input!, new NotebookEditorOptions(options || {}).with({ ignoreOverrides: true }), group) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = this.instantiationService.createInstance(NotebookEditorInput, resource, originalInput.getName(), info.id);
|
||||||
|
this._resourceMapping.set(resource, input);
|
||||||
|
|
||||||
|
return { override: this.editorService.openEditor(input, options, group) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CellContentProvider implements ITextModelContentProvider {
|
||||||
|
|
||||||
|
private readonly _registration: IDisposable;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@ITextModelService textModelService: ITextModelService,
|
||||||
|
@IModelService private readonly _modelService: IModelService,
|
||||||
|
@IModeService private readonly _modeService: IModeService,
|
||||||
|
@INotebookService private readonly _notebookService: INotebookService,
|
||||||
|
) {
|
||||||
|
this._registration = textModelService.registerTextModelContentProvider('vscode-notebook', this);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this._registration.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async provideTextContent(resource: URI): Promise<ITextModel | null> {
|
||||||
|
const existing = this._modelService.getModel(resource);
|
||||||
|
if (existing) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
const data = CellUri.parse(resource);
|
||||||
|
// const data = parseCellUri(resource);
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const info = getFirstNotebookInfo(this._notebookService, data.notebook);
|
||||||
|
if (!info) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const notebook = await this._notebookService.resolveNotebook(info.id, data.notebook);
|
||||||
|
if (!notebook) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (let cell of notebook.cells) {
|
||||||
|
if (cell.uri.toString() === resource.toString()) {
|
||||||
|
let bufferFactory = cell.resolveTextBufferFactory();
|
||||||
|
return this._modelService.createModel(
|
||||||
|
bufferFactory,
|
||||||
|
cell.language ? this._modeService.create(cell.language) : this._modeService.createByFilepathOrFirstLine(resource, cell.source[0]),
|
||||||
|
resource
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||||
|
workbenchContributionsRegistry.registerWorkbenchContribution(NotebookContribution, LifecyclePhase.Starting);
|
||||||
|
workbenchContributionsRegistry.registerWorkbenchContribution(CellContentProvider, LifecyclePhase.Starting);
|
||||||
|
|
||||||
|
registerSingleton(INotebookService, NotebookService);
|
||||||
|
|
||||||
|
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
|
||||||
|
configurationRegistry.registerConfiguration({
|
||||||
|
id: 'notebook',
|
||||||
|
order: 100,
|
||||||
|
title: nls.localize('notebookConfigurationTitle', "Notebook"),
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
'notebook.displayOrder': {
|
||||||
|
markdownDescription: nls.localize('notebook.displayOrder.description', "Priority list for output mime types"),
|
||||||
|
type: ['array'],
|
||||||
|
items: {
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
default: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,318 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor {
|
||||||
|
box-sizing: border-box;
|
||||||
|
line-height: 22px;
|
||||||
|
user-select: initial;
|
||||||
|
-webkit-user-select: initial;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell.markdown {
|
||||||
|
user-select: text;
|
||||||
|
-webkit-user-select: text;
|
||||||
|
white-space: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .monaco-scrollable-element {
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .monaco-list-rows {
|
||||||
|
min-height: 100%;
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell-list-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .notebook-content-widgets {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .output {
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
user-select: text;
|
||||||
|
transform: translate3d(0px, 0px, 0px);
|
||||||
|
cursor: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .output p {
|
||||||
|
white-space: initial;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .output > div.foreground {
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .output .multi-mimetype-output {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
left: -28px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .output .error_message {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .output pre.traceback {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .output .traceback > span {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .output .display img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row {
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row:focus-within {
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .menu {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 28px;
|
||||||
|
visibility: hidden;
|
||||||
|
width: 16px;
|
||||||
|
margin: auto;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .menu.mouseover,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row:hover .menu {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row:hover {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row.selected,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row.focused {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .menu.mouseover,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .menu:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .monaco-toolbar {
|
||||||
|
visibility: hidden;
|
||||||
|
margin-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.focused .monaco-toolbar,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row:hover .monaco-toolbar {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-tree.focused.no-focused-item:focus:before,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list:not(.element-focused):focus:before {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row .notebook-cell-focus-indicator {
|
||||||
|
display: block;
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
width: 6px;
|
||||||
|
border-left-width: 2px;
|
||||||
|
border-left-style: solid;
|
||||||
|
left: 28px;
|
||||||
|
top: 22px;
|
||||||
|
bottom: 8px;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row.focused .notebook-cell-focus-indicator {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notebook-webview {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000000;
|
||||||
|
left: 373px;
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* markdown */
|
||||||
|
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown a:focus,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown input:focus,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown select:focus,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown textarea:focus {
|
||||||
|
outline: 1px solid -webkit-focus-ring-color;
|
||||||
|
outline-offset: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown hr {
|
||||||
|
border: 0;
|
||||||
|
height: 2px;
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown h1 {
|
||||||
|
padding-bottom: 0.3em;
|
||||||
|
line-height: 1.2;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown h1,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown h2,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown h3 {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table th,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table td {
|
||||||
|
border: 1px solid ;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > thead > tr > th {
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > thead > tr > th,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > thead > tr > td,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > tbody > tr > th,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > tbody > tr > td {
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > tbody > tr + tr > td {
|
||||||
|
border-top: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown blockquote {
|
||||||
|
margin: 0 7px 0 5px;
|
||||||
|
padding: 0 16px 0 10px;
|
||||||
|
border-left-width: 5px;
|
||||||
|
border-left-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown code {
|
||||||
|
font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.357em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown body.wordWrap pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown pre:not(.hljs),
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown pre.hljs code > div {
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown pre code {
|
||||||
|
color: var(--vscode-editor-foreground);
|
||||||
|
tab-size: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown .latex-block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown .latex {
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown .latex img,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown .latex-block img {
|
||||||
|
filter: brightness(0) invert(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown .latex img,
|
||||||
|
.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown .latex-block img {
|
||||||
|
filter: brightness(0) invert(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Theming */
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown pre {
|
||||||
|
background-color: rgba(220, 220, 220, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown pre {
|
||||||
|
background-color: rgba(10, 10, 10, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown pre {
|
||||||
|
background-color: rgb(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hc-black .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown h1 {
|
||||||
|
border-color: rgb(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > thead > tr > th {
|
||||||
|
border-color: rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > thead > tr > th {
|
||||||
|
border-color: rgba(255, 255, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown h1,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown hr,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > tbody > tr > td {
|
||||||
|
border-color: rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown h1,
|
||||||
|
.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown hr,
|
||||||
|
.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table > tbody > tr > td {
|
||||||
|
border-color: rgba(255, 255, 255, 0.18);
|
||||||
|
}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
|
||||||
|
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
|
||||||
|
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||||
|
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookCellViewModel';
|
||||||
|
import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer';
|
||||||
|
import { IOutput, CellKind, IRenderOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
|
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { NotebookViewModel, IModelDecorationsChangeAccessor } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
|
||||||
|
import { FindMatch } from 'vs/editor/common/model';
|
||||||
|
import { Range } from 'vs/editor/common/core/range';
|
||||||
|
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||||
|
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
|
||||||
|
export const KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED = new RawContextKey<boolean>('notebookFindWidgetFocused', false);
|
||||||
|
|
||||||
|
export const NOTEBOOK_EDITOR_FOCUSED = new RawContextKey<boolean>('notebookEditorFocused', false);
|
||||||
|
|
||||||
|
export interface NotebookLayoutInfo {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
fontInfo: BareFontInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICellViewModel {
|
||||||
|
readonly id: string;
|
||||||
|
handle: number;
|
||||||
|
uri: URI;
|
||||||
|
cellKind: CellKind;
|
||||||
|
state: CellState;
|
||||||
|
focusMode: CellFocusMode;
|
||||||
|
getText(): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INotebookEditor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notebook view model attached to the current editor
|
||||||
|
*/
|
||||||
|
viewModel: NotebookViewModel | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Focus the notebook editor cell list
|
||||||
|
*/
|
||||||
|
focus(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select & focus cell
|
||||||
|
*/
|
||||||
|
selectElement(cell: ICellViewModel): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout info for the notebook editor
|
||||||
|
*/
|
||||||
|
getLayoutInfo(): NotebookLayoutInfo;
|
||||||
|
/**
|
||||||
|
* Fetch the output renderers for notebook outputs.
|
||||||
|
*/
|
||||||
|
getOutputRenderer(): OutputRenderer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a new cell around `cell`
|
||||||
|
*/
|
||||||
|
insertNotebookCell(cell: ICellViewModel, type: CellKind, direction: 'above' | 'below', initialText?: string): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a cell from the notebook
|
||||||
|
*/
|
||||||
|
deleteNotebookCell(cell: ICellViewModel): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move a cell up one spot
|
||||||
|
*/
|
||||||
|
moveCellUp(cell: ICellViewModel): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move a cell down one spot
|
||||||
|
*/
|
||||||
|
moveCellDown(cell: ICellViewModel): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch the cell into editing mode.
|
||||||
|
*
|
||||||
|
* For code cell, the monaco editor will be focused.
|
||||||
|
* For markdown cell, it will switch from preview mode to editing mode, which focuses the monaco editor.
|
||||||
|
*/
|
||||||
|
editNotebookCell(cell: ICellViewModel): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quit cell editing mode.
|
||||||
|
*/
|
||||||
|
saveNotebookCell(cell: ICellViewModel): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Focus the container of a cell (the monaco editor inside is not focused).
|
||||||
|
*/
|
||||||
|
focusNotebookCell(cell: ICellViewModel, focusEditor: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current active cell
|
||||||
|
*/
|
||||||
|
getActiveCell(): ICellViewModel | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout the cell with a new height
|
||||||
|
*/
|
||||||
|
layoutNotebookCell(cell: ICellViewModel, height: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the output in webview layer
|
||||||
|
*/
|
||||||
|
createInset(cell: ICellViewModel, output: IOutput, shadowContent: string, offset: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the output from the webview layer
|
||||||
|
*/
|
||||||
|
removeInset(output: IOutput): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger the editor to scroll from scroll event programmatically
|
||||||
|
*/
|
||||||
|
triggerScroll(event: IMouseWheelEvent): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reveal cell into viewport.
|
||||||
|
*/
|
||||||
|
revealInView(cell: ICellViewModel): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reveal cell into viewport center.
|
||||||
|
*/
|
||||||
|
revealInCenter(cell: ICellViewModel): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reveal cell into viewport center if cell is currently out of the viewport.
|
||||||
|
*/
|
||||||
|
revealInCenterIfOutsideViewport(cell: ICellViewModel): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reveal a line in notebook cell into viewport with minimal scrolling.
|
||||||
|
*/
|
||||||
|
revealLineInView(cell: ICellViewModel, line: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reveal a line in notebook cell into viewport center.
|
||||||
|
*/
|
||||||
|
revealLineInCenter(cell: ICellViewModel, line: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reveal a line in notebook cell into viewport center.
|
||||||
|
*/
|
||||||
|
revealLineInCenterIfOutsideViewport(cell: ICellViewModel, line: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reveal a range in notebook cell into viewport with minimal scrolling.
|
||||||
|
*/
|
||||||
|
revealRangeInView(cell: ICellViewModel, range: Range): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reveal a range in notebook cell into viewport center.
|
||||||
|
*/
|
||||||
|
revealRangeInCenter(cell: ICellViewModel, range: Range): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reveal a range in notebook cell into viewport center.
|
||||||
|
*/
|
||||||
|
revealRangeInCenterIfOutsideViewport(cell: ICellViewModel, range: Range): void;
|
||||||
|
|
||||||
|
setCellSelection(cell: ICellViewModel, selection: Range): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the decorations on cells.
|
||||||
|
* The notebook is virtualized and this method should be called to create/delete editor decorations safely.
|
||||||
|
*/
|
||||||
|
changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show Find Widget.
|
||||||
|
*
|
||||||
|
* Currently Find is still part of the NotebookEditor core
|
||||||
|
*/
|
||||||
|
showFind(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide Find Widget
|
||||||
|
*/
|
||||||
|
hideFind(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CellRenderTemplate {
|
||||||
|
container: HTMLElement;
|
||||||
|
cellContainer: HTMLElement;
|
||||||
|
menuContainer?: HTMLElement;
|
||||||
|
toolbar: ToolBar;
|
||||||
|
focusIndicator?: HTMLElement;
|
||||||
|
editingContainer?: HTMLElement;
|
||||||
|
outputContainer?: HTMLElement;
|
||||||
|
editor?: CodeEditorWidget;
|
||||||
|
disposables: DisposableStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IOutputTransformContribution {
|
||||||
|
/**
|
||||||
|
* Dispose this contribution.
|
||||||
|
*/
|
||||||
|
dispose(): void;
|
||||||
|
|
||||||
|
render(output: IOutput, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CellFindMatch {
|
||||||
|
cell: CellViewModel;
|
||||||
|
matches: FindMatch[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CellRevealType {
|
||||||
|
Line,
|
||||||
|
Range
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CellRevealPosition {
|
||||||
|
Top,
|
||||||
|
Center
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CellState {
|
||||||
|
/**
|
||||||
|
* Default state.
|
||||||
|
* For markdown cell, it's Markdown preview.
|
||||||
|
* For code cell, the browser focus should be on the container instead of the editor
|
||||||
|
*/
|
||||||
|
Preview,
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eding mode. Source for markdown or code is rendered in editors and the state will be persistent.
|
||||||
|
*/
|
||||||
|
Editing
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CellFocusMode {
|
||||||
|
Container,
|
||||||
|
Editor
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CursorAtBoundary {
|
||||||
|
None,
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
Both
|
||||||
|
}
|
||||||
@@ -0,0 +1,760 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as nls from 'vs/nls';
|
||||||
|
import { getZoomLevel } from 'vs/base/browser/browser';
|
||||||
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
|
||||||
|
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||||
|
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||||
|
import 'vs/css!./notebook';
|
||||||
|
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||||
|
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
|
||||||
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||||
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
|
import { contrastBorder, editorBackground, focusBorder, foreground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground, registerColor } from 'vs/platform/theme/common/colorRegistry';
|
||||||
|
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||||
|
import { EditorOptions, IEditorMemento, IEditorCloseEvent } from 'vs/workbench/common/editor';
|
||||||
|
import { INotebookEditor, NotebookLayoutInfo, CellState, NOTEBOOK_EDITOR_FOCUSED, CellFocusMode, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||||
|
import { NotebookEditorInput, NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
|
||||||
|
import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService';
|
||||||
|
import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer';
|
||||||
|
import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView';
|
||||||
|
import { CodeCellRenderer, MarkdownCellRenderer, NotebookCellListDelegate } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer';
|
||||||
|
import { IOutput, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
|
import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview';
|
||||||
|
import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils';
|
||||||
|
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||||
|
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||||
|
import { IEditor, ICompositeCodeEditor } from 'vs/editor/common/editorCommon';
|
||||||
|
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
|
||||||
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
|
import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList';
|
||||||
|
import { NotebookFindWidget } from 'vs/workbench/contrib/notebook/browser/contrib/notebookFindWidget';
|
||||||
|
import { NotebookViewModel, INotebookEditorViewState, IModelDecorationsChangeAccessor } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
|
||||||
|
import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
|
||||||
|
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookCellViewModel';
|
||||||
|
import { Range } from 'vs/editor/common/core/range';
|
||||||
|
import { CELL_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants';
|
||||||
|
import { Color, RGBA } from 'vs/base/common/color';
|
||||||
|
|
||||||
|
const $ = DOM.$;
|
||||||
|
const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState';
|
||||||
|
|
||||||
|
export class NotebookEditorOptions extends EditorOptions {
|
||||||
|
|
||||||
|
readonly cellOptions?: IResourceEditorInput;
|
||||||
|
|
||||||
|
constructor(options: Partial<NotebookEditorOptions>) {
|
||||||
|
super();
|
||||||
|
this.overwrite(options);
|
||||||
|
this.cellOptions = options.cellOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
with(options: Partial<NotebookEditorOptions>): NotebookEditorOptions {
|
||||||
|
return new NotebookEditorOptions({ ...this, ...options });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotebookCodeEditors implements ICompositeCodeEditor {
|
||||||
|
|
||||||
|
private readonly _disposables = new DisposableStore();
|
||||||
|
private readonly _onDidChangeActiveEditor = new Emitter<this>();
|
||||||
|
readonly onDidChangeActiveEditor: Event<this> = this._onDidChangeActiveEditor.event;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private _list: NotebookCellList,
|
||||||
|
private _renderedEditors: Map<ICellViewModel, ICodeEditor | undefined>
|
||||||
|
) {
|
||||||
|
_list.onDidChangeFocus(_e => this._onDidChangeActiveEditor.fire(this), undefined, this._disposables);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this._onDidChangeActiveEditor.dispose();
|
||||||
|
this._disposables.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
get activeCodeEditor(): IEditor | undefined {
|
||||||
|
const [focused] = this._list.getFocusedElements();
|
||||||
|
return focused instanceof CellViewModel
|
||||||
|
? this._renderedEditors.get(focused)
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotebookEditor extends BaseEditor implements INotebookEditor {
|
||||||
|
static readonly ID: string = 'workbench.editor.notebook';
|
||||||
|
private rootElement!: HTMLElement;
|
||||||
|
private body!: HTMLElement;
|
||||||
|
private webview: BackLayerWebView | null = null;
|
||||||
|
private list: NotebookCellList | undefined;
|
||||||
|
private control: ICompositeCodeEditor | undefined;
|
||||||
|
private renderedEditors: Map<ICellViewModel, ICodeEditor | undefined> = new Map();
|
||||||
|
private notebookViewModel: NotebookViewModel | undefined;
|
||||||
|
private localStore: DisposableStore = this._register(new DisposableStore());
|
||||||
|
private editorMemento: IEditorMemento<INotebookEditorViewState>;
|
||||||
|
private readonly groupListener = this._register(new MutableDisposable());
|
||||||
|
private fontInfo: BareFontInfo | undefined;
|
||||||
|
private dimension: DOM.Dimension | null = null;
|
||||||
|
private editorFocus: IContextKey<boolean> | null = null;
|
||||||
|
private outputRenderer: OutputRenderer;
|
||||||
|
private findWidget: NotebookFindWidget;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@ITelemetryService telemetryService: ITelemetryService,
|
||||||
|
@IThemeService themeService: IThemeService,
|
||||||
|
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||||
|
@IStorageService storageService: IStorageService,
|
||||||
|
@IWebviewService private webviewService: IWebviewService,
|
||||||
|
@INotebookService private notebookService: INotebookService,
|
||||||
|
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||||
|
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||||
|
@IEnvironmentService private readonly environmentSerice: IEnvironmentService,
|
||||||
|
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||||
|
) {
|
||||||
|
super(NotebookEditor.ID, telemetryService, themeService, storageService);
|
||||||
|
|
||||||
|
this.editorMemento = this.getEditorMemento<INotebookEditorViewState>(editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);
|
||||||
|
this.outputRenderer = new OutputRenderer(this, this.instantiationService);
|
||||||
|
this.findWidget = this.instantiationService.createInstance(NotebookFindWidget, this);
|
||||||
|
this.findWidget.updateTheme(this.themeService.getColorTheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
get viewModel() {
|
||||||
|
return this.notebookViewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
get minimumWidth(): number { return 375; }
|
||||||
|
get maximumWidth(): number { return Number.POSITIVE_INFINITY; }
|
||||||
|
|
||||||
|
// these setters need to exist because this extends from BaseEditor
|
||||||
|
set minimumWidth(value: number) { /*noop*/ }
|
||||||
|
set maximumWidth(value: number) { /*noop*/ }
|
||||||
|
|
||||||
|
|
||||||
|
//#region Editor Core
|
||||||
|
|
||||||
|
|
||||||
|
public get isNotebookEditor() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createEditor(parent: HTMLElement): void {
|
||||||
|
this.rootElement = DOM.append(parent, $('.notebook-editor'));
|
||||||
|
this.createBody(this.rootElement);
|
||||||
|
this.generateFontInfo();
|
||||||
|
this.editorFocus = NOTEBOOK_EDITOR_FOCUSED.bindTo(this.contextKeyService);
|
||||||
|
this._register(this.onDidFocus(() => {
|
||||||
|
this.editorFocus?.set(true);
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(this.onDidBlur(() => {
|
||||||
|
this.editorFocus?.set(false);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateFontInfo(): void {
|
||||||
|
const editorOptions = this.configurationService.getValue<IEditorOptions>('editor');
|
||||||
|
this.fontInfo = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel());
|
||||||
|
}
|
||||||
|
|
||||||
|
private createBody(parent: HTMLElement): void {
|
||||||
|
this.body = document.createElement('div');
|
||||||
|
DOM.addClass(this.body, 'cell-list-container');
|
||||||
|
this.createCellList();
|
||||||
|
DOM.append(parent, this.body);
|
||||||
|
DOM.append(parent, this.findWidget.getDomNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
private createCellList(): void {
|
||||||
|
DOM.addClass(this.body, 'cell-list-container');
|
||||||
|
|
||||||
|
const renders = [
|
||||||
|
this.instantiationService.createInstance(CodeCellRenderer, this, this.renderedEditors),
|
||||||
|
this.instantiationService.createInstance(MarkdownCellRenderer, this),
|
||||||
|
];
|
||||||
|
|
||||||
|
this.list = <NotebookCellList>this.instantiationService.createInstance(
|
||||||
|
NotebookCellList,
|
||||||
|
'NotebookCellList',
|
||||||
|
this.body,
|
||||||
|
this.instantiationService.createInstance(NotebookCellListDelegate),
|
||||||
|
renders,
|
||||||
|
this.contextKeyService,
|
||||||
|
{
|
||||||
|
setRowLineHeight: false,
|
||||||
|
setRowHeight: false,
|
||||||
|
supportDynamicHeights: true,
|
||||||
|
horizontalScrolling: false,
|
||||||
|
keyboardSupport: false,
|
||||||
|
mouseSupport: true,
|
||||||
|
multipleSelectionSupport: false,
|
||||||
|
enableKeyboardNavigation: true,
|
||||||
|
overrideStyles: {
|
||||||
|
listBackground: editorBackground,
|
||||||
|
listActiveSelectionBackground: editorBackground,
|
||||||
|
listActiveSelectionForeground: foreground,
|
||||||
|
listFocusAndSelectionBackground: editorBackground,
|
||||||
|
listFocusAndSelectionForeground: foreground,
|
||||||
|
listFocusBackground: editorBackground,
|
||||||
|
listFocusForeground: foreground,
|
||||||
|
listHoverForeground: foreground,
|
||||||
|
listHoverBackground: editorBackground,
|
||||||
|
listHoverOutline: focusBorder,
|
||||||
|
listFocusOutline: focusBorder,
|
||||||
|
listInactiveSelectionBackground: editorBackground,
|
||||||
|
listInactiveSelectionForeground: foreground,
|
||||||
|
listInactiveFocusBackground: editorBackground,
|
||||||
|
listInactiveFocusOutline: editorBackground,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.control = new NotebookCodeEditors(this.list, this.renderedEditors);
|
||||||
|
this.webview = new BackLayerWebView(this.webviewService, this.notebookService, this, this.environmentSerice);
|
||||||
|
this.list.rowsContainer.appendChild(this.webview.element);
|
||||||
|
this._register(this.list);
|
||||||
|
}
|
||||||
|
|
||||||
|
getControl() {
|
||||||
|
return this.control;
|
||||||
|
}
|
||||||
|
|
||||||
|
onHide() {
|
||||||
|
this.editorFocus?.set(false);
|
||||||
|
if (this.webview) {
|
||||||
|
this.localStore.clear();
|
||||||
|
this.list?.rowsContainer.removeChild(this.webview?.element);
|
||||||
|
this.webview?.dispose();
|
||||||
|
this.webview = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.list?.splice(0, this.list?.length);
|
||||||
|
|
||||||
|
if (this.notebookViewModel && !this.notebookViewModel.isDirty()) {
|
||||||
|
this.notebookService.destoryNotebookDocument(this.notebookViewModel.viewType!, this.notebookViewModel!.notebookDocument);
|
||||||
|
this.notebookViewModel.dispose();
|
||||||
|
this.notebookViewModel = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onHide();
|
||||||
|
}
|
||||||
|
|
||||||
|
setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void {
|
||||||
|
super.setEditorVisible(visible, group);
|
||||||
|
this.groupListener.value = ((group as IEditorGroupView).onWillCloseEditor(e => this.onWillCloseEditorInGroup(e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onWillCloseEditorInGroup(e: IEditorCloseEvent): void {
|
||||||
|
const editor = e.editor;
|
||||||
|
if (!(editor instanceof NotebookEditorInput)) {
|
||||||
|
return; // only handle files
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editor === this.input) {
|
||||||
|
this.saveTextEditorViewState(editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
super.focus();
|
||||||
|
this.editorFocus?.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setInput(input: NotebookEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
|
||||||
|
if (this.input instanceof NotebookEditorInput) {
|
||||||
|
this.saveTextEditorViewState(this.input);
|
||||||
|
}
|
||||||
|
|
||||||
|
await super.setInput(input, options, token);
|
||||||
|
const model = await input.resolve();
|
||||||
|
|
||||||
|
if (this.notebookViewModel === undefined || !this.notebookViewModel.equal(model) || this.webview === null) {
|
||||||
|
this.detachModel();
|
||||||
|
await this.attachModel(input, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reveal cell if editor options tell to do so
|
||||||
|
if (options instanceof NotebookEditorOptions && options.cellOptions) {
|
||||||
|
const cellOptions = options.cellOptions;
|
||||||
|
const cell = this.notebookViewModel!.viewCells.find(cell => cell.uri.toString() === cellOptions.resource.toString());
|
||||||
|
if (cell) {
|
||||||
|
this.revealInCenterIfOutsideViewport(cell);
|
||||||
|
const editor = this.renderedEditors.get(cell)!;
|
||||||
|
if (editor) {
|
||||||
|
if (cellOptions.options?.selection) {
|
||||||
|
const { selection } = cellOptions.options;
|
||||||
|
editor.setSelection({
|
||||||
|
...selection,
|
||||||
|
endLineNumber: selection.endLineNumber || selection.startLineNumber,
|
||||||
|
endColumn: selection.endColumn || selection.startColumn
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!cellOptions.options?.preserveFocus) {
|
||||||
|
editor.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearInput(): void {
|
||||||
|
if (this.input && this.input instanceof NotebookEditorInput && !this.input.isDisposed()) {
|
||||||
|
this.saveTextEditorViewState(this.input);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.clearInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
private detachModel() {
|
||||||
|
this.localStore.clear();
|
||||||
|
this.notebookViewModel?.dispose();
|
||||||
|
this.notebookViewModel = undefined;
|
||||||
|
this.webview?.clearInsets();
|
||||||
|
this.webview?.clearPreloadsCache();
|
||||||
|
this.findWidget.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async attachModel(input: NotebookEditorInput, model: NotebookEditorModel) {
|
||||||
|
if (!this.webview) {
|
||||||
|
this.webview = new BackLayerWebView(this.webviewService, this.notebookService, this, this.environmentSerice);
|
||||||
|
this.list?.rowsContainer.insertAdjacentElement('afterbegin', this.webview!.element);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.notebookViewModel = this.instantiationService.createInstance(NotebookViewModel, input.viewType!, model);
|
||||||
|
const viewState = this.loadTextEditorViewState(input);
|
||||||
|
this.notebookViewModel.restoreEditorViewState(viewState);
|
||||||
|
|
||||||
|
this.localStore.add(this.notebookViewModel.onDidChangeViewCells((e) => {
|
||||||
|
if (e.synchronous) {
|
||||||
|
e.splices.reverse().forEach((diff) => {
|
||||||
|
this.list?.splice(diff[0], diff[1], diff[2]);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
DOM.scheduleAtNextAnimationFrame(() => {
|
||||||
|
e.splices.reverse().forEach((diff) => {
|
||||||
|
this.list?.splice(diff[0], diff[1], diff[2]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.webview?.updateRendererPreloads(this.notebookViewModel.renderers);
|
||||||
|
|
||||||
|
this.localStore.add(this.list!.onWillScroll(e => {
|
||||||
|
this.webview!.updateViewScrollTop(-e.scrollTop, []);
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.localStore.add(this.list!.onDidChangeContentHeight(() => {
|
||||||
|
const scrollTop = this.list?.scrollTop || 0;
|
||||||
|
const scrollHeight = this.list?.scrollHeight || 0;
|
||||||
|
this.webview!.element.style.height = `${scrollHeight}px`;
|
||||||
|
let updateItems: { cell: CellViewModel, output: IOutput, cellTop: number }[] = [];
|
||||||
|
|
||||||
|
if (this.webview?.insetMapping) {
|
||||||
|
this.webview?.insetMapping.forEach((value, key) => {
|
||||||
|
let cell = value.cell;
|
||||||
|
let index = this.notebookViewModel!.getViewCellIndex(cell);
|
||||||
|
let cellTop = this.list?.getAbsoluteTop(index) || 0;
|
||||||
|
if (this.webview!.shouldUpdateInset(cell, key, cellTop)) {
|
||||||
|
updateItems.push({
|
||||||
|
cell: cell,
|
||||||
|
output: key,
|
||||||
|
cellTop: cellTop
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updateItems.length) {
|
||||||
|
this.webview?.updateViewScrollTop(-scrollTop, updateItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.localStore.add(this.list!.onDidChangeFocus((e) => {
|
||||||
|
if (e.elements.length > 0) {
|
||||||
|
this.notebookService.updateNotebookActiveCell(input.viewType!, input.resource!, e.elements[0].handle);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.list?.splice(0, this.list?.length || 0);
|
||||||
|
this.list?.splice(0, 0, this.notebookViewModel!.viewCells as CellViewModel[]);
|
||||||
|
this.list?.layout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveTextEditorViewState(input: NotebookEditorInput): void {
|
||||||
|
if (this.group && this.notebookViewModel) {
|
||||||
|
const state = this.notebookViewModel.saveEditorViewState();
|
||||||
|
this.editorMemento.saveEditorState(this.group, input.resource, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadTextEditorViewState(input: NotebookEditorInput): INotebookEditorViewState | undefined {
|
||||||
|
if (this.group) {
|
||||||
|
return this.editorMemento.loadEditorState(this.group, input.resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||||
|
}
|
||||||
|
|
||||||
|
layout(dimension: DOM.Dimension): void {
|
||||||
|
this.dimension = new DOM.Dimension(dimension.width, dimension.height);
|
||||||
|
DOM.toggleClass(this.rootElement, 'mid-width', dimension.width < 1000 && dimension.width >= 600);
|
||||||
|
DOM.toggleClass(this.rootElement, 'narrow-width', dimension.width < 600);
|
||||||
|
DOM.size(this.body, dimension.width, dimension.height);
|
||||||
|
this.list?.layout(dimension.height, dimension.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected saveState(): void {
|
||||||
|
if (this.input instanceof NotebookEditorInput) {
|
||||||
|
this.saveTextEditorViewState(this.input);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Editor Features
|
||||||
|
|
||||||
|
selectElement(cell: ICellViewModel) {
|
||||||
|
const index = this.notebookViewModel?.getViewCellIndex(cell);
|
||||||
|
|
||||||
|
if (index !== undefined) {
|
||||||
|
this.list?.setSelection([index]);
|
||||||
|
this.list?.setFocus([index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revealInView(cell: ICellViewModel) {
|
||||||
|
const index = this.notebookViewModel?.getViewCellIndex(cell);
|
||||||
|
|
||||||
|
if (index !== undefined) {
|
||||||
|
this.list?.revealInView(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revealInCenterIfOutsideViewport(cell: ICellViewModel) {
|
||||||
|
const index = this.notebookViewModel?.getViewCellIndex(cell);
|
||||||
|
|
||||||
|
if (index !== undefined) {
|
||||||
|
this.list?.revealInCenterIfOutsideViewport(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revealInCenter(cell: ICellViewModel) {
|
||||||
|
const index = this.notebookViewModel?.getViewCellIndex(cell);
|
||||||
|
|
||||||
|
if (index !== undefined) {
|
||||||
|
this.list?.revealInCenter(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revealLineInView(cell: ICellViewModel, line: number): void {
|
||||||
|
const index = this.notebookViewModel?.getViewCellIndex(cell);
|
||||||
|
|
||||||
|
if (index !== undefined) {
|
||||||
|
this.list?.revealLineInView(index, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revealLineInCenter(cell: ICellViewModel, line: number) {
|
||||||
|
const index = this.notebookViewModel?.getViewCellIndex(cell);
|
||||||
|
|
||||||
|
if (index !== undefined) {
|
||||||
|
this.list?.revealLineInCenter(index, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revealLineInCenterIfOutsideViewport(cell: ICellViewModel, line: number) {
|
||||||
|
const index = this.notebookViewModel?.getViewCellIndex(cell);
|
||||||
|
|
||||||
|
if (index !== undefined) {
|
||||||
|
this.list?.revealLineInCenterIfOutsideViewport(index, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revealRangeInView(cell: ICellViewModel, range: Range): void {
|
||||||
|
const index = this.notebookViewModel?.getViewCellIndex(cell);
|
||||||
|
|
||||||
|
if (index !== undefined) {
|
||||||
|
this.list?.revealRangeInView(index, range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revealRangeInCenter(cell: ICellViewModel, range: Range): void {
|
||||||
|
const index = this.notebookViewModel?.getViewCellIndex(cell);
|
||||||
|
|
||||||
|
if (index !== undefined) {
|
||||||
|
this.list?.revealRangeInCenter(index, range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revealRangeInCenterIfOutsideViewport(cell: ICellViewModel, range: Range): void {
|
||||||
|
const index = this.notebookViewModel?.getViewCellIndex(cell);
|
||||||
|
|
||||||
|
if (index !== undefined) {
|
||||||
|
this.list?.revealRangeInCenterIfOutsideViewport(index, range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCellSelection(cell: ICellViewModel, range: Range): void {
|
||||||
|
const index = this.notebookViewModel?.getViewCellIndex(cell);
|
||||||
|
|
||||||
|
if (index !== undefined) {
|
||||||
|
this.list?.setCellSelection(index, range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any {
|
||||||
|
return this.notebookViewModel?.changeDecorations(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Find Delegate
|
||||||
|
|
||||||
|
public showFind() {
|
||||||
|
this.findWidget.reveal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public hideFind() {
|
||||||
|
this.findWidget.hide();
|
||||||
|
this.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Cell operations
|
||||||
|
layoutNotebookCell(cell: ICellViewModel, height: number) {
|
||||||
|
let relayout = (cell: ICellViewModel, height: number) => {
|
||||||
|
let index = this.notebookViewModel!.getViewCellIndex(cell);
|
||||||
|
if (index >= 0) {
|
||||||
|
this.list?.updateElementHeight(index, height);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DOM.scheduleAtNextAnimationFrame(() => {
|
||||||
|
relayout(cell, height);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async insertNotebookCell(cell: ICellViewModel, type: CellKind, direction: 'above' | 'below', initialText: string = ''): Promise<void> {
|
||||||
|
const newLanguages = this.notebookViewModel!.languages;
|
||||||
|
const language = newLanguages && newLanguages.length ? newLanguages[0] : 'markdown';
|
||||||
|
const index = this.notebookViewModel!.getViewCellIndex(cell);
|
||||||
|
const insertIndex = direction === 'above' ? index : index + 1;
|
||||||
|
const newModeCell = await this.notebookService.createNotebookCell(this.notebookViewModel!.viewType, this.notebookViewModel!.uri, insertIndex, language, type);
|
||||||
|
newModeCell!.source = initialText.split(/\r?\n/g);
|
||||||
|
const newCell = this.notebookViewModel!.insertCell(insertIndex, newModeCell!, true);
|
||||||
|
this.list?.setFocus([insertIndex]);
|
||||||
|
|
||||||
|
if (type === CellKind.Markdown) {
|
||||||
|
newCell.state = CellState.Editing;
|
||||||
|
}
|
||||||
|
|
||||||
|
DOM.scheduleAtNextAnimationFrame(() => {
|
||||||
|
this.list?.revealInCenterIfOutsideViewport(insertIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteNotebookCell(cell: ICellViewModel): Promise<void> {
|
||||||
|
(cell as CellViewModel).save();
|
||||||
|
const index = this.notebookViewModel!.getViewCellIndex(cell);
|
||||||
|
await this.notebookService.deleteNotebookCell(this.notebookViewModel!.viewType, this.notebookViewModel!.uri, index);
|
||||||
|
this.notebookViewModel!.deleteCell(index, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
moveCellDown(cell: ICellViewModel): void {
|
||||||
|
const index = this.notebookViewModel!.getViewCellIndex(cell);
|
||||||
|
const newIdx = index + 1;
|
||||||
|
this.moveCellToIndex(cell, index, newIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
moveCellUp(cell: ICellViewModel): void {
|
||||||
|
const index = this.notebookViewModel!.getViewCellIndex(cell);
|
||||||
|
const newIdx = index - 1;
|
||||||
|
this.moveCellToIndex(cell, index, newIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private moveCellToIndex(cell: ICellViewModel, index: number, newIdx: number): void {
|
||||||
|
if (!this.notebookViewModel!.moveCellToIdx(index, newIdx, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DOM.scheduleAtNextAnimationFrame(() => {
|
||||||
|
this.list?.revealInCenterIfOutsideViewport(index + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
editNotebookCell(cell: CellViewModel): void {
|
||||||
|
cell.state = CellState.Editing;
|
||||||
|
|
||||||
|
this.renderedEditors.get(cell)?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
saveNotebookCell(cell: ICellViewModel): void {
|
||||||
|
cell.state = CellState.Preview;
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveCell() {
|
||||||
|
let elements = this.list?.getFocusedElements();
|
||||||
|
|
||||||
|
if (elements && elements.length) {
|
||||||
|
return elements[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
focusNotebookCell(cell: ICellViewModel, focusEditor: boolean) {
|
||||||
|
const index = this.notebookViewModel!.getViewCellIndex(cell);
|
||||||
|
|
||||||
|
if (focusEditor) {
|
||||||
|
this.list?.setFocus([index]);
|
||||||
|
this.list?.setSelection([index]);
|
||||||
|
this.list?.focusView();
|
||||||
|
|
||||||
|
cell.state = CellState.Editing;
|
||||||
|
cell.focusMode = CellFocusMode.Editor;
|
||||||
|
this.revealInCenterIfOutsideViewport(cell);
|
||||||
|
} else {
|
||||||
|
let itemDOM = this.list?.domElementAtIndex(index);
|
||||||
|
if (document.activeElement && itemDOM && itemDOM.contains(document.activeElement)) {
|
||||||
|
(document.activeElement as HTMLElement).blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.state = CellState.Preview;
|
||||||
|
cell.focusMode = CellFocusMode.Editor;
|
||||||
|
|
||||||
|
this.list?.setFocus([index]);
|
||||||
|
this.list?.setSelection([index]);
|
||||||
|
this.revealInCenterIfOutsideViewport(cell);
|
||||||
|
this.list?.focusView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region MISC
|
||||||
|
|
||||||
|
getLayoutInfo(): NotebookLayoutInfo {
|
||||||
|
if (!this.list) {
|
||||||
|
throw new Error('Editor is not initalized successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: this.dimension!.width,
|
||||||
|
height: this.dimension!.height,
|
||||||
|
fontInfo: this.fontInfo!
|
||||||
|
};
|
||||||
|
}
|
||||||
|
getFontInfo(): BareFontInfo | undefined {
|
||||||
|
return this.fontInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerScroll(event: IMouseWheelEvent) {
|
||||||
|
this.list?.triggerScrollFromMouseWheelEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
createInset(cell: CellViewModel, output: IOutput, shadowContent: string, offset: number) {
|
||||||
|
if (!this.webview) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let preloads = this.notebookViewModel!.renderers;
|
||||||
|
|
||||||
|
if (!this.webview!.insetMapping.has(output)) {
|
||||||
|
let index = this.notebookViewModel!.getViewCellIndex(cell);
|
||||||
|
let cellTop = this.list?.getAbsoluteTop(index) || 0;
|
||||||
|
|
||||||
|
this.webview!.createInset(cell, output, cellTop, offset, shadowContent, preloads);
|
||||||
|
} else {
|
||||||
|
let index = this.notebookViewModel!.getViewCellIndex(cell);
|
||||||
|
let cellTop = this.list?.getAbsoluteTop(index) || 0;
|
||||||
|
let scrollTop = this.list?.scrollTop || 0;
|
||||||
|
|
||||||
|
this.webview!.updateViewScrollTop(-scrollTop, [{ cell: cell, output: output, cellTop: cellTop }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeInset(output: IOutput) {
|
||||||
|
if (!this.webview) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.webview!.removeInset(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOutputRenderer(): OutputRenderer {
|
||||||
|
return this.outputRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
const embeddedEditorBackground = 'walkThrough.embeddedEditorBackground';
|
||||||
|
|
||||||
|
export const focusedCellIndicator = registerColor('notebook.focusedCellIndicator', {
|
||||||
|
light: new Color(new RGBA(102, 175, 224)),
|
||||||
|
dark: new Color(new RGBA(12, 125, 157)),
|
||||||
|
hc: new Color(new RGBA(0, 73, 122))
|
||||||
|
}, nls.localize('notebook.focusedCellIndicator', "The color of the focused notebook cell indicator."));
|
||||||
|
|
||||||
|
|
||||||
|
registerThemingParticipant((theme, collector) => {
|
||||||
|
const color = getExtraColor(theme, embeddedEditorBackground, { dark: 'rgba(0, 0, 0, .4)', extra_dark: 'rgba(200, 235, 255, .064)', light: '#f4f4f4', hc: null });
|
||||||
|
if (color) {
|
||||||
|
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .monaco-editor-background,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell .margin-view-overlays { background: ${color}; }`);
|
||||||
|
}
|
||||||
|
const link = theme.getColor(textLinkForeground);
|
||||||
|
if (link) {
|
||||||
|
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell a { color: ${link}; }`);
|
||||||
|
}
|
||||||
|
const activeLink = theme.getColor(textLinkActiveForeground);
|
||||||
|
if (activeLink) {
|
||||||
|
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell a:hover,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .cell a:active { color: ${activeLink}; }`);
|
||||||
|
}
|
||||||
|
const shortcut = theme.getColor(textPreformatForeground);
|
||||||
|
if (shortcut) {
|
||||||
|
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor code,
|
||||||
|
.monaco-workbench .part.editor > .content .notebook-editor .shortcut { color: ${shortcut}; }`);
|
||||||
|
}
|
||||||
|
const border = theme.getColor(contrastBorder);
|
||||||
|
if (border) {
|
||||||
|
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-editor { border-color: ${border}; }`);
|
||||||
|
}
|
||||||
|
const quoteBackground = theme.getColor(textBlockQuoteBackground);
|
||||||
|
if (quoteBackground) {
|
||||||
|
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor blockquote { background: ${quoteBackground}; }`);
|
||||||
|
}
|
||||||
|
const quoteBorder = theme.getColor(textBlockQuoteBorder);
|
||||||
|
if (quoteBorder) {
|
||||||
|
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor blockquote { border-color: ${quoteBorder}; }`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const inactiveListItem = theme.getColor('list.inactiveSelectionBackground');
|
||||||
|
|
||||||
|
if (inactiveListItem) {
|
||||||
|
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { background-color: ${inactiveListItem}; }`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const focusedCellIndicatorColor = theme.getColor(focusedCellIndicator);
|
||||||
|
if (focusedCellIndicatorColor) {
|
||||||
|
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.focused .notebook-cell-focus-indicator { border-color: ${focusedCellIndicatorColor}; }`);
|
||||||
|
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.selected .notebook-cell-focus-indicator { border-color: ${focusedCellIndicatorColor}; }`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cell Margin
|
||||||
|
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > div.cell { padding: 8px ${CELL_MARGIN}px 8px ${CELL_MARGIN}px; }`);
|
||||||
|
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { margin: 8px ${CELL_MARGIN}px; }`);
|
||||||
|
});
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { EditorInput, EditorModel, IEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor';
|
||||||
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
|
import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService';
|
||||||
|
import { ICell, NotebookCellsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
|
||||||
|
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
|
||||||
|
|
||||||
|
export class NotebookEditorModel extends EditorModel {
|
||||||
|
private _dirty = false;
|
||||||
|
|
||||||
|
protected readonly _onDidChangeDirty = this._register(new Emitter<void>());
|
||||||
|
readonly onDidChangeDirty = this._onDidChangeDirty.event;
|
||||||
|
|
||||||
|
private readonly _onDidChangeCells = new Emitter<NotebookCellsSplice[]>();
|
||||||
|
get onDidChangeCells(): Event<NotebookCellsSplice[]> { return this._onDidChangeCells.event; }
|
||||||
|
|
||||||
|
|
||||||
|
get notebook() {
|
||||||
|
return this._notebook;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private _notebook: NotebookTextModel
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if (_notebook && _notebook.onDidChangeCells) {
|
||||||
|
this._register(_notebook.onDidChangeContent(() => {
|
||||||
|
this._dirty = true;
|
||||||
|
this._onDidChangeDirty.fire();
|
||||||
|
}));
|
||||||
|
this._register(_notebook.onDidChangeCells((e) => {
|
||||||
|
this._onDidChangeCells.fire(e);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isDirty() {
|
||||||
|
return this._dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNotebook(): NotebookTextModel {
|
||||||
|
return this._notebook;
|
||||||
|
}
|
||||||
|
|
||||||
|
insertCell(cell: ICell, index: number) {
|
||||||
|
let notebook = this.getNotebook();
|
||||||
|
|
||||||
|
if (notebook) {
|
||||||
|
let mainCell = new NotebookCellTextModel(URI.revive(cell.uri), cell.handle, cell.source, cell.language, cell.cellKind, cell.outputs);
|
||||||
|
this.notebook.insertNewCell(index, mainCell);
|
||||||
|
this._dirty = true;
|
||||||
|
this._onDidChangeDirty.fire();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCell(index: number) {
|
||||||
|
let notebook = this.getNotebook();
|
||||||
|
|
||||||
|
if (notebook) {
|
||||||
|
this.notebook.removeCell(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(): Promise<boolean> {
|
||||||
|
if (this._notebook) {
|
||||||
|
this._dirty = false;
|
||||||
|
this._onDidChangeDirty.fire();
|
||||||
|
// todo, flush all states
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotebookEditorInput extends EditorInput {
|
||||||
|
static readonly ID: string = 'workbench.input.notebook';
|
||||||
|
private promise: Promise<NotebookEditorModel> | null = null;
|
||||||
|
private textModel: NotebookEditorModel | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public resource: URI,
|
||||||
|
public name: string,
|
||||||
|
public readonly viewType: string | undefined,
|
||||||
|
@INotebookService private readonly notebookService: INotebookService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
getTypeId(): string {
|
||||||
|
return NotebookEditorInput.ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
getName(): string {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDirty() {
|
||||||
|
return this.textModel?.isDirty() || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined> {
|
||||||
|
if (this.textModel) {
|
||||||
|
await this.notebookService.save(this.textModel.notebook.viewType, this.textModel.notebook.uri);
|
||||||
|
await this.textModel.save();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async revert(group: GroupIdentifier, options?: IRevertOptions): Promise<void> {
|
||||||
|
if (this.textModel) {
|
||||||
|
// TODO@rebornix we need hashing
|
||||||
|
await this.textModel.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolve(): Promise<NotebookEditorModel> {
|
||||||
|
if (!this.promise) {
|
||||||
|
await this.notebookService.canResolve(this.viewType!);
|
||||||
|
|
||||||
|
this.promise = this.notebookService.resolveNotebook(this.viewType!, this.resource).then(notebook => {
|
||||||
|
this.textModel = new NotebookEditorModel(notebook!);
|
||||||
|
this.textModel.onDidChangeDirty(() => this._onDidChangeDirty.fire());
|
||||||
|
return this.textModel;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
|
import { BrandedService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||||
|
|
||||||
|
export type IOutputTransformCtor = IConstructorSignature1<INotebookEditor, IOutputTransformContribution>;
|
||||||
|
|
||||||
|
export interface IOutputTransformDescription {
|
||||||
|
id: string;
|
||||||
|
kind: CellOutputKind;
|
||||||
|
ctor: IOutputTransformCtor;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace NotebookRegistry {
|
||||||
|
export function getOutputTransformContributions(): IOutputTransformDescription[] {
|
||||||
|
return NotebookRegistryImpl.INSTANCE.getNotebookOutputTransform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerOutputTransform<Services extends BrandedService[]>(id: string, kind: CellOutputKind, ctor: { new(editor: INotebookEditor, ...services: Services): IOutputTransformContribution }): void {
|
||||||
|
NotebookRegistryImpl.INSTANCE.registerOutputTransform(id, kind, ctor);
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotebookRegistryImpl {
|
||||||
|
|
||||||
|
static readonly INSTANCE = new NotebookRegistryImpl();
|
||||||
|
|
||||||
|
private readonly outputTransforms: IOutputTransformDescription[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.outputTransforms = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOutputTransform<Services extends BrandedService[]>(id: string, kind: CellOutputKind, ctor: { new(editor: INotebookEditor, ...services: Services): IOutputTransformContribution }): void {
|
||||||
|
this.outputTransforms.push({ id: id, kind: kind, ctor: ctor as IOutputTransformCtor });
|
||||||
|
}
|
||||||
|
|
||||||
|
getNotebookOutputTransform(): IOutputTransformDescription[] {
|
||||||
|
return this.outputTransforms.slice(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,337 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
|
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import { notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint';
|
||||||
|
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
|
||||||
|
import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol';
|
||||||
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
|
import { INotebookTextModel, ICell, INotebookMimeTypeSelector, INotebookRendererInfo, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
|
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||||
|
import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer';
|
||||||
|
import { Iterable } from 'vs/base/common/iterator';
|
||||||
|
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
|
||||||
|
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
|
||||||
|
|
||||||
|
function MODEL_ID(resource: URI): string {
|
||||||
|
return resource.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const INotebookService = createDecorator<INotebookService>('notebookService');
|
||||||
|
|
||||||
|
export interface IMainNotebookController {
|
||||||
|
resolveNotebook(viewType: string, uri: URI): Promise<NotebookTextModel | undefined>;
|
||||||
|
executeNotebook(viewType: string, uri: URI): Promise<void>;
|
||||||
|
updateNotebookActiveCell(uri: URI, cellHandle: number): void;
|
||||||
|
createRawCell(uri: URI, index: number, language: string, type: CellKind): Promise<NotebookCellTextModel | undefined>;
|
||||||
|
deleteCell(uri: URI, index: number): Promise<boolean>
|
||||||
|
executeNotebookActiveCell(uri: URI): void;
|
||||||
|
destoryNotebookDocument(notebook: INotebookTextModel): Promise<void>;
|
||||||
|
save(uri: URI): Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INotebookService {
|
||||||
|
_serviceBrand: undefined;
|
||||||
|
canResolve(viewType: string): Promise<void>;
|
||||||
|
onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }>;
|
||||||
|
registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): void;
|
||||||
|
unregisterNotebookProvider(viewType: string): void;
|
||||||
|
registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]): void;
|
||||||
|
unregisterNotebookRenderer(handle: number): void;
|
||||||
|
getRendererInfo(handle: number): INotebookRendererInfo | undefined;
|
||||||
|
resolveNotebook(viewType: string, uri: URI): Promise<NotebookTextModel | undefined>;
|
||||||
|
executeNotebook(viewType: string, uri: URI): Promise<void>;
|
||||||
|
executeNotebookActiveCell(viewType: string, uri: URI): Promise<void>;
|
||||||
|
getContributedNotebookProviders(resource: URI): readonly NotebookProviderInfo[];
|
||||||
|
getNotebookProviderResourceRoots(): URI[];
|
||||||
|
updateNotebookActiveCell(viewType: string, resource: URI, cellHandle: number): void;
|
||||||
|
createNotebookCell(viewType: string, resource: URI, index: number, language: string, type: CellKind): Promise<ICell | undefined>;
|
||||||
|
deleteNotebookCell(viewType: string, resource: URI, index: number): Promise<boolean>;
|
||||||
|
destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void;
|
||||||
|
updateActiveNotebookDocument(viewType: string, resource: URI): void;
|
||||||
|
save(viewType: string, resource: URI): Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotebookProviderInfoStore {
|
||||||
|
private readonly contributedEditors = new Map<string, NotebookProviderInfo>();
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.contributedEditors.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
get(viewType: string): NotebookProviderInfo | undefined {
|
||||||
|
return this.contributedEditors.get(viewType);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(info: NotebookProviderInfo): void {
|
||||||
|
if (this.contributedEditors.has(info.id)) {
|
||||||
|
console.log(`Custom editor with id '${info.id}' already registered`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.contributedEditors.set(info.id, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
getContributedNotebook(resource: URI): readonly NotebookProviderInfo[] {
|
||||||
|
return [...Iterable.filter(this.contributedEditors.values(), customEditor => customEditor.matches(resource))];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotebookOutputRendererInfoStore {
|
||||||
|
private readonly contributedRenderers = new Map<string, NotebookOutputRendererInfo>();
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.contributedRenderers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
get(viewType: string): NotebookOutputRendererInfo | undefined {
|
||||||
|
return this.contributedRenderers.get(viewType);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(info: NotebookOutputRendererInfo): void {
|
||||||
|
if (this.contributedRenderers.has(info.id)) {
|
||||||
|
console.log(`Custom notebook output renderer with id '${info.id}' already registered`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.contributedRenderers.set(info.id, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
getContributedRenderer(mimeType: string): readonly NotebookOutputRendererInfo[] {
|
||||||
|
return Array.from(this.contributedRenderers.values()).filter(customEditor =>
|
||||||
|
customEditor.matches(mimeType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModelData implements IDisposable {
|
||||||
|
private readonly _modelEventListeners = new DisposableStore();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public model: NotebookTextModel,
|
||||||
|
onWillDispose: (model: INotebookTextModel) => void
|
||||||
|
) {
|
||||||
|
this._modelEventListeners.add(model.onWillDispose(() => onWillDispose(model)));
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this._modelEventListeners.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class NotebookService extends Disposable implements INotebookService {
|
||||||
|
_serviceBrand: undefined;
|
||||||
|
private readonly _notebookProviders = new Map<string, { controller: IMainNotebookController, extensionData: NotebookExtensionDescription }>();
|
||||||
|
private readonly _notebookRenderers = new Map<number, { extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[] }>();
|
||||||
|
notebookProviderInfoStore: NotebookProviderInfoStore = new NotebookProviderInfoStore();
|
||||||
|
notebookRenderersInfoStore: NotebookOutputRendererInfoStore = new NotebookOutputRendererInfoStore();
|
||||||
|
private readonly _models: { [modelId: string]: ModelData; };
|
||||||
|
private _onDidChangeActiveEditor = new Emitter<{ viewType: string, uri: URI }>();
|
||||||
|
onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }> = this._onDidChangeActiveEditor.event;
|
||||||
|
private _resolvePool = new Map<string, () => void>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IExtensionService private readonly extensionService: IExtensionService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._models = {};
|
||||||
|
notebookProviderExtensionPoint.setHandler((extensions) => {
|
||||||
|
this.notebookProviderInfoStore.clear();
|
||||||
|
|
||||||
|
for (const extension of extensions) {
|
||||||
|
for (const notebookContribution of extension.value) {
|
||||||
|
this.notebookProviderInfoStore.add(new NotebookProviderInfo({
|
||||||
|
id: notebookContribution.viewType,
|
||||||
|
displayName: notebookContribution.displayName,
|
||||||
|
selector: notebookContribution.selector || [],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// console.log(this._notebookProviderInfoStore);
|
||||||
|
});
|
||||||
|
|
||||||
|
notebookRendererExtensionPoint.setHandler((renderers) => {
|
||||||
|
this.notebookRenderersInfoStore.clear();
|
||||||
|
|
||||||
|
for (const extension of renderers) {
|
||||||
|
for (const notebookContribution of extension.value) {
|
||||||
|
this.notebookRenderersInfoStore.add(new NotebookOutputRendererInfo({
|
||||||
|
id: notebookContribution.viewType,
|
||||||
|
displayName: notebookContribution.displayName,
|
||||||
|
mimeTypes: notebookContribution.mimeTypes || []
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(this.notebookRenderersInfoStore);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async canResolve(viewType: string): Promise<void> {
|
||||||
|
if (this._notebookProviders.has(viewType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.extensionService.activateByEvent(`onNotebookEditor:${viewType}`);
|
||||||
|
|
||||||
|
let resolve: () => void;
|
||||||
|
const promise = new Promise<void>(r => { resolve = r; });
|
||||||
|
this._resolvePool.set(viewType, resolve!);
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController) {
|
||||||
|
this._notebookProviders.set(viewType, { extensionData, controller });
|
||||||
|
|
||||||
|
let resolve = this._resolvePool.get(viewType);
|
||||||
|
if (resolve) {
|
||||||
|
resolve();
|
||||||
|
this._resolvePool.delete(viewType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterNotebookProvider(viewType: string): void {
|
||||||
|
this._notebookProviders.delete(viewType);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]) {
|
||||||
|
this._notebookRenderers.set(handle, { extensionData, type, selectors, preloads });
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterNotebookRenderer(handle: number) {
|
||||||
|
this._notebookRenderers.delete(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRendererInfo(handle: number): INotebookRendererInfo | undefined {
|
||||||
|
const renderer = this._notebookRenderers.get(handle);
|
||||||
|
|
||||||
|
if (renderer) {
|
||||||
|
return {
|
||||||
|
id: renderer.extensionData.id,
|
||||||
|
extensionLocation: URI.revive(renderer.extensionData.location),
|
||||||
|
preloads: renderer.preloads
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||||
|
}
|
||||||
|
|
||||||
|
async resolveNotebook(viewType: string, uri: URI): Promise<NotebookTextModel | undefined> {
|
||||||
|
const provider = this._notebookProviders.get(viewType);
|
||||||
|
if (!provider) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notebookModel = await provider.controller.resolveNotebook(viewType, uri);
|
||||||
|
if (!notebookModel) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// new notebook model created
|
||||||
|
const modelId = MODEL_ID(uri);
|
||||||
|
const modelData = new ModelData(
|
||||||
|
notebookModel,
|
||||||
|
(model) => this._onWillDispose(model),
|
||||||
|
);
|
||||||
|
this._models[modelId] = modelData;
|
||||||
|
return modelData.model;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateNotebookActiveCell(viewType: string, resource: URI, cellHandle: number): void {
|
||||||
|
let provider = this._notebookProviders.get(viewType);
|
||||||
|
|
||||||
|
if (provider) {
|
||||||
|
provider.controller.updateNotebookActiveCell(resource, cellHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNotebookCell(viewType: string, resource: URI, index: number, language: string, type: CellKind): Promise<NotebookCellTextModel | undefined> {
|
||||||
|
let provider = this._notebookProviders.get(viewType);
|
||||||
|
|
||||||
|
if (provider) {
|
||||||
|
return provider.controller.createRawCell(resource, index, language, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined; // {{SQL CARBON EDIT}} strict-null-check
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteNotebookCell(viewType: string, resource: URI, index: number): Promise<boolean> {
|
||||||
|
let provider = this._notebookProviders.get(viewType);
|
||||||
|
|
||||||
|
if (provider) {
|
||||||
|
return provider.controller.deleteCell(resource, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeNotebook(viewType: string, uri: URI): Promise<void> {
|
||||||
|
let provider = this._notebookProviders.get(viewType);
|
||||||
|
|
||||||
|
if (provider) {
|
||||||
|
return provider.controller.executeNotebook(viewType, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeNotebookActiveCell(viewType: string, uri: URI): Promise<void> {
|
||||||
|
let provider = this._notebookProviders.get(viewType);
|
||||||
|
|
||||||
|
if (provider) {
|
||||||
|
await provider.controller.executeNotebookActiveCell(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getContributedNotebookProviders(resource: URI): readonly NotebookProviderInfo[] {
|
||||||
|
return this.notebookProviderInfoStore.getContributedNotebook(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
getContributedNotebookOutputRenderers(mimeType: string): readonly NotebookOutputRendererInfo[] {
|
||||||
|
return this.notebookRenderersInfoStore.getContributedRenderer(mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
getNotebookProviderResourceRoots(): URI[] {
|
||||||
|
let ret: URI[] = [];
|
||||||
|
this._notebookProviders.forEach(val => {
|
||||||
|
ret.push(URI.revive(val.extensionData.location));
|
||||||
|
});
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void {
|
||||||
|
let provider = this._notebookProviders.get(viewType);
|
||||||
|
|
||||||
|
if (provider) {
|
||||||
|
provider.controller.destoryNotebookDocument(notebook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateActiveNotebookDocument(viewType: string, resource: URI): void {
|
||||||
|
this._onDidChangeActiveEditor.fire({ viewType, uri: resource });
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(viewType: string, resource: URI): Promise<boolean> {
|
||||||
|
let provider = this._notebookProviders.get(viewType);
|
||||||
|
|
||||||
|
if (provider) {
|
||||||
|
return provider.controller.save(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onWillDispose(model: INotebookTextModel): void {
|
||||||
|
let modelId = MODEL_ID(model.uri);
|
||||||
|
let modelData = this._models[modelId];
|
||||||
|
|
||||||
|
delete this._models[modelId];
|
||||||
|
modelData?.dispose();
|
||||||
|
|
||||||
|
// this._onModelRemoved.fire(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,362 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
import { IListRenderer, IListVirtualDelegate, ListError } from 'vs/base/browser/ui/list/list';
|
||||||
|
import { Event } from 'vs/base/common/event';
|
||||||
|
import { ScrollEvent } from 'vs/base/common/scrollable';
|
||||||
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
|
import { IListService, IWorkbenchListOptions, WorkbenchList } from 'vs/platform/list/browser/listService';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
|
||||||
|
import { isMacintosh } from 'vs/base/common/platform';
|
||||||
|
import { NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
|
import { Range } from 'vs/editor/common/core/range';
|
||||||
|
import { CellRevealType, CellRevealPosition, CursorAtBoundary } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||||
|
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
|
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookCellViewModel';
|
||||||
|
|
||||||
|
export class NotebookCellList extends WorkbenchList<CellViewModel> implements IDisposable {
|
||||||
|
get onWillScroll(): Event<ScrollEvent> { return this.view.onWillScroll; }
|
||||||
|
|
||||||
|
get rowsContainer(): HTMLElement {
|
||||||
|
return this.view.containerDomNode;
|
||||||
|
}
|
||||||
|
private _previousSelectedElements: CellViewModel[] = [];
|
||||||
|
private _localDisposableStore = new DisposableStore();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private listUser: string,
|
||||||
|
container: HTMLElement,
|
||||||
|
delegate: IListVirtualDelegate<CellViewModel>,
|
||||||
|
renderers: IListRenderer<CellViewModel, any>[],
|
||||||
|
contextKeyService: IContextKeyService,
|
||||||
|
options: IWorkbenchListOptions<CellViewModel>,
|
||||||
|
@IListService listService: IListService,
|
||||||
|
@IThemeService themeService: IThemeService,
|
||||||
|
@IConfigurationService configurationService: IConfigurationService,
|
||||||
|
@IKeybindingService keybindingService: IKeybindingService
|
||||||
|
|
||||||
|
) {
|
||||||
|
super(listUser, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService);
|
||||||
|
|
||||||
|
this._previousSelectedElements = this.getSelectedElements();
|
||||||
|
this._localDisposableStore.add(this.onDidChangeSelection((e) => {
|
||||||
|
this._previousSelectedElements.forEach(element => {
|
||||||
|
if (e.elements.indexOf(element) < 0) {
|
||||||
|
element.onDeselect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._previousSelectedElements = e.elements;
|
||||||
|
}));
|
||||||
|
|
||||||
|
const notebookEditorCursorAtBoundaryContext = NOTEBOOK_EDITOR_CURSOR_BOUNDARY.bindTo(contextKeyService);
|
||||||
|
notebookEditorCursorAtBoundaryContext.set('none');
|
||||||
|
|
||||||
|
let cursorSelectionListener: IDisposable | null = null;
|
||||||
|
let textEditorAttachListener: IDisposable | null = null;
|
||||||
|
|
||||||
|
const recomputeContext = (element: CellViewModel) => {
|
||||||
|
switch (element.cursorAtBoundary()) {
|
||||||
|
case CursorAtBoundary.Both:
|
||||||
|
notebookEditorCursorAtBoundaryContext.set('both');
|
||||||
|
break;
|
||||||
|
case CursorAtBoundary.Top:
|
||||||
|
notebookEditorCursorAtBoundaryContext.set('top');
|
||||||
|
break;
|
||||||
|
case CursorAtBoundary.Bottom:
|
||||||
|
notebookEditorCursorAtBoundaryContext.set('bottom');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
notebookEditorCursorAtBoundaryContext.set('none');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cursor Boundary context
|
||||||
|
this._localDisposableStore.add(this.onDidChangeSelection((e) => {
|
||||||
|
if (e.elements.length) {
|
||||||
|
cursorSelectionListener?.dispose();
|
||||||
|
textEditorAttachListener?.dispose();
|
||||||
|
// we only validate the first focused element
|
||||||
|
const focusedElement = e.elements[0];
|
||||||
|
|
||||||
|
cursorSelectionListener = focusedElement.onDidChangeCursorSelection(() => {
|
||||||
|
recomputeContext(focusedElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
textEditorAttachListener = focusedElement.onDidChangeEditorAttachState(() => {
|
||||||
|
if (focusedElement.editorAttached) {
|
||||||
|
recomputeContext(focusedElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
recomputeContext(focusedElement);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset context
|
||||||
|
notebookEditorCursorAtBoundaryContext.set('none');
|
||||||
|
}));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
domElementAtIndex(index: number): HTMLElement | null {
|
||||||
|
return this.view.domElement(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
focusView() {
|
||||||
|
this.view.domNode.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
getAbsoluteTop(index: number): number {
|
||||||
|
if (index < 0 || index >= this.length) {
|
||||||
|
throw new ListError(this.listUser, `Invalid index ${index}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.view.elementTop(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
|
||||||
|
this.view.triggerScrollFromMouseWheelEvent(browserEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateElementHeight(index: number, size: number): void {
|
||||||
|
const focused = this.getSelection();
|
||||||
|
this.view.updateElementHeight(index, size, focused.length ? focused[0] : null);
|
||||||
|
// this.view.updateElementHeight(index, size, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// override
|
||||||
|
domFocus() {
|
||||||
|
if (document.activeElement && this.view.domNode.contains(document.activeElement)) {
|
||||||
|
// for example, when focus goes into monaco editor, if we refocus the list view, the editor will lose focus.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isMacintosh && document.activeElement && isContextMenuFocused()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.domFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _revealRange(index: number, range: Range, revealType: CellRevealType, newlyCreated: boolean, alignToBottom: boolean) {
|
||||||
|
const element = this.view.element(index);
|
||||||
|
const scrollTop = this.view.getScrollTop();
|
||||||
|
const wrapperBottom = scrollTop + this.view.renderHeight;
|
||||||
|
const startLineNumber = range.startLineNumber;
|
||||||
|
const lineOffset = element.getLineScrollTopOffset(startLineNumber);
|
||||||
|
const elementTop = this.view.elementTop(index);
|
||||||
|
const lineTop = elementTop + lineOffset;
|
||||||
|
|
||||||
|
// TODO@rebornix 30 ---> line height * 1.5
|
||||||
|
if (lineTop < scrollTop) {
|
||||||
|
this.view.setScrollTop(lineTop - 30);
|
||||||
|
} else if (lineTop > wrapperBottom) {
|
||||||
|
this.view.setScrollTop(scrollTop + lineTop - wrapperBottom + 30);
|
||||||
|
} else if (newlyCreated) {
|
||||||
|
// newly scrolled into view
|
||||||
|
if (alignToBottom) {
|
||||||
|
// align to the bottom
|
||||||
|
this.view.setScrollTop(scrollTop + lineTop - wrapperBottom + 30);
|
||||||
|
} else {
|
||||||
|
// align to to top
|
||||||
|
this.view.setScrollTop(lineTop - 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (revealType === CellRevealType.Range) {
|
||||||
|
element.revealRangeInCenter(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO@rebornix TEST & Fix potential bugs
|
||||||
|
// List items have real dynamic heights, which means after we set `scrollTop` based on the `elementTop(index)`, the element at `index` might still be removed from the view once all relayouting tasks are done.
|
||||||
|
// For example, we scroll item 10 into the view upwards, in the first round, items 7, 8, 9, 10 are all in the viewport. Then item 7 and 8 resize themselves to be larger and finally item 10 is removed from the view.
|
||||||
|
// To ensure that item 10 is always there, we need to scroll item 10 to the top edge of the viewport.
|
||||||
|
private _revealRangeInternal(index: number, range: Range, revealType: CellRevealType) {
|
||||||
|
const scrollTop = this.view.getScrollTop();
|
||||||
|
const wrapperBottom = scrollTop + this.view.renderHeight;
|
||||||
|
const elementTop = this.view.elementTop(index);
|
||||||
|
const element = this.view.element(index);
|
||||||
|
|
||||||
|
if (element.editorAttached) {
|
||||||
|
this._revealRange(index, range, revealType, false, false);
|
||||||
|
} else {
|
||||||
|
const elementHeight = this.view.elementHeight(index);
|
||||||
|
let upwards = false;
|
||||||
|
|
||||||
|
if (elementTop + elementHeight < scrollTop) {
|
||||||
|
// scroll downwards
|
||||||
|
this.view.setScrollTop(elementTop);
|
||||||
|
upwards = false;
|
||||||
|
} else if (elementTop > wrapperBottom) {
|
||||||
|
// scroll upwards
|
||||||
|
this.view.setScrollTop(elementTop - this.view.renderHeight / 2);
|
||||||
|
upwards = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const editorAttachedPromise = new Promise((resolve, reject) => {
|
||||||
|
element.onDidChangeEditorAttachState(state => state ? resolve() : reject());
|
||||||
|
});
|
||||||
|
|
||||||
|
editorAttachedPromise.then(() => {
|
||||||
|
this._revealRange(index, range, revealType, true, upwards);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revealLineInView(index: number, line: number) {
|
||||||
|
this._revealRangeInternal(index, new Range(line, 1, line, 1), CellRevealType.Line);
|
||||||
|
}
|
||||||
|
|
||||||
|
revealRangeInView(index: number, range: Range): void {
|
||||||
|
this._revealRangeInternal(index, range, CellRevealType.Range);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _revealRangeInCenterInternal(index: number, range: Range, revealType: CellRevealType) {
|
||||||
|
const reveal = (index: number, range: Range, revealType: CellRevealType) => {
|
||||||
|
const element = this.view.element(index);
|
||||||
|
let lineOffset = element.getLineScrollTopOffset(range.startLineNumber);
|
||||||
|
let lineOffsetInView = this.view.elementTop(index) + lineOffset;
|
||||||
|
this.view.setScrollTop(lineOffsetInView - this.view.renderHeight / 2);
|
||||||
|
|
||||||
|
if (revealType === CellRevealType.Range) {
|
||||||
|
element.revealRangeInCenter(range);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const elementTop = this.view.elementTop(index);
|
||||||
|
const viewItemOffset = elementTop;
|
||||||
|
this.view.setScrollTop(viewItemOffset - this.view.renderHeight / 2);
|
||||||
|
const element = this.view.element(index);
|
||||||
|
|
||||||
|
if (!element.editorAttached) {
|
||||||
|
getEditorAttachedPromise(element).then(() => reveal(index, range, revealType));
|
||||||
|
} else {
|
||||||
|
reveal(index, range, revealType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revealLineInCenter(index: number, line: number) {
|
||||||
|
this._revealRangeInCenterInternal(index, new Range(line, 1, line, 1), CellRevealType.Line);
|
||||||
|
}
|
||||||
|
|
||||||
|
revealRangeInCenter(index: number, range: Range): void {
|
||||||
|
this._revealRangeInCenterInternal(index, range, CellRevealType.Range);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _revealRangeInCenterIfOutsideViewportInternal(index: number, range: Range, revealType: CellRevealType) {
|
||||||
|
const reveal = (index: number, range: Range, revealType: CellRevealType) => {
|
||||||
|
const element = this.view.element(index);
|
||||||
|
let lineOffset = element.getLineScrollTopOffset(range.startLineNumber);
|
||||||
|
let lineOffsetInView = this.view.elementTop(index) + lineOffset;
|
||||||
|
this.view.setScrollTop(lineOffsetInView - this.view.renderHeight / 2);
|
||||||
|
|
||||||
|
if (revealType === CellRevealType.Range) {
|
||||||
|
setTimeout(() => {
|
||||||
|
element.revealRangeInCenter(range);
|
||||||
|
}, 240);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollTop = this.view.getScrollTop();
|
||||||
|
const wrapperBottom = scrollTop + this.view.renderHeight;
|
||||||
|
const elementTop = this.view.elementTop(index);
|
||||||
|
const viewItemOffset = elementTop;
|
||||||
|
const element = this.view.element(index);
|
||||||
|
|
||||||
|
if (viewItemOffset < scrollTop || viewItemOffset > wrapperBottom) {
|
||||||
|
// let it render
|
||||||
|
this.view.setScrollTop(viewItemOffset - this.view.renderHeight / 2);
|
||||||
|
|
||||||
|
// after rendering, it might be pushed down due to markdown cell dynamic height
|
||||||
|
const elementTop = this.view.elementTop(index);
|
||||||
|
this.view.setScrollTop(elementTop - this.view.renderHeight / 2);
|
||||||
|
|
||||||
|
// reveal editor
|
||||||
|
if (!element.editorAttached) {
|
||||||
|
getEditorAttachedPromise(element).then(() => reveal(index, range, revealType));
|
||||||
|
} else {
|
||||||
|
// for example markdown
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (element.editorAttached) {
|
||||||
|
element.revealRangeInCenter(range);
|
||||||
|
} else {
|
||||||
|
// for example, markdown cell in preview mode
|
||||||
|
getEditorAttachedPromise(element).then(() => reveal(index, range, revealType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revealLineInCenterIfOutsideViewport(index: number, line: number) {
|
||||||
|
this._revealRangeInCenterIfOutsideViewportInternal(index, new Range(line, 1, line, 1), CellRevealType.Line);
|
||||||
|
}
|
||||||
|
|
||||||
|
revealRangeInCenterIfOutsideViewport(index: number, range: Range): void {
|
||||||
|
this._revealRangeInCenterIfOutsideViewportInternal(index, range, CellRevealType.Range);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _revealInternal(index: number, ignoreIfInsideViewport: boolean, revealPosition: CellRevealPosition) {
|
||||||
|
const scrollTop = this.view.getScrollTop();
|
||||||
|
const wrapperBottom = scrollTop + this.view.renderHeight;
|
||||||
|
const elementTop = this.view.elementTop(index);
|
||||||
|
|
||||||
|
if (ignoreIfInsideViewport && elementTop >= scrollTop && elementTop < wrapperBottom) {
|
||||||
|
// inside the viewport
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first render
|
||||||
|
const viewItemOffset = revealPosition === CellRevealPosition.Top ? elementTop : (elementTop - this.view.renderHeight / 2);
|
||||||
|
this.view.setScrollTop(viewItemOffset);
|
||||||
|
|
||||||
|
// second scroll as markdown cell is dynamic
|
||||||
|
const newElementTop = this.view.elementTop(index);
|
||||||
|
const newViewItemOffset = revealPosition === CellRevealPosition.Top ? newElementTop : (newElementTop - this.view.renderHeight / 2);
|
||||||
|
this.view.setScrollTop(newViewItemOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
revealInView(index: number) {
|
||||||
|
this._revealInternal(index, true, CellRevealPosition.Top);
|
||||||
|
}
|
||||||
|
|
||||||
|
revealInCenter(index: number) {
|
||||||
|
this._revealInternal(index, false, CellRevealPosition.Center);
|
||||||
|
}
|
||||||
|
|
||||||
|
revealInCenterIfOutsideViewport(index: number) {
|
||||||
|
this._revealInternal(index, true, CellRevealPosition.Center);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCellSelection(index: number, range: Range) {
|
||||||
|
const element = this.view.element(index);
|
||||||
|
if (element.editorAttached) {
|
||||||
|
element.setSelection(range);
|
||||||
|
} else {
|
||||||
|
getEditorAttachedPromise(element).then(() => { element.setSelection(range); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._localDisposableStore.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEditorAttachedPromise(element: CellViewModel) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
Event.once(element.onDidChangeEditorAttachState)(state => state ? resolve() : reject());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isContextMenuFocused() {
|
||||||
|
return !!DOM.findParentWithClass(<HTMLElement>document.activeElement, 'context-view');
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { IOutput, IRenderOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
|
import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry';
|
||||||
|
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||||
|
import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||||
|
|
||||||
|
export class OutputRenderer {
|
||||||
|
protected readonly _contributions: { [key: string]: IOutputTransformContribution; };
|
||||||
|
protected readonly _mimeTypeMapping: { [key: number]: IOutputTransformContribution; };
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
notebookEditor: INotebookEditor,
|
||||||
|
private readonly instantiationService: IInstantiationService
|
||||||
|
) {
|
||||||
|
this._contributions = {};
|
||||||
|
this._mimeTypeMapping = {};
|
||||||
|
|
||||||
|
let contributions = NotebookRegistry.getOutputTransformContributions();
|
||||||
|
|
||||||
|
for (const desc of contributions) {
|
||||||
|
try {
|
||||||
|
const contribution = this.instantiationService.createInstance(desc.ctor, notebookEditor);
|
||||||
|
this._contributions[desc.id] = contribution;
|
||||||
|
this._mimeTypeMapping[desc.kind] = contribution;
|
||||||
|
} catch (err) {
|
||||||
|
onUnexpectedError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNoop(output: IOutput, container: HTMLElement): IRenderOutput {
|
||||||
|
const contentNode = document.createElement('p');
|
||||||
|
|
||||||
|
contentNode.innerText = `No renderer could be found for output. It has the following output type: ${output.outputKind}`;
|
||||||
|
container.appendChild(contentNode);
|
||||||
|
return {
|
||||||
|
hasDynamicHeight: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render(output: IOutput, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput {
|
||||||
|
let transform = this._mimeTypeMapping[output.outputKind];
|
||||||
|
|
||||||
|
if (transform) {
|
||||||
|
return transform.render(output, container, preferredMimeType);
|
||||||
|
} else {
|
||||||
|
return this.renderNoop(output, container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,386 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { IRenderOutput, CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
|
import { registerOutputTransform } from 'vs/workbench/contrib/notebook/browser/notebookRegistry';
|
||||||
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
import { RGBA, Color } from 'vs/base/common/color';
|
||||||
|
import { ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||||
|
|
||||||
|
class ErrorTransform implements IOutputTransformContribution {
|
||||||
|
constructor(
|
||||||
|
public editor: INotebookEditor,
|
||||||
|
@IThemeService private readonly themeService: IThemeService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
render(output: any, container: HTMLElement): IRenderOutput {
|
||||||
|
const traceback = document.createElement('pre');
|
||||||
|
DOM.addClasses(traceback, 'traceback');
|
||||||
|
if (output.traceback) {
|
||||||
|
for (let j = 0; j < output.traceback.length; j++) {
|
||||||
|
traceback.appendChild(handleANSIOutput(output.traceback[j], this.themeService));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
container.appendChild(traceback);
|
||||||
|
return {
|
||||||
|
hasDynamicHeight: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOutputTransform('notebook.output.error', CellOutputKind.Error, ErrorTransform);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param text The content to stylize.
|
||||||
|
* @returns An {@link HTMLSpanElement} that contains the potentially stylized text.
|
||||||
|
*/
|
||||||
|
export function handleANSIOutput(text: string, themeService: IThemeService): HTMLSpanElement {
|
||||||
|
|
||||||
|
const root: HTMLSpanElement = document.createElement('span');
|
||||||
|
const textLength: number = text.length;
|
||||||
|
|
||||||
|
let styleNames: string[] = [];
|
||||||
|
let customFgColor: RGBA | undefined;
|
||||||
|
let customBgColor: RGBA | undefined;
|
||||||
|
let currentPos: number = 0;
|
||||||
|
let buffer: string = '';
|
||||||
|
|
||||||
|
while (currentPos < textLength) {
|
||||||
|
|
||||||
|
let sequenceFound: boolean = false;
|
||||||
|
|
||||||
|
// Potentially an ANSI escape sequence.
|
||||||
|
// See http://ascii-table.com/ansi-escape-sequences.php & https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||||
|
if (text.charCodeAt(currentPos) === 27 && text.charAt(currentPos + 1) === '[') {
|
||||||
|
|
||||||
|
const startPos: number = currentPos;
|
||||||
|
currentPos += 2; // Ignore 'Esc[' as it's in every sequence.
|
||||||
|
|
||||||
|
let ansiSequence: string = '';
|
||||||
|
|
||||||
|
while (currentPos < textLength) {
|
||||||
|
const char: string = text.charAt(currentPos);
|
||||||
|
ansiSequence += char;
|
||||||
|
|
||||||
|
currentPos++;
|
||||||
|
|
||||||
|
// Look for a known sequence terminating character.
|
||||||
|
if (char.match(/^[ABCDHIJKfhmpsu]$/)) {
|
||||||
|
sequenceFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sequenceFound) {
|
||||||
|
|
||||||
|
// Flush buffer with previous styles.
|
||||||
|
appendStylizedStringToContainer(root, buffer, styleNames, customFgColor, customBgColor);
|
||||||
|
|
||||||
|
buffer = '';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Certain ranges that are matched here do not contain real graphics rendition sequences. For
|
||||||
|
* the sake of having a simpler expression, they have been included anyway.
|
||||||
|
*/
|
||||||
|
if (ansiSequence.match(/^(?:[34][0-8]|9[0-7]|10[0-7]|[013]|4|[34]9)(?:;[349][0-7]|10[0-7]|[013]|[245]|[34]9)?(?:;[012]?[0-9]?[0-9])*;?m$/)) {
|
||||||
|
|
||||||
|
const styleCodes: number[] = ansiSequence.slice(0, -1) // Remove final 'm' character.
|
||||||
|
.split(';') // Separate style codes.
|
||||||
|
.filter(elem => elem !== '') // Filter empty elems as '34;m' -> ['34', ''].
|
||||||
|
.map(elem => parseInt(elem, 10)); // Convert to numbers.
|
||||||
|
|
||||||
|
if (styleCodes[0] === 38 || styleCodes[0] === 48) {
|
||||||
|
// Advanced color code - can't be combined with formatting codes like simple colors can
|
||||||
|
// Ignores invalid colors and additional info beyond what is necessary
|
||||||
|
const colorType = (styleCodes[0] === 38) ? 'foreground' : 'background';
|
||||||
|
|
||||||
|
if (styleCodes[1] === 5) {
|
||||||
|
set8BitColor(styleCodes, colorType);
|
||||||
|
} else if (styleCodes[1] === 2) {
|
||||||
|
set24BitColor(styleCodes, colorType);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setBasicFormatters(styleCodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Unsupported sequence so simply hide it.
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
currentPos = startPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sequenceFound === false) {
|
||||||
|
buffer += text.charAt(currentPos);
|
||||||
|
currentPos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush remaining text buffer if not empty.
|
||||||
|
if (buffer) {
|
||||||
|
appendStylizedStringToContainer(root, buffer, styleNames, customFgColor, customBgColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the foreground or background color by clearing the current color
|
||||||
|
* and adding the new one.
|
||||||
|
* @param colorType If `'foreground'`, will change the foreground color, if
|
||||||
|
* `'background'`, will change the background color.
|
||||||
|
* @param color Color to change to. If `undefined` or not provided,
|
||||||
|
* will clear current color without adding a new one.
|
||||||
|
*/
|
||||||
|
function changeColor(colorType: 'foreground' | 'background', color?: RGBA | undefined): void {
|
||||||
|
if (colorType === 'foreground') {
|
||||||
|
customFgColor = color;
|
||||||
|
} else if (colorType === 'background') {
|
||||||
|
customBgColor = color;
|
||||||
|
}
|
||||||
|
styleNames = styleNames.filter(style => style !== `code-${colorType}-colored`);
|
||||||
|
if (color !== undefined) {
|
||||||
|
styleNames.push(`code-${colorType}-colored`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate and set basic ANSI formatting. Supports bold, italic, underline,
|
||||||
|
* normal foreground and background colors, and bright foreground and
|
||||||
|
* background colors. Not to be used for codes containing advanced colors.
|
||||||
|
* Will ignore invalid codes.
|
||||||
|
* @param styleCodes Array of ANSI basic styling numbers, which will be
|
||||||
|
* applied in order. New colors and backgrounds clear old ones; new formatting
|
||||||
|
* does not.
|
||||||
|
* @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code }
|
||||||
|
*/
|
||||||
|
function setBasicFormatters(styleCodes: number[]): void {
|
||||||
|
for (let code of styleCodes) {
|
||||||
|
switch (code) {
|
||||||
|
case 0: {
|
||||||
|
styleNames = [];
|
||||||
|
customFgColor = undefined;
|
||||||
|
customBgColor = undefined;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
styleNames.push('code-bold');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
styleNames.push('code-italic');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 4: {
|
||||||
|
styleNames.push('code-underline');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 39: {
|
||||||
|
changeColor('foreground', undefined);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 49: {
|
||||||
|
changeColor('background', undefined);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
setBasicColor(code);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate and set styling for complicated 24-bit ANSI color codes.
|
||||||
|
* @param styleCodes Full list of integer codes that make up the full ANSI
|
||||||
|
* sequence, including the two defining codes and the three RGB codes.
|
||||||
|
* @param colorType If `'foreground'`, will set foreground color, if
|
||||||
|
* `'background'`, will set background color.
|
||||||
|
* @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit }
|
||||||
|
*/
|
||||||
|
function set24BitColor(styleCodes: number[], colorType: 'foreground' | 'background'): void {
|
||||||
|
if (styleCodes.length >= 5 &&
|
||||||
|
styleCodes[2] >= 0 && styleCodes[2] <= 255 &&
|
||||||
|
styleCodes[3] >= 0 && styleCodes[3] <= 255 &&
|
||||||
|
styleCodes[4] >= 0 && styleCodes[4] <= 255) {
|
||||||
|
const customColor = new RGBA(styleCodes[2], styleCodes[3], styleCodes[4]);
|
||||||
|
changeColor(colorType, customColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate and set styling for advanced 8-bit ANSI color codes.
|
||||||
|
* @param styleCodes Full list of integer codes that make up the ANSI
|
||||||
|
* sequence, including the two defining codes and the one color code.
|
||||||
|
* @param colorType If `'foreground'`, will set foreground color, if
|
||||||
|
* `'background'`, will set background color.
|
||||||
|
* @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit }
|
||||||
|
*/
|
||||||
|
function set8BitColor(styleCodes: number[], colorType: 'foreground' | 'background'): void {
|
||||||
|
let colorNumber = styleCodes[2];
|
||||||
|
const color = calcANSI8bitColor(colorNumber);
|
||||||
|
|
||||||
|
if (color) {
|
||||||
|
changeColor(colorType, color);
|
||||||
|
} else if (colorNumber >= 0 && colorNumber <= 15) {
|
||||||
|
// Need to map to one of the four basic color ranges (30-37, 90-97, 40-47, 100-107)
|
||||||
|
colorNumber += 30;
|
||||||
|
if (colorNumber >= 38) {
|
||||||
|
// Bright colors
|
||||||
|
colorNumber += 52;
|
||||||
|
}
|
||||||
|
if (colorType === 'background') {
|
||||||
|
colorNumber += 10;
|
||||||
|
}
|
||||||
|
setBasicColor(colorNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate and set styling for basic bright and dark ANSI color codes. Uses
|
||||||
|
* theme colors if available. Automatically distinguishes between foreground
|
||||||
|
* and background colors; does not support color-clearing codes 39 and 49.
|
||||||
|
* @param styleCode Integer color code on one of the following ranges:
|
||||||
|
* [30-37, 90-97, 40-47, 100-107]. If not on one of these ranges, will do
|
||||||
|
* nothing.
|
||||||
|
*/
|
||||||
|
function setBasicColor(styleCode: number): void {
|
||||||
|
const theme = themeService.getColorTheme();
|
||||||
|
let colorType: 'foreground' | 'background' | undefined;
|
||||||
|
let colorIndex: number | undefined;
|
||||||
|
|
||||||
|
if (styleCode >= 30 && styleCode <= 37) {
|
||||||
|
colorIndex = styleCode - 30;
|
||||||
|
colorType = 'foreground';
|
||||||
|
} else if (styleCode >= 90 && styleCode <= 97) {
|
||||||
|
colorIndex = (styleCode - 90) + 8; // High-intensity (bright)
|
||||||
|
colorType = 'foreground';
|
||||||
|
} else if (styleCode >= 40 && styleCode <= 47) {
|
||||||
|
colorIndex = styleCode - 40;
|
||||||
|
colorType = 'background';
|
||||||
|
} else if (styleCode >= 100 && styleCode <= 107) {
|
||||||
|
colorIndex = (styleCode - 100) + 8; // High-intensity (bright)
|
||||||
|
colorType = 'background';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colorIndex !== undefined && colorType) {
|
||||||
|
const colorName = ansiColorIdentifiers[colorIndex];
|
||||||
|
const color = theme.getColor(colorName);
|
||||||
|
if (color) {
|
||||||
|
changeColor(colorType, color.rgba);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param root The {@link HTMLElement} to append the content to.
|
||||||
|
* @param stringContent The text content to be appended.
|
||||||
|
* @param cssClasses The list of CSS styles to apply to the text content.
|
||||||
|
* @param linkDetector The {@link LinkDetector} responsible for generating links from {@param stringContent}.
|
||||||
|
* @param customTextColor If provided, will apply custom color with inline style.
|
||||||
|
* @param customBackgroundColor If provided, will apply custom color with inline style.
|
||||||
|
*/
|
||||||
|
export function appendStylizedStringToContainer(
|
||||||
|
root: HTMLElement,
|
||||||
|
stringContent: string,
|
||||||
|
cssClasses: string[],
|
||||||
|
customTextColor?: RGBA,
|
||||||
|
customBackgroundColor?: RGBA
|
||||||
|
): void {
|
||||||
|
if (!root || !stringContent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = linkify(stringContent, true);
|
||||||
|
container.className = cssClasses.join(' ');
|
||||||
|
if (customTextColor) {
|
||||||
|
container.style.color =
|
||||||
|
Color.Format.CSS.formatRGB(new Color(customTextColor));
|
||||||
|
}
|
||||||
|
if (customBackgroundColor) {
|
||||||
|
container.style.backgroundColor =
|
||||||
|
Color.Format.CSS.formatRGB(new Color(customBackgroundColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
root.appendChild(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkify(text: string, splitLines?: boolean): HTMLElement {
|
||||||
|
if (splitLines) {
|
||||||
|
const lines = text.split('\n');
|
||||||
|
for (let i = 0; i < lines.length - 1; i++) {
|
||||||
|
lines[i] = lines[i] + '\n';
|
||||||
|
}
|
||||||
|
if (!lines[lines.length - 1]) {
|
||||||
|
// Remove the last element ('') that split added.
|
||||||
|
lines.pop();
|
||||||
|
}
|
||||||
|
const elements = lines.map(line => linkify(line));
|
||||||
|
if (elements.length === 1) {
|
||||||
|
// Do not wrap single line with extra span.
|
||||||
|
return elements[0];
|
||||||
|
}
|
||||||
|
const container = document.createElement('span');
|
||||||
|
elements.forEach(e => container.appendChild(e));
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.createElement('span');
|
||||||
|
container.appendChild(document.createTextNode(text));
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the color from the color set defined in the ANSI 8-bit standard.
|
||||||
|
* Standard and high intensity colors are not defined in the standard as specific
|
||||||
|
* colors, so these and invalid colors return `undefined`.
|
||||||
|
* @see {@link https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit } for info.
|
||||||
|
* @param colorNumber The number (ranging from 16 to 255) referring to the color
|
||||||
|
* desired.
|
||||||
|
*/
|
||||||
|
export function calcANSI8bitColor(colorNumber: number): RGBA | undefined {
|
||||||
|
if (colorNumber % 1 !== 0) {
|
||||||
|
// Should be integer
|
||||||
|
// {{SQL CARBON EDIT}} @todo anthonydresser 4/12/19 this is necessary because we don't use strict null checks
|
||||||
|
return undefined;
|
||||||
|
} if (colorNumber >= 16 && colorNumber <= 231) {
|
||||||
|
// Converts to one of 216 RGB colors
|
||||||
|
colorNumber -= 16;
|
||||||
|
|
||||||
|
let blue: number = colorNumber % 6;
|
||||||
|
colorNumber = (colorNumber - blue) / 6;
|
||||||
|
let green: number = colorNumber % 6;
|
||||||
|
colorNumber = (colorNumber - green) / 6;
|
||||||
|
let red: number = colorNumber;
|
||||||
|
|
||||||
|
// red, green, blue now range on [0, 5], need to map to [0,255]
|
||||||
|
const convFactor: number = 255 / 5;
|
||||||
|
blue = Math.round(blue * convFactor);
|
||||||
|
green = Math.round(green * convFactor);
|
||||||
|
red = Math.round(red * convFactor);
|
||||||
|
|
||||||
|
return new RGBA(red, green, blue);
|
||||||
|
} else if (colorNumber >= 232 && colorNumber <= 255) {
|
||||||
|
// Converts to a grayscale value
|
||||||
|
colorNumber -= 232;
|
||||||
|
const colorLevel: number = Math.round(colorNumber / 23 * 255);
|
||||||
|
return new RGBA(colorLevel, colorLevel, colorLevel);
|
||||||
|
} else {
|
||||||
|
// {{SQL CARBON EDIT}} @todo anthonydresser 4/12/19 this is necessary because we don't use strict null checks
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { IRenderOutput, CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
|
import { registerOutputTransform } from 'vs/workbench/contrib/notebook/browser/notebookRegistry';
|
||||||
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||||
|
import { isArray } from 'vs/base/common/types';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||||
|
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||||
|
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||||
|
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import { MarkdownRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/mdRenderer';
|
||||||
|
|
||||||
|
class RichRenderer implements IOutputTransformContribution {
|
||||||
|
private _mdRenderer: MarkdownRenderer;
|
||||||
|
private _richMimeTypeRenderers = new Map<string, (output: any, container: HTMLElement) => IRenderOutput>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public notebookEditor: INotebookEditor,
|
||||||
|
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||||
|
@IModelService private readonly modelService: IModelService,
|
||||||
|
@IModeService private readonly modeService: IModeService
|
||||||
|
) {
|
||||||
|
this._mdRenderer = instantiationService.createInstance(MarkdownRenderer);
|
||||||
|
this._richMimeTypeRenderers.set('application/json', this.renderJSON.bind(this));
|
||||||
|
this._richMimeTypeRenderers.set('application/javascript', this.renderJavaScript.bind(this));
|
||||||
|
this._richMimeTypeRenderers.set('text/html', this.renderHTML.bind(this));
|
||||||
|
this._richMimeTypeRenderers.set('image/svg+xml', this.renderSVG.bind(this));
|
||||||
|
this._richMimeTypeRenderers.set('text/markdown', this.renderMarkdown.bind(this));
|
||||||
|
this._richMimeTypeRenderers.set('image/png', this.renderPNG.bind(this));
|
||||||
|
this._richMimeTypeRenderers.set('image/jpeg', this.renderJavaScript.bind(this));
|
||||||
|
this._richMimeTypeRenderers.set('text/plain', this.renderPlainText.bind(this));
|
||||||
|
this._richMimeTypeRenderers.set('text/x-javascript', this.renderCode.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
render(output: any, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput {
|
||||||
|
if (!output.data) {
|
||||||
|
const contentNode = document.createElement('p');
|
||||||
|
contentNode.innerText = `No data could be found for output.`;
|
||||||
|
container.appendChild(contentNode);
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasDynamicHeight: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!preferredMimeType || !this._richMimeTypeRenderers.has(preferredMimeType)) {
|
||||||
|
const contentNode = document.createElement('p');
|
||||||
|
let mimeTypes = [];
|
||||||
|
for (const property in output.data) {
|
||||||
|
mimeTypes.push(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mimeTypesMessage = mimeTypes.join(', ');
|
||||||
|
|
||||||
|
contentNode.innerText = `No renderer could be found for output. It has the following MIME types: ${mimeTypesMessage}`;
|
||||||
|
container.appendChild(contentNode);
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasDynamicHeight: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let renderer = this._richMimeTypeRenderers.get(preferredMimeType);
|
||||||
|
return renderer!(output, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderJSON(output: any, container: HTMLElement) {
|
||||||
|
let data = output.data['application/json'];
|
||||||
|
let str = JSON.stringify(data, null, '\t');
|
||||||
|
|
||||||
|
const editor = this.instantiationService.createInstance(CodeEditorWidget, container, {
|
||||||
|
...getOutputSimpleEditorOptions(),
|
||||||
|
dimension: {
|
||||||
|
width: 0,
|
||||||
|
height: 0
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
isSimpleWidget: true
|
||||||
|
});
|
||||||
|
|
||||||
|
let mode = this.modeService.create('json');
|
||||||
|
let resource = URI.parse(`notebook-output-${Date.now()}.json`);
|
||||||
|
const textModel = this.modelService.createModel(str, mode, resource, false);
|
||||||
|
editor.setModel(textModel);
|
||||||
|
|
||||||
|
let width = this.notebookEditor.getLayoutInfo().width;
|
||||||
|
let fontInfo = this.notebookEditor.getLayoutInfo().fontInfo;
|
||||||
|
let height = Math.min(textModel.getLineCount(), 16) * (fontInfo.lineHeight || 18);
|
||||||
|
|
||||||
|
editor.layout({
|
||||||
|
height,
|
||||||
|
width
|
||||||
|
});
|
||||||
|
|
||||||
|
container.style.height = `${height + 16}px`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasDynamicHeight: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCode(output: any, container: HTMLElement) {
|
||||||
|
let data = output.data['text/x-javascript'];
|
||||||
|
let str = isArray(data) ? data.join('') : data;
|
||||||
|
|
||||||
|
const editor = this.instantiationService.createInstance(CodeEditorWidget, container, {
|
||||||
|
...getOutputSimpleEditorOptions(),
|
||||||
|
dimension: {
|
||||||
|
width: 0,
|
||||||
|
height: 0
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
isSimpleWidget: true
|
||||||
|
});
|
||||||
|
|
||||||
|
let mode = this.modeService.create('javascript');
|
||||||
|
let resource = URI.parse(`notebook-output-${Date.now()}.js`);
|
||||||
|
const textModel = this.modelService.createModel(str, mode, resource, false);
|
||||||
|
editor.setModel(textModel);
|
||||||
|
|
||||||
|
let width = this.notebookEditor.getLayoutInfo().width;
|
||||||
|
let fontInfo = this.notebookEditor.getLayoutInfo().fontInfo;
|
||||||
|
let height = Math.min(textModel.getLineCount(), 16) * (fontInfo.lineHeight || 18);
|
||||||
|
|
||||||
|
editor.layout({
|
||||||
|
height,
|
||||||
|
width
|
||||||
|
});
|
||||||
|
|
||||||
|
container.style.height = `${height + 16}px`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasDynamicHeight: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderJavaScript(output: any, container: HTMLElement) {
|
||||||
|
let data = output.data['application/javascript'];
|
||||||
|
let str = isArray(data) ? data.join('') : data;
|
||||||
|
let scriptVal = `<script type="application/javascript">${str}</script>`;
|
||||||
|
return {
|
||||||
|
shadowContent: scriptVal,
|
||||||
|
hasDynamicHeight: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderHTML(output: any, container: HTMLElement) {
|
||||||
|
let data = output.data['text/html'];
|
||||||
|
let str = isArray(data) ? data.join('') : data;
|
||||||
|
return {
|
||||||
|
shadowContent: str,
|
||||||
|
hasDynamicHeight: false
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSVG(output: any, container: HTMLElement) {
|
||||||
|
let data = output.data['image/svg+xml'];
|
||||||
|
let str = isArray(data) ? data.join('') : data;
|
||||||
|
return {
|
||||||
|
shadowContent: str,
|
||||||
|
hasDynamicHeight: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMarkdown(output: any, container: HTMLElement) {
|
||||||
|
let data = output.data['text/markdown'];
|
||||||
|
const str = isArray(data) ? data.join('') : data;
|
||||||
|
const mdOutput = document.createElement('div');
|
||||||
|
mdOutput.appendChild(this._mdRenderer.render({ value: str, isTrusted: false, supportThemeIcons: true }).element);
|
||||||
|
container.appendChild(mdOutput);
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasDynamicHeight: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPNG(output: any, container: HTMLElement) {
|
||||||
|
const image = document.createElement('img');
|
||||||
|
image.src = `data:image/png;base64,${output.data['image/png']}`;
|
||||||
|
const display = document.createElement('div');
|
||||||
|
DOM.addClasses(display, 'display');
|
||||||
|
display.appendChild(image);
|
||||||
|
container.appendChild(display);
|
||||||
|
return {
|
||||||
|
hasDynamicHeight: true
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
renderJPEG(output: any, container: HTMLElement) {
|
||||||
|
const image = document.createElement('img');
|
||||||
|
image.src = `data:image/jpeg;base64,${output.data['image/jpeg']}`;
|
||||||
|
const display = document.createElement('div');
|
||||||
|
DOM.addClasses(display, 'display');
|
||||||
|
display.appendChild(image);
|
||||||
|
container.appendChild(display);
|
||||||
|
return {
|
||||||
|
hasDynamicHeight: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPlainText(output: any, container: HTMLElement) {
|
||||||
|
let data = output.data['text/plain'];
|
||||||
|
let str = isArray(data) ? data.join('') : data;
|
||||||
|
const contentNode = document.createElement('p');
|
||||||
|
contentNode.innerText = str;
|
||||||
|
container.appendChild(contentNode);
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasDynamicHeight: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOutputTransform('notebook.output.rich', CellOutputKind.Rich, RichRenderer);
|
||||||
|
|
||||||
|
|
||||||
|
export function getOutputSimpleEditorOptions(): IEditorOptions {
|
||||||
|
return {
|
||||||
|
readOnly: true,
|
||||||
|
wordWrap: 'on',
|
||||||
|
overviewRulerLanes: 0,
|
||||||
|
glyphMargin: false,
|
||||||
|
selectOnLineNumbers: false,
|
||||||
|
hideCursorInOverviewRuler: true,
|
||||||
|
selectionHighlight: false,
|
||||||
|
lineDecorationsWidth: 0,
|
||||||
|
overviewRulerBorder: false,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
renderLineHighlight: 'none',
|
||||||
|
minimap: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
lineNumbers: 'off',
|
||||||
|
scrollbar: {
|
||||||
|
alwaysConsumeMouseWheel: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { IRenderOutput, CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
|
import { registerOutputTransform } from 'vs/workbench/contrib/notebook/browser/notebookRegistry';
|
||||||
|
import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||||
|
|
||||||
|
class StreamRenderer implements IOutputTransformContribution {
|
||||||
|
constructor(
|
||||||
|
editor: INotebookEditor
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
render(output: any, container: HTMLElement): IRenderOutput {
|
||||||
|
const contentNode = document.createElement('p');
|
||||||
|
contentNode.innerText = output.text;
|
||||||
|
container.appendChild(contentNode);
|
||||||
|
return {
|
||||||
|
hasDynamicHeight: false
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOutputTransform('notebook.output.stream', CellOutputKind.Text, StreamRenderer);
|
||||||
@@ -0,0 +1,415 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
import * as path from 'vs/base/common/path';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import * as UUID from 'vs/base/common/uuid';
|
||||||
|
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||||
|
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||||
|
import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService';
|
||||||
|
import { IOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
|
import { IWebviewService, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview';
|
||||||
|
import { WebviewResourceScheme } from 'vs/workbench/contrib/webview/common/resourceLoader';
|
||||||
|
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookCellViewModel';
|
||||||
|
import { CELL_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants';
|
||||||
|
|
||||||
|
export interface IDimentionMessage {
|
||||||
|
type: 'dimension';
|
||||||
|
id: string;
|
||||||
|
data: DOM.Dimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IScrollAckMessage {
|
||||||
|
type: 'scroll-ack';
|
||||||
|
data: { top: number };
|
||||||
|
version: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IClearMessage {
|
||||||
|
type: 'clear';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICreationRequestMessage {
|
||||||
|
type: 'html';
|
||||||
|
content: string;
|
||||||
|
id: string;
|
||||||
|
outputId: string;
|
||||||
|
top: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IContentWidgetTopRequest {
|
||||||
|
id: string;
|
||||||
|
top: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IViewScrollTopRequestMessage {
|
||||||
|
type: 'view-scroll';
|
||||||
|
top?: number;
|
||||||
|
widgets: IContentWidgetTopRequest[];
|
||||||
|
version: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IScrollRequestMessage {
|
||||||
|
type: 'scroll';
|
||||||
|
id: string;
|
||||||
|
top: number;
|
||||||
|
widgetTop?: number;
|
||||||
|
version: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUpdatePreloadResourceMessage {
|
||||||
|
type: 'preload';
|
||||||
|
resources: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type IMessage = IDimentionMessage | IScrollAckMessage;
|
||||||
|
|
||||||
|
let version = 0;
|
||||||
|
export class BackLayerWebView extends Disposable {
|
||||||
|
element: HTMLElement;
|
||||||
|
webview: WebviewElement;
|
||||||
|
insetMapping: Map<IOutput, { outputId: string, cell: CellViewModel, cacheOffset: number | undefined }> = new Map();
|
||||||
|
reversedInsetMapping: Map<string, IOutput> = new Map();
|
||||||
|
preloadsCache: Map<string, boolean> = new Map();
|
||||||
|
localResourceRootsCache: URI[] | undefined = undefined;
|
||||||
|
rendererRootsCache: URI[] = [];
|
||||||
|
|
||||||
|
constructor(public webviewService: IWebviewService, public notebookService: INotebookService, public notebookEditor: INotebookEditor, public environmentSerice: IEnvironmentService) {
|
||||||
|
super();
|
||||||
|
this.element = document.createElement('div');
|
||||||
|
|
||||||
|
this.element.style.width = `calc(100% - ${CELL_MARGIN * 2}px)`;
|
||||||
|
this.element.style.height = '1400px';
|
||||||
|
this.element.style.position = 'absolute';
|
||||||
|
this.element.style.margin = `0px 0 0px ${CELL_MARGIN}px`;
|
||||||
|
|
||||||
|
const loader = URI.file(path.join(environmentSerice.appRoot, '/out/vs/loader.js')).with({ scheme: WebviewResourceScheme });
|
||||||
|
|
||||||
|
let content = /* html */`
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
#container > div > div {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 8px;
|
||||||
|
margin: 8px 0;
|
||||||
|
background-color: var(--vscode-list-inactiveSelectionBackground);
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
padding: 0px;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body style="overflow: hidden;">
|
||||||
|
<script>
|
||||||
|
self.require = {};
|
||||||
|
</script>
|
||||||
|
<script src="${loader}"></script>
|
||||||
|
<div id="__vscode_preloads"></div>
|
||||||
|
<div id='container' class="widgetarea" style="position: absolute;width:100%;top: 0px"></div>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
const vscode = acquireVsCodeApi();
|
||||||
|
|
||||||
|
const preservedScriptAttributes = {
|
||||||
|
type: true,
|
||||||
|
src: true,
|
||||||
|
nonce: true,
|
||||||
|
noModule: true,
|
||||||
|
async: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// derived from https://github.com/jquery/jquery/blob/d0ce00cdfa680f1f0c38460bc51ea14079ae8b07/src/core/DOMEval.js
|
||||||
|
const domEval = (container) => {
|
||||||
|
var arr = Array.from(container.getElementsByTagName('script'));
|
||||||
|
for (let n = 0; n < arr.length; n++) {
|
||||||
|
let node = arr[n];
|
||||||
|
let scriptTag = document.createElement('script');
|
||||||
|
scriptTag.text = node.innerText;
|
||||||
|
for (let key in preservedScriptAttributes ) {
|
||||||
|
const val = node[key] || node.getAttribute && node.getAttribute(key);
|
||||||
|
if (val) {
|
||||||
|
scriptTag.setAttribute(key, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: should script with src not be removed?
|
||||||
|
container.appendChild(scriptTag).parentNode.removeChild(scriptTag);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let observers = [];
|
||||||
|
|
||||||
|
const resizeObserve = (container, id) => {
|
||||||
|
const resizeObserver = new ResizeObserver(entries => {
|
||||||
|
for (let entry of entries) {
|
||||||
|
if (entry.target.id === id && entry.contentRect) {
|
||||||
|
vscode.postMessage({
|
||||||
|
type: 'dimension',
|
||||||
|
id: id,
|
||||||
|
data: {
|
||||||
|
height: entry.contentRect.height
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
resizeObserver.observe(container);
|
||||||
|
observers.push(resizeObserver);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', event => {
|
||||||
|
let id = event.data.id;
|
||||||
|
|
||||||
|
switch (event.data.type) {
|
||||||
|
case 'html':
|
||||||
|
{
|
||||||
|
let cellOutputContainer = document.getElementById(id);
|
||||||
|
let outputId = event.data.outputId;
|
||||||
|
if (!cellOutputContainer) {
|
||||||
|
let newElement = document.createElement('div');
|
||||||
|
|
||||||
|
newElement.id = id;
|
||||||
|
document.getElementById('container').appendChild(newElement);
|
||||||
|
cellOutputContainer = newElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
let outputNode = document.createElement('div');
|
||||||
|
outputNode.style.position = 'absolute';
|
||||||
|
outputNode.style.top = event.data.top + 'px';
|
||||||
|
|
||||||
|
outputNode.id = outputId;
|
||||||
|
let content = event.data.content;
|
||||||
|
outputNode.innerHTML = content;
|
||||||
|
cellOutputContainer.appendChild(outputNode);
|
||||||
|
|
||||||
|
// eval
|
||||||
|
domEval(outputNode);
|
||||||
|
resizeObserve(outputNode, outputId);
|
||||||
|
|
||||||
|
vscode.postMessage({
|
||||||
|
type: 'dimension',
|
||||||
|
id: outputId,
|
||||||
|
data: {
|
||||||
|
height: outputNode.clientHeight
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'view-scroll':
|
||||||
|
{
|
||||||
|
// const date = new Date();
|
||||||
|
// console.log('----- will scroll ---- ', date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds());
|
||||||
|
|
||||||
|
for (let i = 0; i < event.data.widgets.length; i++) {
|
||||||
|
let widget = document.getElementById(event.data.widgets[i].id);
|
||||||
|
widget.style.top = event.data.widgets[i].top + 'px';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'clear':
|
||||||
|
document.getElementById('container').innerHTML = '';
|
||||||
|
for (let i = 0; i < observers.length; i++) {
|
||||||
|
observers[i].disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
observers = [];
|
||||||
|
break;
|
||||||
|
case 'clearOutput':
|
||||||
|
let output = document.getElementById(id);
|
||||||
|
output.parentNode.removeChild(output);
|
||||||
|
// @TODO remove observer
|
||||||
|
break;
|
||||||
|
case 'preload':
|
||||||
|
let resources = event.data.resources;
|
||||||
|
let preloadsContainer = document.getElementById('__vscode_preloads');
|
||||||
|
for (let i = 0; i < resources.length; i++) {
|
||||||
|
let scriptTag = document.createElement('script');
|
||||||
|
scriptTag.setAttribute('src', resources[i]);
|
||||||
|
preloadsContainer.appendChild(scriptTag)
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}());
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.webview = this._createInset(webviewService, content);
|
||||||
|
this.webview.mountTo(this.element);
|
||||||
|
|
||||||
|
this._register(this.webview.onDidWheel(e => {
|
||||||
|
this.notebookEditor.triggerScroll(e);
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._register(this.webview.onMessage((data: IMessage) => {
|
||||||
|
if (data.type === 'dimension') {
|
||||||
|
let output = this.reversedInsetMapping.get(data.id);
|
||||||
|
|
||||||
|
if (!output) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cell = this.insetMapping.get(output)!.cell;
|
||||||
|
let height = data.data.height;
|
||||||
|
let outputHeight = height === 0 ? 0 : height + 16;
|
||||||
|
|
||||||
|
if (cell) {
|
||||||
|
let outputIndex = cell.outputs.indexOf(output);
|
||||||
|
cell.updateOutputHeight(outputIndex, outputHeight);
|
||||||
|
this.notebookEditor.layoutNotebookCell(cell, cell.getCellTotalHeight());
|
||||||
|
}
|
||||||
|
} else if (data.type === 'scroll-ack') {
|
||||||
|
// const date = new Date();
|
||||||
|
// const top = data.data.top;
|
||||||
|
// console.log('ack top ', top, ' version: ', data.version, ' - ', date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createInset(webviewService: IWebviewService, content: string) {
|
||||||
|
this.localResourceRootsCache = [...this.notebookService.getNotebookProviderResourceRoots(), URI.file(this.environmentSerice.appRoot)];
|
||||||
|
const webview = webviewService.createWebviewElement('' + UUID.generateUuid(), {
|
||||||
|
enableFindWidget: false,
|
||||||
|
}, {
|
||||||
|
allowScripts: true,
|
||||||
|
localResourceRoots: this.localResourceRootsCache
|
||||||
|
});
|
||||||
|
webview.html = content;
|
||||||
|
return webview;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldUpdateInset(cell: CellViewModel, output: IOutput, cellTop: number) {
|
||||||
|
let outputCache = this.insetMapping.get(output)!;
|
||||||
|
let outputIndex = cell.outputs.indexOf(output);
|
||||||
|
|
||||||
|
let outputOffsetInOutputContainer = cell.getOutputOffset(outputIndex);
|
||||||
|
let outputOffset = cellTop + cell.editorHeight + 16 /* editor padding */ + 8 + outputOffsetInOutputContainer;
|
||||||
|
|
||||||
|
if (outputOffset === outputCache.cacheOffset) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateViewScrollTop(top: number, items: { cell: CellViewModel, output: IOutput, cellTop: number }[]) {
|
||||||
|
let widgets: IContentWidgetTopRequest[] = items.map(item => {
|
||||||
|
let outputCache = this.insetMapping.get(item.output)!;
|
||||||
|
let id = outputCache.outputId;
|
||||||
|
let outputIndex = item.cell.outputs.indexOf(item.output);
|
||||||
|
|
||||||
|
let outputOffsetInOutputContainer = item.cell.getOutputOffset(outputIndex);
|
||||||
|
let outputOffset = item.cellTop + item.cell.editorHeight + 16 /* editor padding */ + 16 + outputOffsetInOutputContainer;
|
||||||
|
outputCache.cacheOffset = outputOffset;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
top: outputOffset
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let message: IViewScrollTopRequestMessage = {
|
||||||
|
top,
|
||||||
|
type: 'view-scroll',
|
||||||
|
version: version++,
|
||||||
|
widgets: widgets
|
||||||
|
};
|
||||||
|
|
||||||
|
this.webview.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
createInset(cell: CellViewModel, output: IOutput, cellTop: number, offset: number, shadowContent: string, preloads: Set<number>) {
|
||||||
|
this.updateRendererPreloads(preloads);
|
||||||
|
let initialTop = cellTop + offset;
|
||||||
|
let outputId = UUID.generateUuid();
|
||||||
|
|
||||||
|
let message: ICreationRequestMessage = {
|
||||||
|
type: 'html',
|
||||||
|
content: shadowContent,
|
||||||
|
id: cell.id,
|
||||||
|
outputId: outputId,
|
||||||
|
top: initialTop
|
||||||
|
};
|
||||||
|
|
||||||
|
this.webview.sendMessage(message);
|
||||||
|
this.insetMapping.set(output, { outputId: outputId, cell: cell, cacheOffset: initialTop });
|
||||||
|
this.reversedInsetMapping.set(outputId, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeInset(output: IOutput) {
|
||||||
|
let outputCache = this.insetMapping.get(output);
|
||||||
|
if (!outputCache) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = outputCache.outputId;
|
||||||
|
|
||||||
|
this.webview.sendMessage({
|
||||||
|
type: 'clearOutput',
|
||||||
|
id: id
|
||||||
|
});
|
||||||
|
this.insetMapping.delete(output);
|
||||||
|
this.reversedInsetMapping.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearInsets() {
|
||||||
|
this.webview.sendMessage({
|
||||||
|
type: 'clear'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.insetMapping = new Map();
|
||||||
|
this.reversedInsetMapping = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRendererPreloads(preloads: Set<number>) {
|
||||||
|
let resources: string[] = [];
|
||||||
|
let extensionLocations: URI[] = [];
|
||||||
|
preloads.forEach(preload => {
|
||||||
|
let rendererInfo = this.notebookService.getRendererInfo(preload);
|
||||||
|
|
||||||
|
if (rendererInfo) {
|
||||||
|
let preloadResources = rendererInfo.preloads.map(preloadResource => preloadResource.with({ scheme: WebviewResourceScheme }));
|
||||||
|
extensionLocations.push(rendererInfo.extensionLocation);
|
||||||
|
preloadResources.forEach(e => {
|
||||||
|
if (!this.preloadsCache.has(e.toString())) {
|
||||||
|
resources.push(e.toString());
|
||||||
|
this.preloadsCache.set(e.toString(), true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rendererRootsCache = extensionLocations;
|
||||||
|
const mixedResourceRoots = [...(this.localResourceRootsCache || []), ...this.rendererRootsCache];
|
||||||
|
|
||||||
|
this.webview.contentOptions = {
|
||||||
|
allowScripts: true,
|
||||||
|
enableCommandUris: true,
|
||||||
|
localResourceRoots: mixedResourceRoots
|
||||||
|
};
|
||||||
|
|
||||||
|
let message: IUpdatePreloadResourceMessage = {
|
||||||
|
type: 'preload',
|
||||||
|
resources: resources
|
||||||
|
};
|
||||||
|
|
||||||
|
this.webview.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearPreloadsCache() {
|
||||||
|
this.preloadsCache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user