Discussions
Code styles
Developers have different opinions on code styles. Here we list all cases where elastic tabstops provide new ways of code styling or force you into a certain style (which is not desired). All-in-all elastic tabstops are going to break all your code style rules and they probably have to be adopted once (including the linters).
Variable definitions and assignments
Variable definitions and assigments can be aligned very well with elastic tabstops:
public int myint = 123
protected float myfloat = 0.5
private string mystr = "abc"
with elastic tabstops:
public↔ int↔ myint↔ = 123
protected↔ float↔ myfloat↔ = 0.5
private↔ string↔ mystr↔ = "abc"
Numbers could be further right-aligned with elastic tabstops (if your implementation supports it) or by manually right-aligning with spaces:
public↔ int↔ myint↔ =↔ 123
protected↔ float↔ myfloat↔ =↔ 0.5 // right-aligned
private↔ string↔ mystr↔ =↔ "abc"
Optional modifiers (like const) can be aligned as well and fill up using empty tabstops where they are omitted:
public↔ const↔ int↔ myint↔ =↔ 123
protected↔ ↔ float↔ myfloat↔ =↔ 0.5 // right-aligned
private↔ ↔ string↔ mystr↔ =↔ "abc"
Instruction splitting
Let’s say you have the following section but you don’t want to inroduce blank lines because they belong semantically together:
myint = 123
myfloat = 0.123
mystr = "abc"
foo(myint, myfloat, mystr)
foobar(myint, myfloat, mystr)
myint = 234
myfloat = 0.234
mystr = "def"
foo(myint, myfloat, mystr)
foobar(myint, myfloat, mystr)
with elastic tabstops:
myint↔ = 123
myfloat↔ = 0.123
mystr↔ = "abc"
foo↔ (myint, myfloat, mystr)
foobarbaz↔ (myint, myfloat, mystr)
myint↔ = 234
myfloat↔ = 0.234
mystr↔ = "def"
foo↔ (myint, myfloat, mystr)
foobarbaz↔ (myint, myfloat, mystr)
This introduces a lot of space for different programming concepts (variable assignment, function calls). You can break the alignment with additional blank lines:
myint↔ = 123
myfloat↔ = 0.123
mystr↔ = "abc"
foo↔ (myint, myfloat, mystr)
foobarbaz↔ (myint, myfloat, mystr)
myint↔ = 234
myfloat↔ = 0.234
mystr↔ = "def"
foo↔ (myint, myfloat, mystr)
foobarbaz↔ (myint, myfloat, mystr)
This is just a small issue but it was elastic tabstops that made you insert blank lines where you would have otherwise kept the section together for semantic reasons.
This can be mitigated if language-aware features are implemented for different programming language concepts (function blocks, condition blocks etc.) and use different padding settings.
Configuration splitting
You will also find yourself splitting sections if variable names have very different lengths. This usually happens for configuration definitions:
conf_width = 1
conf_height = 2
conf_depth = 3
conf_a_long_name = "abc"
conf_a_very_long_name = "def"
conf_a_very_very_long_name = "ghi"
conf_distance = 0.5
conf_angle = 3.14
conf_offset = 1.23
with elastic tabstops:
conf_width↔ = 1
conf_height↔ = 2
conf_depth↔ = 3
conf_a_long_name↔ = "abc"
conf_a_very_long_name↔ = "def"
conf_a_very_very_long_name↔ = "ghi"
conf_distance↔ = 0.5
conf_angle↔ = 3.14
conf_offset↔ = 1.23
Note how some assignments are now very far away from their variable names.
with additional blank lines:
conf_width↔ = 1
conf_height↔ = 2
conf_depth↔ = 3
conf_a_long_name↔ = "abc"
conf_a_very_long_name↔ = "def"
conf_a_very_very_long_name↔ = "ghi"
conf_distance↔ = 0.5
conf_angle↔ = 3.14
conf_offset↔ = 1.23
This is just a small issue but it was elastic tabstops that made you to insert blank lines where you would have otherwise kept the section together for semantic reasons.
Function calls
Different function calls operating on similar data can be aligned making their relation more clear:
foo(a, b, c)
foobar(a, b, c)
foobarbaz(a, b, c)
with elastic tabstops:
foo↔ (a, b, c)
foobar↔ (a, b, c)
foobarbaz↔ (a, b, c)
With spaces-0 this wouldn’t even introduce additional spaces if no space before function calls is required by the code style:
foo↔ (a, b, c)
foobar↔ (a, b, c)
foobarbaz(a, b, c)
╱╲ // there is an elastic tabstop hiding inside
This padding can be set globally for all elastic tabstops. To use different paddings depending on the programming language concept (function blocks, condition blocks etc.) requires language-aware features.
Function calls (totally aligned)
Depending on what you want to convey or how the function calls are related semantically this can be pushed to the extreme. These examples may seem extreme but they are meant as a base for discussions.
myint x = foo("Alfa", 1, a, b, c)
myfloat yyy = foobar("Bravo", 123, a, b, c)
mystr zzzzz = foobarbaz("Charlie", 12345, a, b, c)
with elastic tabstops:
myint↔ x↔ = foo↔ ("Alfa"↔ , 1↔ , a, b, c)
myfloat↔ yyy↔ = foobar↔ ("Bravo"↔ , 123↔ , a, b, c)
mystr↔ zzzzz↔ = foobarbaz↔ ("Charlie"↔ , 12345↔ , a, b, c)
Totally aligned code works very well for vertical selections and parameter order can be changed very easily.
If the function calls are not related you might only want to align to the assignment level:
myint↔ x↔ = foo("Alfa", 1, a, b, c)
myfloat↔ yyy↔ = foobar("Bravo", 123, a, b, c)
mystr↔ zzzzz↔ = foobarbaz("Charlie", 12345, a, b, c)
If the function calls have a different parameter count but still do belong semantically together you can insert empty elastic tabstops to still keep them aligned. This is useful for programming languages were you refer to optional parameters with keywords (like Python):
x = foo("Alfa", a=1)
yyy = foobar("Bravo", a=123, c=345)
zzzzz = foobarbaz("Charlie", b=23456)
with elastic tabstops:
x↔ = foo↔ ("Alfa"↔ , a=1↔ , b=2↔ ↔ )
yyy↔ = foobar↔ ("Bravo"↔ , a=123↔ ↔ , c=345↔ )
zzzzz↔ = foobarbaz↔ ("Charlie"↔ ↔ , b=23456↔ , c=34567↔ )
If you format the code manually all of this is optional. However it becomes relevant if you are using an auto-formatter in your team and have to settle on one style. Keep in mind that most auto-formatters are not smart enough to understand semantic relationships.
Switch-cases
Switch-cases can be aligned very well with elastic tabstops:
switch(myvar)
{
→ case "Alfa":
→ foo(1, 2, 3);
→ break;
→ case "Bravo":
→ foobar(123, 234, 345);
→ break;
→ case "Charlie":
→ foobarbaz(12345, 23456, 34567);
→ break;
}
with elastic tabstops:
switch(myvar)
{
→ case "Alfa":↔ foo(1, 2, 3);
→ ↔ break;
→ case "Bravo":↔ foobar(123, 234, 345);
→ ↔ break;
→ case "Charlie": ↔ foobarbaz(12345, 23456, 34567);
→ ↔ break;
}
if you introduce blank lines between cases:
switch(myvar)
{
→ case "Alfa":↔ foo(1, 2, 3);
→ ↔ break;
→ case "Bravo":↔ foobar(123, 234, 345);
→ ↔ break;
→ case "Charlie":↔ foobarbaz(12345, 23456, 34567);
→ ↔ break;
}
or as one-liners for short case-blocks:
switch(myvar)
{
→ case "Alfa":↔ foo(1, 2, 3);↔ break;
→ case "Bravo":↔ foobar(123, 234, 456);↔ break;
→ case "Charlie":↔ foobarbaz(12345, 23456, 34567);↔ break;
}
and if you want total alignment:
switch(myvar)
{
→ case "Alfa"↔ : foo↔ (1↔ , 2↔ , 3↔ ); break;
→ case "Bravo"↔ : foobar↔ (123↔ , 234↔ , 345↔ ); break;
→ case "Charlie"↔ : foobarbaz↔ (12345↔ , 23456↔ , 34567↔ ); break;
}
and reverse if your implementation supports right-aligned numbers:
switch(myvar)
{
→ case "Alfa"↔ : foo↔ (↔ 1,↔ 2,↔ 3); break;
→ case "Bravo"↔ : foobar↔ (↔ 123,↔ 234,↔ 345); break;
→ case "Charlie"↔ : foobarbaz↔ (↔ 12345,↔ 23456,↔ 34567); break;
}
If you format the code manually all of this is optional. However it becomes relevant if you are using an auto-formatter in your team and have to settle on one style. Keep in mind that most auto-formatters are not smart enough to understand semantic relationships.
If-Elseif
Elastic tabstops also work quite well if you prefer if-elif one-liners instead of switch-cases:
if (myvar == "Alfa") { foo(1, 2, 3); }
else if (myvar == "Bravo") { foobar(123, 234, 456); }
else if (myvar == "Charlie") { foobarbaz(12345, 23456, 34567); }
else { print("error"); }
with elastic tabstops:
if↔ (myvar == "Alfa")↔ { foo(1, 2, 3); }
else if↔ (myvar == "Bravo")↔ { foobar(123, 234, 456); }
else if↔ (myvar == "Charlie")↔ { foobarbaz(12345, 23456, 34567); }
else↔ ↔ { print("error"); }
with else on the same line:
if (myvar == "Alfa")↔ { foo(1, 2, 3); } else
if (myvar == "Bravo")↔ { foobar(123, 234, 456); } else
if (myvar == "Charlie")↔ { foobarbaz(12345, 23456, 34567); }
else↔ { print("error"); }
with else on the same line and additional elastic tabstops for the blocks to make the else more visible:
if (myvar == "Alfa")↔ { foo(1, 2, 3);↔ } else
if (myvar == "Bravo")↔ { foobar(123, 234, 456);↔ } else
if (myvar == "Charlie")↔ { foobarbaz(12345, 23456, 34567);↔ }
else↔ { print("error"); }
with hanging ifs:
↔ if (myvar == "Alfa")↔ { foo(1, 2, 3); }
else↔ if (myvar == "Bravo")↔ { foobar(123, 234, 456); }
else↔ if (myvar == "Charlie")↔ { foobarbaz(12345, 23456, 34567); }
else↔ ↔ { print("error"); }
Note that indent-aware languages (like Python) don’t like this!
if you want everything aligned (extreme):
if↔ (myvar == "Alfa"↔ ) { foo↔ (1↔ , 2↔ , 3↔ ); }
else if↔ (myvar == "Bravo"↔ ) { foobar↔ (123↔ , 234↔ , 456↔ ); }
else if↔ (myvar == "Charlie"↔ ) { foobarbaz↔ (12345↔ , 23456↔ , 34567↔ ); }
else↔ ↔ { print("error"); }
Totally aligned code works very well for vertical selections and paramter order can be changed very easily. Note however that the last else statement doesn’t really fit the structure.
If you format the code manually all of this is optional. However it becomes relevant if you are using an auto-formatter in your team and have to settle on one style. Keep in mind that most auto-formatters are not smart enough to understand semantic relationships.
Dictionary accessors
mydict["Alfa"]["foo"]["a"] = 1
mydict["Alfa"]["foobar"]["bb"] = 2
mydict["Alfa"]["foobarbaz"]["ccc"] = 3
mydict["Bravo"]["foo"]["a"] = 4
mydict["Bravo"]["foobar"]["bb"] = 5
mydict["Bravo"]["foobarbaz"]["ccc"] = 6
mydict["Charlie"]["foo"]["a"] = 7
mydict["Charlie"]["foobar"]["bb"] = 8
mydict["Charlie"]["foobarbaz"]["ccc"] = 9
with elastic tabstops:
mydict["Alfa"]↔ ["foo"]↔ ["a"]↔ = 1
mydict["Alfa"]↔ ["foobar"]↔ ["bb"]↔ = 2
mydict["Alfa"]↔ ["foobarbaz"]↔ ["ccc"]↔ = 3
mydict["Bravo"]↔ ["foo"]↔ ["a"]↔ = 4
mydict["Bravo"]↔ ["foobar"]↔ ["bb"]↔ = 5
mydict["Bravo"]↔ ["foobarbaz"]↔ ["ccc"]↔ = 6
mydict["Charlie"]↔ ["foo"]↔ ["a"]↔ = 7
mydict["Charlie"]↔ ["foobar"]↔ ["bb"]↔ = 8
mydict["Charlie"]↔ ["foobarbaz"]↔ ["ccc"]↔ = 9
with elastic tabstops but optimized for vertical selections:
mydict["Alfa"↔ ]["foo"↔ ]["a"↔ ] = 1
mydict["Alfa"↔ ]["foobar"↔ ]["bb"↔ ] = 2
mydict["Alfa"↔ ]["foobarbaz"↔ ]["ccc"↔ ] = 3
mydict["Bravo"↔ ]["foo"↔ ]["a"↔ ] = 4
mydict["Bravo"↔ ]["foobar"↔ ]["bb"↔ ] = 5
mydict["Bravo"↔ ]["foobarbaz"↔ ]["ccc"↔ ] = 6
mydict["Charlie"↔ ]["foo"↔ ]["a"↔ ] = 7
mydict["Charlie"↔ ]["foobar"↔ ]["bb"↔ ] = 8
mydict["Charlie"↔ ]["foobarbaz"↔ ]["ccc"↔ ] = 9
if the programming language (or preprocessor) supports “ignore non-escaped tab character at end of string”:
mydict["Alfa↔ "]["foo↔ "]["a↔ "] = 1
mydict["Alfa↔ "]["foobar↔ "]["bb↔ "] = 2
mydict["Alfa↔ "]["foobarbaz↔ "]["ccc↔ "] = 3
mydict["Bravo↔ "]["foo↔ "]["a↔ "] = 4
mydict["Bravo↔ "]["foobar↔ "]["bb↔ "] = 5
mydict["Bravo↔ "]["foobarbaz↔ "]["ccc↔ "] = 6
mydict["Charlie↔ "]["foo↔ "]["a↔ "] = 7
mydict["Charlie↔ "]["foobar↔ "]["bb↔ "] = 8
mydict["Charlie↔ "]["foobarbaz↔ "]["ccc↔ "] = 9
Matrices
Matrices can be aligned into tabular form with elastic tabstops very well:
mat = [
→ [ -1, 0, 1 ],
→ [ 0, 1, 1 ],
→ [ 123, 234, 456 ]
]
with elastic tabstops:
mat = [
→ [ -1↔ , 0↔ , 1↔ ],
→ [ 0↔ , 1↔ , 1↔ ],
→ [ 123↔ , 234↔ , 456↔ ]
]
with elastic tabstops and right-aligned numbers:
mat = [
→ [↔ -1,↔ 0,↔ 1],
→ [↔ 0,↔ 1,↔ 1],
→ [↔ 123,↔ 234,↔ 456]
]
in this case (only numbers) it could also be done with alignment spaces if you care about right-alignment but your implementation doesn’t support right-aligned numbers:
mat = [
→ [··-1,···0,···1·],
→ [···0,···1,···1·],
→ [·123,·234,·456·]
]
However, as with alignment spaces in general, this requries a lot of manual work if the numbers change.
Indentaware elastic tabstops
braces on same line code style
This happens when aligned lines connect with the block below.
without indent awareness:
foo↔ (v => "Alfa")↔ // some
foobar↔ (v => "Bravo")↔ // comments
foobarbaz↔ (v => {↔ // here
↔ myint↔ = 123
↔ myfloat↔ = 3.14
↔ mystr↔ = "abc"
↔ return "Charlie"
})
with indent awareness:
foo↔ (v => "Alfa")↔ // some
foobar↔ (v => "Bravo")↔ // comments
foobarbaz↔ (v => {↔ // here
→ myint↔ = 123
→ myfloat↔ = 3.14
→ mystr↔ = "abc"
→ return "Charlie"
})
indent scoped languages
without indent awareness:
def foo(x, y):
→ if x > y:
→ → temp↔ = x↔ # store temp
→ → x↔ = y↔ # swap
→ → y↔ = temp↔ # restore temp
→ start↔ = 0↔ # set start
→ end↔ = 12345↔ # set end
without indent awareness but additional blank lines:
def foo(x, y):
→ if x > y:
→ → temp↔ = x↔ # store temp
→ → x↔ = y↔ # swap
→ → y↔ = temp↔ # restore temp
→ start↔ = 0↔ # set start
→ end↔ = 12345↔ # set end
with indent awarenesss:
def foo(x, y):
→ if x > y:
→ → temp↔ = x↔ # store temp
→ → x↔ = y↔ # swap
→ → y↔ = temp↔ # restore temp
→ start↔ = 0↔ # set start
→ end↔ = 12345↔ # set end
In certain situation indent awareness might not be desired however.
multiline statements
foo(↔ aVeryLongParameter,
↔ anotherParameterWithLongName,
↔ lastParameterWithWiiiideName)
{
...
}
but can be fixed with a different code style:
foo(
↔ aVeryLongParameter,
↔ anotherParameterWithLongName,
↔ lastParameterWithWiiiideName)
{
...
}
or using additional indent-breakers:
foo(↔ aVeryLongParameter,
· ↔ anotherParameterWithLongName,
· ↔ lastParameterWithWiiiideName)
{
...
}
switch-case example
without indent awareness:
switch(myvar)
{
→ case "Alfa":↔ foo(1, 2, 3);
→ ↔ break;
→ case "Bravo":↔ foobar(123, 234, 345);
→ ↔ break;
→ case "Charlie":↔ foobarbaz(12345, 23456, 34567);
→ ↔ break;
}
with indent awareness:
switch(myvar)
{
→ case "Alfa":↔ foo(1, 2, 3);
→ → break;
→ case "Bravo":↔ foobar(123, 234, 345);
→ → break;
→ case "Charlie":↔ foobarbaz(12345, 23456, 34567);
→ → break;
}
but can be fixed with additional indent-breakers:
switch(myvar)
{
→ case "Alfa":↔ foo(1, 2, 3);
→ ·↔ break;
→ case "Bravo":↔ foobar(123, 234, 345);
→ ·↔ break;
→ case "Charlie":↔ foobarbaz(12345, 23456, 34567);
→ ·↔ break;
→ → break;
}
see discussion here https://github.com/nick-gravgaard/ElasticNotepad/issues/4
Code folding
When you have multiple code section which are related hierarchically you can align them when their blocks are folded:
myObj.addFooListener(foo => // listen to the foo
{
→ myint = 123
→ myfloat = 0.123
→ mystr = "abc"
→ do(myint, myfloat, mystr, foo)
})
myObj.addFoobarListener(foobar => // listen to the foobar
{
→ myint = 234
→ myfloat = 0.234
→ mystr = "def"
→ do(myint, myfloat, mystr, foobar)
})
collapsed it looks like this:
> myObj.addFooListener(foo => // listen to the foo
> myObj.addFoobarListener(foobar => // listen to the foobar
with elastic tabstops:
> myObj.addFooListener↔ (foo↔ => // listen to the foo
> myObj.addFoobarListener↔ (foobar↔ => // listen to the foobar
However this enforces a code style where code blocks are on a new line otherwise they would fuse with the code below in expanded state:
myObj.addFooListener↔ (foo↔ => { // listen to the foo
↔ myint↔ = 123
↔ myfloat↔ = 0.123
↔ mystr↔ = "abc"
↔ do(myint, myfloat, mystr, foo)
})
myObj.addFoobarListener↔ (foobar↔ => { // listen to the foobar
↔ myint↔ = 234
↔ myfloat↔ = 0.234
↔ mystr↔ = "def"
↔ do(myint, myfloat, mystr, foobar)
})
Forward alignment
When writing code you may find yourself adding elastic tabstops for future alignment even though the text doesn’t benefit from alignment yet.
Some elastic tabstops already added:
foo↔ (a, b, c)
public↔ int↔ myint↔ = 123
because we anticipate the code will end up like this in the future:
foo↔ (a, b, c)
foobar↔ (a, b, c)
foobarbaz↔ (a, b, c)
public↔ int↔ myint↔ = 123
protected↔ float↔ myfloat↔ = 0.5
private↔ string↔ mystr↔ = "abc"
For others and auto-formatters it may not be clear however why these tabs were added.
JSON
{
→ "myint": 123,
→ "myfloat": 0.5,
→ "mystr": "abc",
→ "myobj0": {
→ → "id": 0,
→ → "name": "Alfa"
→ }
→ "myobj1": {
→ → "id": 1,
→ → "name": "Bravo"
→ }
→ "myarr": [
→ → 0,
→ → 1,
→ → 2
→ ]
}
With naive elastic tabstops we get this undesired rendering while not even aligning objects which span over multiple lines:
{
→ "myint"↔ : 123,
→ "myfloat"↔ : 0.5,
→ "mystr"↔ : "abc",
→ "myobj0"↔ : {
→ ↔ "id"↔ : 0,
→ ↔ "name"↔ : "Alfa"
→ }
→ "myobj1"↔ : { // note how this colon is NOT aligned with the others
→ ↔ "id"↔ : 1,
→ ↔ "name"↔ : "Bravo"
→ }
→ "myarr"↔ : [ // note how this colon is NOT aligned with the others
→ ↔ 0,
→ ↔ 1,
→ ↔ 2
→ ]
}
Elastic tabstops therefore enforce a code style where either blocks are on a new line or the closing brace is on the same line (and require a lot of extra tabs):
variant A - blocks on new lines:
{
→ "myint"↔ : 123,
→ "myfloat"↔ : 0.5,
→ "mystr"↔ : "abc",
→ "myobj0": // not how colons are at least aligned within the block
→ {
→ → "id"↔ : 0,
→ → "name"↔ : "Alfa"
→ }
→ "myobj1": // not how colons are at least aligned within the block
→ {
→ → "id"↔ : 1,
→ → "name"↔ : "Bravo"
→ }
→ "myarr":
→ [
→ → 0,
→ → 1,
→ → 2
→ ]
}
This variant works quite well and is simple to implement although the alignment is not perfect and a code style is enforced
variant B - block begin on new line, block close on the same line:
{
→ "myint"↔ : 123,
→ "myfloat"↔ : 0.5,
→ "mystr"↔ : "abc",
→ "myobj0"↔ : {
→ ↔ ↔ "id"↔ : 0,
→ ↔ ↔ "name"↔ : "Alfa" }
→ "myobj1"↔ : {
→ ↔ ↔ ↔ "id"↔ : 1,
→ ↔ ↔ ↔ "name"↔ : "Bravo" }
→ "myarr"↔ : [
→ ↔ ↔ ↔ 0,
→ ↔ ↔ ↔ 1,
→ ↔ ↔ ↔ 2 ]
}
variant C - block begin on same line, block close on the same line:
{
→ "myint"↔ : 123,
→ "myfloat"↔ : 0.5,
→ "mystr"↔ : "abc",
→ "myobj0"↔ : {↔ "id"↔ : 0,
→ ↔ ↔ "name"↔ : "Alfa" }
→ "myobj1"↔ : {↔ "id"↔ : 1,
→ ↔ ↔ "name"↔ : "Bravo" }
→ "myarr"↔ : [↔ 0,
→ ↔ ↔ 1,
→ ↔ ↔ 2 ]
}
This variant will break down sooner or later when we want to add additional alignments somewhere else (like the numbers or comments) or the order of element changes
Some of these issues can also be mitigated by introducing blank lines to break the alignment block.
Right-aligning numbers
Depending on your elastic tabstops implementation it may also be possible to right-align numbers.
myint↔ =↔ 123
myfloat↔ =↔ 3.56
mystr↔ =↔ "abc"
While the standard settings should be fine for most cases there are a lot of subtle details on how to handle alignment, especially with mixed number notations (integer, float, scientific, binaries) and columns with mixed numbers and strings. Here is a quick overview where this could affect you:
Which number notations should be detected at all?
myint↔ =↔ 123
myfloat↔ =↔ 3.56
myhex↔ =↔ 0xC0C0
myscientific↔ =↔ 3.5e7
myspecial↔ =↔ NaN
mystr↔ =↔ "2nd"
Note that hex numbers are usually integers, scientific notations can be both and special numbers vary across programming languages.
How should strings be aligned in columns which contain numbers?
all to right:
↔ 12345
↔ abc
↔ abcdefgh
↔ 0.2345
strings to left:
↔ 12345
↔ abc
↔ abcdefgh
↔ 0.2345
strings as int:
↔ 12345
↔ abc
↔ abcdefgh
↔ 0.2345
strings to right:
↔ 12345
↔ abc
↔ abcdefgh
↔ 0.2345
Preferred position of tab in number-only columns?
without number alignment support:
mat = [
→ [ -1↔ , 0↔ , 1↔ ],
→ [ 0↔ , 1↔ , 1↔ ],
→ [ 123↔ , 234↔ , 456↔ ]
]
with number alignment support:
mat = [
→ [↔ -1,↔ 0,↔ 1],
→ [↔ 0,↔ 1,↔ 1],
→ [↔ 123,↔ 234,↔ 456]
]
How should strings after a number be treated?
Alfa↔ increased↔ 123x
Bravo↔ increased↔ 3.14x
Charlie↔ increased↔ 42x
Where should scientific notation align at?
myint↔ =↔ 1e3
myfloat↔ =↔ 1e-3
myintish↔ =↔ 1.2e3
myfloatish↔ =↔ 1.2345e3
For more details see the section Implementation - Right-aligning numbers.
Code editors
Type and value hinting
Some code editors offer type hinting and value hinting which insert new text and might break the alignment.
function(myint, myfloat, mystr) { ... }
with type hinting:
function(myint /* : int */, myfloat /* : float */, mystr /*: string */) { ... }
with value hinting during debugging:
function(myint /* = 123 */, myfloat /* = 3.14 */, mystr /* = "abc" */) { ... }
Tab treatment
[Tab]and[Shift+Tab]behaviourAuto-inserting tabs
Vertical column selection
Positions might be off
Multi-caret positions
Inserting multiple lines
Virtual spaces
Auto-formatters
auto-format document
auto-format on paste
If enabled it will remove all multiple spaces and tabs within a line. If disabled it will not convert spaces-indented code to tabs. Ideally the concept of elastic tabstops is added to auto-formatters themselves.
Code completion
When entering text most code editors offer code completions and the [Tab] is usually used to accept the suggestion. With elastic tabstops we usually want to enter an actual tab character. This can be mitigated by setting a higher delay for suggestions ("editor.quickSuggestionsDelay": 500).
Tools
How certain tools effect the handling of elastic tabstops.
Configurators
Tools like npm reformat the whole package.json when adding a package via commandline npm install acme. Some tools allow to define the auto-formatter but this moves the responsibility to the formatter. Some tools allow to only reformat the changed lines.
Linters
Linters and code style rules may not like…
tabs for indentation
tabs within lines
weird indentation levels
multiple spaces anywhere (like alignment spaces)
additional spaces before certain elements (like alignment spaces)
additional blank lines (which we use to separate alignment blocks)
tabs after line comments
Most of these rules can be overwritten but some linters probably can’t handle the existence of tabs within a line. Ideally the concept of elastic tabstops is added into the linters themselves.
Helpers
Some additional features which might help with elastic tabstops.
Align by character
In the following example it’s pretty clear that we want to align on the equal sign:
public int myint = 123
protected float myfloat = 0.5
private string mystr = "abc"
A feature could help us select a character and add a tab for all adjecent lines.
Programming languages
Todo
Add language specific issues
Begin-block character not on next line
Depending on the programming language or the code style a block may or may not start on the same line as the definition (compare indent-aware languages like Python and C-like languages which use { for blocks). If the function definition uses multiple lines with elastic tabstops this causes followup elastic tabstops to align with the function definition.
→ function(↔ a,
→ ↔ foo) {
→ → var b↔ = 0
→ → var bar↔ = 1
→ }
wrong rendering:
function( a,
foo) {
var b = 0
var bar = 1
}
However, this would require..
a. language-aware feature for
blocksb. force users to adapt a code style with begin-block characters on the next lines (not desired)
c. force users to insert blank lines after function definition (not desired)
for testing (contains actual \t characters):
function( a,
foo) {
var b = 0
var bar = 1
}
Languages with indent-aware block scoping
Todo
provide complete self-contained examples for different languages
Python
def foo(x):
→ → → '''<1 tab before the docstring.
No tab→ <tab
No tab→ <tab
→ → → <tab <another tab
→ → → <tab <another tab
→ → → <tab'''
→ → → if 1 or 2:→ #<Tab before this comment
→ → → → → → → yield True
f(x, y):
→ if x > y:
→ → temp→ := x
→ → x→ := y
→ → y→ := temp
→ start→ := 1
→ end→ := 10
Haskell
main :: IO ()
main = putStrLn "Hello, World!"
Lisp-like
(let loop ((n 1))
→ (if (> n 10)
→ → '()
→ → (cons n
→ → → (loop (+ n 1)))))