a-h / templ

A language for writing HTML user interfaces in Go.
https://templ.guide/
MIT License
8.26k stars 272 forks source link

bug(lsp): duplicated document after using snippet #72

Closed joerdav closed 12 months ago

joerdav commented 1 year ago

I think this bug is related to my use of a snippet, the subsequent code completion.

Recording: asciicast

I have the logs too, but they wouldn't fit in a comment:

https://gist.github.com/joerdav/fe677ce04914bbb27f665f1070429da1

a-h commented 1 year ago

Thanks for that.

I can match up the message at 8 seconds, when it says u declared but not used. This comes from gopls. templ "rewrites" it to update the position of the error message to be correct within the templ file (i.e. not the generated Go file that gopls sees).

{"level":"info","ts":"2023-04-17T14:35:32+01:00","caller":"proxy/client.go:50","msg":"client <- server: PublishDiagnostics"}
{"level":"info","ts":"2023-04-17T14:35:32+01:00","caller":"proxy/client.go:52","msg":"client <- server: PublishDiagnostics: [0]","diagnostic":{"range":{"start":{"line":181,"character":9},"end":{"line":181,"character":10}},"severity":1,"code":"UnusedVar","codeDescription":{"href":"https://pkg.go.dev/golang.org/x/tools/internal/typesinternal?utm_source=gopls#UnusedVar"},"source":"compiler","message":"u declared but not used"}}
{"level":"info","ts":"2023-04-17T14:35:32+01:00","caller":"proxy/client.go:75","msg":"diagnostic [0] rewritten","diagnostic":{"range":{"start":{"line":21,"character":9},"end":{"line":21,"character":10}},"severity":1,"code":"UnusedVar","codeDescription":{"href":"https://pkg.go.dev/golang.org/x/tools/internal/typesinternal?utm_source=gopls#UnusedVar"},"source":"compiler","message":"u declared but not used"}}
{"level":"info","ts":"2023-04-17T14:35:32+01:00","caller":"proxy/client.go:50","msg":"client <- server: PublishDiagnostics"}

I then see the message from the client to the server that a newline was added:

{"level":"info","ts":"2023-04-17T14:35:33+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":5},"contentChanges":[{"range":{"start":{"line":22,"character":7},"end":{"line":23,"character":0}},"rangeLength":1,"text":"\n\t\t\t\n"},{"range":{"start":{"line":23,"character":3},"end":{"line":23,"character":3}},"text":"\t"}]}}

Then the < char:

{"level":"info","ts":"2023-04-17T14:35:33+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":6},"contentChanges":[{"range":{"start":{"line":23,"character":4},"end":{"line":23,"character":4}},"text":"<"}]}}

Then the > char which I think is auto-inserted by the editor:

{"level":"info","ts":"2023-04-17T14:35:35+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":7},"contentChanges":[{"range":{"start":{"line":23,"character":5},"end":{"line":23,"character":5}},"text":">"}]}}

The client then deletes a character at line 23, character 5, then inserts an > and \n\t\t\t\t</. It's slightly odd that the client passes some pointless changes, but it's OK. This is probably the snippet auto-inserting the end tag.

{
  "level": "info",
  "ts": "2023-04-17T14:35:35+01:00",
  "caller": "proxy/server.go:394",
  "msg": "client -> server: DidChange",
  "params": {
    "textDocument": {
      "uri": "file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ",
      "version": 15
    },
    "contentChanges": [
      {
        "range": {
          "start": {
            "line": 23,
            "character": 5
          },
          "end": {
            "line": 23,
            "character": 6
          }
        },
        "rangeLength": 1,
        "text": ""
      },
      {
        "range": {
          "start": {
            "line": 23,
            "character": 5
          },
          "end": {
            "line": 23,
            "character": 5
          }
        },
        "text": ""
      },
      {
        "range": {
          "start": {
            "line": 23,
            "character": 5
          },
          "end": {
            "line": 23,
            "character": 5
          }
        },
        "text": ""
      },
      {
        "range": {
          "start": {
            "line": 23,
            "character": 5
          },
          "end": {
            "line": 23,
            "character": 5
          }
        },
        "text": ">\n\t\t\t\t\t"
      },
      {
        "range": {
          "start": {
            "line": 24,
            "character": 5
          },
          "end": {
            "line": 24,
            "character": 5
          }
        },
        "text": ""
      },
      {
        "range": {
          "start": {
            "line": 24,
            "character": 5
          },
          "end": {
            "line": 24,
            "character": 5
          }
        },
        "text": "\n\t\t\t\t</"
      },
      {
        "range": {
          "start": {
            "line": 25,
            "character": 6
          },
          "end": {
            "line": 25,
            "character": 6
          }
        },
        "text": ">"
      },
      {
        "range": {
          "start": {
            "line": 25,
            "character": 7
          },
          "end": {
            "line": 25,
            "character": 7
          }
        },
        "text": ""
      }
    ]
  }
}

