Skip to content

Commit d320a46

Browse files
initial language server protocol implementation
1 parent f57a807 commit d320a46

File tree

6 files changed

+323
-0
lines changed

6 files changed

+323
-0
lines changed

cmd/langserver-vim/main.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"log"
6+
"os"
7+
8+
"github.com/haya14busa/go-vimlparser/langserver"
9+
"github.com/sourcegraph/jsonrpc2"
10+
)
11+
12+
func main() {
13+
log.Println("langserver-vim: reading on stdin, writing on stdout")
14+
var connOpt []jsonrpc2.ConnOpt
15+
<-jsonrpc2.NewConn(context.Background(), jsonrpc2.NewBufferedStream(stdrwc{}, jsonrpc2.VSCodeObjectCodec{}), langserver.NewHandler(), connOpt...).DisconnectNotify()
16+
log.Println("langserver-vim: connections closed")
17+
}
18+
19+
type stdrwc struct{}
20+
21+
func (stdrwc) Read(p []byte) (int, error) {
22+
return os.Stdin.Read(p)
23+
}
24+
25+
func (stdrwc) Write(p []byte) (int, error) {
26+
return os.Stdout.Write(p)
27+
}
28+
29+
func (stdrwc) Close() error {
30+
if err := os.Stdin.Close(); err != nil {
31+
return err
32+
}
33+
return os.Stdout.Close()
34+
}

langserver/handle_initialize.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package langserver
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
7+
"github.com/sourcegraph/jsonrpc2"
8+
)
9+
10+
func (h *LangHandler) handleInitialize(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) {
11+
if req.Params == nil {
12+
return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidParams}
13+
}
14+
15+
var params InitializeParams
16+
if err := json.Unmarshal(*req.Params, &params); err != nil {
17+
return nil, err
18+
}
19+
20+
return InitializeResult{
21+
Capabilities: ServerCapabilities{
22+
TextDocumentSync: TDSKFull,
23+
DocumentSymbolProvider: true,
24+
},
25+
}, nil
26+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package langserver
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
7+
"github.com/sourcegraph/jsonrpc2"
8+
)
9+
10+
func (h *LangHandler) handleTextDocumentDidOpen(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) {
11+
if req.Params == nil {
12+
return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidParams}
13+
}
14+
15+
var params DidOpenTextDocumentParams
16+
if err := json.Unmarshal(*req.Params, &params); err != nil {
17+
return nil, err
18+
}
19+
20+
f, err := NewVimFile(params.TextDocument)
21+
if err != nil {
22+
return nil, err
23+
} else {
24+
h.files[params.TextDocument.URI] = f
25+
return nil, nil
26+
}
27+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package langserver
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
9+
"github.com/haya14busa/go-vimlparser/ast"
10+
11+
"github.com/sourcegraph/jsonrpc2"
12+
)
13+
14+
func (h *LangHandler) handleTextDocumentSymbols(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) {
15+
if req.Params == nil {
16+
return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidParams}
17+
}
18+
19+
var params DocumentSymbolParams
20+
if err := json.Unmarshal(*req.Params, &params); err != nil {
21+
return nil, err
22+
}
23+
24+
if f, ok := h.files[params.TextDocument.URI]; ok {
25+
node, err := f.GetAst()
26+
if err != nil {
27+
return nil, err
28+
} else {
29+
return getDocumentSymbols(params, node), nil
30+
}
31+
return nil, nil
32+
} else {
33+
return nil, errors.New(fmt.Sprintf("% not open", params.TextDocument.URI))
34+
}
35+
}
36+
37+
func getDocumentSymbols(params DocumentSymbolParams, node ast.Node) []SymbolInformation {
38+
var symbols []SymbolInformation
39+
ast.Inspect(node, func(n ast.Node) bool {
40+
var name string
41+
var kind SymbolKind
42+
var pos ast.Pos
43+
switch x := n.(type) {
44+
case *ast.Function:
45+
switch y := x.Name.(type) {
46+
case *ast.Ident:
47+
kind = SKFunction
48+
name = y.Name
49+
pos = y.NamePos
50+
}
51+
52+
if name != "" {
53+
symbols = append(symbols, SymbolInformation{
54+
Name: name,
55+
Kind: kind,
56+
Location: Location{
57+
URI: params.TextDocument.URI,
58+
Range: Range{
59+
Start: Position{
60+
Line: pos.Line - 1,
61+
Character: pos.Column - 1,
62+
},
63+
End: Position{
64+
Line: pos.Line - 1,
65+
Character: pos.Column + len(name) - 1,
66+
},
67+
},
68+
},
69+
})
70+
return false
71+
}
72+
}
73+
return true
74+
})
75+
return symbols
76+
}

