deploy: current vibn theia state
Some checks failed
Playwright Tests / Playwright Tests (ubuntu-22.04, Node.js 22.x) (push) Has been cancelled
3PP License Check / 3PP License Check (11, 22.x, ubuntu-22.04) (push) Has been cancelled
Publish packages to NPM / Perform Publishing (push) Has been cancelled

Made-with: Cursor
This commit is contained in:
2026-02-27 12:01:08 -08:00
commit 8bb5110148
3782 changed files with 640947 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: [
'../../configs/build.eslintrc.json'
],
parserOptions: {
tsconfigRootDir: __dirname,
project: 'tsconfig.json'
}
};

View File

@@ -0,0 +1,31 @@
<div align='center'>
<br />
<img src='https://raw.githubusercontent.com/eclipse-theia/theia/master/logo/theia.svg?sanitize=true' alt='theia-ext-logo' width='100px' />
<h2>ECLIPSE THEIA - BULK EDIT EXTENSION</h2>
<hr />
</div>
## Description
The `@theia/bulk-edit` extension contributes a `Refactor Preview` widget to the application that displays WorkspaceEdits to end-users.
## Additional Information
- [API documentation for `@theia/bulk-edit`](https://eclipse-theia.github.io/theia/docs/next/modules/_theia_bulk-edit.html)
- [Theia - GitHub](https://github.com/eclipse-theia/theia)
- [Theia - Website](https://theia-ide.org/)
## License
- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)
## Trademark
"Theia" is a trademark of the Eclipse Foundation
<https://www.eclipse.org/theia>

View File

@@ -0,0 +1,53 @@
{
"name": "@theia/bulk-edit",
"version": "1.68.0",
"description": "Theia - Bulk Edit Extension",
"dependencies": {
"@theia/core": "1.68.0",
"@theia/editor": "1.68.0",
"@theia/filesystem": "1.68.0",
"@theia/monaco": "1.68.0",
"@theia/monaco-editor-core": "1.96.302",
"@theia/workspace": "1.68.0",
"tslib": "^2.6.2"
},
"publishConfig": {
"access": "public"
},
"theiaExtensions": [
{
"frontend": "lib/browser/bulk-edit-frontend-module"
}
],
"keywords": [
"theia-extension"
],
"license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0",
"repository": {
"type": "git",
"url": "https://github.com/eclipse-theia/theia.git"
},
"bugs": {
"url": "https://github.com/eclipse-theia/theia/issues"
},
"homepage": "https://github.com/eclipse-theia/theia",
"files": [
"lib",
"src"
],
"scripts": {
"build": "theiaext build",
"clean": "theiaext clean",
"compile": "theiaext compile",
"lint": "theiaext lint",
"test": "theiaext test",
"watch": "theiaext watch"
},
"devDependencies": {
"@theia/ext-scripts": "1.68.0"
},
"nyc": {
"extends": "../../configs/nyc.json"
},
"gitHead": "21358137e41342742707f660b8e222f940a27652"
}

View File

@@ -0,0 +1,34 @@
// *****************************************************************************
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { codicon } from '@theia/core/lib/browser';
import { Command } from '@theia/core/lib/common';
export namespace BulkEditCommands {
export const TOGGLE_VIEW: Command = {
id: 'bulk-edit:toggleView'
};
export const APPLY: Command = {
id: 'bulk-edit:apply',
iconClass: codicon('check')
};
export const DISCARD: Command = {
id: 'bulk-edit:discard',
iconClass: codicon('clear-all')
};
}

View File

@@ -0,0 +1,119 @@
// *****************************************************************************
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable, inject, optional, postConstruct } from '@theia/core/shared/inversify';
import { Widget } from '@theia/core/lib/browser/widgets/widget';
import { CommandRegistry } from '@theia/core/lib/common';
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
import { BulkEditCommands } from './bulk-edit-commands';
import { MonacoBulkEditService } from '@theia/monaco/lib/browser/monaco-bulk-edit-service';
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { BulkEditTreeWidget, BULK_EDIT_TREE_WIDGET_ID, BULK_EDIT_WIDGET_NAME } from './bulk-edit-tree';
import { QuickViewService } from '@theia/core/lib/browser';
import { nls } from '@theia/core/lib/common/nls';
import { ResourceEdit } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/bulkEditService';
@injectable()
export class BulkEditContribution extends AbstractViewContribution<BulkEditTreeWidget> implements TabBarToolbarContribution {
protected edits: ResourceEdit[];
@inject(QuickViewService) @optional()
protected override readonly quickView: QuickViewService;
@inject(MonacoBulkEditService)
protected readonly bulkEditService: MonacoBulkEditService;
constructor() {
super({
widgetId: BULK_EDIT_TREE_WIDGET_ID,
widgetName: BULK_EDIT_WIDGET_NAME,
defaultWidgetOptions: {
area: 'bottom'
}
});
}
@postConstruct()
protected init(): void {
this.bulkEditService.setPreviewHandler((edits: ResourceEdit[]) => this.previewEdit(edits));
}
override registerCommands(registry: CommandRegistry): void {
super.registerCommands(registry);
this.quickView?.hideItem(BULK_EDIT_WIDGET_NAME);
registry.registerCommand(BulkEditCommands.APPLY, {
isEnabled: widget => this.withWidget(widget, () => true),
isVisible: widget => this.withWidget(widget, () => true),
execute: widget => this.withWidget(widget, () => this.apply())
});
registry.registerCommand(BulkEditCommands.DISCARD, {
isEnabled: widget => this.withWidget(widget, () => true),
isVisible: widget => this.withWidget(widget, () => true),
execute: widget => this.withWidget(widget, () => this.discard())
});
}
async registerToolbarItems(toolbarRegistry: TabBarToolbarRegistry): Promise<void> {
toolbarRegistry.registerItem({
id: BulkEditCommands.APPLY.id,
command: BulkEditCommands.APPLY.id,
tooltip: nls.localizeByDefault('Apply Refactoring'),
priority: 0,
});
toolbarRegistry.registerItem({
id: BulkEditCommands.DISCARD.id,
command: BulkEditCommands.DISCARD.id,
tooltip: nls.localizeByDefault('Discard Refactoring'),
priority: 1,
});
}
protected withWidget<T>(widget: Widget | undefined = this.tryGetWidget(), cb: (bulkEdit: BulkEditTreeWidget) => T): T | false {
if (widget instanceof BulkEditTreeWidget) {
return cb(widget);
}
return false;
}
private async previewEdit(edits: ResourceEdit[]): Promise<ResourceEdit[]> {
const widget = await this.openView({ activate: true });
if (widget) {
this.edits = edits;
await widget.initModel(edits);
}
return edits;
}
private apply(): void {
if (this.edits) {
this.edits.forEach(edit => {
if (edit.metadata) {
edit.metadata.needsConfirmation = false;
}
});
this.bulkEditService.apply(this.edits);
}
this.closeView();
}
private discard(): void {
this.edits = [];
this.closeView();
}
}

View File

@@ -0,0 +1,39 @@
// *****************************************************************************
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { ContainerModule } from '@theia/core/shared/inversify';
import { WidgetFactory } from '@theia/core/lib/browser/widget-manager';
import { BulkEditTreeWidget, BULK_EDIT_TREE_WIDGET_ID, createBulkEditTreeWidget } from './bulk-edit-tree';
import { FrontendApplicationContribution, LabelProviderContribution, bindViewContribution } from '@theia/core/lib/browser';
import { BulkEditContribution } from './bulk-edit-contribution';
import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { BulkEditTreeLabelProvider } from './bulk-edit-tree-label-provider';
import '../../src/browser/style/bulk-edit.css';
export default new ContainerModule(bind => {
bind(BulkEditTreeWidget).toDynamicValue(ctx =>
createBulkEditTreeWidget(ctx.container)
);
bind(WidgetFactory).toDynamicValue(context => ({
id: BULK_EDIT_TREE_WIDGET_ID,
createWidget: () => context.container.get(BulkEditTreeWidget)
}));
bindViewContribution(bind, BulkEditContribution);
bind(FrontendApplicationContribution).toService(BulkEditContribution);
bind(TabBarToolbarContribution).toService(BulkEditContribution);
bind(BulkEditTreeLabelProvider).toSelf().inSingletonScope();
bind(LabelProviderContribution).toService(BulkEditTreeLabelProvider);
});

View File

@@ -0,0 +1,71 @@
// *****************************************************************************
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable, inject } from '@theia/core/shared/inversify';
import { LabelProvider, LabelProviderContribution, DidChangeLabelEvent } from '@theia/core/lib/browser/label-provider';
import { BulkEditInfoNode } from './bulk-edit-tree';
import { TreeLabelProvider } from '@theia/core/lib/browser/tree/tree-label-provider';
import { WorkspaceService } from '@theia/workspace/lib/browser';
@injectable()
export class BulkEditTreeLabelProvider implements LabelProviderContribution {
@inject(LabelProvider)
protected readonly labelProvider: LabelProvider;
@inject(TreeLabelProvider)
protected readonly treeLabelProvider: TreeLabelProvider;
@inject(WorkspaceService)
protected readonly workspaceService: WorkspaceService;
canHandle(element: object): number {
return BulkEditInfoNode.is(element) ?
this.treeLabelProvider.canHandle(element) + 1 :
0;
}
getIcon(node: BulkEditInfoNode): string {
return this.labelProvider.getIcon(node.uri);
}
getName(node: BulkEditInfoNode): string {
return this.labelProvider.getName(node.uri);
}
getLongName(node: BulkEditInfoNode): string {
const description: string[] = [];
const rootUri = this.workspaceService.getWorkspaceRootUri(node.uri);
// In a multiple-root workspace include the root name to the label before the parent directory.
if (this.workspaceService.isMultiRootWorkspaceOpened && rootUri) {
description.push(this.labelProvider.getName(rootUri));
}
// If the given resource is not at the workspace root, include the parent directory to the label.
if (rootUri?.toString() !== node.uri.parent.toString()) {
description.push(this.labelProvider.getLongName(node.uri.parent));
}
return description.join(' ● ');
}
getDescription(node: BulkEditInfoNode): string {
return this.labelProvider.getLongName(node.uri.parent);
}
affects(node: BulkEditInfoNode, event: DidChangeLabelEvent): boolean {
return event.affects(node.uri) || event.affects(node.uri.parent);
}
}