Next, you can see the insertion of the t char:

{
  "level": "info",
  "ts": "2023-04-17T14:35:36+01:00",
  "caller": "proxy/server.go:394",
  "msg": "client -> server: DidChange",
  "params": {
    "textDocument": {
      "uri": "file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ",
      "version": 16
    },
    "contentChanges": [
      {
        "range": {
          "start": {
            "line": 23,
            "character": 5
          },
          "end": {
            "line": 23,
            "character": 5
          }
        },
        "text": "t"
      }
    ]
  }
}

The insertion of the d character is basically the same, then the snippet inserts td in the end tag space:

{"level":"info","ts":"2023-04-17T14:35:38+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":18},"contentChanges":[{"range":{"start":{"line":25,"character":6},"end":{"line":25,"character":6}},"text":"td"}]}}

The client then formats everything.

{"level":"info","ts":"2023-04-17T14:35:42+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":19},"contentChanges":[{"range":{"start":{"line":25,"character":9},"end":{"line":26,"character":0}},"rangeLength":1,"text":"\n\t\t\t\t<td>\n\t\t\t\t\t\n\t\t\t\t</td>\n"}]}}

A few seconds later (16 secs in the video), the yanked <td></td> pair gets pasted into line 26:

{"level":"info","ts":"2023-04-17T14:35:42+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":20},"contentChanges":[{"range":{"start":{"line":26,"character":8},"end":{"line":27,"character":0}},"rangeLength":1,"text":"\n\t\t\t\t<td>\n\t\t\t\t\t\n\t\t\t\t</td>\n"}]}}

It then looks like you pasted it again by accident, then did undo. In the LSP, this is shown as a delete of 25 chars:

{
  "level": "info",
  "ts": "2023-04-17T14:35:43+01:00",
  "caller": "proxy/server.go:394",
  "msg": "client -> server: DidChange",
  "params": {
    "textDocument": {
      "uri": "file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ",
      "version": 21
    },
    "contentChanges": [
      {
        "range": {
          "start": {
            "line": 27,
            "character": 0
          },
          "end": {
            "line": 30,
            "character": 0
          }
        },
        "rangeLength": 25,
        "text": ""
      }
    ]
  }
}

There's some formatting... then you type P{ at 23 secs:

{"level":"info","ts":"2023-04-17T14:35:49+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":28},"contentChanges":[{"range":{"start":{"line":27,"character":5},"end":{"line":27,"character":5}},"text":"P"}]}}
{"level":"info","ts":"2023-04-17T14:35:49+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":29},"contentChanges":[{"range":{"start":{"line":27,"character":6},"end":{"line":27,"character":6}},"text":"{"}]}}

This triggers autocompletion, but you're not in a known Go code bit of the template, so templ can't map from the Go code position back to templ (normal behaviour), so it's ignored.

You get rid of the P{ and start again with {.

Then hit { u at 25 seconds:

{"level":"info","ts":"2023-04-17T14:35:51+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":33},"contentChanges":[{"range":{"start":{"line":27,"character":6},"end":{"line":27,"character":6}},"text":"u"}]}}

Followed by the period:

{"level":"info","ts":"2023-04-17T14:35:51+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":34},"contentChanges":[{"range":{"start":{"line":27,"character":7},"end":{"line":27,"character":7}},"text":"."}]}}

