internal/lsp: hold notifications until gopls is initialized

Errors in options generate showMessage notifications, but these
were lost if they were sent before the initialization handshake completes.

Change-Id: I011da2387467bb6bf188201a7294e1bb30805cbc
Reviewed-on: https://go-review.googlesource.com/c/tools/+/263518
Run-TryBot: Peter Weinberger <pjw@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Peter Weinberger <pjw@google.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
index 307660b..11f2f3e 100644
--- a/internal/lsp/general.go
+++ b/internal/lsp/general.go
@@ -161,6 +161,11 @@
 	s.state = serverInitialized
 	s.stateMu.Unlock()
 
+	for _, not := range s.notifications {
+		s.client.ShowMessage(ctx, not)
+	}
+	s.notifications = nil
+
 	options := s.session.Options()
 	defer func() { s.session.SetOptions(options) }()
 
@@ -421,22 +426,34 @@
 	return nil
 }
 
+func (s *Server) eventuallyShowMessage(ctx context.Context, msg *protocol.ShowMessageParams) error {
+	s.stateMu.Lock()
+	defer s.stateMu.Unlock()
+	if s.state == serverInitialized {
+		return s.client.ShowMessage(ctx, msg)
+	}
+	s.notifications = append(s.notifications, msg)
+	return nil
+}
+
 func (s *Server) handleOptionResults(ctx context.Context, results source.OptionResults) error {
 	for _, result := range results {
 		if result.Error != nil {
-			if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
+			msg := &protocol.ShowMessageParams{
 				Type:    protocol.Error,
 				Message: result.Error.Error(),
-			}); err != nil {
+			}
+			if err := s.eventuallyShowMessage(ctx, msg); err != nil {
 				return err
 			}
 		}
 		switch result.State {
 		case source.OptionUnexpected:
-			if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
+			msg := &protocol.ShowMessageParams{
 				Type:    protocol.Error,
 				Message: fmt.Sprintf("unexpected gopls setting %q", result.Name),
-			}); err != nil {
+			}
+			if err := s.eventuallyShowMessage(ctx, msg); err != nil {
 				return err
 			}
 		case source.OptionDeprecated:
@@ -444,7 +461,7 @@
 			if result.Replacement != "" {
 				msg = fmt.Sprintf("%s, use %q instead", msg, result.Replacement)
 			}
-			if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
+			if err := s.eventuallyShowMessage(ctx, &protocol.ShowMessageParams{
 				Type:    protocol.Warning,
 				Message: msg,
 			}); err != nil {
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index 5533b1b..dceb2a7 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -67,6 +67,9 @@
 
 	session source.Session
 
+	// notifications generated before serverInitialized
+	notifications []*protocol.ShowMessageParams
+
 	// changedFiles tracks files for which there has been a textDocument/didChange.
 	changedFilesMu sync.Mutex
 	changedFiles   map[span.URI]struct{}