View File

@@ -0,0 +1,44 @@
// *****************************************************************************
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { SelectionService } from '@theia/core/lib/common/selection-service';
import { SelectionCommandHandler } from '@theia/core/lib/common/selection-command-handler';
import { ResourceFileEdit, ResourceTextEdit } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/bulkEditService';
import { isObject } from '@theia/core/lib/common';
export interface BulkEditNodeSelection {
bulkEdit: ResourceFileEdit | ResourceTextEdit;
}
export namespace BulkEditNodeSelection {
export function is(arg: unknown): arg is BulkEditNodeSelection {
return isObject(arg) && 'bulkEdit' in arg;
}
export class CommandHandler extends SelectionCommandHandler<BulkEditNodeSelection> {
constructor(
protected override readonly selectionService: SelectionService,
protected override readonly options: SelectionCommandHandler.Options<BulkEditNodeSelection>
) {
super(
selectionService,
arg => BulkEditNodeSelection.is(arg) ? arg : undefined,
options
);
}
}
}

View File

@@ -0,0 +1,35 @@
// *****************************************************************************
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { interfaces, Container } from '@theia/core/shared/inversify';
import { BulkEditTreeWidget } from './bulk-edit-tree-widget';
import { BulkEditTree } from './bulk-edit-tree';
import { BulkEditTreeModel } from './bulk-edit-tree-model';
import { createTreeContainer } from '@theia/core/lib/browser';
export function createBulkEditContainer(parent: interfaces.Container): Container {
const child = createTreeContainer(parent, {
tree: BulkEditTree,
widget: BulkEditTreeWidget,
model: BulkEditTreeModel,
});
return child;
}
export function createBulkEditTreeWidget(parent: interfaces.Container): BulkEditTreeWidget {
return createBulkEditContainer(parent).get(BulkEditTreeWidget);
}

