diff --git a/package-lock.json b/package-lock.json index d08fbbc..5a433d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@types/node": "^20.5.0", "@types/vscode": "^1.79.0", "@vscode/test-electron": "^2.4.1", + "eastasianwidth": "^0.3.0", "glob": "^8.1.0", "minimist": "^1.2.7", "mkdirp": "^1.0.4", @@ -508,10 +509,11 @@ } }, "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.3.0.tgz", + "integrity": "sha512-JqasYqGO32J2c91uYKdhu1vNmXGADaLB7OOgjAhjMvpjdvGb0tsYcuwn381MwqCg4YBQDtByQcNlFYuv2kmOug==", + "dev": true, + "license": "MIT" }, "node_modules/emoji-regex": { "version": "10.4.0", @@ -1361,6 +1363,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width/node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -2196,9 +2205,9 @@ "dev": true }, "eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.3.0.tgz", + "integrity": "sha512-JqasYqGO32J2c91uYKdhu1vNmXGADaLB7OOgjAhjMvpjdvGb0tsYcuwn381MwqCg4YBQDtByQcNlFYuv2kmOug==", "dev": true }, "emoji-regex": { @@ -2802,6 +2811,14 @@ "eastasianwidth": "^0.2.0", "emoji-regex": "^10.2.1", "strip-ansi": "^7.0.1" + }, + "dependencies": { + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + } } }, "strip-ansi": { diff --git a/package.json b/package.json index 8543cb0..da44bb7 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "@types/mocha": "^10.0.1", "@types/node": "^20.5.0", "@types/vscode": "^1.79.0", + "eastasianwidth": "^0.3.0", "glob": "^8.1.0", "minimist": "^1.2.7", "mkdirp": "^1.0.4", @@ -93,4 +94,4 @@ "@vscode/test-electron": "^2.4.1" }, "dependencies": {} -} \ No newline at end of file +} diff --git a/src/TableWriter.ts b/src/TableWriter.ts index 8ca559e..ea044ae 100644 --- a/src/TableWriter.ts +++ b/src/TableWriter.ts @@ -1,6 +1,7 @@ import { isNumber } from 'util'; import CsvColumn from './CsvColumn'; import CsvRecord from './CsvRecord'; +import * as eaw from 'eastasianwidth'; /** * Class resposible for rendering a provided list of CsvRecord's into an ASCII table @@ -109,8 +110,9 @@ export default class TableWriter // Calculate left and right padding const isRightAligned = rightAlignNumbers && this.isNumberValue(value); - const leftPaddingLength = useValuePadding ? (isRightAligned ? maxLen - value.length - 1 : 1) : 0; - const rightPaddingLength = isRightAligned ? 0 : maxLen - (ValuePadding.length * 2) - value.length; + const stringWidth = eaw.length(value); + const leftPaddingLength = useValuePadding ? (isRightAligned ? maxLen - stringWidth - 1 : 1) : 0; + const rightPaddingLength = isRightAligned ? 0 : maxLen - (ValuePadding.length * 2) - stringWidth; // Start with column separator? if (i === 0) { @@ -147,8 +149,8 @@ export default class TableWriter const record = records[i]; const columns = record.getColumns(); - for(var colIndex = 0; colIndex < columns.length; colIndex++) { - const len = columns[colIndex].getValue().length; + for (var colIndex = 0; colIndex < columns.length; colIndex++) { + const len = eaw.length(columns[colIndex].getValue()); if (columnLengths[colIndex] === undefined || len > columnLengths[colIndex]) { columnLengths[colIndex] = len; @@ -169,7 +171,7 @@ export default class TableWriter return false; // Look for number-like characters - for(let i = 0; i < value.length; i++) { + for (let i = 0; i < value.length; i++) { const char = value[i]; // Check for number signs diff --git a/src/test/suite/extension.test.ts b/src/test/suite/extension.test.ts index ae559bc..5585198 100644 --- a/src/test/suite/extension.test.ts +++ b/src/test/suite/extension.test.ts @@ -436,4 +436,32 @@ suite('Extension Test Suite', () => { assert.equal("c", columns2[0].getValue()); assert.equal("quoted", columns2[1].getValue()); }); + + test('The width of East Asian character is 2', () => { + // Arrange + const parser = new CsvParser(`\ +origin name,english name +메이플스토리,MapleStory +スーパーマリオ,Super Mario +League Of Legends,League Of Legends +`, ','); + const writer = new TableWriter(); + + // Act + const records = parser.getRecords(); + const result = writer.getFormattedTable(records, false, false, false); + + // Assert + assert.equal(` +|-------------------|-------------------| +| origin name | english name | +|-------------------|-------------------| +| 메이플스토리 | MapleStory | +|-------------------|-------------------| +| スーパーマリオ | Super Mario | +|-------------------|-------------------| +| League Of Legends | League Of Legends | +|-------------------|-------------------| +`.trim(), result.replace(/\r?\n/g, "\n").trim()); + }); });