@ryusei/light
Version:
<div align="center"> <a href="https://light.ryuseijs.com"> <img alt="RyuseiLight" src="https://light.ryuseijs.com/images/svg/logo.svg" width="70"> </a>
150 lines (126 loc) • 3.63 kB
text/typescript
import { CATEGORY_SPACE } from '../../constants/categories';
import { LINE_BREAK } from '../../constants/characters';
import { PROJECT_CODE_SHORT } from '../../constants/project';
import { Renderer } from '../../core/Renderer/Renderer';
import { Token } from '../../types';
import { assign, isObject, startsWith } from '../../utils';
/**
* The class name for added lines.
*
* @private
* @since 0.0.17
*/
export const CLASS_ADDED = 'is-added';
/**
* The class name for deleted lines.
*
* @private
* @since 0.0.17
*/
export const CLASS_DELETED = 'is-deleted';
/**
* The class name for deleted lines.
*
* @private
* @since 0.0.17
*/
export const CLASS_DIFF = `${ PROJECT_CODE_SHORT }__diff`;
/**
* Default options for the Diff component.
*
* @since 0.0.17
*/
const DEFAULT_OPTIONS = {
addedSymbol : '+',
deletedSymbol: '-',
};
/**
* The component for highlighting added/deleted lines.
*
* @since 0.0.17
*/
export function Diff( { event, lines, options }: Renderer ): void {
if ( ! options.diff ) {
return;
}
options.gutter = true;
const diffOptions = assign( {}, DEFAULT_OPTIONS, isObject( options.diff ) ? options.diff : null );
const added: number[] = [];
const deleted: number[] = [];
lines.forEach( ( tokens, index ) => {
if ( tokens.length ) {
const text = tokens[ 0 ][ 1 ];
let processed;
if ( startsWith( text, diffOptions.addedSymbol ) ) {
added.push( index );
processed = true;
} else if ( startsWith( text, diffOptions.deletedSymbol ) ) {
deleted.push( index );
processed = true;
}
if ( processed ) {
convertSymbols( diffOptions.removeSymbols, tokens );
}
}
} );
if ( ! added.length && ! deleted.length ) {
return;
}
event.on( 'line:open', ( append, classes, i ) => {
addClass( added, deleted, i, classes );
} );
event.on( 'gutter:row:open', ( append, classes, i ) => {
addClass( added, deleted, i, classes );
} );
event.on( 'gutter:row:opened', ( append, i ) => {
let content = LINE_BREAK;
if ( added.indexOf( i ) > -1 ) {
content = diffOptions.addedSymbol;
} else if ( deleted.indexOf( i ) > -1 ) {
content = diffOptions.deletedSymbol;
}
append( `<span class="${ CLASS_DIFF }">${ content }</span>` );
}, 20 );
event.on( 'lineNumber:open', ( append, classes, i, data ) => {
data.skip = deleted.indexOf( i ) > -1;
} );
}
/**
* Adds a status class according to the added or deleted lines.
*
* @param added - An array with added line indices.
* @param deleted - An array with deleted line indices.
* @param index - A line index.
* @param classes - An array with line classes.
*/
function addClass( added: number[], deleted: number[], index: number, classes: string[] ): void {
if ( added.indexOf( index ) > -1 ) {
classes.push( CLASS_ADDED );
} else if ( deleted.indexOf( index ) > -1 ) {
classes.push( CLASS_DELETED );
}
}
/**
* Converts +/- symbols to spaces or removes them.
*
* @param remove - Whether to remove symbols or not.
* @param tokens - Target tokens.
*/
function convertSymbols( remove: boolean, tokens: Token[] ): void {
const [ category, text ] = tokens[ 0 ];
if ( remove ) {
if ( text.length === 1 ) {
tokens.shift();
} else {
tokens[ 0 ] = [ category, text.slice( 1 ) ];
}
} else {
const spaceToken: Token = [ CATEGORY_SPACE, ' ' ];
if ( text.length === 1 ) {
tokens[ 0 ] = spaceToken;
} else {
tokens[ 0 ] = [ category, text.slice( 1 ) ];
tokens.unshift( spaceToken );
}
}
}