View File

@@ -0,0 +1,42 @@
// *****************************************************************************
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable, inject } from '@theia/core/shared/inversify';
import { BulkEditNode, BulkEditTree } from './bulk-edit-tree';
import { TreeModelImpl, OpenerService, open, TreeNode } from '@theia/core/lib/browser';
import { ResourceEdit } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/bulkEditService';
@injectable()
export class BulkEditTreeModel extends TreeModelImpl {
@inject(BulkEditTree) protected override readonly tree: BulkEditTree;
@inject(OpenerService) protected readonly openerService: OpenerService;
protected override doOpenNode(node: TreeNode): void {
if (BulkEditNode.is(node)) {
open(this.openerService, node.uri, undefined);
} else {
super.doOpenNode(node);
}
}
revealNode(node: TreeNode): void {
this.doOpenNode(node);
}
async initModel(edits: ResourceEdit[], fileContents: Map<string, string>): Promise<void> {
this.tree.initTree(edits, fileContents);
}
}

View File

@@ -0,0 +1,231 @@
// *****************************************************************************
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable, inject, optional } from '@theia/core/shared/inversify';
import {
DiffUris, TreeWidget, TreeProps, ContextMenuRenderer, TreeNode, TreeModel,
CompositeTreeNode, NodeProps, QuickViewService
} from '@theia/core/lib/browser';
import * as React from '@theia/core/shared/react';
import { BulkEditInfoNode, BulkEditNode } from './bulk-edit-tree';
import { BulkEditTreeModel } from './bulk-edit-tree-model';
import { FileResourceResolver } from '@theia/filesystem/lib/browser';
import URI from '@theia/core/lib/common/uri';
import { EditorWidget, EditorManager, EditorOpenerOptions } from '@theia/editor/lib/browser';
import { MEMORY_TEXT } from '@theia/core/lib/common';
import { Disposable } from '@theia/core/lib/common/disposable';
import { nls } from '@theia/core/lib/common/nls';
import { ResourceEdit, ResourceFileEdit, ResourceTextEdit } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/bulkEditService';
export const BULK_EDIT_TREE_WIDGET_ID = 'bulkedit';
export const BULK_EDIT_WIDGET_NAME = nls.localizeByDefault('Refactor Preview');
@injectable()
export class BulkEditTreeWidget extends TreeWidget {
private editorWidgets: EditorWidget[] = [];
@inject(FileResourceResolver)
protected readonly fileResourceResolver: FileResourceResolver;
@inject(EditorManager)
protected readonly editorManager: EditorManager;
@inject(QuickViewService) @optional()
protected readonly quickView: QuickViewService;
constructor(
@inject(TreeProps) readonly treeProps: TreeProps,
@inject(BulkEditTreeModel) override readonly model: BulkEditTreeModel,
@inject(ContextMenuRenderer) override readonly contextMenuRenderer: ContextMenuRenderer
) {
super(treeProps, model, contextMenuRenderer);
this.id = BULK_EDIT_TREE_WIDGET_ID;
this.title.label = BULK_EDIT_WIDGET_NAME;
this.title.caption = BULK_EDIT_WIDGET_NAME;
this.title.closable = true;
this.addClass('theia-bulk-edit-container');
this.toDispose.push(Disposable.create(() => {
this.disposeEditors();
}));
}
async initModel(edits: ResourceEdit[]): Promise<void> {
await this.model.initModel(edits, await this.getFileContentsMap(edits));
this.quickView?.showItem(BULK_EDIT_WIDGET_NAME);
}
protected override tapNode(node?: TreeNode): void {
super.tapNode(node);
if (BulkEditNode.is(node)) {
this.doOpen(node);
}
}
protected override handleDown(event: KeyboardEvent): void {
const node = this.model.getNextSelectableNode();
super.handleDown(event);
if (BulkEditNode.is(node)) {
this.doOpen(node);
}
}
protected override handleUp(event: KeyboardEvent): void {
const node = this.model.getPrevSelectableNode();
super.handleUp(event);
if (BulkEditNode.is(node)) {
this.doOpen(node);
}
}
protected override renderTree(model: TreeModel): React.ReactNode {
if (CompositeTreeNode.is(model.root) && model.root.children.length > 0) {
return super.renderTree(model);
}
return <div className='theia-widget-noInfo noEdits'>{nls.localizeByDefault('Made no edits')}</div>;
}
protected override renderCaption(node: TreeNode, props: NodeProps): React.ReactNode {
if (BulkEditInfoNode.is(node)) {
return this.decorateBulkEditInfoNode(node);
} else if (BulkEditNode.is(node)) {
return this.decorateBulkEditNode(node);
}
return 'caption';
}
protected decorateBulkEditNode(node: BulkEditNode): React.ReactNode {
if (node?.parent && node?.bulkEdit && ('textEdit' in node?.bulkEdit)) {
const bulkEdit = node.bulkEdit;
const parent = node.parent as BulkEditInfoNode;
if (parent?.fileContents) {
const lines = parent.fileContents.split('\n');
const startLineNum = +bulkEdit.textEdit?.range?.startLineNumber;
if (lines.length > startLineNum) {
const startColumn = +bulkEdit.textEdit.range.startColumn;
const endColumn = +bulkEdit.textEdit.range.endColumn;
const lineText = lines[startLineNum - 1];
const beforeMatch = (startColumn > 26 ? '... ' : '') + lineText.substring(0, startColumn - 1).slice(-25);
const replacedText = lineText.substring(startColumn - 1, endColumn - 1);
const afterMatch = lineText.substring(startColumn - 1 + replacedText.length, startColumn - 1 + replacedText.length + 75);
return <div className='bulkEditNode'>
<div className='message'>
{beforeMatch}
<span className="replaced-text">{replacedText}</span>
<span className="inserted-text">{bulkEdit.textEdit.text}</span>
{afterMatch}
</div>
</div>;
}
}
}
}
protected decorateBulkEditInfoNode(node: BulkEditInfoNode): React.ReactNode {
const icon = this.toNodeIcon(node);
const name = this.toNodeName(node);
const description = this.toNodeDescription(node);
const path = this.labelProvider.getLongName(node.uri.withScheme('bulkedit'));
return <div title={path} className='bulkEditInfoNode'>
{icon && <div className={icon + ' file-icon'}></div>}
<div className='name'>{name}</div>
<div className='path'>{description}</div>
</div>;
}
private async getFileContentsMap(edits: ResourceEdit[]): Promise<Map<string, string>> {
const fileContentMap = new Map<string, string>();
if (edits) {
for (const element of edits) {
if (element) {
const filePath = (('newResource' in element) && (element as ResourceFileEdit).newResource?.path) ||
(('resource' in element) && (element as ResourceTextEdit).resource.path);
if (filePath && !fileContentMap.has(filePath)) {
const fileUri = new URI(filePath).withScheme('file');
const resource = await this.fileResourceResolver.resolve(fileUri);
fileContentMap.set(filePath, await resource.readContents());
}
}
}
}
return fileContentMap;
}
private async doOpen(node: BulkEditNode): Promise<void> {
if (node && node.parent && node.bulkEdit && ('edit' in node.bulkEdit)) {
const resultNode = node.parent as BulkEditInfoNode;
const leftUri = node.uri;
const rightUri = await this.createReplacePreview(resultNode);
const diffUri = DiffUris.encode(leftUri, rightUri);
const editorWidget = await this.editorManager.open(diffUri, this.getEditorOptions(node));
this.editorWidgets.push(editorWidget);
}
}
private async createReplacePreview(bulkEditInfoNode: BulkEditInfoNode): Promise<URI> {
const fileUri = bulkEditInfoNode.uri;
let lines: string[] = [];
if (bulkEditInfoNode?.fileContents) {
lines = bulkEditInfoNode.fileContents.split('\n');
bulkEditInfoNode.children.map((node: BulkEditNode) => {
if (node.bulkEdit && ('textEdit' in node.bulkEdit)) {
const startLineNum = node.bulkEdit.textEdit.range.startLineNumber;
if (lines.length > startLineNum) {
const startColumn = node.bulkEdit.textEdit.range.startColumn;
const endColumn = node.bulkEdit.textEdit.range.endColumn;
const lineText = lines[startLineNum - 1];
const beforeMatch = lineText.substring(0, startColumn - 1);
const replacedText = lineText.substring(startColumn - 1, endColumn - 1);
const afterMatch = lineText.substring(startColumn - 1 + replacedText.length);
lines[startLineNum - 1] = beforeMatch + node.bulkEdit.textEdit.text + afterMatch;
}
}
});
}
return fileUri.withScheme(MEMORY_TEXT).withQuery(lines.join('\n'));
}
private getEditorOptions(node: BulkEditNode): EditorOpenerOptions {
let options = {};
if (('textEdit' in node.bulkEdit) && node?.bulkEdit?.textEdit?.range) {
options = {
selection: {
start: {
line: node.bulkEdit.textEdit.range.startLineNumber - 1,
character: node.bulkEdit.textEdit.range.startColumn - 1
},
end: {
line: node.bulkEdit.textEdit.range.endLineNumber - 1,
character: node.bulkEdit.textEdit.range.endColumn - 1
}
}
};
}
return options;
}
private disposeEditors(): void {
this.editorWidgets.forEach(w => w.dispose());
this.quickView?.hideItem(BULK_EDIT_WIDGET_NAME);
}
}