Then, the n, followed by a:

{"level":"info","ts":"2023-04-17T14:35:52+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":36},"contentChanges":[{"range":{"start":{"line":27,"character":9},"end":{"line":27,"character":9}},"text":"a"}]}}

At this point, this is what's in the editor.

Screenshot 2023-04-20 at 09 01 06

I can't see what was in templ's understanding of the file (you can see that via the web view), but it's probably OK. We're working on line index 27, which is shown as line 28 in the editor.

Next, you can see the deletion of the a:

{"level":"info","ts":"2023-04-17T14:35:53+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":37},"contentChanges":[{"range":{"start":{"line":27,"character":9},"end":{"line":27,"character":10}},"rangeLength":1,"text":""}]}}

Followed by the deletion of the n:

{"level":"info","ts":"2023-04-17T14:35:53+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":38},"contentChanges":[{"range":{"start":{"line":27,"character":8},"end":{"line":27,"character":9}},"rangeLength":1,"text":""}]}}

Period:

{"level":"info","ts":"2023-04-17T14:35:53+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":39},"contentChanges":[{"range":{"start":{"line":27,"character":7},"end":{"line":27,"character":8}},"rangeLength":1,"text":""}]}}

Deletion of the u:

{"level":"info","ts":"2023-04-17T14:35:54+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":40},"contentChanges":[{"range":{"start":{"line":27,"character":6},"end":{"line":27,"character":7}},"rangeLength":1,"text":""}]}}

Deletion of char 6, which is the brace.

{"level":"info","ts":"2023-04-17T14:35:54+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":41},"contentChanges":[{"range":{"start":{"line":27,"character":5},"end":{"line":27,"character":6}},"rangeLength":1,"text":""}]}}

Then the whole of that line (to clear out the whitespace).

{"level":"info","ts":"2023-04-17T14:35:54+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":42},"contentChanges":[{"range":{"start":{"line":27,"character":0},"end":{"line":28,"character":0}},"rangeLength":6,"text":""}]}}

When you save the file, the client rewrites everything from the end of the import statement.

{"level":"info","ts":"2023-04-17T14:35:55+01:00","caller":"proxy/server.go:505","msg":"client -> server: DidSave"}
{
  "level": "info",
  "ts": "2023-04-17T14:35:55+01:00",
  "caller": "proxy/server.go:394",
  "msg": "client -> server: DidChange",
  "params": {
    "textDocument": {
      "uri": "file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ",
      "version": 44
    },
    "contentChanges": [
      {
        "range": {
          "start": {
            "line": 0,
            "character": 18
          },
          "end": {
            "line": 53,
            "character": 0
          }
        },
        "rangeLength": 1078,
        "text": "\n\nimport \"examples/shared\"\n\ntempl demo(users map[int]user) {\n\t<h3 class=\"subtitle\">Select Rows And Activate Or Deactivate Below</h3>\n\t<table class=\"table\">\n\t\t<thead>\n\t\t\t<tr>\n\t\t\t\t<td></td>\n\t\t\t\t<td>Name</td>\n\t\t\t\t<td>Email</td>\n\t\t\t\t<td>Status</td>\n\t\t\t</tr>\n\t\t</thead>\n\t\t@tbody(users)\n\t</table>\n}\n\ntempl tbody(users map[int]user) {\n\t<tbody id=\"tbody\">\n\t\tfor _, u := range users {\n\t\t\t<tr>\n\t\t\t\t<td></td>\n\t\t\t\t<td></td>\n\t\t\t\t<td></td>\n\t\t\t\t<td></td>\n\t\t\t</tr>\n\t\t}\n\t</tbody>\n}\n\ntempl Index(users map[int]user) {\n\t@shared.Layout(\"Bulk Update\") {\n\t\t<h2 class=\"title\">Bulk Update</h2>\n\t\t<p>This demo shows how to implement a common pattern where rows are selected and then bulk updated. This is accomplished by putting a form around a table, with checkboxes in the table, and then including the checked values in <code>PUT</code>’s to two different endpoints: <code>activate</code>and <code>deactivate</code>:</p>\n\t\t<pre><code class=\"language-html\">\n\t@shared.Raw() {\n\t\t@demo(users)\n\t}\n</code></pre>\n\t\t<h2 class=\"title\">Demo</h2>\n\t\t@demo(users)\n\t}\n}\n"
      }
    ]
  }
}

