| /* eslint-disable eqeqeq */ | |
| /*--------------------------------------------------------- | |
| * Copyright (C) Microsoft Corporation. All rights reserved. | |
| * Licensed under the MIT License. See LICENSE in the project root for license information. | |
| *--------------------------------------------------------*/ | |
| import jsDiff = require('diff'); | |
| import { Position, Range, TextEditorEdit, Uri, WorkspaceEdit } from 'vscode'; | |
| import { getBinPathFromEnvVar, getEnvPath } from './utils/pathUtils'; | |
| let diffToolAvailable: boolean | null = null; | |
| export function isDiffToolAvailable(): boolean { | |
| if (diffToolAvailable == null) { | |
| const envPath = getEnvPath(); | |
| if (!envPath) { | |
| return false; | |
| } | |
| diffToolAvailable = getBinPathFromEnvVar('diff', envPath, false) != null; | |
| } | |
| return diffToolAvailable; | |
| } | |
| export enum EditTypes { | |
| EDIT_DELETE, | |
| EDIT_INSERT, | |
| EDIT_REPLACE | |
| } | |
| export class Edit { | |
| public start: Position; | |
| public end: Position; | |
| public text: string; | |
| private action: number; | |
| constructor(action: number, start: Position) { | |
| this.action = action; | |
| this.start = start; | |
| this.end = start; | |
| this.text = ''; | |
| } | |
| // Applies Edit using given TextEditorEdit | |
| public applyUsingTextEditorEdit(editBuilder: TextEditorEdit): void { | |
| switch (this.action) { | |
| case EditTypes.EDIT_INSERT: | |
| editBuilder.insert(this.start, this.text); | |
| break; | |
| case EditTypes.EDIT_DELETE: | |
| editBuilder.delete(new Range(this.start, this.end)); | |
| break; | |
| case EditTypes.EDIT_REPLACE: | |
| editBuilder.replace(new Range(this.start, this.end), this.text); | |
| break; | |
| } | |
| } | |
| // Applies Edits to given WorkspaceEdit | |
| public applyUsingWorkspaceEdit(workspaceEdit: WorkspaceEdit, fileUri: Uri): void { | |
| switch (this.action) { | |
| case EditTypes.EDIT_INSERT: | |
| workspaceEdit.insert(fileUri, this.start, this.text); | |
| break; | |
| case EditTypes.EDIT_DELETE: | |
| workspaceEdit.delete(fileUri, new Range(this.start, this.end)); | |
| break; | |
| case EditTypes.EDIT_REPLACE: | |
| workspaceEdit.replace(fileUri, new Range(this.start, this.end), this.text); | |
| break; | |
| } | |
| } | |
| } | |
| export interface FilePatch { | |
| fileName: string; | |
| edits: Edit[]; | |
| } | |
| /** | |
| * Uses diff module to parse given array of IUniDiff objects and returns edits for files | |
| * | |
| * @param diffOutput jsDiff.IUniDiff[] | |
| * | |
| * @returns Array of FilePatch objects, one for each file | |
| */ | |
| function parseUniDiffs(diffOutput: jsDiff.IUniDiff[]): FilePatch[] { | |
| const filePatches: FilePatch[] = []; | |
| diffOutput.forEach((uniDiff: jsDiff.IUniDiff) => { | |
| let edit: Edit; | |
| const edits: Edit[] = []; | |
| uniDiff.hunks.forEach((hunk: jsDiff.IHunk) => { | |
| let startLine = hunk.oldStart; | |
| hunk.lines.forEach((line) => { | |
| switch (line.substr(0, 1)) { | |
| case '-': | |
| edit = new Edit(EditTypes.EDIT_DELETE, new Position(startLine - 1, 0)); | |
| edit.end = new Position(startLine, 0); | |
| edits.push(edit); | |
| startLine++; | |
| break; | |
| case '+': | |
| edit = new Edit(EditTypes.EDIT_INSERT, new Position(startLine - 1, 0)); | |
| edit.text += line.substr(1) + '\n'; | |
| edits.push(edit); | |
| break; | |
| case ' ': | |
| startLine++; | |
| break; | |
| } | |
| }); | |
| }); | |
| const fileName = uniDiff.oldFileName; | |
| filePatches.push({ fileName, edits }); | |
| }); | |
| return filePatches; | |
| } | |
| /** | |
| * Returns a FilePatch object by generating diffs between given oldStr and newStr using the diff module | |
| * | |
| * @param fileName string: Name of the file to which edits should be applied | |
| * @param oldStr string | |
| * @param newStr string | |
| * | |
| * @returns A single FilePatch object | |
| */ | |
| export function getEdits(fileName: string, oldStr: string, newStr: string): FilePatch { | |
| if (process.platform === 'win32') { | |
| oldStr = oldStr.split('\r\n').join('\n'); | |
| newStr = newStr.split('\r\n').join('\n'); | |
| } | |
| const unifiedDiffs: jsDiff.IUniDiff = jsDiff.structuredPatch(fileName, fileName, oldStr, newStr, '', ''); | |
| const filePatches: FilePatch[] = parseUniDiffs([unifiedDiffs]); | |
| return filePatches[0]; | |
| } | |
| /** | |
| * Uses diff module to parse given diff string and returns edits for files | |
| * | |
| * @param diffStr : Diff string in unified format. | |
| * http://www.gnu.org/software/diffutils/manual/diffutils.html#Unified-Format | |
| * | |
| * @returns Array of FilePatch objects, one for each file | |
| */ | |
| export function getEditsFromUnifiedDiffStr(diffstr: string): FilePatch[] { | |
| const unifiedDiffs: jsDiff.IUniDiff[] = jsDiff.parsePatch(diffstr); | |
| const filePatches: FilePatch[] = parseUniDiffs(unifiedDiffs); | |
| return filePatches; | |
| } |