Considering tabs

Actual example

I wish a console layout like this:

 Name:  GPT          Source:  internet             Date: Today
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

where name, Source and Date are not fixed. That implies that also tilde line length is not fixed, too.

Based on a code like:

const text = `\n Name:\t${name}\t\tSource:\t${source}\t\tDate:\t${Date()}`;

The easiest way to add a tilde line would be:

const length = text.length; // length('actual' | 'developed')
let tilde = ' ';

for (let i = 0; i < length; i++) {
  tilde += '~'
}

Unfortunately, this cannot be done with the current implementation of length method. Any easy and quick workaround? Tia

PS: I do not even think about of making the mistake to ask for an extra feature or option for making length method more flexible!!!

If I understand correctly, the issue is that the tab character doesn't always render with the same width in a particular terminal? So you need some way of determining what the client has the tab's width configured to be, so you can line up other stuff (like the length of the tilde line) with that?

Is there any reason why the tab character has to be used here instead of spaces, which have a predictable width?

2 Likes

That's not something you can know at runtime, or outside of a browser environment.

@theScottyJam

If I understand correctly, ...

To be honest, I have had the sense/impression that \t (U+0009 CHARACTER TABULATION) is traditionally fixed and equals to 8 spaces. I have no idea whether \t implementation

  • varies on ES engines, terminals, languages, etc. or
  • can be set/changed by user in any of the above-mentioned cases

Is there any reason...

Practical reasons

Assuming var t = '________'; // 8 blank spaces

  • \t can be directly used within:
    • string like let example = '\t', or
    • template literal like let example = `\t`
  • variable t can only be used withing template literal like let example = `${t}`
  • \t character group length is 2; ${t} character group length is 4 ie 100% more!
  • \t and ${t} produce different result, like
var t = '        '; // 8 blank spaces

console.log('123456789012345678901234567890');
console.log('\t1\t10\t100\t1000');
console.log(`${t}1${t}10${t}100${t}1000`);

which prints out on terminal:

12345678901234567890123456789012345678901234567890
        1	    10      100     1000
        1        10        100        1000

\t obviously is more consistent and prettier for (numerical) layouts/printouts than ${t}.

Unless you know what platform you're targeting and how that platform behaves, it's futile to attempt to use the tab character to align items, due to the fact that different platforms can choose to interpret the width of the tab character differently (in some cases, it can be customized by the end-user as well). So, while the numbers look well-aligned when the platform uses a tab size of 8, it might not look so great if the tab size was 4 or 2. This isn't something JavaScript can fix either - this is universally how the tab character just works. There's no reasonable way to ask a terminal emulator to fix the size of a tab to 8 while a JavaScript program is running, and then go back to the user's default afterwards. It's also not possible for JavaScript to detect the platform's tab size, for example, the JavaScript output could be getting sent to a file and viewed in any editor after the program has run - how could you detect the configuration of the file viewer the user will use in the future?