langserver/handler.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package langserver
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
8+
"github.com/haya14busa/go-vimlparser"
9+
"github.com/haya14busa/go-vimlparser/ast"
10+
11+
"github.com/sourcegraph/jsonrpc2"
12+
)
13+
14+
func NewHandler() jsonrpc2.Handler {
15+
var langHandler = &LangHandler{
16+
files: make(map[string]*vimfile),
17+
}
18+
return jsonrpc2.HandlerWithError(langHandler.handle)
19+
}
20+
21+
type LangHandler struct {
22+
files map[string]*vimfile
23+
}
24+
25+
type vimfile struct {
26+
TextDocumentItem
27+
Ast *ast.File
28+
AstError error
29+
}
30+
31+
func NewVimFile(textDocumentItem TextDocumentItem) (result *vimfile, error error) {
32+
return &vimfile{
33+
TextDocumentItem: textDocumentItem,
34+
Ast: nil,
35+
AstError: nil,
36+
}, nil
37+
}
38+
39+
func (f *vimfile) GetAst() (result *ast.File, error error) {
40+
if f.AstError != nil {
41+
return nil, f.AstError
42+
} else if f.Ast != nil {
43+
return f.Ast, nil
44+
} else {
45+
opt := &vimlparser.ParseOption{Neovim: false}
46+
r := bytes.NewBufferString(f.Text)
47+
ast, err := vimlparser.ParseFile(r, f.URI, opt)
48+
f.Ast = ast
49+
f.AstError = err
50+
return ast, err
51+
}
52+
}
53+
54+
func (h *LangHandler) handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) {
55+
switch req.Method {
56+
case "initialize":
57+
return h.handleInitialize(ctx, conn, req)
58+
case "textDocument/didOpen":
59+
return h.handleTextDocumentDidOpen(ctx, conn, req)
60+
case "textDocument/documentSymbol":
61+
return h.handleTextDocumentSymbols(ctx, conn, req)
62+
}
63+
64+
return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeMethodNotFound, Message: fmt.Sprintf("method not supported: %s", req.Method)}
65+
}

langserver/lsp.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package langserver
2+
3+
type InitializeParams struct {
4+
ProcessID int `json:"processId,omitempty"`
5+
RootPath string `json:"rootPath,omitempty"`
6+
InitializationOptions InitializeOptions `json:"initializationOptions,omitempty"`
7+
Capabilities ClientCapabilities `json:"capabilities",omitempty`
8+
Trace string `json:"trace,omitempty"`
9+
}
10+
11+
type InitializeOptions struct {
12+
}
13+
14+
type ClientCapabilities struct {
15+
}
16+
17+
type InitializeResult struct {
18+
Capabilities ServerCapabilities `json:"capabilities,omitempty"`
19+
}
20+
21+
type TextDocumentSyncKind int
22+
23+
const (
24+
TDSKNone TextDocumentSyncKind = 0
25+
TDSKFull = 1
26+
TDSKIncremental = 2
27+
)
28+
29+
type ServerCapabilities struct {
30+
TextDocumentSync TextDocumentSyncKind `json:"textDocumentSync,omitempty"`
31+
DocumentSymbolProvider bool `json:"documentSymbolProvider,omitempty"`
32+
}
33+
34+
type TextDocumentItem struct {
35+
URI string `json:"uri"`
36+
LanguageId string `json:"languageId"`
37+
Version int `json:"version"`
38+
Text string `json:"text"`
39+
}
40+
41+
type TextDocumentIdentifier struct {
42+
URI string `json:"uri"`
43+
}
44+
45+
type DidOpenTextDocumentParams struct {
46+
TextDocument TextDocumentItem `json:"textDocument"`
47+
}
48+
49+
type DocumentSymbolParams struct {
50+
TextDocument TextDocumentIdentifier
51+
}
52+
53+
type SymbolKind int
54+
55+
const (
56+
SKFile SymbolKind = 1
57+
SKModule SymbolKind = 2
58+
SKNamespace SymbolKind = 3
59+
SKPackage SymbolKind = 4
60+
SKClass SymbolKind = 5
61+
SKMethod SymbolKind = 6
62+
SKProperty SymbolKind = 7
63+
SKField SymbolKind = 8
64+
SKConstructor SymbolKind = 9
65+
SKEnum SymbolKind = 10
66+
SKInterface SymbolKind = 11
67+
SKFunction SymbolKind = 12
68+
SKVariable SymbolKind = 13
69+
SKConstant SymbolKind = 14
70+
SKString SymbolKind = 15
71+
SKNumber SymbolKind = 16
72+
SKBoolean SymbolKind = 17
73+
SKArray SymbolKind = 18
74+
)
75+
76+
type SymbolInformation struct {
77+
Name string `json:"name"`
78+
Kind SymbolKind `json:"kind"`
79+
Location Location `json:"location"`
80+
}
81+
82+
type Location struct {
83+
URI string `json:"uri"`
84+
Range Range `json:"range"`
85+
}
86+
87+
type Range struct {
88+
Start Position `json:"start"`
89+
End Position `json:"end"`
90+
}
91+
92+
type Position struct {
93+
Line int `json:"line"`
94+
Character int `json:"character"`
95+
}

0 commit comments

Comments
 (0)