The content looks OK.

import "examples/shared"

templ demo(users map[int]user) {
    <h3 class="subtitle">Select Rows And Activate Or Deactivate Below</h3>
    <table class="table">
        <thead>
            <tr>
                <td></td>
                <td>Name</td>
                <td>Email</td>
                <td>Status</td>
            </tr>
        </thead>
        @tbody(users)
    </table>
}

templ tbody(users map[int]user) {
    <tbody id="tbody">
        for _, u := range users {
            <tr>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
        }
    </tbody>
}

templ Index(users map[int]user) {
    @shared.Layout("Bulk Update") {
        <h2 class="title">Bulk Update</h2>
        <p>This demo shows how to implement a common pattern where rows are selected and then bulk updated. This is accomplished by putting a form around a table, with checkboxes in the table, and then including the checked values in <code>PUT</code>’s to two different endpoints: <code>activate</code>and <code>deactivate</code>:</p>
        <pre><code class="language-html">
    @shared.Raw() {
        @demo(users)
    }
</code></pre>
        <h2 class="title">Demo</h2>
        @demo(users)
    }
}
a-h commented 1 year ago

We see u get inserted at 31 seconds:

{"level":"info","ts":"2023-04-17T14:35:57+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":46},"contentChanges":[{"range":{"start":{"line":24,"character":8},"end":{"line":24,"character":8}},"text":"u"}]}}

Then get deleted.

{"level":"info","ts":"2023-04-17T14:35:58+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":47},"contentChanges":[{"range":{"start":{"line":24,"character":8},"end":{"line":24,"character":9}},"rangeLength":1,"text":""}]}}

Then the open brace and close brace get inserted.

{"level":"info","ts":"2023-04-17T14:35:58+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":48},"contentChanges":[{"range":{"start":{"line":24,"character":8},"end":{"line":24,"character":8}},"text":"{"}]}}
{"level":"info","ts":"2023-04-17T14:35:59+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":49},"contentChanges":[{"range":{"start":{"line":24,"character":9},"end":{"line":24,"character":9}},"text":"}"}]}}

At this point, whatever was in the Go code wasn't liked. Perhaps if there's empty braces, it results in a syntax error in the generated Go code. I expect the generator could be improved to simply ignore empty braces.

{"level":"info","ts":"2023-04-17T14:35:59+01:00","caller":"proxy/client.go:50","msg":"client <- server: PublishDiagnostics"}
{"level":"info","ts":"2023-04-17T14:35:59+01:00","caller":"proxy/client.go:52","msg":"client <- server: PublishDiagnostics: [0]","diagnostic":{"range":{"start":{"line":203,"character":10},"end":{"line":203,"character":10}},"severity":1,"source":"syntax","message":"expected '==', found '='"}}

Next, u and . are added (33 seconds), and this triggers completion. Chopping out the irrelevant lines, you can see it's working fine and returns 3 items.

{"level":"info","ts":"2023-04-17T14:36:00+01:00","caller":"proxy/server.go:309","msg":"client -> server: Completion"}
{"level":"info","ts":"2023-04-17T14:36:00+01:00","caller":"proxy/server.go:70","msg":"updatePosition: found","uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","fromTempl":"24:11","toGo":"202:24"}
{"level":"info","ts":"2023-04-17T14:36:00+01:00","caller":"proxy/server.go:330","msg":"completion: received items","count":3}
{"level":"info","ts":"2023-04-17T14:36:00+01:00","caller":"proxy/server.go:338","msg":"client -> server: Completion end"}