In other words, you can think of the tab character as a fairly garbage character that fails to accomplish the one thing it was designed to do. Sometimes it gets used for other purposes, like indentation in code (where it's not an issue if tab sizes are different), but for the most part, I would just stay away from it as it's not going to do what you want.

As you mentioned, using a hard 8-spaces doesn't work for alignment either. This leaves us with two options:

  1. If you're just wanting to log output into a table in some sort of desent way, but don't care too much about the specific details of how it gets formatted, you can use console.table().

  2. If you do care about the specific details of how it gets formatted, you're going to have to hand-code the logic yourself to format it (customizability requires a bit more work). Luckily, JavaScript does provide some nice helper functions for this scenario, like string.padStart().

This example will visually output the same thing in my terminal, but "exhibit B" will use spaces, so that the output will always look the same on any platform, while "exhibit A" will have issues if it's viewed in other environments.

// exhibit A
console.log(
  ['1', '10', '100', '1000'].join('\t')
);

// exhibit B
console.log(
  ['1', '10', '100', '1000'].map(str => str.padEnd(8)).join('').trim()
)
1 Like

@theScottyJam

... I would just stay away from it as it's not going to do what you want.

I see. Thnxs!

... Luckily, JavaScript does provide some nice helper functions for this scenario, like string.padStart().

There are some practical concerns of using String.prototype.padStart() and String.prototype.padEnd():

  • \t works with numbers, too.
  • When above functions need to apply to numbers, they, at first, have to be converted into strings:
    • for integer: x.toString()
    • for floating point: x.toFixed()
  • If we use additional variables to store the strings, that roughly doubles the data used, which in turn, doubles the memory used.
  • If we decide to convert the numbers in line, then the line code "clutters" by using x.to...().pad...() all the time.

For a printout like the following, using x.to...().pad...() is neither very easy, nor elegant. Yes, after all easy-easy, step-by-step I will do my job but it could be easier.

I will dare to make the mistake to suggest two practical ways to improve that.

Important: Syntax is to get only an idea. So do NOT get stuck on that and start commenting about incompatibility issues with existing code!

\<n| & \>n| OR \sn| & \en| where n is a natural number; for example:

console.log('\<10|Hello!');
console.log('\s10|Hello!');

1234567890
....Hello!

console.log('\>10|Hello!');
console.log('\e10|Hello!');

1234567890
Hello!....

n defaults to 8 and it would be omitted. Hence, we may write:

\<| & \>| OR \s| & \e| or even simpler:

\< & \> OR \s & \e

The above suggestion should work with strings and numbers like \t does.

I wouldn't expect a large printout to be much harder - if it becomes repetitive, I would expect that the user just makes a helper function to help out, that satisfies whatever needs they have.

That said, there's certainly an argument to be made for the suggestion you gave. A number of languages provide a sort of "formatting sublanguage" you can use to concisely describe how you would like to display information. Here's some prior art for Python. In that language, I would be able to accomplish this same task by doing this:

>>> '{:>8}{:>8}{:>8}{:>8}'.format(1, 10, 100, 1000)
'       1      10     100    1000'

The {...} indicates that a value passed into .format(...) should be placed at that point in the string. >8 indicates that this value should right-aligned within 8 spaces. Python supports all sorts of other formatting options to help concisely describe what you're trying to do, like changing a number to a percent, adding a sign, rounding the decimal place, etc.

We could certainly consider adding a .format() function to JavaScript to handle this sort of use case. Maybe, for convenience, it could be a template tag instead, like this:

>>> String.format`${1}%>8${10}%>8${100}%>8${1000}%>8`
'       1      10     100    1000'

Where the % sign right after the %{} indicates I want to format the previous value, then I provide the format description I want to use, which is >8. Or something like that.

@theScottyJam Also, if possible, Number.format() direct function to avoid conversion to string eg String.format`${Number.toString()||toFixed()}%>8`

JavaScript's nature would be to auto-coerce non-string values to a string, which is why I was passing in numbers to the String.format template tag - they would be auto-coerced to strings before formatting is applied. Or, is this not what you mean? Could you maybe expound?

@theScottyJam Oh sorry! I did not pay attention that you directly passed numbers. A format function/method will definitely improve the situation.

Shall we be free to directly write the numbers in any format, like

>>> String.format`${1}%>8${10.00}%>10${1.25e+2}%>12`
'       1     10.00     1.25e+2'

or we shall need to use an appropriate method to coerce first the number to string?

Well, this isn't new syntax, so the String.format tag wouldn't be able to do anything magic like that.

Plus, it doesn't make too much sense either. If you're passing in literals anyways, you might as will pass them in as strings instead of numbers if you want to make sure they're formatted properly. e.g.

String.format`${'1'}%>8${'10.00'}%>10${'1.25e+2'}%>12`

And if you're passing in variables (not literals), than whatever the original format of the numeric literal used when you created the variable has long since been lost (a number variable just holds a number, nothing else). Plus, how would you deal with mixed formats like this:

const x = 2.00 + 3e2;
String.format`${x}%>8`;

So yeah, you'll either have to use the appropriate method to coerce the number to a string in the format you want, or, you'll have to encode in the desired format into the String.format string. A formating string should be capable of doing all sorts of these kinds of things. Here's a handful of examples of how you can ask Python to format a passed-in value in different ways, all of which we could adopt (Most of these I just pulled from their documentation I linked too earlier):

>>> '{:+f}; {:+f}'.format(3.14, -3.14)  # Always show the sign
'+3.140000; -3.140000'
>>> "int: {0:d};  hex: {0:x};  oct: {0:o};  bin: {0:b}".format(42) # Convert to different bases
'int: 42;  hex: 2a;  oct: 52;  bin: 101010'
>>> '{:,}'.format(1234567890) # Show the thousands separator
'1,234,567,890'
>>> '{:.2f}'.format(3.1416) # Only show 2 decimal places
'3.14'
>>> '{:.2E}'.format(123.456) # Scientific notation
'1.23E+02'

To nicely print layouts to a shell the pattern on unix/Linux like systems is to read the TERM environment variable which will contain a name and then look that up in TERMINFO to get things like the tab size.

On node you can use console.table to print a table layout for you rather than calculating it manually. Console | Node.js v18.8.0 Documentation

1 Like