View File

@@ -0,0 +1,73 @@
// *****************************************************************************
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
import * as chai from 'chai';
import { ResourceTextEdit } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/bulkEditService';
import { URI as Uri } from '@theia/core/shared/vscode-uri';
let disableJSDOM = enableJSDOM();
import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider';
FrontendApplicationConfigProvider.set({});
import { Container } from '@theia/core/shared/inversify';
import { BulkEditInfoNode, BulkEditTree } from './bulk-edit-tree';
const expect = chai.expect;
let bulkEditTree: BulkEditTree;
let testContainer: Container;
const fileContextsMap = new Map<string, string>();
let resourceTextEdits: ResourceTextEdit[];
disableJSDOM();
before(() => {
disableJSDOM = enableJSDOM();
testContainer = new Container();
testContainer.bind(BulkEditTree).toSelf();
bulkEditTree = testContainer.get(BulkEditTree);
fileContextsMap.set('/c:/test1.ts', 'aaaaaaaaaaaaaaaaaaa');
fileContextsMap.set('/c:/test2.ts', 'bbbbbbbbbbbbbbbbbbb');
resourceTextEdits = <ResourceTextEdit[]><unknown>[
{
'resource': Uri.file('c:/test1.ts'),
'textEdit': {
'text': 'AAAAA', 'range': { 'startLineNumber': 1, 'startColumn': 5, 'endLineNumber': 1, 'endColumn': 10 }
}
},
{
'resource': Uri.file('c:/test2.ts'),
'textEdit': {
'text': 'BBBBBB', 'range': { 'startLineNumber': 1, 'startColumn': 3, 'endLineNumber': 1, 'endColumn': 8 }
}
}
];
});
after(() => {
disableJSDOM();
});
describe('bulk-edit-tree', () => {
it('initialize tree', () => {
bulkEditTree.initTree(resourceTextEdits, fileContextsMap);
expect((bulkEditTree.root as BulkEditInfoNode).children.length).is.equal(2);
});
});

