Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lsp: Add HTML Intellisense Support to Templ LSP #498

Open
salihdhaifullah opened this issue Feb 7, 2024 · 24 comments
Open

lsp: Add HTML Intellisense Support to Templ LSP #498

salihdhaifullah opened this issue Feb 7, 2024 · 24 comments
Labels
enhancement New feature or request lsp NeedsInvestigation Issue needs some investigation before being fixed

Comments

@salihdhaifullah
Copy link

Currently, the Templ LSP lacks native support for HTML language features. This feature request is to propose the addition of HTML LSP support to the Templ LSP.

@joerdav joerdav changed the title Feature Request: Add HTML Intellisense Support to Templ LSP lsp: Add HTML Intellisense Support to Templ LSP Feb 7, 2024
@joerdav joerdav added enhancement New feature or request lsp labels Feb 7, 2024
@joerdav
Copy link
Collaborator

joerdav commented Feb 7, 2024

Hi @salihdhaifullah thanks for the proposal.

I just wanted to check if you've tried out the HTML options that are outlined in the docs here: https://templ.guide/commands-and-tools/ide-support

I'm using Neovim and have enabled the HTML LSP on templ files and it works perfectly.

If you're using vscode then the option is to enable emmet completion.

If these are not sufficient then can you please reopen this ticket with details of what you found was lacking?

@joerdav joerdav closed this as completed Feb 7, 2024
@yardenshoham
Copy link

I think they mean something like
image

It doesn't work in templ
image

@joerdav
Copy link
Collaborator

joerdav commented Feb 8, 2024

Okay, yes I see that emmet does not provide this feature. In Neovim you can just enable the HTML LSP on templ files which works alongside the templ LSP:

image

I can see that you can modify file associations in vscode, but that means that the templ plugin won't run on the files. I wonder if there is a way to expand the list of filetypes that vscode runs the html LSP on.

@joerdav joerdav reopened this Feb 8, 2024
@joerdav joerdav added the NeedsInvestigation Issue needs some investigation before being fixed label Feb 8, 2024
@joerdav
Copy link
Collaborator

joerdav commented Feb 8, 2024

I've marked the issue as needing some investigation, so we can explore options before taking this as feature for the templ LSP.

@joerdav
Copy link
Collaborator

joerdav commented Feb 8, 2024

Unfortunately, I don't think there is a solution for this. Go templates has the exact same problem in vscode, either you edit the file as a gotmpl or as a html file:

https://github.com/golang/vscode-go/wiki/features#go-template-syntax-highlighting

@yardenshoham
Copy link

Can the templ LSP proxy the HTML LSP?

@joerdav
Copy link
Collaborator

joerdav commented Feb 8, 2024

I think @a-h had some thoughts on this. But I can see proxying another LSP as a pain, it's really frustrating that vscode currently doesn't support multiple file associations.

Often the templ LSP has trouble finding gopls to run and proxy, I think having more executables required for templ LSP would damage the ease of install and config. I guess an option would be for proxying the HTML LSP to be an optional feature if the LSP is found, but again balancing multiple LSPs should be the role of the client as Neovim does.

@yardenshoham
Copy link

Gotcha. I chose templ because I thought it supports this and that (for me) was the "wow" effect. From https://templ.guide/:

Great developer experience: Ships with IDE autocompletion.

I assumed that meant HTML as templ is for HTML.

@joerdav
Copy link
Collaborator

joerdav commented Feb 8, 2024

Ah, no that's mostly for the Go side of it, type safety and autocomplete of Go expressions.

@a-h
Copy link
Owner

a-h commented Feb 8, 2024

We've talked about HTML intellisense support, but haven't reached consensus on what specific features people are talking about, so it's been hard to understand how/if to proceed.

For example, in #498 (comment), the expectation seems to be that if you put a type attribute on an input should provide a list of stuff, with the assumption seems to be that whatever VS Code does is what we want.

The reason I don't know about the expectations of people, is because people (like me) that don't write static HTML have never seen VS Code's behaviour, so we don't "miss" it. For example, here's what you get in VSCode for JSX (React):

jsx

I also tested PHP, which doesn't do anything either. 😁

Assuming we wanted to get all of VS Code's HTML autocomplete behaviour, in https://github.com/microsoft/vscode-extension-samples/blob/main/lsp-embedded-language-service/server/src/languageModes.ts#L46 you can see that VS Code's example embeds the extracted HTML language server (written in JS) into the project. That's how the html1 language is able to define 'areas' of standard HTML.

