Skip to content

Commit

Permalink
feat(indent): much better indent and flow handling, especially when c…
Browse files Browse the repository at this point in the history
…opying/moving

Will now try to maintain flow-ness when moving properties/elements in and out of sometimes empty
objects/arrays.
  • Loading branch information
grantila committed Apr 25, 2022
1 parent f0c5f1c commit 1143aa4
Show file tree
Hide file tree
Showing 6 changed files with 358 additions and 61 deletions.
34 changes: 22 additions & 12 deletions lib/document/document.ts
Expand Up @@ -13,7 +13,7 @@ import { type JsonDocumentOptions } from './types.js'
import { getDocumentOptions } from './utils.js'


export class JsonDocument extends Indentable
export class JsonDocument
{
public readonly options: JsonDocumentOptions;

Expand All @@ -24,8 +24,6 @@ export class JsonDocument extends Indentable
options?: Partial< JsonDocumentOptions >
)
{
super( );

this.options = getDocumentOptions( options );
}

Expand Down Expand Up @@ -63,14 +61,14 @@ export class JsonDocument extends Indentable

toString( ): string
{
const chooseTabs = this.#useTabs(
const tabs = this.#useTabs(
this.root instanceof Indentable
? this.root.tabs
: undefined
);

const rootIndent =
this.rootIndentation.indentString( chooseTabs );
this.rootIndentation.indentString( { tabs, fallback: false } );

if ( this.root instanceof JsonPrimitiveBase )
return rootIndent + this.root.raw;
Expand All @@ -80,28 +78,38 @@ export class JsonDocument extends Indentable
if ( node instanceof JsonPrimitiveBase )
return node.raw;

const indent = node.flow ? '' : node.indentString( chooseTabs );
const indent = node.calculatedFlow
? ''
: ( parentIndent + node.indentString( { tabs } ) );

if ( node instanceof JsonArray )
{
const ret = [ node.flow ? '[ ' : '[\n' ];
if ( node.elements.length === 0 )
return '[ ]';

const ret = [ node.calculatedFlow ? '[ ' : '[\n' ];

node.elements.forEach( ( element, i ) =>
{
ret.push( indent + stringify( element, indent ) );

if ( i < node.elements.length - 1 )
ret.push( node.flow ? ', ' : ',\n' );
ret.push( node.calculatedFlow ? ', ' : ',\n' );
else
ret.push( node.flow ? ' ' : `\n${parentIndent}` );
ret.push(
node.calculatedFlow ? ' ' : `\n${parentIndent}`
);
} );

ret.push( ']' );
return ret.join( '' );
}
else if ( node instanceof JsonObject )
{
const ret = [ node.flow ? '{ ' : '{\n' ];
if ( node.properties.length === 0 )
return '{ }';

const ret = [ node.calculatedFlow ? '{ ' : '{\n' ];

node.properties.forEach( ( prop, i ) =>
{
Expand All @@ -113,9 +121,11 @@ export class JsonDocument extends Indentable
);

if ( i < node.properties.length - 1 )
ret.push( node.flow ? ', ' : ',\n' );
ret.push( node.calculatedFlow ? ', ' : ',\n' );
else
ret.push( node.flow ? ' ' : `\n${parentIndent}` );
ret.push(
node.calculatedFlow ? ' ' : `\n${parentIndent}`
);
} );

ret.push( '}' );
Expand Down
107 changes: 99 additions & 8 deletions lib/document/indentable.ts
@@ -1,9 +1,74 @@
import { JsonValueBase } from './types-internal.js'


interface IndentStringOptions
{
tabs?: boolean;
fallback?: boolean;
}

export class Indentable
{
private _flow: boolean | undefined = false;

constructor( private _depth = -1, private _tabs?: boolean | undefined )
{
}

public getChildrenNodes( )
: ReadonlyArray< JsonValueBase & ( Indentable | { } ) >
{
return [ ];
}

/**
* Flow means a one-line object/array.
*
* Defaults to undefined, and is only set if set by
*/
get flow( )
{
return this._flow;
}

set flow( flow: boolean | undefined )
{
this._flow = flow;
}

/**
* Returns the flow, or if undefined, false if _any_ child node is false
* (i.e. comes from a source with a non-flow container). Fallbacks to true.
*/
get calculatedFlow( )
{
const recurseChildrenFlow = () =>
{
const recurse = ( node: Indentable ): false | undefined =>
{
const children = node.getChildrenNodes( );

for ( const child of children )
{
if ( child.sourceParentFlow === false )
return false;
}

for ( const child of children )
{
if ( child instanceof Indentable )
return recurse( child );
}

return undefined;
}

return recurse( this );
};

return recurseChildrenFlow( ) ?? this.flow ?? true;
}

/**
* The indentation depth of this collection.
*
Expand All @@ -15,6 +80,18 @@ export class Indentable
return this._depth;
}

/**
* Get the depth, and if not set, fallback to default depth if chosen
*/
getDepth( fallback = true )
{
return ( this.depth === -1 && fallback )
? this.tabs
? 1
: 2
: this.depth;
}

/**
* Whether tabs or spaces are used.
*
Expand All @@ -30,15 +107,15 @@ export class Indentable
return this.tabs ? '\t' : ' ';
}

setIndent( depth: number, tabs: boolean ): void;
setIndent( depth: number, tabs?: boolean ): void;
setIndent( from: Indentable ): void;

setIndent( depth: number | Indentable, tabs?: boolean )
{
if ( typeof depth === 'number' )
{
this._depth = depth;
this._tabs = tabs!;
this._tabs = tabs ?? this.tabs;
}
else
{
Expand All @@ -47,22 +124,36 @@ export class Indentable
}
}

/**
* Return the depth as if it was tabs or spaces
*/
depthAs( asTabs: boolean ): number
{
const tabs = this.tabs ?? false;

return asTabs === tabs
? this.depth
: asTabs
? this.depth / 2
: this.depth * 2;
}

/**
* Gets the indentation string given the indentable settings.
*
* If `tabs` is set to true or false, this will overwrite the settings in
* this indentable, and change tabs into spaces or vice versa.
*/
indentString( tabs?: boolean )
indentString( options?: IndentStringOptions )
{
if ( this.depth <= 0 )
return '';
const { tabs, fallback = true } = options ?? { };
const curDepth = Math.max( 0, this.getDepth( fallback ) );

const char = tabs === true ? '\t' : tabs === false ? ' ' : this.char;
const depth =
( tabs === undefined || !!tabs === this.tabs )
? this.depth
: tabs === true ? this.depth / 2 : this.depth * 2;
( tabs === undefined || !tabs === !this.tabs )
? curDepth
: tabs === true ? curDepth / 2 : curDepth * 2;

return char.repeat( depth );
}
Expand Down

0 comments on commit 1143aa4

Please sign in to comment.