Skip to content

Commit

Permalink
Add index property to Transform
Browse files Browse the repository at this point in the history
This makes it possible to know which line is being transformed.

Fix: #611
  • Loading branch information
isaacs committed Aug 25, 2023
1 parent 968466f commit 7b89a7c
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 37 deletions.
34 changes: 33 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -1373,7 +1373,33 @@ render(<Example />);

Since `transform` function converts all characters to upper case, final output that's rendered to the terminal will be "HELLO WORLD", not "Hello World".

#### transform(children)
When the output wraps to multiple lines, it can be helpful to know which line is being processed.

For example, to implement a hanging indent component, you can indent all the lines except for the first.

```jsx
import {render, Transform} from 'ink';

const HangingIndent = ({ content, indent = 4, children, ...props }) => (
<Transform transform={(line, index) =>
index === 0 ? line : (' '.repeat(indent) + line)} {...props}>
{children}
</Transform>
)

const text =
'WHEN I WROTE the following pages, or rather the bulk of them, ' +
'I lived alone, in the woods, a mile from any neighbor, in a ' +
'house which I had built myself, on the shore of Walden Pond, ' +
'in Concord, Massachusetts, and earned my living by the labor ' +
'of my hands only. I lived there two years and two months. At ' +
'present I am a sojourner in civilized life again.'

// Other text properties allowed as well
render(<HangingIndent bold dimColor indent={4}>{text}</HangingIndent>)
```

#### transform(outputLine, index)

Type: `Function`

Expand All @@ -1386,6 +1412,12 @@ Type: `string`

Output of child components.

##### index

Type: `number`

The zero-indexed line number of the line currently being transformed.

## Hooks

