github.com/getgauge/gauge@v1.6.9/api/lang/server.go (about)

     1  /*----------------------------------------------------------------
     2   *  Copyright (c) ThoughtWorks, Inc.
     3   *  Licensed under the Apache License, Version 2.0
     4   *  See LICENSE in the project root for license information.
     5   *----------------------------------------------------------------*/
     6  
     7  package lang
     8  
     9  import (
    10  	"context"
    11  	"encoding/json"
    12  	"fmt"
    13  	"log"
    14  	"os"
    15  	"runtime/debug"
    16  
    17  	gm "github.com/getgauge/gauge-proto/go/gauge_messages"
    18  	"github.com/getgauge/gauge/api/infoGatherer"
    19  	"github.com/getgauge/gauge/execution"
    20  	"github.com/getgauge/gauge/gauge"
    21  	"github.com/sourcegraph/jsonrpc2"
    22  )
    23  
    24  type infoProvider interface {
    25  	Init()
    26  	Steps(filterConcepts bool) []*gauge.Step
    27  	AllSteps(filterConcepts bool) []*gauge.Step
    28  	Concepts() []*gm.ConceptInfo
    29  	Params(file string, argType gauge.ArgType) []gauge.StepArg
    30  	Tags() []string
    31  	SearchConceptDictionary(string) *gauge.Concept
    32  	GetAvailableSpecDetails(specs []string) []*infoGatherer.SpecDetail
    33  	GetSpecDirs() []string
    34  }
    35  
    36  var provider infoProvider
    37  
    38  type lspHandler struct {
    39  	jsonrpc2.Handler
    40  }
    41  
    42  type LangHandler struct {
    43  }
    44  
    45  type InitializeParams struct {
    46  	RootPath     string             `json:"rootPath,omitempty"`
    47  	Capabilities ClientCapabilities `json:"capabilities,omitempty"`
    48  }
    49  
    50  func newHandler() jsonrpc2.Handler {
    51  	return lspHandler{jsonrpc2.HandlerWithError((&LangHandler{}).handle)}
    52  }
    53  
    54  func (h lspHandler) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) {
    55  	go h.Handler.Handle(ctx, conn, req)
    56  }
    57  
    58  func (h *LangHandler) handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) {
    59  	defer recoverPanic(req)
    60  	return h.Handle(ctx, conn, req)
    61  }
    62  
    63  func (h *LangHandler) Handle(ctx context.Context, conn jsonrpc2.JSONRPC2, req *jsonrpc2.Request) (interface{}, error) {
    64  	switch req.Method {
    65  	case "initialize":
    66  		if err := cacheInitializeParams(req); err != nil {
    67  			logError(req, err.Error())
    68  			return nil, err
    69  		}
    70  		return gaugeLSPCapabilities(), nil
    71  	case "initialized":
    72  		err := registerFileWatcher(conn, ctx)
    73  		if err != nil {
    74  			logError(req, err.Error())
    75  		}
    76  		err = registerRunnerCapabilities(conn, ctx)
    77  		if err != nil {
    78  			logError(req, err.Error())
    79  		}
    80  		go publishDiagnostics(ctx, conn)
    81  		return nil, nil
    82  	case "shutdown":
    83  		return nil, killRunner()
    84  	case "exit":
    85  		if c, ok := conn.(*jsonrpc2.Conn); ok {
    86  			err := c.Close()
    87  			if err != nil {
    88  				logError(req, err.Error())
    89  			}
    90  			os.Exit(0)
    91  		}
    92  		return nil, nil
    93  	case "$/cancelRequest":
    94  		return nil, nil
    95  	case "textDocument/didOpen":
    96  		err := documentOpened(req, ctx, conn)
    97  		if err != nil {
    98  			logDebug(req, err.Error())
    99  		}
   100  		return nil, err
   101  	case "textDocument/didClose":
   102  		err := documentClosed(req, ctx, conn)
   103  		if err != nil {
   104  			logDebug(req, err.Error())
   105  		}
   106  		return nil, err
   107  	case "textDocument/didChange":
   108  		err := documentChange(req, ctx, conn)
   109  		if err != nil {
   110  			logDebug(req, err.Error())
   111  		}
   112  		return nil, err
   113  	case "workspace/didChangeWatchedFiles":
   114  		err := documentChangeWatchedFiles(req, ctx, conn)
   115  		if err != nil {
   116  			logDebug(req, err.Error())
   117  		}
   118  		return nil, err
   119  	case "textDocument/completion":
   120  		val, err := completion(req)
   121  		if err != nil {
   122  			logDebug(req, err.Error())
   123  		}
   124  		return val, err
   125  	case "completionItem/resolve":
   126  		val, err := resolveCompletion(req)
   127  		if err != nil {
   128  			logDebug(req, err.Error())
   129  		}
   130  		return val, err
   131  	case "textDocument/definition":
   132  		val, err := definition(req)
   133  		if err != nil {
   134  			logDebug(req, err.Error())
   135  			if e := showErrorMessageOnClient(ctx, conn, err); e != nil {
   136  				return nil, fmt.Errorf("unable to send '%s' error to LSP server. %s", err.Error(), e.Error())
   137  			}
   138  
   139  		}
   140  		return val, err
   141  	case "textDocument/formatting":
   142  		data, err := format(req)
   143  		if err != nil {
   144  			logDebug(req, err.Error())
   145  			e := showErrorMessageOnClient(ctx, conn, err)
   146  			if e != nil {
   147  				return nil, fmt.Errorf("unable to send '%s' error to LSP server. %s", err.Error(), e.Error())
   148  			}
   149  		}
   150  		return data, err
   151  	case "textDocument/codeLens":
   152  		val, err := codeLenses(req)
   153  		if err != nil {
   154  			logDebug(req, err.Error())
   155  		}
   156  		return val, err
   157  	case "textDocument/codeAction":
   158  		val, err := codeActions(req)
   159  		if err != nil {
   160  			logDebug(req, err.Error())
   161  		}
   162  		return val, err
   163  	case "textDocument/rename":
   164  		result, err := rename(ctx, conn, req)
   165  		if err != nil {
   166  			logDebug(req, err.Error())
   167  			e := showErrorMessageOnClient(ctx, conn, err)
   168  			if e != nil {
   169  				return nil, fmt.Errorf("unable to send '%s' error to LSP server. %s", err.Error(), e.Error())
   170  			}
   171  			return nil, err
   172  		}
   173  		return result, nil
   174  	case "textDocument/documentSymbol":
   175  		val, err := documentSymbols(req)
   176  		if err != nil {
   177  			logDebug(req, err.Error())
   178  		}
   179  		return val, err
   180  	case "workspace/symbol":
   181  		val, err := workspaceSymbols(req)
   182  		if err != nil {
   183  			logDebug(req, err.Error())
   184  		}
   185  		return val, err
   186  	case "gauge/stepReferences":
   187  		val, err := stepReferences(req)
   188  		if err != nil {
   189  			logDebug(req, err.Error())
   190  		}
   191  		return val, err
   192  	case "gauge/stepValueAt":
   193  		val, err := stepValueAt(req)
   194  		if err != nil {
   195  			logDebug(req, err.Error())
   196  		}
   197  		return val, err
   198  	case "gauge/scenarios":
   199  		val, err := scenarios(req)
   200  		if err != nil {
   201  			logDebug(req, err.Error())
   202  		}
   203  		return val, err
   204  	case "gauge/getImplFiles":
   205  		val, err := getImplFiles(req)
   206  		if err != nil {
   207  			logDebug(req, err.Error())
   208  		}
   209  		return val, err
   210  	case "gauge/putStubImpl":
   211  		if err := sendSaveFilesRequest(ctx, conn); err != nil {
   212  			logDebug(req, err.Error())
   213  			e := showErrorMessageOnClient(ctx, conn, err)
   214  			if e != nil {
   215  				return nil, fmt.Errorf("unable to send '%s' error to LSP server. %s", err.Error(), e.Error())
   216  			}
   217  			return nil, err
   218  		}
   219  		val, err := putStubImpl(req)
   220  		if err != nil {
   221  			logDebug(req, err.Error())
   222  		}
   223  		return val, err
   224  	case "gauge/specs":
   225  		val, err := specs()
   226  		if err != nil {
   227  			logDebug(req, err.Error())
   228  		}
   229  		return val, err
   230  	case "gauge/executionStatus":
   231  		val, err := execution.ReadLastExecutionResult()
   232  		if err != nil {
   233  			logDebug(req, err.Error())
   234  		}
   235  		return val, err
   236  	case "gauge/generateConcept":
   237  		if err := sendSaveFilesRequest(ctx, conn); err != nil {
   238  			e := showErrorMessageOnClient(ctx, conn, err)
   239  			if e != nil {
   240  				return nil, fmt.Errorf("unable to send '%s' error to LSP server. %s", err.Error(), e.Error())
   241  			}
   242  			return nil, err
   243  		}
   244  		return generateConcept(req)
   245  	case "gauge/getRunnerLanguage":
   246  		return lRunner.lspID, nil
   247  	case "gauge/specDirs":
   248  		return provider.GetSpecDirs(), nil
   249  	default:
   250  		return nil, nil
   251  	}
   252  }
   253  
   254  func cacheInitializeParams(req *jsonrpc2.Request) error {
   255  	var params InitializeParams
   256  	var err error
   257  	if err = json.Unmarshal(*req.Params, &params); err != nil {
   258  		return err
   259  	}
   260  	clientCapabilities = params.Capabilities
   261  	return nil
   262  }
   263  
   264  func startLsp(logLevel string) (context.Context, *jsonrpc2.Conn) {
   265  	logInfo(nil, "LangServer: reading on stdin, writing on stdout")
   266  	var connOpt []jsonrpc2.ConnOpt
   267  	if logLevel == "debug" {
   268  		connOpt = append(connOpt, jsonrpc2.LogMessages(log.New(lspWriter{}, "", 0)))
   269  	}
   270  	ctx := context.Background()
   271  	return ctx, jsonrpc2.NewConn(ctx, jsonrpc2.NewBufferedStream(stdRWC{}, jsonrpc2.VSCodeObjectCodec{}), newHandler(), connOpt...)
   272  }
   273  
   274  func initializeRunner() error {
   275  	id, err := getLanguageIdentifier()
   276  	if err != nil || id == "" {
   277  		e := fmt.Errorf("There are version incompatibilities between Gauge and it's plugins in this project. Some features will not work as expected.")
   278  		logDebug(nil, "%s", e.Error())
   279  		return e
   280  	}
   281  	err = startRunner()
   282  	if err != nil {
   283  		logDebug(nil, "%s\nSome of the gauge lsp feature will not work as expected.", err.Error())
   284  		return err
   285  	}
   286  	lRunner.lspID = id
   287  	return nil
   288  }
   289  
   290  func Start(p infoProvider, logLevel string) {
   291  	provider = p
   292  	provider.Init()
   293  	err := initializeRunner()
   294  	ctx, conn := startLsp(logLevel)
   295  	if err != nil {
   296  		_ = showErrorMessageOnClient(ctx, conn, err)
   297  	}
   298  	initialize(ctx, conn)
   299  	<-conn.DisconnectNotify()
   300  	if killRunner() != nil {
   301  		logInfo(nil, "failed to kill runner with pid %d", lRunner.runner.Pid())
   302  	}
   303  	logInfo(nil, "Connection closed")
   304  }
   305  
   306  func recoverPanic(req *jsonrpc2.Request) {
   307  	if r := recover(); r != nil {
   308  		logFatal(req, "%v\n%s", r, string(debug.Stack()))
   309  	}
   310  }