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

Discussion about IDE integration #80

Open
aight8 opened this issue Dec 3, 2020 · 4 comments
Open

Discussion about IDE integration #80

aight8 opened this issue Dec 3, 2020 · 4 comments

Comments

@aight8
Copy link

aight8 commented Dec 3, 2020

I use quicktemplate because I love the static type checking, and the blazing fast performance is a very pleasant side-effect.

How much work it would be to create a full featured IDE integration (intellij-idea/vscode)
I really miss it, and also make some research how this could work.

The functionality should be:

  • HTML support (incl. tag renaming etc. what the IDE support)
  • Inside the % scope the context is treated as go code (especially highlighting, autocompletion!, refactoring, and others)
  • Everything else should be treated as a comment

Beside the separation, I hoped to implement a template plugin which use the inbuilt HTML processor for all the HTML, then define the parts which are go code.
In the background the current file gets compile on every change. The cursor position in the template file is translated to the generated go template file (I saw that all the compiled lines are nicely inlined so there is definitly some information arround ;) or can be extended). The golang language server in queried for the translated cursor position for autocompletion results or whatever. This would work it 90% the time, and that would be great!

Have somebody time to work with me on this in a separate repo or have inputs, docs etc. Then post it here.

@a-h
Copy link

a-h commented Apr 4, 2021

Did you get anywhere with this? One of the things I like about quicktemplate is that you just write Go code, rather than having to have a custom loop syntax etc.

I tried out two plugins for vim and VS Code, but they just provide syntax highlighting. On the Go side of things, I'd like autocomplete, error checking, go to definition, automatic package imports etc., and on the HTML side, tag validation, attribute autocomplete etc.

I think your suggestion of building a Language Server to translate requests back to HTML and Go LSPs (gopls) would be a good way to support common editors.

Without the editor support, editing isn't as slick as you'd get in something like JSX in JavaScript. For example, this looks right, but will fail at compile time, because the strings package hasn't been imported.

Hello is a simple template function.
{% func Hello(name string) %}
	Hello, {%s strings.ToUpper(name) %}!
{% endfunc %}

Here, I wouldn't get autocomplete on the data variable, so I'd have to wait until compile time to see that there's no Z field on the Data struct.

type Data struct {
  A string
  B string
}
{% func Hello(data Data) %}
	Z: {%s data.Z %}!
{% endfunc %}

@a-h
Copy link

a-h commented Apr 5, 2021

Looking into this a bit further, I think the best approach might be to create an LSP for quicktemplate and to add the required features one-by-one.

gopls doesn't seem to be a good basis, because the internal parts of the project can't easily be referenced, but https://github.com/sourcegraph/go-langserver looks useful.

While the sourcegraph example is good, it had loads of configuration and so it was a bit complicated: https://github.com/sourcegraph/go-langserver/blob/4b49d01c8a692968252730d45980091dcec7752e/main.go#L173