So, when @joerdav says that we'd have to find more LSPs to run, that's what he means. Templ proxies through to the gopls language server (literally running a gopls process to do so) and re-maps the positions. So users, to get the additional LSP features, would need to have the HTML LSP pre-installed and on their path to get the feature.

To get templ to work in this way we'd have to do the following:

  • Make it so it's possible to get templ to render just the HTML tags, text and attributes, and blank everything else out (e.g if statements, switch statements etc.).
  • Update the templ parser to store the source code ranges of HTML content, like it does with Go expressions.
  • Get the templ LSP to look for the HTML language server at startup, and if it finds it, start it up.
  • If autocomplete is triggered in a HTML range, pass just the HTML content over to the HTML LSP and get it to execute the autocomplete, applying the edits on the way back

However, I'd rather not spin up a Node.js process just to run a bit of HTML autocomplete, so if all that people really want is autocomplete on tag names, attribute names, and attribute values, we could probably add that directly to the templ LSP. Essentially, the same process, but instead of starting up the LSP, you work out whether the current cursor position is at the element name, attribute name or attribute value position, and trigger the expected autocomplete.

@yardenshoham
Copy link

yardenshoham commented Feb 10, 2024

Having this, in my honest opinion, would make templ better than any other HTML templating engine out there

@a-h
Copy link
Owner

a-h commented Feb 11, 2024

Thinking about how to implement part 1, of just rendering the HTML tags...

There are a few interfaces that represent all of the possible things that can make up a templ Template File. These are things like StringExpression, Element, ConstantAttribute etc.

They all match one of the interface:

templ/parser/v2/types.go

Lines 403 to 407 in 8a1cd80

type Node interface {
IsNode() bool
// Write out the string.
Write(w io.Writer, indent int) error
}

templ/parser/v2/types.go

Lines 159 to 163 in 8a1cd80

// TemplateFileNode can be a Template, CSS, Script or Go.
type TemplateFileNode interface {
IsTemplateFileNode() bool
Write(w io.Writer, indent int) error
}

templ/parser/v2/types.go

Lines 690 to 693 in 8a1cd80

type Attribute interface {
// Write out the string.
Write(w io.Writer, indent int) error
}

Note the Write(w io.Writer, indent bool) error) method on the interface. Templ parses *.templ files into an object model (

templ/parser/v2/types.go

Lines 107 to 116 in 8a1cd80

type TemplateFile struct {
// Header contains comments or whitespace at the top of the file.
Header []TemplateFileGoExpression
// Package expression.
Package Package
// Nodes in the file.
Nodes []TemplateFileNode
// Diagnostics contains any errors or warnings.
Diagnostics []Diagnostic
}
).

To format templ files, templ parses into the TemplateFile struct and calls the Write function.

