Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

On Line Text Click Enhancement #71

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions lib/compute-lines.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export declare enum DiffType {
DEFAULT = 0,
ADDED = 1,
REMOVED = 2
}
export declare enum DiffMethod {
CHARS = "diffChars",
WORDS = "diffWords",
WORDS_WITH_SPACE = "diffWordsWithSpace",
LINES = "diffLines",
TRIMMED_LINES = "diffTrimmedLines",
SENTENCES = "diffSentences",
CSS = "diffCss"
}
export interface DiffInformation {
value?: string | DiffInformation[];
lineNumber?: number;
type?: DiffType;
}
export interface LineInformation {
left?: DiffInformation;
right?: DiffInformation;
}
export interface ComputedLineInformation {
lineInformation: LineInformation[];
diffLines: number[];
}
export interface ComputedDiffInformation {
left?: DiffInformation[];
right?: DiffInformation[];
}
export interface JsDiffChangeObject {
added?: boolean;
removed?: boolean;
value?: string;
}
/**
* [TODO]: Think about moving common left and right value assignment to a
* common place. Better readability?
*
* Computes line wise information based in the js diff information passed. Each
* line contains information about left and right section. Left side denotes
* deletion and right side denotes addition.
*
* @param oldString Old string to compare.
* @param newString New string to compare with old string.
* @param disableWordDiff Flag to enable/disable word diff.
* @param compareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api
*/
declare const computeLineInformation: (oldString: string, newString: string, disableWordDiff?: boolean, compareMethod?: string) => ComputedLineInformation;
export { computeLineInformation };
191 changes: 191 additions & 0 deletions lib/compute-lines.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const diff = require("diff");
const jsDiff = diff;
var DiffType;
(function (DiffType) {
DiffType[DiffType["DEFAULT"] = 0] = "DEFAULT";
DiffType[DiffType["ADDED"] = 1] = "ADDED";
DiffType[DiffType["REMOVED"] = 2] = "REMOVED";
})(DiffType = exports.DiffType || (exports.DiffType = {}));
// See https://github.com/kpdecker/jsdiff/tree/v4.0.1#api for more info on the below JsDiff methods
var DiffMethod;
(function (DiffMethod) {
DiffMethod["CHARS"] = "diffChars";
DiffMethod["WORDS"] = "diffWords";
DiffMethod["WORDS_WITH_SPACE"] = "diffWordsWithSpace";
DiffMethod["LINES"] = "diffLines";
DiffMethod["TRIMMED_LINES"] = "diffTrimmedLines";
DiffMethod["SENTENCES"] = "diffSentences";
DiffMethod["CSS"] = "diffCss";
})(DiffMethod = exports.DiffMethod || (exports.DiffMethod = {}));
/**
* Splits diff text by new line and computes final list of diff lines based on
* conditions.
*
* @param value Diff text from the js diff module.
*/
const constructLines = (value) => {
const lines = value.split('\n');
const isAllEmpty = lines.every((val) => !val);
if (isAllEmpty) {
// This is to avoid added an extra new line in the UI.
if (lines.length === 2) {
return [];
}
lines.pop();
return lines;
}
const lastLine = lines[lines.length - 1];
const firstLine = lines[0];
// Remove the first and last element if they are new line character. This is
// to avoid addition of extra new line in the UI.
if (!lastLine) {
lines.pop();
}
if (!firstLine) {
lines.shift();
}
return lines;
};
/**
* Computes word diff information in the line.
* [TODO]: Consider adding options argument for JsDiff text block comparison
*
* @param oldValue Old word in the line.
* @param newValue New word in the line.
* @param compareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api
*/
const computeDiff = (oldValue, newValue, compareMethod = DiffMethod.CHARS) => {
const diffArray = jsDiff[compareMethod](oldValue, newValue);
const computedDiff = {
left: [],
right: [],
};
diffArray
.forEach(({ added, removed, value }) => {
const diffInformation = {};
if (added) {
diffInformation.type = DiffType.ADDED;
diffInformation.value = value;
computedDiff.right.push(diffInformation);
}
if (removed) {
diffInformation.type = DiffType.REMOVED;
diffInformation.value = value;
computedDiff.left.push(diffInformation);
}
if (!removed && !added) {
diffInformation.type = DiffType.DEFAULT;
diffInformation.value = value;
computedDiff.right.push(diffInformation);
computedDiff.left.push(diffInformation);
}
return diffInformation;
});
return computedDiff;
};
/**
* [TODO]: Think about moving common left and right value assignment to a
* common place. Better readability?
*
* Computes line wise information based in the js diff information passed. Each
* line contains information about left and right section. Left side denotes
* deletion and right side denotes addition.
*
* @param oldString Old string to compare.
* @param newString New string to compare with old string.
* @param disableWordDiff Flag to enable/disable word diff.
* @param compareMethod JsDiff text diff method from https://github.com/kpdecker/jsdiff/tree/v4.0.1#api
*/
const computeLineInformation = (oldString, newString, disableWordDiff = false, compareMethod = DiffMethod.CHARS) => {
const diffArray = diff.diffLines(oldString.trimRight(), newString.trimRight(), {
newlineIsToken: true,
ignoreWhitespace: false,
ignoreCase: false,
});
let rightLineNumber = 0;
let leftLineNumber = 0;
let lineInformation = [];
let counter = 0;
const diffLines = [];
const ignoreDiffIndexes = [];
const getLineInformation = (value, diffIndex, added, removed, evaluateOnlyFirstLine) => {
const lines = constructLines(value);
return lines.map((line, lineIndex) => {
const left = {};
const right = {};
if (ignoreDiffIndexes.includes(`${diffIndex}-${lineIndex}`)
|| (evaluateOnlyFirstLine && lineIndex !== 0)) {
return undefined;
}
if (added || removed) {
if (!diffLines.includes(counter)) {
diffLines.push(counter);
}
if (removed) {
leftLineNumber += 1;
left.lineNumber = leftLineNumber;
left.type = DiffType.REMOVED;
left.value = line || ' ';
// When the current line is of type REMOVED, check the next item in
// the diff array whether it is of type ADDED. If true, the current
// diff will be marked as both REMOVED and ADDED. Meaning, the
// current line is a modification.
const nextDiff = diffArray[diffIndex + 1];
if (nextDiff && nextDiff.added) {
const nextDiffLines = constructLines(nextDiff.value)[lineIndex];
if (nextDiffLines) {
const { value: rightValue, lineNumber, type, } = getLineInformation(nextDiff.value, diffIndex, true, false, true)[0].right;
// When identified as modification, push the next diff to ignore
// list as the next value will be added in this line computation as
// right and left values.
ignoreDiffIndexes.push(`${diffIndex + 1}-${lineIndex}`);
right.lineNumber = lineNumber;
right.type = type;
// Do word level diff and assign the corresponding values to the
// left and right diff information object.
if (disableWordDiff) {
right.value = rightValue;
}
else {
const computedDiff = computeDiff(line, rightValue, compareMethod);
right.value = computedDiff.right;
left.value = computedDiff.left;
}
}
}
}
else {
rightLineNumber += 1;
right.lineNumber = rightLineNumber;
right.type = DiffType.ADDED;
right.value = line;
}
}
else {
leftLineNumber += 1;
rightLineNumber += 1;
left.lineNumber = leftLineNumber;
left.type = DiffType.DEFAULT;
left.value = line;
right.lineNumber = rightLineNumber;
right.type = DiffType.DEFAULT;
right.value = line;
}
counter += 1;
return { right, left };
}).filter(Boolean);
};
diffArray
.forEach(({ added, removed, value }, index) => {
lineInformation = [
...lineInformation,
...getLineInformation(value, index, added, removed),
];
});
return {
lineInformation, diffLines,
};
};
exports.computeLineInformation = computeLineInformation;
141 changes: 141 additions & 0 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
import { LineInformation, DiffMethod } from './compute-lines';
import { ReactDiffViewerStylesOverride } from './styles';
export declare enum LineNumberPrefix {
LEFT = "L",
RIGHT = "R"
}
export interface ReactDiffViewerProps {
oldValue: string;
newValue: string;
splitView?: boolean;
disableWordDiff?: boolean;
compareMethod?: DiffMethod;
extraLinesSurroundingDiff?: number;
hideLineNumbers?: boolean;
showDiffOnly?: boolean;
renderContent?: (source: string) => JSX.Element;
codeFoldMessageRenderer?: (totalFoldedLines: number, leftStartLineNumber: number, rightStartLineNumber: number) => JSX.Element;
onLineNumberClick?: (lineId: string, event: React.MouseEvent<HTMLTableCellElement>) => void;
onLineContentClick?: (params: {
direction: 'leftCode' | 'rightCode';
words: string;
}) => void;
highlightLines?: string[];
styles?: ReactDiffViewerStylesOverride;
useDarkTheme?: boolean;
leftTitle?: string | JSX.Element;
rightTitle?: string | JSX.Element;
}
export interface ReactDiffViewerState {
expandedBlocks?: number[];
}
declare class DiffViewer extends React.Component<ReactDiffViewerProps, ReactDiffViewerState> {
private styles;
static defaultProps: ReactDiffViewerProps;
static propTypes: {
oldValue: PropTypes.Validator<string>;
newValue: PropTypes.Validator<string>;
splitView: PropTypes.Requireable<boolean>;
disableWordDiff: PropTypes.Requireable<boolean>;
compareMethod: PropTypes.Requireable<any>;
renderContent: PropTypes.Requireable<(...args: any[]) => any>;
onLineNumberClick: PropTypes.Requireable<(...args: any[]) => any>;
extraLinesSurroundingDiff: PropTypes.Requireable<number>;
styles: PropTypes.Requireable<object>;
hideLineNumbers: PropTypes.Requireable<boolean>;
showDiffOnly: PropTypes.Requireable<boolean>;
highlightLines: PropTypes.Requireable<string[]>;
leftTitle: PropTypes.Requireable<string | PropTypes.ReactElementLike>;
rightTitle: PropTypes.Requireable<string | PropTypes.ReactElementLike>;
};
constructor(props: ReactDiffViewerProps);
/**
* Resets code block expand to the initial stage. Will be exposed to the parent component via
* refs.
*/
resetCodeBlocks: () => boolean;
/**
* Pushes the target expanded code block to the state. During the re-render,
* this value is used to expand/fold unmodified code.
*/
private onBlockExpand;
/**
* Computes final styles for the diff viewer. It combines the default styles with the user
* supplied overrides. The computed styles are cached with performance in mind.
*
* @param styles User supplied style overrides.
*/
private computeStyles;
/**
* Returns a function with clicked line number in the closure. Returns an no-op function when no
* onLineNumberClick handler is supplied.
*
* @param id Line id of a line.
*/
private onLineNumberClickProxy;
private onLineContentClickProxy;
/**
* Maps over the word diff and constructs the required React elements to show word diff.
*
* @param diffArray Word diff information derived from line information.
* @param renderer Optional renderer to format diff words. Useful for syntax highlighting.
*/
private renderWordDiff;
/**
* Maps over the line diff and constructs the required react elements to show line diff. It calls
* renderWordDiff when encountering word diff. This takes care of both inline and split view line
* renders.
*
* @param lineNumber Line number of the current line.
* @param type Type of diff of the current line.
* @param prefix Unique id to prefix with the line numbers.
* @param value Content of the line. It can be a string or a word diff array.
* @param additionalLineNumber Additional line number to be shown. Useful for rendering inline
* diff view. Right line number will be passed as additionalLineNumber.
* @param additionalPrefix Similar to prefix but for additional line number.
*/
private renderLine;
/**
* Generates lines for split view.
*
* @param obj Line diff information.
* @param obj.left Life diff information for the left pane of the split view.
* @param obj.right Life diff information for the right pane of the split view.
* @param index React key for the lines.
*/
private renderSplitView;
/**
* Generates lines for inline view.
*
* @param obj Line diff information.
* @param obj.left Life diff information for the added section of the inline view.
* @param obj.right Life diff information for the removed section of the inline view.
* @param index React key for the lines.
*/
renderInlineView: ({ left, right }: LineInformation, index: number) => JSX.Element;
/**
* Returns a function with clicked block number in the closure.
*
* @param id Cold fold block id.
*/
private onBlockClickProxy;
/**
* Generates cold fold block. It also uses the custom message renderer when available to show
* cold fold messages.
*
* @param num Number of skipped lines between two blocks.
* @param blockNumber Code fold block id.
* @param leftBlockLineNumber First left line number after the current code fold block.
* @param rightBlockLineNumber First right line number after the current code fold block.
*/
private renderSkippedLineIndicator;
/**
* Generates the entire diff view.
*/
private renderDiff;
render: () => JSX.Element;
}
export default DiffViewer;
export { ReactDiffViewerStylesOverride, DiffMethod };
Loading