Next, we see n being added, presumably with a view to select name from the list.

{"level":"info","ts":"2023-04-17T14:36:01+01:00","caller":"proxy/server.go:394","msg":"client -> server: DidChange","params":{"textDocument":{"uri":"file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ","version":52},"contentChanges":[{"range":{"start":{"line":24,"character":11},"end":{"line":24,"character":11}},"text":"n"}]}}

A diagnostic comes back, because there's no field called n.

{"level":"info","ts":"2023-04-17T14:36:01+01:00","caller":"proxy/client.go:52","msg":"client <- server: PublishDiagnostics: [0]","diagnostic":{"range":{"start":{"line":202,"character":24},"end":{"line":202,"character":25}},"severity":1,"code":"MissingFieldOrMethod","codeDescription":{"href":"https://pkg.go.dev/golang.org/x/tools/internal/typesinternal?utm_source=gopls#MissingFieldOrMethod"},"source":"compiler","message":"u.n undefined (type user has no field or method n)"}}

Then we see the selection get made. The Neovim client does something a bit weird! It writes it character at a time (a, m, e), then deletes it, then inserts name

{
  "level": "info",
  "ts": "2023-04-17T14:36:01+01:00",
  "caller": "proxy/server.go:394",
  "msg": "client -> server: DidChange",
  "params": {
    "textDocument": {
      "uri": "file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ",
      "version": 57
    },
    "contentChanges": [
      {
        "range": {
          "start": {
            "line": 24,
            "character": 12
          },
          "end": {
            "line": 24,
            "character": 12
          }
        },
        "text": "a"
      },
      {
        "range": {
          "start": {
            "line": 24,
            "character": 13
          },
          "end": {
            "line": 24,
            "character": 13
          }
        },
        "text": "m"
      },
      {
        "range": {
          "start": {
            "line": 24,
            "character": 14
          },
          "end": {
            "line": 24,
            "character": 14
          }
        },
        "text": "e"
      },
      {
        "range": {
          "start": {
            "line": 24,
            "character": 11
          },
          "end": {
            "line": 24,
            "character": 15
          }
        },
        "rangeLength": 4,
        "text": ""
      },
      {
        "range": {
          "start": {
            "line": 24,
            "character": 11
          },
          "end": {
            "line": 24,
            "character": 11
          }
        },
        "text": "name"
      }
    ]
  }
}
a-h commented 1 year ago

The client then does something very weird. It inserts a copy of everything at line index 0, col index 18.

{
  "level": "info",
  "ts": "2023-04-17T14:36:01+01:00",
  "caller": "proxy/server.go:394",
  "msg": "client -> server: DidChange",
  "params": {
    "textDocument": {
      "uri": "file:///Users/joe.davidson/src/joerdav/go-htmx-examples/bulkupdate/templates.templ",
      "version": 58
    },
    "contentChanges": [
      {
        "range": {
          "start": {
            "line": 0,
            "character": 18
          },
          "end": {
            "line": 0,
            "character": 18
          }
        },
        "text": "\n\n\t\t\t\timport \"examples/shared\"\n\n\t\t\t\ttempl demo(users map[int]user) {\n\t\t\t\t\t<h3 class=\"subtitle\">Select Rows And Activate Or Deactivate Below</h3>\n\t\t\t\t\t<table class=\"table\">\n\t\t\t\t\t\t<thead>\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td></td>\n\t\t\t\t\t\t\t\t<td>Name</td>\n\t\t\t\t\t\t\t\t<td>Email</td>\n\t\t\t\t\t\t\t\t<td>Status</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t</thead>\n\t\t\t\t\t\t@tbody(users)\n\t\t\t\t\t</table>\n\t\t\t\t}\n\n\t\t\t\ttempl tbody(users map[int]user) {\n\t\t\t\t\t<tbody id=\"tbody\">\n\t\t\t\t\t\tfor _, u := range users {\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td></td>\n\t\t\t\t\t\t\t\t<td>{u.name}</td>\n\t\t\t\t\t\t\t\t<td></td>\n\t\t\t\t\t\t\t\t<td></td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t}\n\t\t\t\t\t</tbody>\n\t\t\t\t}\n\n\t\t\t\ttempl Index(users map[int]user) {\n\t\t\t\t\t@shared.Layout(\"Bulk Update\") {\n\t\t\t\t\t\t<h2 class=\"title\">Bulk Update</h2>\n\t\t\t\t\t\t<p>This demo shows how to implement a common pattern where rows are selected and then bulk updated. This is accomplished by putting a form around a table, with checkboxes in the table, and then including the checked values in <code>PUT</code>’s to two different endpoints: <code>activate</code>and <code>deactivate</code>:</p>\n\t\t\t\t\t\t<pre><code class=\"language-html\">\n\t\t\t\t\t@shared.Raw() {\n\t\t\t\t\t\t@demo(users)\n\t\t\t\t\t}\n\t\t\t\t</code></pre>\n\t\t\t\t\t\t<h2 class=\"title\">Demo</h2>\n\t\t\t\t\t\t@demo(users)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpackage bulkupdate"
      }
    ]
  }
}

