Repository: hannah-sten/texify-idea
Issue: Improper aligning of tabular's columns

## Version information
| Tool | Version |
| --- | --- |
| IDE | IntelliJ Ultimate 2021.2.2 |
| OS | Debian 12 (testing) |
| TeXiFy | 0.7.11 |

## Bugs
I found several situations in which columns are not properly aligned by the formatter. I think the latest updates to TeXiFy have significantly improved the behaviour (I no longer surround all my tables with `@formatter:off`/`@formatter:on`), for which I am very thankful, but the following are some quirks that still remain. I have bundled them together into this single issue so as to not spam you to death, but if you want I can split this one into several issues.

### Bug 1
<details>
  <summary>Incorrect width of escaped characters</summary>

#### Steps to reproduce
1. Create a basic `tabular` environment with multiple columns and multiple rows containing some text.
2. Add an escaped ampersand (`\\&`) in one of the tabular's cells.
3. Reformat the file.

#### Minimal working example
```latex
\\documentclass{article}

\\begin{document}
    \\begin{table}[ht]
        \\caption{My caption}
        \\label{tab:my-label}

        \\begin{tabular}{lll}
            Lorem ipsum & Dolor sit amet   & Consectetur   \\\\
            Ut placerat & Dapibus \\& lacus & Ac vestibulum \\\\
        \\end{tabular}
    \\end{table}
\\end{document}
```

#### Expected behavior
In the MWE above, nothing should change when reformatting.

#### Actual behavior
The formatter formats the file to the snippet below. The formatter seems to think that `\\&` is only one character wide, and accordingly removes one space from between `amet` and `&`. Reformatting again changes nothing. Removing `\\& ` from the MWE and then reformatting gives the desired behaviour. Replacing the `\\&` with another escaped character, such as `\\#`, does not result in buggy behaviour.

```latex
\\documentclass{article}

\\begin{document}
    \\begin{table}[ht]
        \\caption{My caption}
        \\label{tab:my-label}

        \\begin{tabular}{lll}
            Lorem ipsum & Dolor sit amet  & Consectetur   \\\\
            Ut placerat & Dapibus \\& lacus & Ac vestibulum \\\\
        \\end{tabular}
    \\end{table}
\\end{document}
```

</details>

### Bug 2
<details>
  <summary>Alignment gave up</summary>

#### Steps to reproduce
1. Create a basic `tabular` environment with multiple columns and multiple rows containing some text.
2. Add an escaped ampersand (`\\&`) in one of the tabular's cells.
3. Reformat the file.

I have not been able to figure out why the behaviour in this situation is different from `Bug 1: Incorrect width of escaped characters`.

#### Minimal working example
```latex
\\documentclass{article}

\\begin{document}
    \\begin{table}[ht]
        \\caption{My caption}
        \\label{tab:my-label}

        \\begin{tabular}{ll}
            Pellentesque habitant & Aliquam   \\\\
            Aliquam \\& mattis     & Venenatis
        \\end{tabular}
    \\end{table}
\\end{document}
```

#### Expected behavior
In the MWE above, nothing should change when reformatting.

#### Actual behavior
The formatter formats the file to the snippet below. It's almost as if the formatter decided to give up trying to align the columns and just compacted them instead (but only on the second row, as the first row is spaced correctly). Reformatting again changes nothing. Removing `\\& ` from the MWE and then reformatting gives the desired behaviour.

```latex
\\documentclass{article}

\\begin{document}
    \\begin{table}[ht]
        \\caption{My caption}
        \\label{tab:my-label}

        \\begin{tabular}{ll}
            Pellentesque habitant & Aliquam   \\\\
            Aliquam \\& mattis & Venenatis
        \\end{tabular}
    \\end{table}
\\end{document}
```

</details>

### Bug 3
<details>
  <summary>Two passes needed given excessive whitespace</summary>

#### Steps to reproduce
1. Create a basic `tabular` environment with multiple columns and multiple rows containing some text.
2. In a cell with multiple words, add additional spaces between those words.
3. Reformat the file.

#### Minimal working example
```latex
\\documentclass{article}

\\begin{document}
    \\begin{table}[ht]
        \\caption{My caption}
        \\label{tab:my-label}

        \\begin{tabular}{ll}
            Lorem ipsum         & Dolor sit \\\\
            Morbi  pellentesque & Sed ante
        \\end{tabular}
    \\end{table}
\\end{document}
```

#### Expected behavior
The formatter should format the file to the snippet below. That is, it removes the extra space from between `Morbi`and `pellentesque` and removes one space from between `ipsum` and `&`.

