From ee9d42129da25fb1851bd4ab608fa2c24eb36d6e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 18 Dec 2019 12:08:06 +0100 Subject: [PATCH] first cut of panel and changes tree --- build/lib/i18n.resources.json | 4 + .../ui/highlightedlabel/highlightedLabel.ts | 3 +- .../bulkEdit/browser/bulkEdit.contribution.ts | 53 +++++++++ .../contrib/bulkEdit/browser/bulkEdit.css | 10 ++ .../contrib/bulkEdit/browser/bulkEditPanel.ts | 110 ++++++++++++++++++ .../bulkEdit/browser/bulkEditPreview.ts | 52 +++++++++ .../contrib/bulkEdit/browser/bulkEditTree.ts | 35 +++--- src/vs/workbench/workbench.common.main.ts | 3 + 8 files changed, 255 insertions(+), 15 deletions(-) create mode 100644 src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts create mode 100644 src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css create mode 100644 src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts create mode 100644 src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index e9a4f279631..b497e93f42f 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -30,6 +30,10 @@ "name": "vs/workbench/api/common", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/bulkEdit", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/cli", "project": "vscode-workbench" diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index 163dffb2d5a..9bb9292a4cd 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -10,6 +10,7 @@ import { escape } from 'vs/base/common/strings'; export interface IHighlight { start: number; end: number; + extraClasses?: string; } export class HighlightedLabel { @@ -69,7 +70,7 @@ export class HighlightedLabel { htmlContent += ''; pos = highlight.end; } - htmlContent += ''; + htmlContent += ``; const substring = this.text.substring(highlight.start, highlight.end); htmlContent += this.supportCodicons ? renderCodicons(escape(substring)) : escape(substring); htmlContent += ''; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts new file mode 100644 index 00000000000..1d6f1930587 --- /dev/null +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { WorkspaceEdit } from 'vs/editor/common/modes'; +import { BulkEditPanel } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPanel'; +import { Extensions as PanelExtensions, PanelDescriptor, PanelRegistry } from 'vs/workbench/browser/panel'; +import { localize } from 'vs/nls'; + +class BulkEditPreviewContribution { + + constructor( + @IPanelService private _panelService: IPanelService, + @IBulkEditService bulkEditService: IBulkEditService, + ) { + + bulkEditService.setPreviewHandler(edit => this._previewEdit(edit)); + } + + private async _previewEdit(edit: WorkspaceEdit) { + + const panel = this._panelService.openPanel(BulkEditPanel.ID, true); + if (!(panel instanceof BulkEditPanel)) { + // error? + return edit; + } + + const apply = await panel.setInput(edit); + if (!apply) { + return { edits: [] }; + } + // todo@joh get 'real' edit + return edit; + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( + BulkEditPreviewContribution, LifecyclePhase.Ready +); + +Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( + BulkEditPanel, + BulkEditPanel.ID, + localize('panel', "Refactor Preview"), + 'bulkEditPanel', + 10 +)); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css new file mode 100644 index 00000000000..c46eb7bd817 --- /dev/null +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEdit.css @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .bulk-edit-panel .highlight.remove { + text-decoration: line-through; +} + + diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts new file mode 100644 index 00000000000..fa7add19ca6 --- /dev/null +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPanel.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./bulkEdit'; +import { Panel } from 'vs/workbench/browser/panel'; +import { Dimension, addClass } from 'vs/base/browser/dom'; +import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; +import { WorkspaceEdit } from 'vs/editor/common/modes'; +import { Edit, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; +import { FuzzyScore } from 'vs/base/common/filters'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { Action, IAction } from 'vs/base/common/actions'; +import { diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistry'; + +export class BulkEditPanel extends Panel { + + static readonly ID = 'BulkEditPanel'; + private static EmptyWorkspaceEdit = { edits: [] }; + + private _tree!: WorkbenchAsyncDataTree; + private _acceptAction: IAction; + private _discardAction: IAction; + + constructor( + @IInstantiationService private readonly _instaService: IInstantiationService, + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService + ) { + super(BulkEditPanel.ID, telemetryService, themeService, storageService); + + this._acceptAction = new Action('ok', 'Apply', 'codicon-check', false, async () => this._done(true)); + this._discardAction = new Action('ok', 'Discard', 'codicon-trash', false, async () => this._done(false)); + } + + create(parent: HTMLElement): void { + super.create(parent); + + addClass(parent, 'bulk-edit-panel'); + + const treeContainer = document.createElement('div'); + treeContainer.style.width = '100%'; + treeContainer.style.height = '100%'; + parent.appendChild(treeContainer); + + this._tree = this._instaService.createInstance( + WorkbenchAsyncDataTree, this.getId(), treeContainer, + new BulkEditDelegate(), + [new TextEditElementRenderer(), this._instaService.createInstance(FileElementRenderer)], + this._instaService.createInstance(BulkEditDataSource), + { + identityProvider: new BulkEditIdentityProvider() + } + ); + } + + layout(dimension: Dimension): void { + this._tree.layout(dimension.height, dimension.width); + } + + private _currentResolve?: (apply: boolean) => void; + + setInput(edit: WorkspaceEdit): Promise { + + if (this._currentResolve) { + this._currentResolve(false); + this._currentResolve = undefined; + } + + this._acceptAction.enabled = true; + this._discardAction.enabled = true; + + return new Promise(async resolve => { + this._currentResolve = resolve; + await this._tree.setInput(edit); + this._tree.domFocus(); + this._tree.focusFirst(); + }); + } + + private _done(accept: boolean): void { + if (this._currentResolve) { + this._currentResolve(accept); + this._acceptAction.enabled = false; + this._discardAction.enabled = false; + this._tree.setInput(BulkEditPanel.EmptyWorkspaceEdit); + } + } + + getActions() { + return [this._acceptAction, this._discardAction]; + } +} + +registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + + const diffInsertedColor = theme.getColor(diffInserted); + if (diffInsertedColor) { + collector.addRule(`.monaco-workbench .bulk-edit-panel .highlight.insert { background-color: ${diffInsertedColor}; }`); + } + const diffRemovedColor = theme.getColor(diffRemoved); + if (diffRemovedColor) { + collector.addRule(`.monaco-workbench .bulk-edit-panel .highlight.remove { background-color: ${diffRemovedColor}; }`); + } +}); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts new file mode 100644 index 00000000000..c5fdb4f4ce1 --- /dev/null +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +// import * as modes from 'vs/editor/common/modes'; +// import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; +// import { URI } from 'vs/base/common/uri'; +// import { ITextModel } from 'vs/editor/common/model'; +// import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +// import { IModeService } from 'vs/editor/common/services/modeService'; +// import { IModelService } from 'vs/editor/common/services/modelService'; +// import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; + +// export class BulkEditPreviewProvider implements ITextModelContentProvider { + +// static readonly Schema = 'vscode-bulkeditpreview'; + +// static asPreviewUri(uri: URI): URI { +// return URI.from({ scheme: BulkEditPreviewProvider.Schema, path: uri.toString() }); +// } + +// static fromPreviewUri(uri: URI): URI { +// return URI.parse(uri.path); +// } + +// constructor( +// @IModeService private readonly _modeService: IModeService, +// @IModelService private readonly _modelService: IModelService, +// @ITextModelService private readonly textModelResolverService: ITextModelService +// ) { +// this.textModelResolverService.registerTextModelContentProvider(BulkEditPreviewProvider.Schema, this); +// } + +// async provideTextContent(previewUri: URI) { + +// const resourceUri = BulkEditPreviewProvider.fromPreviewUri(previewUri); + +// const ref = await this.textModelResolverService.createModelReference(resourceUri); + +// const sourceModel = ref.object.textEditorModel; + +// const previewModel = this._modelService.createModel( +// createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()), +// this._modeService.create(sourceModel.getLanguageIdentifier().language), +// previewUri +// ); + +// return null; +// } +// } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 03c015bc851..372366e9b0f 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -29,7 +29,7 @@ export class FileElement { } export class TextEditElement { - constructor(readonly line: string, readonly highlight: IHighlight, readonly edit: modes.TextEdit) { } + constructor(readonly edit: modes.TextEdit, readonly prefix: string, readonly selecting: string, readonly inserting: string, readonly suffix: string) { } } export type Edit = FileElement | TextEditElement; @@ -64,17 +64,14 @@ export class BulkEditDataSource implements IAsyncDataSource { const range = Range.lift(edit.range); - const start = textModel.getOffsetAt(range.getStartPosition()); - const end = textModel.getOffsetAt(range.getEndPosition()); - const len = end - start; - const previewStart = textModel.getPositionAt(start - 20); - const previewEnd = textModel.getPositionAt(end + 30); - - const preview = textModel.getValueInRange(Range.fromPositions(previewStart, previewEnd)); - const previewOffset = start - textModel.getOffsetAt(previewStart); - - return new TextEditElement(preview, { start: previewOffset, end: previewOffset + len }, edit); + return new TextEditElement( + edit, + textModel.getValueInRange(new Range(range.startLineNumber, 1, range.startLineNumber, range.startColumn)), // line start to edit start, + textModel.getValueInRange(range), + edit.text, + textModel.getValueInRange(new Range(range.endLineNumber, range.endColumn, range.endLineNumber, range.endColumn + 20)) + ); }); return result; @@ -148,14 +145,24 @@ export class TextEditElementRenderer implements ITreeRenderer, _index: number, template: TextEditElementTemplate): void { - template.label.set(node.element.line, [node.element.highlight], undefined, true); + renderElement({ element }: ITreeNode, _index: number, template: TextEditElementTemplate): void { + + let value = ''; + value += element.prefix; + value += element.selecting; + value += element.inserting; + value += element.suffix; + + let selectHighlight: IHighlight = { start: element.prefix.length, end: element.prefix.length + element.selecting.length, extraClasses: 'remove' }; + let insertHighlight: IHighlight = { start: selectHighlight.end, end: selectHighlight.end + element.inserting.length, extraClasses: 'insert' }; + + template.label.set(value, [selectHighlight, insertHighlight], undefined, true); } disposeTemplate(_template: TextEditElementTemplate): void { } } -export class Delegate implements IListVirtualDelegate { +export class BulkEditDelegate implements IListVirtualDelegate { getHeight(): number { return 23; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 2c97f517bbc..c437848af7e 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -150,6 +150,9 @@ import 'vs/workbench/contrib/files/browser/files.contribution'; // Backup import 'vs/workbench/contrib/backup/common/backup.contribution'; +// bulkEdit +import 'vs/workbench/contrib/bulkEdit/browser/bulkEdit.contribution'; + // Search import 'vs/workbench/contrib/search/browser/search.contribution'; import 'vs/workbench/contrib/search/browser/searchView';