That's clearly the wrong thing for the client to do, since it's doing an insert instead of an overwrite. I'd want to check that it's definitely the LSP client that's doing the wrong thing, but the code that handles this process at https://github.com/a-h/templ/blob/6f8e2543a136f2f0aeb6a0beb1c9f76132ad1737/cmd/templ/lspcmd/proxy/server.go#L393-L434 logs the input params before doing any modifications to the range, so I think it's correct.

Definitely worth checking what the templ LSP web view thinks it has internally, to check that it's the same as what you're seeing in your editor.

joerdav commented 1 year ago

Awesome thanks for the breakdown of that! I'll gather some more evidence, and try avoid making mistakes in the recording, to save some time!

joerdav commented 1 year ago

Interestingly this stops happening when I disable my format on save plugin. Maybe rather than the lsp this is related to templ fmt (or my usage of it). Or at the very least it's related to templ lsp not playing well with formatters.

joerdav commented 1 year ago

Here's the log file of my most recent example, with fewer random mistakes! https://gist.github.com/joerdav/be6178059b52ab55cd79098700fdcdab

a-h commented 1 year ago

To help debug this (and other LSP related issues), I've started creating a Nix flake in the root of the project that includes all of the development dependencies (Go, gopls, xc etc.) and sets up a Neovim environment with the LSP configuration all included.

You can use it with xc nix-develop, which runs nix develop --impure if you have Nix installed, or you can use the Docker build script to build it inside a Docker container (xc docker-build) which uses the Nix Docker image and builds all the Nix stuff inside there.

Docker is useful to make sure that none of your system's existing programs are being used. It's relatively slow (2 mins) to build a Docker container from my Mac using Docker and Nix, because it rebuilds all of the Nix stuff from scratch each run. I need to set up a Linux remote builder at some point, then I can generate a Docker image directly from Nix itself.

Then you can use the Docker image in your examples directory to test.

docker run -it --rm -v `pwd`:/go-htmx-examples templ:latest

At the moment, the LSP config was borrowed from my personal configuration, so it's a bit heavyweight. I've been cutting it down.

The branch is here:

https://github.com/a-h/templ/tree/neovim_docker

a-h commented 1 year ago

To rule out issues with the plugins, I've updated everything to the latest version, and also upgraded to Neovim 0.9.0 in the Docker container to make sure that it's not an issue with Neovim 0.8.0.

a-h commented 1 year ago

Looks like the problem is with Luasnip, or cmp-luasnip. I ripped everything out except the bare minimum LSP config and it was still a problem, so I swapped out to vsnip and... the problems went away.

There's a config here, with a Docker build etc.

https://github.com/a-h/templ/tree/minimal_neovim

You need to use :set filetype = templ to get the LSP to take effect (I ripped out everything I could!).

joerdav commented 1 year ago

Ah yep, I'm on luaSnip too so that adds up!

a-h commented 12 months ago

Going to close this, I don't think there's a clear issue or reproduction. Feel free to re-open though.