From a5f7fd3879fd71f166fcdcf7dd6dfe20ccc65e8d Mon Sep 17 00:00:00 2001 From: chai2010 Date: Sat, 6 May 2023 00:45:02 +0800 Subject: [PATCH] =?UTF-8?q?lsp=20=E6=9B=B4=E6=96=B0=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/app/wacli/wacli.go | 17 +++ internal/lsp/{consts.go => a_consts.go} | 2 + internal/lsp/{types.go => a_types.go} | 8 ++ internal/lsp/rpc.go | 60 ---------- internal/lsp/server.go | 146 ++++++++++++++---------- internal/lsp/server_cmd.go | 113 ++++++++++++++++++ 6 files changed, 227 insertions(+), 119 deletions(-) rename internal/lsp/{consts.go => a_consts.go} (96%) rename internal/lsp/{types.go => a_types.go} (99%) delete mode 100644 internal/lsp/rpc.go diff --git a/internal/app/wacli/wacli.go b/internal/app/wacli/wacli.go index 54d4188..017c13f 100644 --- a/internal/app/wacli/wacli.go +++ b/internal/app/wacli/wacli.go @@ -21,6 +21,7 @@ import ( "wa-lang.org/wa/internal/app/apputil" "wa-lang.org/wa/internal/app/yacc" "wa-lang.org/wa/internal/config" + "wa-lang.org/wa/internal/lsp" ) func Main() { @@ -457,6 +458,22 @@ func Main() { }, }, + { + Name: "lsp", + Usage: "run wa lang server", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "log-file", + Usage: "set log file", + Value: "", + }, + }, + Action: func(c *cli.Context) error { + lsp.NewLSPServer(&lsp.Option{}).Run() + return nil + }, + }, + { Name: "logo", Usage: "print wa-lang text format logo", diff --git a/internal/lsp/consts.go b/internal/lsp/a_consts.go similarity index 96% rename from internal/lsp/consts.go rename to internal/lsp/a_consts.go index 026e802..a9d0611 100644 --- a/internal/lsp/consts.go +++ b/internal/lsp/a_consts.go @@ -24,4 +24,6 @@ const ( k_textDocument_references = "textDocument/references" k_textDocument_hover = "textDocument/hover" k_textDocument_codeAction = "textDocument/codeAction" + + k_window_logMessage = "window/logMessage" ) diff --git a/internal/lsp/types.go b/internal/lsp/a_types.go similarity index 99% rename from internal/lsp/types.go rename to internal/lsp/a_types.go index 22b8515..2ae9663 100644 --- a/internal/lsp/types.go +++ b/internal/lsp/a_types.go @@ -4,6 +4,14 @@ package lsp +type DocumentURI string + +type File struct { + LanguageID string + Text string + Version int +} + type InitializeParams struct { ProcessID int `json:"processId,omitempty"` RootURI string `json:"rootUri,omitempty"` diff --git a/internal/lsp/rpc.go b/internal/lsp/rpc.go deleted file mode 100644 index 7e31a46..0000000 --- a/internal/lsp/rpc.go +++ /dev/null @@ -1,60 +0,0 @@ -// 版权 @2023 凹语言 作者。保留所有权利。 - -package lsp - -import ( - "encoding/json" - "fmt" - "io" -) - -type lspChannel struct { - r io.Reader - w io.Writer - dec *json.Decoder -} - -type lspRequest struct { - ID int `json:"id"` - JSONRPC string `json:"jsonrpc"` - Method string `json:"method"` - Params []json.RawMessage `json:"params"` -} - -type lspResponse struct { - ID int `json:"id"` - JSONRPC string `json:"jsonrpc"` - Result interface{} `json:"result"` - Error *lspError `json:"error"` -} - -// JSONRPC错误 -type lspError struct { - Code int `json:"code"` - Message string `json:"message"` -} - -func (err *lspError) Error() string { - return fmt.Sprintf("lsp.Error{code:%d, message: %q}", err.Code, err.Message) -} - -func (p *lspChannel) RecvRequest() (req *lspRequest, err error) { - if p.dec == nil { - p.dec = json.NewDecoder(p.r) - } - req = new(lspRequest) - err = p.dec.Decode(&req) - return -} - -func (p *lspChannel) SendRespose(resp *lspResponse) error { - reqBody, err := json.Marshal(resp) - if err != nil { - return err - } - _, err = p.w.Write(reqBody) - if err != nil { - return err - } - return nil -} diff --git a/internal/lsp/server.go b/internal/lsp/server.go index a0bf0de..e23ee3c 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -4,95 +4,123 @@ package lsp import ( "context" - "encoding/json" "fmt" + "io" + "io/ioutil" + "log" "os" + "sync" + + "wa-lang.org/wa/internal/3rdparty/jsonrpc2" ) +type Option struct { + LogFile string +} + type LSPServer struct { - conn *lspChannel + mu sync.Mutex + + conn *jsonrpc2.Conn + rootPath string + shutdown bool + + logger *log.Logger + loglevel int } -func NewLSPServer() *LSPServer { - return &LSPServer{ - conn: &lspChannel{r: os.Stdin, w: os.Stdout}, +func (p *Option) clone() *Option { + if p == nil { + return &Option{} + } + return &Option{ + LogFile: p.LogFile, } } -func (p *LSPServer) Run() error { - ctx := context.Background() - for { - req, err := p.conn.RecvRequest() - if err != nil { - return err - } - - var resp = &lspResponse{ - ID: req.ID, - JSONRPC: req.JSONRPC, - } - resp.Result, err = p.handleMethod(ctx, req) - if err != nil { - resp.Error = &lspError{ - Message: err.Error(), - } - } - - if err = p.conn.SendRespose(resp); err != nil { - // log - } - } +func NewLSPServer(opt *Option) *LSPServer { + return &LSPServer{} } -func (p *LSPServer) handleMethod(ctx context.Context, req *lspRequest) (result interface{}, err error) { - var argsData []byte - if len(req.Params) > 0 { - argsData = []byte(req.Params[0]) +func (p *LSPServer) Run() { + var rwc = struct { + io.Writer + io.ReadCloser + }{ + ReadCloser: ioutil.NopCloser(os.Stdin), + Writer: os.Stdout, + } + + conn := jsonrpc2.NewConn( + context.Background(), + jsonrpc2.NewBufferedStream(rwc, jsonrpc2.VSCodeObjectCodec{}), + jsonrpc2.HandlerWithError(p.handle), + ) + ch := conn.DisconnectNotify() + <-ch +} + +func (h *LSPServer) handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) { + if req.Notif { + switch req.Method { + case k_initialized: + return + case k_textDocument_didOpen: + return h.handleTextDocumentDidOpen(ctx, conn, req) + case k_textDocument_didChange: + return h.handleTextDocumentDidChange(ctx, conn, req) + case k_textDocument_didSave: + return h.handleTextDocumentDidSave(ctx, conn, req) + case k_textDocument_didClose: + return h.handleTextDocumentDidClose(ctx, conn, req) + case k_exit: + return nil, nil + } + return } switch req.Method { - case "initialized": + case k_initialize: + return nil, fmt.Errorf("TODO") + case k_initialized: return - case "textDocument/didOpen": - var params DidOpenTextDocumentParams - if err := json.Unmarshal(argsData, ¶ms); err != nil { - return nil, err - } - return nil, fmt.Errorf("TODO") - case "textDocument/didChange": - return nil, fmt.Errorf("TODO") - case "textDocument/didSave": - return nil, fmt.Errorf("TODO") - case "textDocument/didClose": - return nil, fmt.Errorf("TODO") - case "exit": + case k_shutdown: return nil, fmt.Errorf("TODO") - case "initialize": + case k_workspace_executeCommand: return nil, fmt.Errorf("TODO") - case "shutdown": + case k_workspace_didChangeConfiguration: return nil, fmt.Errorf("TODO") - case "textDocument/formatting": + case k_workspace_workspaceFolders: return nil, fmt.Errorf("TODO") - case "textDocument/documentSymbol": + case k_workspace_didChangeWorkspaceFolders: return nil, fmt.Errorf("TODO") - case "textDocument/completion": + + case k_textDocument_didOpen: + //var params DidOpenTextDocumentParams + //if err := json.Unmarshal(argsData, ¶ms); err != nil { + // return nil, err + //} return nil, fmt.Errorf("TODO") - case "textDocument/definition": + case k_textDocument_didChange: return nil, fmt.Errorf("TODO") - case "textDocument/references": + case k_textDocument_didSave: return nil, fmt.Errorf("TODO") - case "textDocument/hover": + case k_textDocument_didClose: return nil, fmt.Errorf("TODO") - case "textDocument/codeAction": + case k_textDocument_formatting: return nil, fmt.Errorf("TODO") - case "workspace/executeCommand": + case k_textDocument_documentSymbol: return nil, fmt.Errorf("TODO") - case "workspace/didChangeConfiguration": + case k_textDocument_completion: return nil, fmt.Errorf("TODO") - case "workspace/workspaceFolders": + case k_textDocument_definition: return nil, fmt.Errorf("TODO") - case "workspace/didChangeWorkspaceFolders": + case k_textDocument_references: + return nil, fmt.Errorf("TODO") + case k_textDocument_hover: + return nil, fmt.Errorf("TODO") + case k_textDocument_codeAction: return nil, fmt.Errorf("TODO") } return nil, nil diff --git a/internal/lsp/server_cmd.go b/internal/lsp/server_cmd.go index fde0f46..91dce2c 100644 --- a/internal/lsp/server_cmd.go +++ b/internal/lsp/server_cmd.go @@ -3,9 +3,122 @@ package lsp import ( + "context" + "encoding/json" "fmt" + + "wa-lang.org/wa/internal/3rdparty/jsonrpc2" ) +func (h *LSPServer) 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 + } + + return nil, fmt.Errorf("TODO") +} + +func (h *LSPServer) handleTextDocumentDidChange(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 DidChangeTextDocumentParams + if err := json.Unmarshal(*req.Params, ¶ms); err != nil { + return nil, err + } + + if len(params.ContentChanges) == 1 { + // todo + } + return nil, fmt.Errorf("TODO") +} + +func (h *LSPServer) handleTextDocumentDidSave(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 DidSaveTextDocumentParams + if err := json.Unmarshal(*req.Params, ¶ms); err != nil { + return nil, err + } + + if params.Text != nil { + // update + } else { + // save + } + + return nil, fmt.Errorf("TODO") +} + +func (h *LSPServer) handleTextDocumentDidClose(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 DidCloseTextDocumentParams + if err := json.Unmarshal(*req.Params, ¶ms); err != nil { + return nil, err + } + + return nil, fmt.Errorf("TODO") +} + +func (h *LSPServer) 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} + } + h.conn = conn + + var params InitializeParams + if err := json.Unmarshal(*req.Params, ¶ms); err != nil { + return nil, err + } + + // Only try to parse the workspace root if its not null. Otherwise initialize will fail + if params.RootURI != "" { + // + } + + var completion = &CompletionProvider{ + TriggerCharacters: []string{"*"}, + } + + return InitializeResult{ + Capabilities: ServerCapabilities{ + TextDocumentSync: TDSKFull, + DocumentFormattingProvider: true, + DocumentSymbolProvider: true, + DefinitionProvider: true, + ReferencesProvider: true, + CompletionProvider: completion, + HoverProvider: true, + CodeActionProvider: true, + Workspace: &ServerCapabilitiesWorkspace{ + WorkspaceFolders: WorkspaceFoldersServerCapabilities{ + Supported: true, + ChangeNotifications: true, + }, + }, + }, + }, nil +} + +func (h *LSPServer) logMessage(typ MessageType, message string) { + h.conn.Notify(context.Background(), + k_window_logMessage, &LogMessageParams{ + Type: typ, Message: message, + }, + ) +} + func (p *LSPServer) executeCommand(params *ExecuteCommandParams) (interface{}, error) { if len(params.Arguments) != 1 { return nil, fmt.Errorf("invalid command")