### useInput(inputHandler, options?)
Expand Down
2 changes: 1 addition & 1 deletion src/components/Transform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export type Props = {
/**
* Function which transforms children output. It accepts children and must return transformed children too.
*/
readonly transform: (children: string) => string;
readonly transform: (children: string, index: number) => string;

readonly children?: ReactNode;
};
Expand Down
2 changes: 1 addition & 1 deletion src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ declare namespace Ink {
style?: Styles;

// eslint-disable-next-line @typescript-eslint/naming-convention
internal_transform?: (children: string) => string;
internal_transform?: (children: string, index: number) => string;
};
}
6 changes: 4 additions & 2 deletions src/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,9 @@ export default class Output {

let offsetY = 0;

for (let line of lines) {
for (let index = 0; index < lines.length; index++) {

Check failure on line 190 in src/output.ts

View workflow job for this annotation

GitHub Actions / Node.js 14

Use a `for-of` loop instead of this `for` loop.

Check failure on line 190 in src/output.ts

View workflow job for this annotation

GitHub Actions / Node.js 16

Use a `for-of` loop instead of this `for` loop.

Check failure on line 190 in src/output.ts

View workflow job for this annotation

GitHub Actions / Node.js 18

Use a `for-of` loop instead of this `for` loop.
let line = lines[index];
if (line === undefined) continue;
const currentLine = output[y + offsetY];

// Line can be missing if `text` is taller than height of pre-initialized `this.output`
Expand All @@ -196,7 +198,7 @@ export default class Output {
}

for (const transformer of transformers) {
line = transformer(line);
line = transformer(line, index);
}

const characters = styledCharsFromTokens(tokenize(line));
Expand Down
2 changes: 1 addition & 1 deletion src/render-node-to-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const applyPaddingToText = (node: DOMElement, text: string): string => {
return text;
};

export type OutputTransformer = (s: string) => string;
export type OutputTransformer = (s: string, index: number) => string;

// After nodes are laid out, render each to output object, which later gets rendered to terminal
const renderNodeToOutput = (
Expand Down
44 changes: 22 additions & 22 deletions src/squash-text-nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,32 @@ import {type DOMElement} from './dom.js';
const squashTextNodes = (node: DOMElement): string => {
let text = '';

if (node.childNodes.length > 0) {
for (const childNode of node.childNodes) {
let nodeText = '';
for (let index = 0; index < node.childNodes.length; index++) {
const childNode = node.childNodes[index];
if (childNode === undefined) continue;
let nodeText = '';

if (childNode.nodeName === '#text') {
nodeText = childNode.nodeValue;
} else {
if (
childNode.nodeName === 'ink-text' ||
childNode.nodeName === 'ink-virtual-text'
) {
nodeText = squashTextNodes(childNode);
}

// Since these text nodes are being concatenated, `Output` instance won't be able to
// apply children transform, so we have to do it manually here for each text node
if (
nodeText.length > 0 &&
typeof childNode.internal_transform === 'function'
) {
nodeText = childNode.internal_transform(nodeText);
}
if (childNode.nodeName === '#text') {
nodeText = childNode.nodeValue;
} else {
if (
childNode.nodeName === 'ink-text' ||
childNode.nodeName === 'ink-virtual-text'
) {
nodeText = squashTextNodes(childNode);
}

text += nodeText;
// Since these text nodes are being concatenated, `Output` instance won't be able to
// apply children transform, so we have to do it manually here for each text node
if (
nodeText.length > 0 &&
typeof childNode.internal_transform === 'function'
) {
nodeText = childNode.internal_transform(nodeText, index);
}
}

text += nodeText;
}

return text;
Expand Down
30 changes: 21 additions & 9 deletions test/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,46 +253,58 @@ test('fragment', t => {

test('transform children', t => {
const output = renderToString(
<Transform transform={(string: string) => `[${string}]`}>
<Transform transform={(string: string, index: number) => `[${index}: ${string}]`}>

Check failure on line 256 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 14

Replace `·transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}` with `⏎↹↹↹transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}⏎↹↹`

Check failure on line 256 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 16

Replace `·transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}` with `⏎↹↹↹transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}⏎↹↹`

Check failure on line 256 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 18

Replace `·transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}` with `⏎↹↹↹transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}⏎↹↹`
<Text>
<Transform transform={(string: string) => `{${string}}`}>
<Transform transform={(string: string, index: number) => `{${index}: ${string}}`}>

Check failure on line 258 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 14

Replace `·transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}` with `⏎↹↹↹↹↹transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}⏎↹↹↹↹`

Check failure on line 258 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 16

Replace `·transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}` with `⏎↹↹↹↹↹transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}⏎↹↹↹↹`

Check failure on line 258 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 18

Replace `·transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}` with `⏎↹↹↹↹↹transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}⏎↹↹↹↹`
<Text>test</Text>
</Transform>
</Text>
</Transform>
);

t.is(output, '[{test}]');
t.is(output, '[0: {0: test}]');
});

test('squash multiple text nodes', t => {
const output = renderToString(
<Transform transform={(string: string) => `[${string}]`}>
<Transform transform={(string: string, index: number) => `[${index}: ${string}]`}>

Check failure on line 270 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 14

Replace `·transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}` with `⏎↹↹↹transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}⏎↹↹`

Check failure on line 270 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 16

Replace `·transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}` with `⏎↹↹↹transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}⏎↹↹`

Check failure on line 270 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 18

Replace `·transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}` with `⏎↹↹↹transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}⏎↹↹`
<Text>
<Transform transform={(string: string) => `{${string}}`}>
<Transform transform={(string: string, index: number) => `{${index}: ${string}}`}>

Check failure on line 272 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 14

Replace `·transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}` with `⏎↹↹↹↹↹transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}⏎↹↹↹↹`

Check failure on line 272 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 16

Replace `·transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}` with `⏎↹↹↹↹↹transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}⏎↹↹↹↹`

Check failure on line 272 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 18

Replace `·transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}` with `⏎↹↹↹↹↹transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}⏎↹↹↹↹`
{/* prettier-ignore */}
<Text>hello{' '}world</Text>
</Transform>
</Text>
</Transform>
);

t.is(output, '[{hello world}]');
t.is(output, '[0: {0: hello world}]');
});

test('transform with multiple lines', t => {
const output = renderToString(
<Transform transform={(string: string, index: number) => `[${index}: ${string}]`}>

Check failure on line 285 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 14

Replace `·transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}` with `⏎↹↹↹transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}⏎↹↹`

Check failure on line 285 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 16

Replace `·transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}` with `⏎↹↹↹transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}⏎↹↹`

Check failure on line 285 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 18

Replace `·transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}` with `⏎↹↹↹transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}⏎↹↹`
{/* prettier-ignore */}
<Text>hello{' '}world{'\n'}goodbye{' '}world</Text>
</Transform>
);

t.is(output, '[0: hello world]\n[1: goodbye world]');
});


Check failure on line 294 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 14

Delete `⏎`

Check failure on line 294 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 16

Delete `⏎`

Check failure on line 294 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 18

Delete `⏎`
test('squash multiple nested text nodes', t => {
const output = renderToString(
<Transform transform={(string: string) => `[${string}]`}>
<Transform transform={(string: string, index: number) => `[${index}: ${string}]`}>

Check failure on line 297 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 14

Replace `·transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}` with `⏎↹↹↹transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}⏎↹↹`

Check failure on line 297 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 16

Replace `·transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}` with `⏎↹↹↹transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}⏎↹↹`

Check failure on line 297 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 18

Replace `·transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}` with `⏎↹↹↹transform={(string:·string,·index:·number)·=>·`[${index}:·${string}]`}⏎↹↹`
<Text>
<Transform transform={(string: string) => `{${string}}`}>
<Transform transform={(string: string, index: number) => `{${index}: ${string}}`}>

Check failure on line 299 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 14

Replace `·transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}` with `⏎↹↹↹↹↹transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}⏎↹↹↹↹`

Check failure on line 299 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 16

Replace `·transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}` with `⏎↹↹↹↹↹transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}⏎↹↹↹↹`

Check failure on line 299 in test/components.tsx

View workflow job for this annotation

GitHub Actions / Node.js 18

Replace `·transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}` with `⏎↹↹↹↹↹transform={(string:·string,·index:·number)·=>·`{${index}:·${string}}`}⏎↹↹↹↹`
hello
<Text> world</Text>
</Transform>
</Text>
</Transform>
);

t.is(output, '[{hello world}]');
t.is(output, '[0: {0: hello world}]');
});

test('squash empty `<Text>` nodes', t => {
Expand Down

0 comments on commit 7b89a7c

Please sign in to comment.