# 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 ### 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](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]` behaviour * Auto-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 `blocks` * b. 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→ 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))))) ```