<commit_before>/*---------------------------------------------------------------------------------------------
 *  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 { ITextModelService } from 'vs/editor/common/services/resolverService';
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
import { URI } from 'vs/base/common/uri';
import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { Range } from 'vs/editor/common/core/range';
import * as dom from 'vs/base/browser/dom';
import { ITextModel } from 'vs/editor/common/model';
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { TextModel } from 'vs/editor/common/model/textModel';
import { BulkFileOperations, BulkFileOperation, BulkFileOperationType, BulkTextEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview';
import { FileKind } from 'vs/platform/files/common/files';
import { localize } from 'vs/nls';
import { ILabelService } from 'vs/platform/label/common/label';

// --- VIEW MODEL

export class FileElement {

	readonly uri: URI;

	constructor(readonly edit: BulkFileOperation) {
		this.uri = edit.uri;
	}
}

export class TextEditElement {

	constructor(
		readonly parent: FileElement,
		readonly edit: BulkTextEdit,
		readonly prefix: string, readonly selecting: string, readonly inserting: string, readonly suffix: string
	) { }
}

export type BulkEditElement = FileElement | TextEditElement;

// --- DATA SOURCE

export class BulkEditDataSource implements IAsyncDataSource<BulkFileOperations, BulkEditElement> {

	constructor(@ITextModelService private readonly _textModelService: ITextModelService) { }

	hasChildren(element: BulkFileOperations | BulkEditElement): boolean {
		if (element instanceof FileElement) {
			return element.edit.textEdits.length > 0;
		}
		if (element instanceof TextEditElement) {
			return false;
		}
		return true;
	}

	async getChildren(element: BulkFileOperations | BulkEditElement): Promise<BulkEditElement[]> {

		// root -> file/text edits
		if (element instanceof BulkFileOperations) {
			return element.fileOperations.map(op => new FileElement(op));
		}

		// file: text edit
		if (element instanceof FileElement && element.edit.textEdits.length > 0) {
			// const previewUri = BulkEditPreviewProvider.asPreviewUri(element.edit.resource);
			let textModel: ITextModel;
			let textModelDisposable: IDisposable;
			try {
				const ref = await this._textModelService.createModelReference(element.edit.uri);
				textModel = ref.object.textEditorModel;
				textModelDisposable = ref;
			} catch {
				textModel = TextModel.createFromString('');
				textModelDisposable = textModel;
			}

			const result = element.edit.textEdits.map(edit => {
				const range = Range.lift(edit.edit.range);

				const tokens = textModel.getLineTokens(range.endLineNumber);
				let suffixLen = 0;
				for (let idx = tokens.findTokenIndexAtOffset(range.endColumn); suffixLen < 50 && idx < tokens.getCount(); idx++) {
					suffixLen += tokens.getEndOffset(idx) - tokens.getStartOffset(idx);
				}

				return new TextEditElement(
					element,
					edit,
					textModel.getValueInRange(new Range(range.startLineNumber, 1, range.startLineNumber, range.startColumn)), // line start to edit start,
					textModel.getValueInRange(range),
					edit.edit.text,
					textModel.getValueInRange(new Range(range.endLineNumber, range.endColumn, range.endLineNumber, range.endColumn + suffixLen))
				);
			});

			textModelDisposable.dispose();
			return result;
		}

		return [];
	}
}

// --- IDENT

export class BulkEditIdentityProvider implements IIdentityProvider<BulkEditElement> {

	getId(element: BulkEditElement): { toString(): string; } {
		if (element instanceof FileElement) {
			return element.uri;
		} else {
			return element.parent.uri.toString() + JSON.stringify(element.edit.edit);
		}
	}
}

// --- RENDERER

class FileElementTemplate {

	private readonly _disposables = new DisposableStore();
	private readonly _localDisposables = new DisposableStore();

	private readonly _checkbox: HTMLInputElement;
	private readonly _label: IResourceLabel;
	private readonly _details: HTMLSpanElement;

	constructor(
		container: HTMLElement,
		resourceLabels: ResourceLabels,
		@ILabelService private readonly _labelService: ILabelService,
	) {

		this._checkbox = document.createElement('input');
		this._checkbox.className = 'edit-checkbox';
		this._checkbox.type = 'checkbox';
		this._checkbox.setAttribute('role', 'checkbox');
		container.appendChild(this._checkbox);

		this._label = resourceLabels.create(container, { supportHighlights: true });

		this._details = document.createElement('span');
		this._details.className = 'details';
		container.appendChild(this._details);
	}

	dispose(): void {
		this._localDisposables.dispose();
		this._disposables.dispose();
		this._label.dispose();
	}

	set(element: FileElement, score: FuzzyScore | undefined) {
		this._localDisposables.clear();
		this._localDisposables.add(dom.addDisposableListener(this._checkbox, 'change', (() => element.edit.updateChecked(this._checkbox.checked))));
		this._checkbox.checked = element.edit.isChecked();

		this._label.setFile(element.uri, {
			matches: createMatches(score),
			fileKind: FileKind.FILE,
			fileDecorations: { colors: true, badges: false },
		});

		// details
		if (element.edit.type & BulkFileOperationType.Rename && element.edit.newUri) {
			this._details.innerText = localize('detail.rename', "(renaming to {0})", this._labelService.getUriLabel(element.edit.newUri, { relative: true }));
		} else if (element.edit.type & BulkFileOperationType.Create) {
			this._details.innerText = localize('detail.create', "(creating)");
		} else if (element.edit.type & BulkFileOperationType.Create) {
			this._details.innerText = localize('detail.del', "(deleting)");
		} else {
			this._details.innerText = '';
		}
	}
}

export class FileElementRenderer implements ITreeRenderer<FileElement, FuzzyScore, FileElementTemplate> {

	static readonly id: string = 'FileElementRenderer';

	readonly templateId: string = FileElementRenderer.id;

	constructor(<commit_msg>for rename-file, show the new name more prominent, #77728<commit_after>/*---------------------------------------------------------------------------------------------
 *  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 { ITextModelService } from 'vs/editor/common/services/resolverService';
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
import { URI } from 'vs/base/common/uri';
import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { Range } from 'vs/editor/common/core/range';
import * as dom from 'vs/base/browser/dom';
import { ITextModel } from 'vs/editor/common/model';
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { TextModel } from 'vs/editor/common/model/textModel';
import { BulkFileOperations, BulkFileOperation, BulkFileOperationType, BulkTextEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview';
import { FileKind } from 'vs/platform/files/common/files';
import { localize } from 'vs/nls';
import { ILabelService } from 'vs/platform/label/common/label';

// --- VIEW MODEL

export class FileElement {

	readonly uri: URI;

	constructor(readonly edit: BulkFileOperation) {
		this.uri = edit.uri;
	}
}

export class TextEditElement {

	constructor(
		readonly parent: FileElement,
		readonly edit: BulkTextEdit,
		readonly prefix: string, readonly selecting: string, readonly inserting: string, readonly suffix: string
	) { }
}

export type BulkEditElement = FileElement | TextEditElement;

// --- DATA SOURCE

export class BulkEditDataSource implements IAsyncDataSource<BulkFileOperations, BulkEditElement> {

	constructor(@ITextModelService private readonly _textModelService: ITextModelService) { }

	hasChildren(element: BulkFileOperations | BulkEditElement): boolean {
		if (element instanceof FileElement) {
			return element.edit.textEdits.length > 0;
		}
		if (element instanceof TextEditElement) {
			return false;
		}
		return true;
	}

	async getChildren(element: BulkFileOperations | BulkEditElement): Promise<BulkEditElement[]> {

		// root -> file/text edits
		if (element instanceof BulkFileOperations) {
			return element.fileOperations.map(op => new FileElement(op));
		}

		// file: text edit
		if (element instanceof FileElement && element.edit.textEdits.length > 0) {
			// const previewUri = BulkEditPreviewProvider.asPreviewUri(element.edit.resource);
			let textModel: ITextModel;
			let textModelDisposable: IDisposable;
			try {
				const ref = await this._textModelService.createModelReference(element.edit.uri);
				textModel = ref.object.textEditorModel;
				textModelDisposable = ref;
			} catch {
				textModel = TextModel.createFromString('');
				textModelDisposable = textModel;
			}

			const result = element.edit.textEdits.map(edit => {
				const range = Range.lift(edit.edit.range);

				const tokens = textModel.getLineTokens(range.endLineNumber);
				let suffixLen = 0;
				for (let idx = tokens.findTokenIndexAtOffset(range.endColumn); suffixLen < 50 && idx < tokens.getCount(); idx++) {
					suffixLen += tokens.getEndOffset(idx) - tokens.getStartOffset(idx);
				}

				return new TextEditElement(
					element,
					edit,
					textModel.getValueInRange(new Range(range.startLineNumber, 1, range.startLineNumber, range.startColumn)), // line start to edit start,
					textModel.getValueInRange(range),
					edit.edit.text,
					textModel.getValueInRange(new Range(range.endLineNumber, range.endColumn, range.endLineNumber, range.endColumn + suffixLen))
				);
			});

			textModelDisposable.dispose();
			return result;
		}

		return [];
	}
}

// --- IDENT

export class BulkEditIdentityProvider implements IIdentityProvider<BulkEditElement> {

	getId(element: BulkEditElement): { toString(): string; } {
		if (element instanceof FileElement) {
			return element.uri;
		} else {
			return element.parent.uri.toString() + JSON.stringify(element.edit.edit);
		}
	}
}

// --- RENDERER

class FileElementTemplate {

	private readonly _disposables = new DisposableStore();
	private readonly _localDisposables = new DisposableStore();

	private readonly _checkbox: HTMLInputElement;
	private readonly _label: IResourceLabel;
	private readonly _details: HTMLSpanElement;

	constructor(
		container: HTMLElement,
		resourceLabels: ResourceLabels,
		@ILabelService private readonly _labelService: ILabelService,
	) {

		this._checkbox = document.createElement('input');
		this._checkbox.className = 'edit-checkbox';
		this._checkbox.type = 'checkbox';
		this._checkbox.setAttribute('role', 'checkbox');
		container.appendChild(this._checkbox);

		this._label = resourceLabels.create(container, { supportHighlights: true });

		this._details = document.createElement('span');
		this._details.className = 'details';
		container.appendChild(this._details);
	}

	dispose(): void {
		this._localDisposables.dispose();
		this._disposables.dispose();
		this._label.dispose();
	}

	set(element: FileElement, score: FuzzyScore | undefined) {
		this._localDisposables.clear();
		this._localDisposables.add(dom.addDisposableListener(this._checkbox, 'change', (() => element.edit.updateChecked(this._checkbox.checked))));
		this._checkbox.checked = element.edit.isChecked();

		if (element.edit.type & BulkFileOperationType.Rename && element.edit.newUri) {
			// rename: NEW NAME (old name)
			this._label.setFile(element.edit.newUri, {
				matches: createMatches(score),
				fileKind: FileKind.FILE,
				fileDecorations: { colors: true, badges: false },
			});

			this._details.innerText = localize(
				'detail.rename', "(renaming from {0})",
				this._labelService.getUriLabel(element.uri, { relative: true })
			);

		} else {
			// create, delete, edit: NAME
			this._label.setFile(element.uri, {
				matches: createMatches(score),
				fileKind: FileKind.FILE,
				fileDecorations: { colors: true, badges: false },
			});

			if (element.edit.type & BulkFileOperationType.Create) {
				this._details.innerText = localize('detail.create', "(creating)");
			} else if (element.edit.type & BulkFileOperationType.Create) {
				this._details.innerText = localize('detail.del', "(deleting)");
			} else {
				this._details.innerText = '';
			}
		}
	}
}

export class FileElementRenderer implements ITreeRenderer<FileElement, FuzzyScore, FileElementTemplate> {

	static readonly id: string = 'FileElementRenderer';

	readonly templateId: string = FileElementRenderer.id;

	constructor(
{'fraction_non_alphanumeric': 0.1427832002366164, 'fraction_numerical': 0.0014049097900029578, 'mean_word_length': 4.024145616641902, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4889890', 'n_tokens_mistral': 4331, 'n_tokens_neox': 3888, 'n_words': 852}