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

Add index property to Transform #619

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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 @@

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.
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved
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 @@
}

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('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
Loading