tliron / glsp

Language Server Protocol SDK for Go
Apache License 2.0
165 stars 22 forks source link

Features status #2

Closed mickael-menu closed 3 years ago

mickael-menu commented 3 years ago

Albeit in early stage, your project is looking very promising!

It would be helpful to have a list of supported features to know if glsp could be a good fit for a given project. I'm trying to figure out if I should go with glsp or https://github.com/sourcegraph/go-lsp.

I would be willing to contribute as well if something I need is missing.

tliron commented 3 years ago

Thank you! I think the README mentions the supported features.

The main difference between this and go-lsp is also mentioned in the README: much more comprehensive coverage. Also, the code is documented with links to every struct and message in the LSP specification, so you can double-check as well as get instruction on how to use each message. What else do you think should be listed?

I started this project because go-lsp was insufficient.

mickael-menu commented 3 years ago

Thank you! I think the README mentions the supported features.

Ha I assumed it was more a goal than the actual state of the project, because of the disclaimer. Then that sounds great!

This is an early release. Some features are not yet fully implemented.

Also, the code is documented with links to every struct and message in the LSP specification, so you can double-check as well as get instruction on how to use each message. What else do you think should be listed?

The code is well documented and clear. Maybe a minimal implementation example could help to get started. I played a bit with glsp but didn't manage to make it work with coc.nvim yet.

First, I got the following crash: panic: logging not configured [recovered], which I solved by adding:

_ "github.com/tliron/kutil/logging/simple"

But it is not very obvious how to actually get the log messages.

At the moment my test LSP server seems to be stuck in the "starting" phase of coc.nvim. I couldn't figure out how to make it run despite checking your Puccini LS. Do you have any idea what could be missing?

package cmd

import (
    "fmt"
    "log"

    "github.com/tliron/glsp"
    protocol "github.com/tliron/glsp/protocol_3_16"
    "github.com/tliron/glsp/server"
    _ "github.com/tliron/kutil/logging/simple"
)

// LSP starts a server implementing the Language Server Protocol.
type LSP struct{}

var handler = protocol.Handler{}
var logger *log.Logger

func (cmd *LSP) Run() error {
    handler.Initialize = initialize
    handler.Initialized = initialized
    handler.Shutdown = shutdown
    handler.LogTrace = logTrace
    handler.SetTrace = setTrace
    handler.TextDocumentCompletion = textDocumentCompletion

    debug := true
    server := server.NewServer(&handler, "zk-lsp", debug)
    fmt.Println("STARTING")
    return server.RunTCP(":4230")
}

// initialize implements protocol.InitializeFunc
// Returns: InitializeResult | InitializeError
func initialize(context *glsp.Context, params *protocol.InitializeParams) (interface{}, error) {
    log.Println("INITIALIZE")

    // clientCapabilities = &params.Capabilities
    if params.Trace != nil {
        protocol.SetTraceValue(*params.Trace)
    }

    resolveProvider := true
    serverCapabilities := handler.CreateServerCapabilities()
    serverCapabilities.TextDocumentSync = protocol.TextDocumentSyncKindIncremental
    serverCapabilities.CompletionProvider = &protocol.CompletionOptions{
        ResolveProvider:   &resolveProvider,
        TriggerCharacters: []string{"#"},
    }

    version := "1.0"
    return &protocol.InitializeResult{
        Capabilities: serverCapabilities,
        ServerInfo: &protocol.InitializeResultServerInfo{
            Name:    "zk-lsp",
            Version: &version,
        },
    }, nil
}

// protocol.TextDocumentCompletionFunc signature
// Returns: []CompletionItem | CompletionList | nil
func textDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (interface{}, error) {
    var completionItems []protocol.CompletionItem
    completionItems = append(completionItems, protocol.CompletionItem{
        Label: "server",
    })
    log.Println("COMPLETION")
    return completionItems, nil
}

// protocol.InitializedFunc signature
func initialized(context *glsp.Context, params *protocol.InitializedParams) error {
    log.Println("INITIALIZED")
    return nil
}

// protocol.ShutdownFunc signature
func shutdown(context *glsp.Context) error {
    protocol.SetTraceValue(protocol.TraceValueOff)
    return nil
}

// protocol.LogTraceFunc signature
func logTrace(context *glsp.Context, params *protocol.LogTraceParams) error {
    return nil
}

// protocol.SetTraceFunc signature
func setTrace(context *glsp.Context, params *protocol.SetTraceParams) error {
    protocol.SetTraceValue(params.Value)
    return nil
}
tliron commented 3 years ago

