From d320a462dc82a48dea0a5581e3e53f5a1440910d Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Sat, 7 Jan 2017 22:22:22 -0800 Subject: [PATCH 1/3] initial language server protocol implementation --- cmd/langserver-vim/main.go | 34 ++++++++ langserver/handle_initialize.go | 26 ++++++ langserver/handle_text_document_did_open.go | 27 ++++++ langserver/handle_text_document_symbol.go | 76 +++++++++++++++++ langserver/handler.go | 65 ++++++++++++++ langserver/lsp.go | 95 +++++++++++++++++++++ 6 files changed, 323 insertions(+) create mode 100644 cmd/langserver-vim/main.go create mode 100644 langserver/handle_initialize.go create mode 100644 langserver/handle_text_document_did_open.go create mode 100644 langserver/handle_text_document_symbol.go create mode 100644 langserver/handler.go create mode 100644 langserver/lsp.go diff --git a/cmd/langserver-vim/main.go b/cmd/langserver-vim/main.go new file mode 100644 index 0000000..b537e53 --- /dev/null +++ b/cmd/langserver-vim/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "context" + "log" + "os" + + "github.com/haya14busa/go-vimlparser/langserver" + "github.com/sourcegraph/jsonrpc2" +) + +func main() { + log.Println("langserver-vim: reading on stdin, writing on stdout") + var connOpt []jsonrpc2.ConnOpt + <-jsonrpc2.NewConn(context.Background(), jsonrpc2.NewBufferedStream(stdrwc{}, jsonrpc2.VSCodeObjectCodec{}), langserver.NewHandler(), connOpt...).DisconnectNotify() + log.Println("langserver-vim: connections closed") +} + +type stdrwc struct{} + +func (stdrwc) Read(p []byte) (int, error) { + return os.Stdin.Read(p) +} + +func (stdrwc) Write(p []byte) (int, error) { + return os.Stdout.Write(p) +} + +func (stdrwc) Close() error { + if err := os.Stdin.Close(); err != nil { + return err + } + return os.Stdout.Close() +} diff --git a/langserver/handle_initialize.go b/langserver/handle_initialize.go new file mode 100644 index 0000000..3bc74b0 --- /dev/null +++ b/langserver/handle_initialize.go @@ -0,0 +1,26 @@ +package langserver + +import ( + "context" + "encoding/json" + + "github.com/sourcegraph/jsonrpc2" +) + +func (h *LangHandler) handleInitialize(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) { + if req.Params == nil { + return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidParams} + } + + var params InitializeParams + if err := json.Unmarshal(*req.Params, ¶ms); err != nil { + return nil, err + } + + return InitializeResult{ + Capabilities: ServerCapabilities{ + TextDocumentSync: TDSKFull, + DocumentSymbolProvider: true, + }, + }, nil +} diff --git a/langserver/handle_text_document_did_open.go b/langserver/handle_text_document_did_open.go new file mode 100644 index 0000000..145bace --- /dev/null +++ b/langserver/handle_text_document_did_open.go @@ -0,0 +1,27 @@ +package langserver + +import ( + "context" + "encoding/json" + + "github.com/sourcegraph/jsonrpc2" +) + +func (h *LangHandler) handleTextDocumentDidOpen(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) { + if req.Params == nil { + return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidParams} + } + + var params DidOpenTextDocumentParams + if err := json.Unmarshal(*req.Params, ¶ms); err != nil { + return nil, err + } + + f, err := NewVimFile(params.TextDocument) + if err != nil { + return nil, err + } else { + h.files[params.TextDocument.URI] = f + return nil, nil + } +} diff --git a/langserver/handle_text_document_symbol.go b/langserver/handle_text_document_symbol.go new file mode 100644 index 0000000..07d59a1 --- /dev/null +++ b/langserver/handle_text_document_symbol.go @@ -0,0 +1,76 @@ +package langserver + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/haya14busa/go-vimlparser/ast" + + "github.com/sourcegraph/jsonrpc2" +) + +func (h *LangHandler) handleTextDocumentSymbols(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) { + if req.Params == nil { + return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidParams} + } + + var params DocumentSymbolParams + if err := json.Unmarshal(*req.Params, ¶ms); err != nil { + return nil, err + } + + if f, ok := h.files[params.TextDocument.URI]; ok { + node, err := f.GetAst() + if err != nil { + return nil, err + } else { + return getDocumentSymbols(params, node), nil + } + return nil, nil + } else { + return nil, errors.New(fmt.Sprintf("% not open", params.TextDocument.URI)) + } +} + +func getDocumentSymbols(params DocumentSymbolParams, node ast.Node) []SymbolInformation { + var symbols []SymbolInformation + ast.Inspect(node, func(n ast.Node) bool { + var name string + var kind SymbolKind + var pos ast.Pos + switch x := n.(type) { + case *ast.Function: + switch y := x.Name.(type) { + case *ast.Ident: + kind = SKFunction + name = y.Name + pos = y.NamePos + } + + if name != "" { + symbols = append(symbols, SymbolInformation{ + Name: name, + Kind: kind, + Location: Location{ + URI: params.TextDocument.URI, + Range: Range{ + Start: Position{ + Line: pos.Line - 1, + Character: pos.Column - 1, + }, + End: Position{ + Line: pos.Line - 1, + Character: pos.Column + len(name) - 1, + }, + }, + }, + }) + return false + } + } + return true + }) + return symbols +} diff --git a/langserver/handler.go b/langserver/handler.go new file mode 100644 index 0000000..03db40f --- /dev/null +++ b/langserver/handler.go @@ -0,0 +1,65 @@ +package langserver + +import ( + "bytes" + "context" + "fmt" + + "github.com/haya14busa/go-vimlparser" + "github.com/haya14busa/go-vimlparser/ast" + + "github.com/sourcegraph/jsonrpc2" +) + +func NewHandler() jsonrpc2.Handler { + var langHandler = &LangHandler{ + files: make(map[string]*vimfile), + } + return jsonrpc2.HandlerWithError(langHandler.handle) +} + +type LangHandler struct { + files map[string]*vimfile +} + +type vimfile struct { + TextDocumentItem + Ast *ast.File + AstError error +} + +func NewVimFile(textDocumentItem TextDocumentItem) (result *vimfile, error error) { + return &vimfile{ + TextDocumentItem: textDocumentItem, + Ast: nil, + AstError: nil, + }, nil +} + +func (f *vimfile) GetAst() (result *ast.File, error error) { + if f.AstError != nil { + return nil, f.AstError + } else if f.Ast != nil { + return f.Ast, nil + } else { + opt := &vimlparser.ParseOption{Neovim: false} + r := bytes.NewBufferString(f.Text) + ast, err := vimlparser.ParseFile(r, f.URI, opt) + f.Ast = ast + f.AstError = err + return ast, err + } +} + +func (h *LangHandler) handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) { + switch req.Method { + case "initialize": + return h.handleInitialize(ctx, conn, req) + case "textDocument/didOpen": + return h.handleTextDocumentDidOpen(ctx, conn, req) + case "textDocument/documentSymbol": + return h.handleTextDocumentSymbols(ctx, conn, req) + } + + return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeMethodNotFound, Message: fmt.Sprintf("method not supported: %s", req.Method)} +} diff --git a/langserver/lsp.go b/langserver/lsp.go new file mode 100644 index 0000000..cd74326 --- /dev/null +++ b/langserver/lsp.go @@ -0,0 +1,95 @@ +package langserver + +type InitializeParams struct { + ProcessID int `json:"processId,omitempty"` + RootPath string `json:"rootPath,omitempty"` + InitializationOptions InitializeOptions `json:"initializationOptions,omitempty"` + Capabilities ClientCapabilities `json:"capabilities",omitempty` + Trace string `json:"trace,omitempty"` +} + +type InitializeOptions struct { +} + +type ClientCapabilities struct { +} + +type InitializeResult struct { + Capabilities ServerCapabilities `json:"capabilities,omitempty"` +} + +type TextDocumentSyncKind int + +const ( + TDSKNone TextDocumentSyncKind = 0 + TDSKFull = 1 + TDSKIncremental = 2 +) + +type ServerCapabilities struct { + TextDocumentSync TextDocumentSyncKind `json:"textDocumentSync,omitempty"` + DocumentSymbolProvider bool `json:"documentSymbolProvider,omitempty"` +} + +type TextDocumentItem struct { + URI string `json:"uri"` + LanguageId string `json:"languageId"` + Version int `json:"version"` + Text string `json:"text"` +} + +type TextDocumentIdentifier struct { + URI string `json:"uri"` +} + +type DidOpenTextDocumentParams struct { + TextDocument TextDocumentItem `json:"textDocument"` +} + +type DocumentSymbolParams struct { + TextDocument TextDocumentIdentifier +} + +type SymbolKind int + +const ( + SKFile SymbolKind = 1 + SKModule SymbolKind = 2 + SKNamespace SymbolKind = 3 + SKPackage SymbolKind = 4 + SKClass SymbolKind = 5 + SKMethod SymbolKind = 6 + SKProperty SymbolKind = 7 + SKField SymbolKind = 8 + SKConstructor SymbolKind = 9 + SKEnum SymbolKind = 10 + SKInterface SymbolKind = 11 + SKFunction SymbolKind = 12 + SKVariable SymbolKind = 13 + SKConstant SymbolKind = 14 + SKString SymbolKind = 15 + SKNumber SymbolKind = 16 + SKBoolean SymbolKind = 17 + SKArray SymbolKind = 18 +) + +type SymbolInformation struct { + Name string `json:"name"` + Kind SymbolKind `json:"kind"` + Location Location `json:"location"` +} + +type Location struct { + URI string `json:"uri"` + Range Range `json:"range"` +} + +type Range struct { + Start Position `json:"start"` + End Position `json:"end"` +} + +type Position struct { + Line int `json:"line"` + Character int `json:"character"` +} From 3351ff24325722afe35f6cd028e5d0bb4202cc38 Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Sun, 8 Jan 2017 10:19:58 -0800 Subject: [PATCH 2/3] fix some golint issues --- langserver/handle_text_document_did_open.go | 7 +++---- langserver/handle_text_document_symbol.go | 8 ++------ langserver/handler.go | 2 +- langserver/lsp.go | 4 ++-- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/langserver/handle_text_document_did_open.go b/langserver/handle_text_document_did_open.go index 145bace..c9217de 100644 --- a/langserver/handle_text_document_did_open.go +++ b/langserver/handle_text_document_did_open.go @@ -17,11 +17,10 @@ func (h *LangHandler) handleTextDocumentDidOpen(ctx context.Context, conn *jsonr return nil, err } - f, err := NewVimFile(params.TextDocument) + f, err := newVimFile(params.TextDocument) if err != nil { return nil, err - } else { - h.files[params.TextDocument.URI] = f - return nil, nil } + h.files[params.TextDocument.URI] = f + return nil, nil } diff --git a/langserver/handle_text_document_symbol.go b/langserver/handle_text_document_symbol.go index 07d59a1..4a469ee 100644 --- a/langserver/handle_text_document_symbol.go +++ b/langserver/handle_text_document_symbol.go @@ -3,7 +3,6 @@ package langserver import ( "context" "encoding/json" - "errors" "fmt" "github.com/haya14busa/go-vimlparser/ast" @@ -25,13 +24,10 @@ func (h *LangHandler) handleTextDocumentSymbols(ctx context.Context, conn *jsonr node, err := f.GetAst() if err != nil { return nil, err - } else { - return getDocumentSymbols(params, node), nil } - return nil, nil - } else { - return nil, errors.New(fmt.Sprintf("% not open", params.TextDocument.URI)) + return getDocumentSymbols(params, node), nil } + return nil, fmt.Errorf("% not open", params.TextDocument.URI) } func getDocumentSymbols(params DocumentSymbolParams, node ast.Node) []SymbolInformation { diff --git a/langserver/handler.go b/langserver/handler.go index 03db40f..9d20392 100644 --- a/langserver/handler.go +++ b/langserver/handler.go @@ -28,7 +28,7 @@ type vimfile struct { AstError error } -func NewVimFile(textDocumentItem TextDocumentItem) (result *vimfile, error error) { +func newVimFile(textDocumentItem TextDocumentItem) (result *vimfile, error error) { return &vimfile{ TextDocumentItem: textDocumentItem, Ast: nil, diff --git a/langserver/lsp.go b/langserver/lsp.go index cd74326..c5eaf6b 100644 --- a/langserver/lsp.go +++ b/langserver/lsp.go @@ -4,7 +4,7 @@ type InitializeParams struct { ProcessID int `json:"processId,omitempty"` RootPath string `json:"rootPath,omitempty"` InitializationOptions InitializeOptions `json:"initializationOptions,omitempty"` - Capabilities ClientCapabilities `json:"capabilities",omitempty` + Capabilities ClientCapabilities `json:"capabilities,omitempty"` Trace string `json:"trace,omitempty"` } @@ -33,7 +33,7 @@ type ServerCapabilities struct { type TextDocumentItem struct { URI string `json:"uri"` - LanguageId string `json:"languageId"` + LanguageID string `json:"languageId"` Version int `json:"version"` Text string `json:"text"` } From 2fc51dea173e0958a6b04486e264d7f306cdab6d Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Sun, 8 Jan 2017 10:25:06 -0800 Subject: [PATCH 3/3] fixed go vet issues --- langserver/handle_text_document_symbol.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langserver/handle_text_document_symbol.go b/langserver/handle_text_document_symbol.go index 4a469ee..e2df410 100644 --- a/langserver/handle_text_document_symbol.go +++ b/langserver/handle_text_document_symbol.go @@ -27,7 +27,7 @@ func (h *LangHandler) handleTextDocumentSymbols(ctx context.Context, conn *jsonr } return getDocumentSymbols(params, node), nil } - return nil, fmt.Errorf("% not open", params.TextDocument.URI) + return nil, fmt.Errorf("%s not open", params.TextDocument.URI) } func getDocumentSymbols(params DocumentSymbolParams, node ast.Node) []SymbolInformation {