```latex
\\documentclass{article}

\\begin{document}
    \\begin{table}[ht]
        \\caption{My caption}
        \\label{tab:my-label}

        \\begin{tabular}{ll}
            Lorem ipsum        & Dolor sit \\\\
            Morbi pellentesque & Sed ante
        \\end{tabular}
    \\end{table}
\\end{document}
```

#### Actual behavior
The formatter formats the file to the snippet below. That is, it behaves correctly on the second row, but removes one space _too many_ from the first row. Running the formatter again results in the desired behaviour.

I think this issue can be resolved by making the formatter compact the table first, and then calculating and applying the required column widths. (Or maybe it already does that, in which case the width calculating has an off-by-one error.)

```latex
\\documentclass{article}

\\begin{document}
    \\begin{table}[ht]
        \\caption{My caption}
        \\label{tab:my-label}

        \\begin{tabular}{ll}
            Lorem ipsum       & Dolor sit \\\\
            Morbi pellentesque & Sed ante
        \\end{tabular}
    \\end{table}
\\end{document}
```

</details>


Changed files:
src/nl/hannahsten/texifyidea/formatting/spacingrules/TableAlignRule.kt, test/nl/hannahsten/texifyidea/formatting/TableAlignTest.kt

diff --git a/src/nl/hannahsten/texifyidea/formatting/spacingrules/TableAlignRule.kt b/src/nl/hannahsten/texifyidea/formatting/spacingrules/TableAlignRule.kt
index 9c4892ddb..1b04d5af8 100644
--- a/src/nl/hannahsten/texifyidea/formatting/spacingrules/TableAlignRule.kt
+++ b/src/nl/hannahsten/texifyidea/formatting/spacingrules/TableAlignRule.kt
@@ -92,7 +92,8 @@ fun getAmpersandOffsets(contentTextOffset: Int, indent: String, contentLines: Mu
     return contentLines.map { line ->
         val indices = mutableListOf<Int>()
         line.withIndex().forEach { (i, it) ->
-            if (it == '&') indices.add(currentOffset)
+            // Do not count escaped ampersands: \\&
+            if ((it == '&') && (i == 0 || line[i - 1] != '\\\\')) indices.add(currentOffset)
             if (it == '\\\\' && if (i < line.length - 1) line[i + 1] == '\\\\' else false) indices.add(currentOffset)
             currentOffset++
         }
@@ -159,7 +160,8 @@ private fun removeExtraSpaces(contentLinesWithoutRules: MutableList<String>): Li
                 }
                 value in setOf(' ', '\\n') -> {
                     if (i > 0 && i < line.length - 1) {
-                        val isAfterSpaceOrSeparator = line[i - 1] in setOf(' ', '&', '\\n')
+                        // Spaces after an ignored ampersand are not removed
+                        val isAfterSpaceOrSeparator = (line[i - 1] in setOf(' ', '&', '\\n') && (i < 2 || line[i - 2] != '\\\\'))
                         val isBeforeSpaceOrSeparator = line[i + 1] in setOf(' ', '&', '\\n')
                         val isBeforeDoubleBackslash = i < line.length - 2 && line[i + 1] == '\\\\' && line[i + 2] == '\\\\'
                         if (isAfterSpaceOrSeparator || isBeforeSpaceOrSeparator || isBeforeDoubleBackslash) removedSpaces++
diff --git a/test/nl/hannahsten/texifyidea/formatting/TableAlignTest.kt b/test/nl/hannahsten/texifyidea/formatting/TableAlignTest.kt
index c35e9c6ae..22f994c9f 100644
--- a/test/nl/hannahsten/texifyidea/formatting/TableAlignTest.kt
+++ b/test/nl/hannahsten/texifyidea/formatting/TableAlignTest.kt
@@ -361,6 +361,20 @@ class TableAlignTest : BasePlatformTestCase() {
         """.trimIndent()
     }
 
+    fun testEscapedAmpersands2() {
+        """
+            \\begin{tabular}{lll}
+                Lorem ipsum & Dolor sit           & Consectetur   \\\\
+                Ut \\& & Dapibus placerat \\& lacus & Ac vestibulum \\\\
+            \\end{tabular}
+        """.trimIndent() `should be reformatted to` """
+            \\begin{tabular}{lll}
+                Lorem ipsum & Dolor sit                 & Consectetur   \\\\
+                Ut \\&       & Dapibus placerat \\& lacus & Ac vestibulum \\\\
+            \\end{tabular}
+        """.trimIndent()
+    }
+
     fun testVeryWideTable() {
         val start = """
 \\documentclass[11pt]{article}