Some of the pain points you are experiencing are why I put a "early release" warning. The problem is that indeed I did not do testing in many environments, for example no testing with coc.vim. Also I'm not sure how correct my TCP code is. Any help in debugging this would be very appreciated! I will assist in whatever way I can.

Logging can help a lot, of course. :) To enable logging you need to call logging.Configure with the verbosity (max is 2). See this example.

mickael-menu commented 3 years ago

Thanks, that did the trick for the logging 👍 There's some progress!

I figured that using stdio might be simpler than TCP, however I get the following error from Vim:

Screenshot 2021-03-27 at 21 08 40
2021-03-27T21:08:28.109 INFO (pid:30131) [services] - registered service "languageserver.zk"
2021-03-27T21:08:28.113 INFO (pid:30131) [services] - zk state change: stopped => starting
2021-03-27T21:08:28.117 INFO (pid:30131) [plugin] - coc.nvim 0.0.79-6fe357fc97 initialized with node: v15.1.0 after 54ms
2021-03-27T21:08:28.123 INFO (pid:30131) [language-client-index] - Language server "languageserver.zk" started with 30133
2021-03-27T21:08:28.501 ERROR (pid:30131) [server] - uncaughtException Error: Header must provide a Content-Length property.
    at StreamMessageReader.onData (/Users/mickael/.vim/plugged/coc.nvim/build/index.js:18138:27)
    at Socket.<anonymous> (/Users/mickael/.vim/plugged/coc.nvim/build/index.js:18123:18)
    at Socket.emit (node:events:327:20)
    at addChunk (node:internal/streams/readable:304:12)
    at readableAddChunk (node:internal/streams/readable:279:9)
    at Socket.Readable.push (node:internal/streams/readable:218:10)
    at Pipe.onStreamRead (node:internal/stream_base_commons:192:23)

And here are the logs from glsp, the connection seems to have been successful:

2021/03/27 21:08:28.497  INFO [zk-lsp.server] reading from stdin, writing to stdout
2021/03/27 21:08:28.499 DEBUG [zk-lsp.rpc] jsonrpc2: --> request #0: initialize: {"processId":30131,"rootPath":"/Users/mickael/Dropbox/Notes","rootUri":"file:///Users/mickael/Dropbox/Notes","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},"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,"signatureInformation":{"documentationFormat":["markdown","plaintext"],"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":"verbose","workspaceFolders":[{"uri":"file:///Users/mickael/Dropbox/Notes","name":"Notes"}],"clientInfo":{"name":"coc.nvim","version":"0.0.79"},"workDoneToken":"9a61d353-d957-4adb-9cec-fd1ddf994a0b"}
2021/03/27 21:08:28.499 DEBUG [zk-lsp.rpc] jsonrpc2: <-- result #0: initialize: {"capabilities":{"textDocumentSync":2,"completionProvider":{"triggerCharacters":["#"],"resolveProvider":true}},"serverInfo":{"name":"zk-lsp","version":"1.0"}}
mickael-menu commented 3 years ago

It might be that coc.nvim is more demanding, because I managed to run my LS with Sublime Text:

:: --> zk initialize(1): {'initializationOptions': {}, 'rootPath': None, 'clientInfo': {'version': '0.14.3', 'name': 'Sublime Text LSP'}, 'capabilities': {'workspace': {'workspaceEdit': {'documentChanges': True, 'failureHandling': 'abort'}, 'didChangeConfiguration': {'dynamicRegistration': True}, 'applyEdit': True, 'executeCommand': {}, 'configuration': 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]}}, 'workspaceFolders': True}, 'window': {'showMessage': {'messageActionItem': {'additionalPropertiesSupport': True}}, 'workDoneProgress': True}, 'experimental': {}, 'textDocument': {'hover': {'contentFormat': ['markdown', 'plaintext'], 'dynamicRegistration': True}, 'declaration': {'dynamicRegistration': True, 'linkSupport': True}, 'completion': {'completionItem': {'snippetSupport': True}, 'dynamicRegistration': True, '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]}}, 'rangeFormatting': {'dynamicRegistration': True}, 'references': {'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}, 'definition': {'dynamicRegistration': True, 'linkSupport': True}, 'synchronization': {'didSave': True, 'willSave': True, 'dynamicRegistration': True, 'willSaveWaitUntil': True}, 'documentHighlight': {'dynamicRegistration': True}, 'colorProvider': {'dynamicRegistration': True}, 'signatureHelp': {'signatureInformation': {'documentationFormat': ['markdown', 'plaintext'], 'parameterInformation': {'labelOffsetSupport': True}}, 'dynamicRegistration': True}, 'publishDiagnostics': {'relatedInformation': True}, 'rename': {'dynamicRegistration': True}, 'typeDefinition': {'dynamicRegistration': True, 'linkSupport': True}, 'implementation': {'dynamicRegistration': True, 'linkSupport': True}, 'formatting': {'dynamicRegistration': True}, 'codeAction': {'codeActionLiteralSupport': {'codeActionKind': {'valueSet': []}}, 'dynamicRegistration': True}}}, 'rootUri': None, 'processId': 31466, 'workspaceFolders': None}
zk: 2021/03/27 21:45:08 INITIALIZE
:: <<< zk 1: {'capabilities': {'completionProvider': {'triggerCharacters': ['#'], 'resolveProvider': True}, 'textDocumentSync': 2}, 'serverInfo': {'version': '1.0', 'name': 'zk-lsp'}}
zk: 2021/03/27 21:45:08 INITIALIZED
::  -> zk initialized: {}
2021/03/27 21:45:08.263  INFO [zk-lsp.server] reading from stdin, writing to stdout
2021/03/27 21:45:08.264 DEBUG [zk-lsp.rpc] jsonrpc2: --> request #1: initialize: {"initializationOptions":{},"rootPath":null,"clientInfo":{"version":"0.14.3","name":"Sublime Text LSP"},"capabilities":{"workspace":{"workspaceEdit":{"documentChanges":true,"failureHandling":"abort"},"didChangeConfiguration":{"dynamicRegistration":true},"applyEdit":true,"executeCommand":{},"configuration":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]}},"workspaceFolders":true},"window":{"showMessage":{"messageActionItem":{"additionalPropertiesSupport":true}},"workDoneProgress":true},"experimental":{},"textDocument":{"hover":{"contentFormat":["markdown","plaintext"],"dynamicRegistration":true},"declaration":{"dynamicRegistration":true,"linkSupport":true},"completion":{"completionItem":{"snippetSupport":true},"dynamicRegistration":true,"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]}},"rangeFormatting":{"dynamicRegistration":true},"references":{"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},"definition":{"dynamicRegistration":true,"linkSupport":true},"synchronization":{"didSave":true,"willSave":true,"dynamicRegistration":true,"willSaveWaitUntil":true},"documentHighlight":{"dynamicRegistration":true},"colorProvider":{"dynamicRegistration":true},"signatureHelp":{"signatureInformation":{"documentationFormat":["markdown","plaintext"],"parameterInformation":{"labelOffsetSupport":true}},"dynamicRegistration":true},"publishDiagnostics":{"relatedInformation":true},"rename":{"dynamicRegistration":true},"typeDefinition":{"dynamicRegistration":true,"linkSupport":true},"implementation":{"dynamicRegistration":true,"linkSupport":true},"formatting":{"dynamicRegistration":true},"codeAction":{"codeActionLiteralSupport":{"codeActionKind":{"valueSet":[]}},"dynamicRegistration":true}}},"rootUri":null,"processId":31466,"workspaceFolders":null}
2021/03/27 21:45:08.265 DEBUG [zk-lsp.rpc] jsonrpc2: <-- result #1: initialize: {"capabilities":{"textDocumentSync":2,"completionProvider":{"triggerCharacters":["#"],"resolveProvider":true}},"serverInfo":{"name":"zk-lsp","version":"1.0"}}
2021/03/27 21:45:08.265 DEBUG [zk-lsp.rpc] jsonrpc2: --> notif: initialized: {}

Is there a way to debug the actual JSON-RPC messages sent with glsp, to see if the Content-Length header is really missing?

I was able to run a similar LS made with go-lsp, so I think my coc.nvim setup is fine.

mickael-menu commented 3 years ago

So, it was really dumb...

I figured out a way to print the output from glsp, and it seemed correct:

2021/03/28 11:29:48.590  INFO [zk-lsp.server] WRITING: "Content-Length: 192\r\n\r\n{\"id\":0,\"result\":{\"capabilities\":{\"textDocumentSync\":2,\"completionProvider\":{\"triggerCharacters\":[\"#\"],\"resolveProvider\":true}},\"serverInfo\":{\"name\":\"zk-lsp\",\"version\":\"1.0\"}},\"jsonrpc\":\"2.0\"}"

The issue was that I added some stdout log in my cli app, which of course was messing up the protocol communication when using the stdio mode. After removing it, it's working properly and I get some basic completion 👍

Closing this issue, thanks and keep up the good work!

tliron commented 3 years ago

Ha, that might have happened to me once or twice, too...

By the way, one quirk of GLSP is that it absolutely requires the Content-Length header. It is not optional.