func (tf TemplateFile) Write(w io.Writer) error {
.

If this Write function took a context aware thing, then the TemplateFile could render out just the HTML parts, and that could be presented to a number of downstream HTML-expecting LSPs, like the HTMX one, and the extracted VS Code HTML server.

Note how the positions of the HTML are retained - the Go expressions are just blanked out, or turned to valid HTML that takes up the same space.

Heres an example of what could look like.

https://go.dev/play/p/rs3KRDUqlpx

You get the first output for everything, and the second output for just HTML.

<a href='google.com' style={css()}>Click</a>
<a href='google.com' style="     ">Click</a>

@a-h a-h mentioned this issue Feb 13, 2024
@stephenafamo
Copy link
Contributor

I think this is great, and while I understand how this would perhaps make the templ LSP seem "heavy", I think the developer experience is very much worth it.

After HTML, we could also proxy JS to another LSP (personally I've been using Biome over tsserver).

It would also then simplify the instructions for Emmet/Tailwind config since the instruction could then be to simply "turn them on" in the templ LSP configuration.

The downside of configurable LSP proxies is that it would be difficult to detect the user's LSP configuration, I wonder if templ does that for gopls at all.

@alehechka
Copy link
Contributor

alehechka commented Feb 19, 2024

I'm a believer that HTML LSP support in templ would be a massive win for the tooling of this language. One crazy idea I have would be to utilize the HTML LSP information that other tools have put together to statically generate the equivalent of it into the templ LSP. So for example, VSCode uses https://github.com/microsoft/vscode-html-languageservice for it's HTML and all of the needed data is available in JSON format here. We could create some type of automated process that translates that JSON into Go code to be used as the HTML LSP in templ. Then maybe have a cronjob check on the source to see if it's changed and if so re-run the generation script with the updates. The data source does get published to npm, so that'd be a fairly easy way to check on updates.

@a-h
Copy link
Owner

a-h commented Feb 19, 2024

That doesn't sound like a crazy idea to me @alehechka. I've started work on an LSP test suite at https://github.com/a-h/templ/tree/lsp_test_suite to improve the overall testing of the LSP integrations. It's not quite there yet, but I hope to get to a point where it's easy to add additional tests, which would make the prospect of implementing the HTML LSP in Go less intimidating. I think that the work to add positions to the templ parser results (boolean attribute/constant attribute/element etc.) would still be needed to make this possible.

@alehechka
Copy link
Contributor

@a-h, I'd love to help implement this if it's the route you'd like to go. Sounds like there's some upfront work to be done before pulling in the HTML source data. I have yet to contribute to the LSP side of things, so I'll spend some time reading up on the code to get familiar with it and see where I can plug-in to help out.

They also have the data available for CSS here. I personally use tailwind so this isn't as big of a deal for me, but I'm sure other devs would appreciate it as well long-term.

@Oudwins
Copy link

Oudwins commented Mar 12, 2024

Will the work on this issue help us get html LSP auto completion for templ.Attributes? Or some other way of passing through html attribute auto complete from a templ component into an element?

Not sure if I have explained it very well. I am refering to something like this:

templ Button(attrs templ.Attributes) {
    <button type="button" {attrs...} >
    {children...}
    </button>
}

Button(templ.Attributes{"auto-completion here"})

Together with a tailwind-merge port to go (of which we have 2 now), this is something that has been discussed a lot when talking about building reusable components. Personally I think this is one of the most important features missing right now.

@alehechka
Copy link
Contributor

alehechka commented Mar 15, 2024

Wanted to give an update on the HTML LSP baseline work.

@a-h
Copy link
Owner

a-h commented Mar 15, 2024

Nice work!

On replacing string content with spaces, we might need to use a function like this instead of strings.Repeat, because some strings will be multiline.

func whiteSpaceString(of string) string {
	output := []rune(of)
	for i, r := range output {
		if unicode.IsSpace(r) {
			continue
		}
		output[i] = ' '
	}
	return string(output)
}

@alehechka
Copy link
Contributor

Oooh, that's a really good call! Where newlines were involved the code was getting pretty ugly because I've been separately writing the newline characters with the current WriteContext so they get properly included in the output. Same goes for indents.

@alehechka
Copy link
Contributor

@Oudwins

Didn't want your comment above to get ignored. The updates I have been working on will add support exclusively for HTML auto complete (elements and constant attributes).

I think auto complete for templ.Attributes would fall out of scope for this initial implementation.

However, once the HTML LSP proxy is introduced, it may be possible to then configure string key/value pairs within the templ.Attributes to proxy a request as attributes.

@Oudwins
Copy link

Oudwins commented Mar 16, 2024

@alehechka no worries. Yes. Those were precisely my thoughts. As of right now it would be completely impossible but the html LSP proxying will make it possible, which makes this vital in my opinion. The comment was just to confirm that I was right in my assumptions.

@m50
Copy link

m50 commented Mar 27, 2024

We've talked about HTML intellisense support, but haven't reached consensus on what specific features people are talking about, so it's been hard to understand how/if to proceed.

One of the biggest annoyances that seems to be missing with the VSCode templ extension that exists with PHP and JSX (and obviously HTML) is autoclosing tags. In PHP if I type <div>, then it will prompt me to add the closing tag. In JSX, it automatically adds the closing tag if I didn't manually close it. In Templ, it doesn't do anything, and there doesn't appear to be a way to enable this in vscode at all.

I also tested PHP, which doesn't do anything either. 😁

Also, fwiw, PHP does support HTML autocomplete in VSCode, though it does appear to be a little spotty at times. Maybe this isn't in the default VSCode PHP implementation (which is absolute garbage, and PHP should never be done out of the box in VS Code 😅), not sure, since I am using Intelephense for my PHP language server.
image
(The error under the <?php line is because declare(strict_types=1) needs to be the very first line, and I'm putting a line above it, fwiw)

@DannyJJK
Copy link

Also, fwiw, PHP does support HTML autocomplete in VSCode, though it does appear to be a little spotty at times. Maybe this isn't in the default VSCode PHP implementation (which is absolute garbage, and PHP should never be done out of the box in VS Code 😅), not sure, since I am using Intelephense for my PHP language server.

I believe this is intelephense providing the autocomplete. I have used intelephense in other editors and it provides some level of HTML autocomplete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request lsp NeedsInvestigation Issue needs some investigation before being fixed
Projects
None yet
Development

No branches or pull requests

9 participants