View File

@@ -0,0 +1,114 @@
// *****************************************************************************
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable } from '@theia/core/shared/inversify';
import { TreeNode, CompositeTreeNode, SelectableTreeNode, ExpandableTreeNode, TreeImpl } from '@theia/core/lib/browser';
import { UriSelection } from '@theia/core/lib/common/selection';
import { BulkEditNodeSelection } from './bulk-edit-node-selection';
import URI from '@theia/core/lib/common/uri';
import { ResourceFileEdit, ResourceTextEdit } from '@theia/monaco/lib/browser/monaco-workspace';
import {
ResourceEdit, ResourceFileEdit as MonacoResourceFileEdit, ResourceTextEdit as MonacoResourceTextEdit
} from '@theia/monaco-editor-core/esm/vs/editor/browser/services/bulkEditService';
@injectable()
export class BulkEditTree extends TreeImpl {
public async initTree(edits: ResourceEdit[], fileContents: Map<string, string>): Promise<void> {
this.root = <CompositeTreeNode>{
visible: false,
id: 'theia-bulk-edit-tree-widget',
name: 'BulkEditTree',
children: this.getChildren(edits, fileContents),
parent: undefined
};
}
private getChildren(edits: ResourceEdit[], fileContentsMap: Map<string, string>): BulkEditInfoNode[] {
let bulkEditInfos: BulkEditInfoNode[] = [];
if (edits) {
bulkEditInfos = edits
.map(edit => this.getResourcePath(edit))
.filter((path, index, arr) => path && arr.indexOf(path) === index)
.map((path: string) => this.createBulkEditInfo(path, new URI(path), fileContentsMap.get(path)))
.filter(Boolean);
if (bulkEditInfos.length > 0) {
bulkEditInfos.forEach(editInfo => {
editInfo.children = edits.filter(edit =>
((('resource' in edit) && (edit as MonacoResourceTextEdit)?.resource?.path === editInfo.id)) ||
(('newResource' in edit) && (edit as MonacoResourceFileEdit)?.newResource?.path === editInfo.id))
.map((edit, index) => this.createBulkEditNode(('resource' in edit ? edit as MonacoResourceTextEdit :
edit as MonacoResourceFileEdit), index, editInfo));
});
}
}
return bulkEditInfos;
}
private createBulkEditNode(bulkEdit: MonacoResourceFileEdit | MonacoResourceTextEdit, index: number, parent: BulkEditInfoNode): BulkEditNode {
const id = parent.id + '_' + index;
const existing = this.getNode(id);
if (BulkEditNode.is(existing)) {
existing.bulkEdit = bulkEdit;
return existing;
}
return {
id,
name: 'bulkEdit',
parent,
selected: false,
uri: parent.uri,
bulkEdit
};
}
private createBulkEditInfo(id: string, uri: URI, fileContents: string | undefined): BulkEditInfoNode {
return {
id,
uri,
expanded: true,
selected: false,
parent: this.root as BulkEditInfoNode,
fileContents,
children: []
};
}
private getResourcePath(edit: ResourceEdit): string | undefined {
return ResourceTextEdit.is(edit) ? edit.resource.path :
ResourceFileEdit.is(edit) && edit.newResource ? edit.newResource.path : undefined;
}
}
export interface BulkEditNode extends UriSelection, SelectableTreeNode {
parent: CompositeTreeNode;
bulkEdit: MonacoResourceFileEdit | MonacoResourceTextEdit;
}
export namespace BulkEditNode {
export function is(node: TreeNode | undefined): node is BulkEditNode {
return UriSelection.is(node) && SelectableTreeNode.is(node) && BulkEditNodeSelection.is(node);
}
}
export interface BulkEditInfoNode extends UriSelection, SelectableTreeNode, ExpandableTreeNode {
parent: CompositeTreeNode;
fileContents?: string;
}
export namespace BulkEditInfoNode {
export function is(node: unknown): node is BulkEditInfoNode {
return ExpandableTreeNode.is(node) && UriSelection.is(node) && 'fileContents' in node;
}
}

