Skip to content

Commit

Permalink
fix: correctly handle arbitrary unicode character widths
Browse files Browse the repository at this point in the history
This improves support for emojis and non-latin
languages. I've had to use an older version of
`string-width`, since anything more recent uses
ESM and we're not yet compatible with that (see

Closes maticzav#249
  • Loading branch information
Kenneth-Sills committed Aug 6, 2024
1 parent a281c17 commit a88bca5
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 4 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"component"
],
"dependencies": {
"object-hash": "^2.0.3"
"object-hash": "^2.0.3",
"string-width": "^4.2.3"
},
"devDependencies": {
"@types/jest": "26.0.24",
Expand Down
7 changes: 4 additions & 3 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react'
import { Box, Text } from 'ink'
import { sha1 } from 'object-hash'
import stringWidth from 'string-width'

/* Table */

Expand Down Expand Up @@ -86,13 +87,13 @@ export default class Table<T extends ScalarDict> extends React.Component<
const { columns, padding } = this.getConfig()

const widths: Column<T>[] = columns.map((key) => {
const header = String(key).length
const header = stringWidth(String(key))
/* Get the width of each cell in the column */
const data = this.props.data.map((data) => {
const value = data[key]

if (value == undefined || value == null) return 0
return String(value).length
return stringWidth(String(value))
})

const width = Math.max(...data, header) + padding * 2
Expand Down Expand Up @@ -314,7 +315,7 @@ function row<T extends ScalarDict>(

// margins
const ml = config.padding
const mr = column.width - String(value).length - config.padding
const mr = column.width - stringWidth(String(value)) - config.padding

return (
/* prettier-ignore */
Expand Down
82 changes: 82 additions & 0 deletions tests/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from 'react'
import { Box, Text } from 'ink'
import { render } from 'ink-testing-library'

import stringWidth from 'string-width';

import Table, { Header, Skeleton, Cell } from '../src'

// Helpers -------------------------------------------------------------------
Expand Down Expand Up @@ -485,4 +487,84 @@ test('Renders table with custom skeleton.', () => {
expect(actual()).toBe(expected())
})

test('Renders table with wide characters.', () => {
const data = [
{ name: '全角', width: 4 },
{ name: 'ハンカク', width: 4 },
{ name: '😀', width: 2 },
]

const { lastFrame: actual } = render(<Table data={data} skeleton={Custom} />)

const { lastFrame: expected } = render(
<>
<Box>
{custom('┌')}
{custom('──────')}
{custom('┬')}
{custom('───────')}
{custom('┐')}
</Box>
<Box>
{custom('│')}
{header(' name ')}
{custom('│')}
{header(' width ')}
{custom('│')}
</Box>
<Box>
{custom('├')}
{custom('──────')}
{custom('┼')}
{custom('───────')}
{custom('┤')}
</Box>
<Box>
{custom('│')}
{cell(' 全角 ')}
{custom('│')}
{cell(' 4 ')}
{custom('│')}
</Box>
<Box>
{custom('├')}
{custom('──────')}
{custom('┼')}
{custom('───────')}
{custom('┤')}
</Box>
<Box>
{custom('│')}
{cell(' ハンカク ')}
{custom('│')}
{cell(' 4 ')}
{custom('│')}
</Box>
<Box>
{custom('├')}
{custom('──────')}
{custom('┼')}
{custom('───────')}
{custom('┤')}
</Box>
<Box>
{custom('│')}
{cell(' 😀 ')}
{custom('│')}
{cell(' 2 ')}
{custom('│')}
</Box>
<Box>
{custom('└')}
{custom('──────')}
{custom('┴')}
{custom('───────')}
{custom('┘')}
</Box>
</>,
)

expect(actual()).toBe(expected())
})

// ---------------------------------------------------------------------------
21 changes: 21 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,11 @@ ansi-regex@^5.0.0:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==

ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==

ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
Expand Down Expand Up @@ -3667,13 +3672,29 @@ string-width@^4.2.2:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"

string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"

strip-ansi@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
dependencies:
ansi-regex "^5.0.0"

strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"

strip-bom@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
Expand Down

0 comments on commit a88bca5

Please sign in to comment.