diff --git a/internal/lsp/helper/README.md b/internal/lsp/helper/README.md new file mode 100644 index 0000000000..e27e769908 --- /dev/null +++ b/internal/lsp/helper/README.md @@ -0,0 +1,33 @@ +# Generate server_gen.go + +`helper` generates boilerplate code for server.go by processing the +generated code in `protocol/tsserver.go`. + +First, build `helper` in this directore (`go build .`). + +In directory `lsp`, executing `go generate server.go` generates the stylized file +`server_gen.go` that contains stubs for type `Server`. + +It decides what stubs are needed and their signatures +by looking at the `Server` interface (`-t` flag). These all look somewhat like +`Resolve(context.Context, *CompletionItem) (*CompletionItem, error)`. + +It then parses the `lsp` directory (`-u` flag) to see if there is a corresponding +implementation function (which in this case would be named `resolve`). If so +it discovers the parameter names needed, and generates (in `server_gen.go`) code +like + +``` go +func (s *Server) resolve(ctx context.Context, params *protocol.CompletionItem) (*protocol.CompletionItem, error) { + return s.resolve(ctx, params) +} +``` + +If `resolve` is not defined (and it is not), then the body of the generated function is + +```go + return nil, notImplemented("resolve") +``` + +So to add a capability currently not implemented, just define it somewhere in `lsp`. +In this case, just define `func (s *Server) resolve(...)` and re-generate `server_gen.go`. diff --git a/internal/lsp/helper/helper.go b/internal/lsp/helper/helper.go new file mode 100644 index 0000000000..a1cc1dc573 --- /dev/null +++ b/internal/lsp/helper/helper.go @@ -0,0 +1,244 @@ +// Invoke with //go:generate helper/helper -t Server -d protocol/tsserver.go -u lsp -o server_gen.go +// invoke in internal/lsp +package main + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "log" + "os" + "sort" + "strings" + "text/template" +) + +var ( + typ = flag.String("t", "Server", "generate code for this type") + def = flag.String("d", "", "the file the type is defined in") // this relies on punning + use = flag.String("u", "", "look for uses in this package") + out = flag.String("o", "", "where to write the generated file") +) + +func main() { + log.SetFlags(log.Lshortfile) + flag.Parse() + if *typ == "" || *def == "" || *use == "" || *out == "" { + flag.PrintDefaults() + return + } + // read the type definition and see what methods we're looking for + doTypes() + + // parse the package and see which methods are defined + doUses() + + output() +} + +// replace "\\\n" with nothing before using +var tmpl = ` +package lsp + +// code generated by helper. DO NOT EDIT. + +import ( + "context" + + "golang.org/x/tools/internal/lsp/protocol" +) + +{{range $key, $v := .Stuff}} +func (s *{{$.Type}}) {{$v.Name}}({{.Param}}) {{.Result}} { + {{if ne .Found ""}} return s.{{.Internal}}({{.Invoke}})\ + {{else}}return {{if lt 1 (len .Results)}}nil, {{end}}notImplemented("{{.Name}}"){{end}} +} +{{end}} +` + +func output() { + // put in empty param names as needed + for _, t := range types { + if t.paramnames == nil { + t.paramnames = make([]string, len(t.paramtypes)) + } + for i, p := range t.paramtypes { + cm := "" + if i > 0 { + cm = ", " + } + t.Param += fmt.Sprintf("%s%s %s", cm, t.paramnames[i], p) + t.Invoke += fmt.Sprintf("%s%s", cm, t.paramnames[i]) + } + if len(t.Results) > 1 { + t.Result = "(" + } + for i, r := range t.Results { + cm := "" + if i > 0 { + cm = ", " + } + t.Result += fmt.Sprintf("%s%s", cm, r) + } + if len(t.Results) > 1 { + t.Result += ")" + } + } + + fd, err := os.Create(*out) + if err != nil { + log.Fatal(err) + } + t, err := template.New("foo").Parse(tmpl) + if err != nil { + log.Fatal(err) + } + type par struct { + Type string + Stuff []*Function + } + p := par{*typ, types} + if false { // debugging the template + t.Execute(os.Stderr, &p) + } + buf := bytes.NewBuffer(nil) + err = t.Execute(buf, &p) + if err != nil { + log.Fatal(err) + } + ans := bytes.Replace(buf.Bytes(), []byte("\\\n"), []byte{}, -1) + fd.Write(ans) +} + +func doUses() { + fset := token.NewFileSet() + pkgs, err := parser.ParseDir(fset, *use, nil, 0) + if err != nil { + log.Fatalf("%q:%v", *use, err) + } + pkg := pkgs["lsp"] // CHECK + files := pkg.Files + for fname, f := range files { + for _, d := range f.Decls { + fd, ok := d.(*ast.FuncDecl) + if !ok { + continue + } + nm := fd.Name.String() + if isExported(nm) { + // we're looking for things like didChange + continue + } + if fx, ok := byname[nm]; ok { + if fx.Found != "" { + log.Fatalf("found %s in %s and %s", fx.Internal, fx.Found, fname) + } + fx.Found = fname + // and the Paramnames + ft := fd.Type + for _, f := range ft.Params.List { + nm := "" + if len(f.Names) > 0 { + nm = f.Names[0].String() + } + fx.paramnames = append(fx.paramnames, nm) + } + } + } + } + if false { + for i, f := range types { + log.Printf("%d %s %s", i, f.Internal, f.Found) + } + } +} + +type Function struct { + Name string + Internal string // first letter lower case + paramtypes []string + paramnames []string + Results []string + Param string + Result string // do it in code, easier than in a template + Invoke string + Found string // file it was found in +} + +var types []*Function +var byname = map[string]*Function{} // internal names + +func doTypes() { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, *def, nil, 0) + if err != nil { + log.Fatal(err) + } + fd, err := os.Create("/tmp/ast") + if err != nil { + log.Fatal(err) + } + ast.Fprint(fd, fset, f, ast.NotNilFilter) + ast.Inspect(f, inter) + sort.Slice(types, func(i, j int) bool { return types[i].Name < types[j].Name }) + if false { + for i, f := range types { + log.Printf("%d %s(%v) %v", i, f.Name, f.paramtypes, f.Results) + } + } +} + +func inter(n ast.Node) bool { + x, ok := n.(*ast.TypeSpec) + if !ok || x.Name.Name != *typ { + return true + } + m := x.Type.(*ast.InterfaceType).Methods.List + for _, fld := range m { + fn := fld.Type.(*ast.FuncType) + p := fn.Params.List + r := fn.Results.List + fx := &Function{ + Name: fld.Names[0].String(), + } + fx.Internal = strings.ToLower(fx.Name[:1]) + fx.Name[1:] + for _, f := range p { + fx.paramtypes = append(fx.paramtypes, whatis(f.Type)) + } + for _, f := range r { + fx.Results = append(fx.Results, whatis(f.Type)) + } + types = append(types, fx) + byname[fx.Internal] = fx + } + return false +} + +func whatis(x ast.Expr) string { + switch n := x.(type) { + case *ast.SelectorExpr: + return whatis(n.X) + "." + n.Sel.String() + case *ast.StarExpr: + return "*" + whatis(n.X) + case *ast.Ident: + if isExported(n.Name) { + // these are from package protocol + return "protocol." + n.Name + } + return n.Name + case *ast.ArrayType: + return "[]" + whatis(n.Elt) + case *ast.InterfaceType: + return "interface{}" + default: + log.Fatalf("Fatal %T", x) + return fmt.Sprintf("%T", x) + } +} + +func isExported(n string) bool { + return n[0] >= 'A' && n[0] <= 'Z' +} diff --git a/internal/lsp/server.go b/internal/lsp/server.go index d6c87cb621..9de3c06b52 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -103,193 +103,15 @@ type sentDiagnostics struct { snapshotID uint64 } -// General - -func (s *Server) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) { - return s.initialize(ctx, params) -} - -func (s *Server) Initialized(ctx context.Context, params *protocol.InitializedParams) error { - return s.initialized(ctx, params) -} - -func (s *Server) Shutdown(ctx context.Context) error { - return s.shutdown(ctx) -} - -func (s *Server) Exit(ctx context.Context) error { - return s.exit(ctx) -} - -func (s *Server) CancelRequest(ctx context.Context, params *protocol.CancelParams) error { +func (s *Server) cancelRequest(ctx context.Context, params *protocol.CancelParams) error { return nil } -// Workspace - -func (s *Server) DidChangeWorkspaceFolders(ctx context.Context, params *protocol.DidChangeWorkspaceFoldersParams) error { - return s.changeFolders(ctx, params.Event) +func (s *Server) codeLens(ctx context.Context, params *protocol.CodeLensParams) ([]protocol.CodeLens, error) { + return nil, nil } -func (s *Server) DidChangeConfiguration(ctx context.Context, params *protocol.DidChangeConfigurationParams) error { - return s.updateConfiguration(ctx, params.Settings) -} - -func (s *Server) DidChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) error { - return s.didChangeWatchedFiles(ctx, params) -} - -func (s *Server) Symbol(context.Context, *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) { - return nil, notImplemented("Symbol") -} - -func (s *Server) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) { - return s.executeCommand(ctx, params) -} - -// Text Synchronization - -func (s *Server) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error { - return s.didOpen(ctx, params) -} - -func (s *Server) DidChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error { - return s.didChange(ctx, params) -} - -func (s *Server) WillSave(context.Context, *protocol.WillSaveTextDocumentParams) error { - return notImplemented("WillSave") -} - -func (s *Server) WillSaveWaitUntil(context.Context, *protocol.WillSaveTextDocumentParams) ([]protocol.TextEdit, error) { - return nil, notImplemented("WillSaveWaitUntil") -} - -func (s *Server) DidSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error { - return s.didSave(ctx, params) -} - -func (s *Server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error { - return s.didClose(ctx, params) -} - -// Language Features - -func (s *Server) Completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) { - return s.completion(ctx, params) -} - -func (s *Server) Resolve(ctx context.Context, item *protocol.CompletionItem) (*protocol.CompletionItem, error) { - return nil, notImplemented("completionItem/resolve") -} - -func (s *Server) Hover(ctx context.Context, params *protocol.HoverParams) (*protocol.Hover, error) { - return s.hover(ctx, params) -} - -func (s *Server) SignatureHelp(ctx context.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) { - return s.signatureHelp(ctx, params) -} - -func (s *Server) Definition(ctx context.Context, params *protocol.DefinitionParams) (protocol.Definition, error) { - return s.definition(ctx, params) -} - -func (s *Server) TypeDefinition(ctx context.Context, params *protocol.TypeDefinitionParams) (protocol.Definition, error) { - return s.typeDefinition(ctx, params) -} - -func (s *Server) Implementation(ctx context.Context, params *protocol.ImplementationParams) (protocol.Definition, error) { - return s.implementation(ctx, params) -} - -func (s *Server) References(ctx context.Context, params *protocol.ReferenceParams) ([]protocol.Location, error) { - return s.references(ctx, params) -} - -func (s *Server) DocumentHighlight(ctx context.Context, params *protocol.DocumentHighlightParams) ([]protocol.DocumentHighlight, error) { - return s.documentHighlight(ctx, params) -} - -func (s *Server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]protocol.DocumentSymbol, error) { - return s.documentSymbol(ctx, params) -} - -func (s *Server) CodeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { - return s.codeAction(ctx, params) -} - -func (s *Server) CodeLens(context.Context, *protocol.CodeLensParams) ([]protocol.CodeLens, error) { - return nil, nil // ignore -} - -func (s *Server) ResolveCodeLens(context.Context, *protocol.CodeLens) (*protocol.CodeLens, error) { - return nil, notImplemented("ResolveCodeLens") -} - -func (s *Server) DocumentLink(ctx context.Context, params *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) { - return s.documentLink(ctx, params) -} - -func (s *Server) ResolveDocumentLink(context.Context, *protocol.DocumentLink) (*protocol.DocumentLink, error) { - return nil, notImplemented("ResolveDocumentLink") -} - -func (s *Server) DocumentColor(context.Context, *protocol.DocumentColorParams) ([]protocol.ColorInformation, error) { - return nil, notImplemented("DocumentColor") -} - -func (s *Server) ColorPresentation(context.Context, *protocol.ColorPresentationParams) ([]protocol.ColorPresentation, error) { - return nil, notImplemented("ColorPresentation") -} - -func (s *Server) Formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) { - return s.formatting(ctx, params) -} - -func (s *Server) RangeFormatting(ctx context.Context, params *protocol.DocumentRangeFormattingParams) ([]protocol.TextEdit, error) { - return nil, notImplemented("RangeFormatting") -} - -func (s *Server) OnTypeFormatting(context.Context, *protocol.DocumentOnTypeFormattingParams) ([]protocol.TextEdit, error) { - return nil, notImplemented("OnTypeFormatting") -} - -func (s *Server) Rename(ctx context.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) { - return s.rename(ctx, params) -} - -func (s *Server) Declaration(context.Context, *protocol.DeclarationParams) (protocol.Declaration, error) { - return nil, notImplemented("Declaration") -} - -func (s *Server) FoldingRange(ctx context.Context, params *protocol.FoldingRangeParams) ([]protocol.FoldingRange, error) { - return s.foldingRange(ctx, params) -} - -func (s *Server) LogTraceNotification(context.Context, *protocol.LogTraceParams) error { - return notImplemented("LogtraceNotification") -} - -func (s *Server) PrepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (interface{}, error) { - // TODO(suzmue): support sending placeholder text. - return s.prepareRename(ctx, params) -} - -func (s *Server) Progress(context.Context, *protocol.ProgressParams) error { - return notImplemented("Progress") -} - -func (s *Server) SetTraceNotification(context.Context, *protocol.SetTraceParams) error { - return notImplemented("SetTraceNotification") -} - -func (s *Server) SelectionRange(context.Context, *protocol.SelectionRangeParams) ([]protocol.SelectionRange, error) { - return nil, notImplemented("SelectionRange") -} - -// Nonstandard requests -func (s *Server) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) { +func (s *Server) nonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) { paramMap := params.(map[string]interface{}) if method == "gopls/diagnoseFiles" { for _, file := range paramMap["files"].([]interface{}) { @@ -323,3 +145,5 @@ func (s *Server) NonstandardRequest(ctx context.Context, method string, params i func notImplemented(method string) *jsonrpc2.Error { return jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not yet implemented", method) } + +//go:generate helper/helper -d protocol/tsserver.go -o server_gen.go -u . diff --git a/internal/lsp/server_gen.go b/internal/lsp/server_gen.go new file mode 100644 index 0000000000..1b95ce630b --- /dev/null +++ b/internal/lsp/server_gen.go @@ -0,0 +1,185 @@ +package lsp + +// code generated by helper. DO NOT EDIT. + +import ( + "context" + + "golang.org/x/tools/internal/lsp/protocol" +) + +func (s *Server) CodeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { + return s.codeAction(ctx, params) +} + +func (s *Server) CodeLens(ctx context.Context, params *protocol.CodeLensParams) ([]protocol.CodeLens, error) { + return s.codeLens(ctx, params) +} + +func (s *Server) ColorPresentation(context.Context, *protocol.ColorPresentationParams) ([]protocol.ColorPresentation, error) { + return nil, notImplemented("ColorPresentation") +} + +func (s *Server) Completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) { + return s.completion(ctx, params) +} + +func (s *Server) Declaration(context.Context, *protocol.DeclarationParams) (protocol.Declaration, error) { + return nil, notImplemented("Declaration") +} + +func (s *Server) Definition(ctx context.Context, params *protocol.DefinitionParams) (protocol.Definition, error) { + return s.definition(ctx, params) +} + +func (s *Server) DidChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error { + return s.didChange(ctx, params) +} + +func (s *Server) DidChangeConfiguration(ctx context.Context, changed *protocol.DidChangeConfigurationParams) error { + return s.didChangeConfiguration(ctx, changed) +} + +func (s *Server) DidChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) error { + return s.didChangeWatchedFiles(ctx, params) +} + +func (s *Server) DidChangeWorkspaceFolders(ctx context.Context, params *protocol.DidChangeWorkspaceFoldersParams) error { + return s.didChangeWorkspaceFolders(ctx, params) +} + +func (s *Server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error { + return s.didClose(ctx, params) +} + +func (s *Server) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error { + return s.didOpen(ctx, params) +} + +func (s *Server) DidSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error { + return s.didSave(ctx, params) +} + +func (s *Server) DocumentColor(context.Context, *protocol.DocumentColorParams) ([]protocol.ColorInformation, error) { + return nil, notImplemented("DocumentColor") +} + +func (s *Server) DocumentHighlight(ctx context.Context, params *protocol.DocumentHighlightParams) ([]protocol.DocumentHighlight, error) { + return s.documentHighlight(ctx, params) +} + +func (s *Server) DocumentLink(ctx context.Context, params *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) { + return s.documentLink(ctx, params) +} + +func (s *Server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]protocol.DocumentSymbol, error) { + return s.documentSymbol(ctx, params) +} + +func (s *Server) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) { + return s.executeCommand(ctx, params) +} + +func (s *Server) Exit(ctx context.Context) error { + return s.exit(ctx) +} + +func (s *Server) FoldingRange(ctx context.Context, params *protocol.FoldingRangeParams) ([]protocol.FoldingRange, error) { + return s.foldingRange(ctx, params) +} + +func (s *Server) Formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) { + return s.formatting(ctx, params) +} + +func (s *Server) Hover(ctx context.Context, params *protocol.HoverParams) (*protocol.Hover, error) { + return s.hover(ctx, params) +} + +func (s *Server) Implementation(ctx context.Context, params *protocol.ImplementationParams) (protocol.Definition, error) { + return s.implementation(ctx, params) +} + +func (s *Server) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) { + return s.initialize(ctx, params) +} + +func (s *Server) Initialized(ctx context.Context, params *protocol.InitializedParams) error { + return s.initialized(ctx, params) +} + +func (s *Server) LogTraceNotification(context.Context, *protocol.LogTraceParams) error { + return notImplemented("LogTraceNotification") +} + +func (s *Server) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) { + return s.nonstandardRequest(ctx, method, params) +} + +func (s *Server) OnTypeFormatting(context.Context, *protocol.DocumentOnTypeFormattingParams) ([]protocol.TextEdit, error) { + return nil, notImplemented("OnTypeFormatting") +} + +func (s *Server) PrepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (interface{}, error) { + return s.prepareRename(ctx, params) +} + +func (s *Server) Progress(context.Context, *protocol.ProgressParams) error { + return notImplemented("Progress") +} + +func (s *Server) RangeFormatting(context.Context, *protocol.DocumentRangeFormattingParams) ([]protocol.TextEdit, error) { + return nil, notImplemented("RangeFormatting") +} + +func (s *Server) References(ctx context.Context, params *protocol.ReferenceParams) ([]protocol.Location, error) { + return s.references(ctx, params) +} + +func (s *Server) Rename(ctx context.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) { + return s.rename(ctx, params) +} + +func (s *Server) Resolve(context.Context, *protocol.CompletionItem) (*protocol.CompletionItem, error) { + return nil, notImplemented("Resolve") +} + +func (s *Server) ResolveCodeLens(context.Context, *protocol.CodeLens) (*protocol.CodeLens, error) { + return nil, notImplemented("ResolveCodeLens") +} + +func (s *Server) ResolveDocumentLink(context.Context, *protocol.DocumentLink) (*protocol.DocumentLink, error) { + return nil, notImplemented("ResolveDocumentLink") +} + +func (s *Server) SelectionRange(context.Context, *protocol.SelectionRangeParams) ([]protocol.SelectionRange, error) { + return nil, notImplemented("SelectionRange") +} + +func (s *Server) SetTraceNotification(context.Context, *protocol.SetTraceParams) error { + return notImplemented("SetTraceNotification") +} + +func (s *Server) Shutdown(ctx context.Context) error { + return s.shutdown(ctx) +} + +func (s *Server) SignatureHelp(ctx context.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) { + return s.signatureHelp(ctx, params) +} + +func (s *Server) Symbol(context.Context, *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) { + return nil, notImplemented("Symbol") +} + +func (s *Server) TypeDefinition(ctx context.Context, params *protocol.TypeDefinitionParams) (protocol.Definition, error) { + return s.typeDefinition(ctx, params) +} + +func (s *Server) WillSave(context.Context, *protocol.WillSaveTextDocumentParams) error { + return notImplemented("WillSave") +} + +func (s *Server) WillSaveWaitUntil(context.Context, *protocol.WillSaveTextDocumentParams) ([]protocol.TextEdit, error) { + return nil, notImplemented("WillSaveWaitUntil") +} diff --git a/internal/lsp/workspace.go b/internal/lsp/workspace.go index 2a5d2b0110..aec40acb9c 100644 --- a/internal/lsp/workspace.go +++ b/internal/lsp/workspace.go @@ -13,7 +13,8 @@ import ( errors "golang.org/x/xerrors" ) -func (s *Server) changeFolders(ctx context.Context, event protocol.WorkspaceFoldersChangeEvent) error { +func (s *Server) didChangeWorkspaceFolders(ctx context.Context, params *protocol.DidChangeWorkspaceFoldersParams) error { + event := params.Event for _, folder := range event.Removed { view := s.session.View(folder.Name) if view != nil { @@ -41,7 +42,7 @@ func (s *Server) addView(ctx context.Context, name string, uri span.URI) (source return s.session.NewView(ctx, name, uri, options) } -func (s *Server) updateConfiguration(ctx context.Context, changed interface{}) error { +func (s *Server) didChangeConfiguration(ctx context.Context, changed interface{}) error { // go through all the views getting the config for _, view := range s.session.Views() { options := s.session.Options()