View File

@@ -0,0 +1,21 @@
// *****************************************************************************
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
export * from './bulk-edit-tree';
export * from './bulk-edit-tree-model';
export * from './bulk-edit-node-selection';
export * from './bulk-edit-tree-widget';
export * from './bulk-edit-tree-container';

View File

@@ -0,0 +1,66 @@
/********************************************************************************
* Copyright (c) 2021 SAP SE or an SAP affiliate company and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
********************************************************************************/
.theia-bulk-edit-container {
font-size: var(--theia-ui-font-size1);
}
.theia-bulk-edit-container .bulkEditNode,
.theia-bulk-edit-container .bulkEditInfoNode {
display: flex;
align-items: center;
}
.theia-bulk-edit-container .bulkEditNode,
.theia-bulk-edit-container .bulkEditInfoNode {
width: calc(100% - 32px);
}
.theia-bulk-edit-container .bulkEditNode div,
.theia-bulk-edit-container .bulkEditInfoNode div {
margin-right: 5px;
}
.theia-bulk-edit-container .bulkEditInfoNode .name,
.theia-bulk-edit-container .bulkEditInfoNode .path {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.theia-bulk-edit-container .bulkEditInfoNode .path {
font-size: var(--theia-ui-font-size0);
color: var(--theia-descriptionForeground);
align-self: flex-end;
white-space: nowrap;
}
.theia-bulk-edit-container .bulkEditNode .message {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.theia-bulk-edit-container .bulkEditNode .message .replaced-text {
text-decoration: line-through;
background: var(--theia-diffEditor-removedTextBackground);
border-color: var(--theia-diffEditor-removedTextBorder);
}
.theia-bulk-edit-container .bulkEditNode .message .inserted-text {
background: var(--theia-diffEditor-insertedTextBackground);
border: 1px solid var(--theia-diffEditor-insertedTextBorder);
}

View File

@@ -0,0 +1,28 @@
{
"extends": "../../configs/base.tsconfig",
"compilerOptions": {
"composite": true,
"rootDir": "src",
"outDir": "lib"
},
"include": [
"src"
],
"references": [
{
"path": "../core"
},
{
"path": "../editor"
},
{
"path": "../filesystem"
},
{
"path": "../monaco"
},
{
"path": "../workspace"
}
]
}