So, I cut it down to be a minimal example and hooked it up to Neovim with the CoC plugin (https://github.com/neoclide/coc.nvim) for testing. To get Neovim to send commands to my LSP binary, I had to update my CoC config to add in my .qtpl language server.

{
  "languageserver": {
    "qtpl": {
      "command": "/Users/adrian/github.com/a-h/qt-lsp/qt-lsp",
      "filetypes": ["qtpl"]
    }
}

Next, I had to tell Neovim that the file I was working on was a qtpl file with the :set filetype=qtpl command.

Then, I could read the logs the LSP left behind:

{"level":"info","ts":1617638502.683124,"caller":"qt-lsp/main.go:23","msg":"Starting up..."}
{"level":"info","ts":1617638502.683669,"caller":"qt-lsp/handler.go:26","msg":"request","req":{"method":"initialize","params":{"processId":47279,"rootPath":"/Users/adrian/github.com/a-h","rootUri":"file:///Users/adrian/github.com/a-h","capabilities":{"workspace":{"applyEdit":true,"workspaceEdit":{"documentChanges":true,"resourceOperations":["create","rename","delete"],"failureHandling":"textOnlyTransactional"},"didChangeConfiguration":{"dynamicRegistration":true},"didChangeWatchedFiles":{"dynamicRegistration":true},"symbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"tagSupport":{"valueSet":[1]}},"executeCommand":{"dynamicRegistration":true},"configuration":true,"workspaceFolders":true},"textDocument":{"publishDiagnostics":{"relatedInformation":true,"versionSupport":false,"tagSupport":{"valueSet":[1,2]}},"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"contextSupport":true,"completionItem":{"snippetSupport":true,"commitCharactersSupport":true,"documentationFormat":["markdown","plaintext"],"deprecatedSupport":true,"preselectSupport":true,"tagSupport":{"valueSet":[1]}},"completionItemKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"signatureHelp":{"dynamicRegistration":true,"contextSupport":true,"signatureInformation":{"documentationFormat":["markdown","plaintext"],"activeParameterSupport":true,"parameterInformation":{"labelOffsetSupport":true}}},"definition":{"dynamicRegistration":true},"references":{"dynamicRegistration":true},"documentHighlight":{"dynamicRegistration":true},"documentSymbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"hierarchicalDocumentSymbolSupport":true,"tagSupport":{"valueSet":[1]}},"codeAction":{"dynamicRegistration":true,"isPreferredSupport":true,"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}}},"codeLens":{"dynamicRegistration":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true},"onTypeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true,"prepareSupport":true},"documentLink":{"dynamicRegistration":true,"tooltipSupport":true},"typeDefinition":{"dynamicRegistration":true},"implementation":{"dynamicRegistration":true},"declaration":{"dynamicRegistration":true},"colorProvider":{"dynamicRegistration":true},"foldingRange":{"dynamicRegistration":true,"rangeLimit":5000,"lineFoldingOnly":true},"selectionRange":{"dynamicRegistration":true}},"window":{"workDoneProgress":true}},"initializationOptions":{},"trace":"off","workspaceFolders":[{"uri":"file:///Users/adrian/github.com/a-h","name":"a-h"}],"clientInfo":{"name":"coc.nvim","version":"0.0.80"},"workDoneToken":"d4b23a3d-6591-477f-92b9-2ca9433ee73b"},"id":0,"jsonrpc":"2.0"}}
{"level":"info","ts":1617638502.683837,"caller":"qt-lsp/handler.go:32","msg":"response","resp":{"capabilities":{"textDocumentSync":2,"hoverProvider":true,"signatureHelpProvider":{"triggerCharacters":["(",","]},"definitionProvider":true,"typeDefinitionProvider":true,"referencesProvider":true,"documentSymbolProvider":true,"implementationProvider":true}}}

From here, I think I could work out how to add an autocomplete for functions that are in scope, to suggest imports etc., and to enable snippets.

@a-h
Copy link

a-h commented Apr 6, 2021

I got a "Hello World" running in Neovim. It doesn't actually do anything useful, just returns a constant for autocomplete requests.

Here's what that looks like:

https://asciinema.org/a/D0efR6Inmel9JHEZiqHxbCSuR

Here's the demo language server:

https://github.com/a-h/qt-lsp

@a-h
Copy link

a-h commented May 3, 2021

I had a look at implementing the idea in quicktemplate, but the templating language felt a bit too complex to bite off adding the features in one go (things like the spacing operators, support for outputs other than HTML, inclusion of arbitrary files etc.). Also, wasn't sure how the way that the generator emitted code would work, was hoping there would be an object model to parse into, then generate from that.

So I took some things that I liked about quicktemplate, and built something that would be easy to add IDE support, and that targets HTML only. I built a parser that parses to an object model, a Go code generator (compiler) that maintains a sourcemap during code generation, and an LSP that is able to proxy to gopls over at https://github.com/a-h/templ

The proxy can present autocomplete suggestions so far, but it's not currently possible to use them because I've only rewritten the LSP requests on the way to gopls, I still need to remap the text ranges on the way back. However, it's showing some promise as an approach:

https://asciinema.org/a/JEiVL1AJOro2OgXDsBtkX0cCb

The syntax highlighting in the demo comes from a vim plugin my friend made.

I think the same approach I've used could work for quicktemplate, by maintaining the source map and using an LSP proxy. What do you think @valyala?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants