github.com/tiagovtristao/plz@v13.4.0+incompatible/tools/build_langserver/langserver/handler.go (about)

     1  package langserver
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"runtime/debug"
     9  	"sync"
    10  
    11  	"github.com/sourcegraph/jsonrpc2"
    12  	"gopkg.in/op/go-logging.v1"
    13  
    14  	"github.com/thought-machine/please/src/core"
    15  	"github.com/thought-machine/please/tools/build_langserver/lsp"
    16  )
    17  
    18  var log = logging.MustGetLogger("lsp")
    19  
    20  // NewHandler creates a BUILD file language server handler
    21  func NewHandler() jsonrpc2.Handler {
    22  	h := &LsHandler{
    23  		IsServerDown: false,
    24  	}
    25  	return langHandler{jsonrpc2.HandlerWithError(h.Handle)}
    26  }
    27  
    28  // handler wraps around LsHandler to correctly handler requests in the correct order
    29  type langHandler struct {
    30  	jsonrpc2.Handler
    31  }
    32  
    33  // LsHandler is the main handler struct of the language server handler
    34  type LsHandler struct {
    35  	init     *lsp.InitializeParams
    36  	analyzer *Analyzer
    37  	mu       sync.Mutex
    38  	conn     *jsonrpc2.Conn
    39  
    40  	workspace *workspaceStore
    41  
    42  	repoRoot     string
    43  	requestStore *requestStore
    44  
    45  	IsServerDown         bool
    46  	supportedCompletions []lsp.CompletionItemKind
    47  
    48  	diagPublisher *diagnosticsPublisher
    49  }
    50  
    51  // Handle function takes care of all the incoming from the client, and returns the correct response
    52  func (h *LsHandler) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error) {
    53  	defer func() {
    54  		if r := recover(); r != nil {
    55  			log.Error("Panic in handler: %s: %s", r, debug.Stack())
    56  		}
    57  	}()
    58  	if req.Method != "initialize" && h.init == nil {
    59  		return nil, fmt.Errorf("server must be initialized")
    60  	}
    61  	h.conn = conn
    62  
    63  	log.Info("handling method %s with params: %s", req.Method, req.Params)
    64  	methods := map[string]func(ctx context.Context, req *jsonrpc2.Request) (result interface{}, err error){
    65  		"initialize":                 h.handleInit,
    66  		"initialzed":                 h.handleInitialized,
    67  		"shutdown":                   h.handleShutDown,
    68  		"exit":                       h.handleExit,
    69  		"$/cancelRequest":            h.handleCancel,
    70  		"textDocument/hover":         h.handleHover,
    71  		"textDocument/completion":    h.handleCompletion,
    72  		"textDocument/signatureHelp": h.handleSignature,
    73  		"textDocument/definition":    h.handleDefinition,
    74  		"textDocument/formatting":    h.handleReformatting,
    75  		"textDocument/references":    h.handleReferences,
    76  		"textDocument/rename":        h.handleRename,
    77  	}
    78  
    79  	if req.Method != "initialize" && req.Method != "exit" &&
    80  		req.Method != "initialzed" && req.Method != "shutdown" {
    81  		ctx = h.requestStore.Store(ctx, req)
    82  		defer h.requestStore.Cancel(req.ID)
    83  	}
    84  
    85  	if method, ok := methods[req.Method]; ok {
    86  		result, err := method(ctx, req)
    87  		if err != nil {
    88  			log.Error("Error handling %s: %s", req.Method, err)
    89  		}
    90  		return result, err
    91  	}
    92  
    93  	return h.handleTDRequests(ctx, req)
    94  }
    95  
    96  func (h *LsHandler) handleInit(ctx context.Context, req *jsonrpc2.Request) (result interface{}, err error) {
    97  	if h.init != nil {
    98  		return nil, errors.New("language server is already initialized")
    99  	}
   100  	if req.Params == nil {
   101  		return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidParams}
   102  	}
   103  
   104  	var params lsp.InitializeParams
   105  	if err := json.Unmarshal(*req.Params, &params); err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	// Set the Init state of the handler
   110  	h.mu.Lock()
   111  	defer h.mu.Unlock()
   112  
   113  	// TODO(bnmetrics): Ideas: this could essentially be a bit fragile.
   114  	// maybe we can defer until user send a request with first file URL
   115  	core.FindRepoRoot()
   116  
   117  	// TODO(bnm): remove stuff with reporoot
   118  	params.EnsureRoot()
   119  	h.repoRoot = string(params.RootURI)
   120  	h.workspace = newWorkspaceStore(params.RootURI)
   121  	h.diagPublisher = newDiagnosticsPublisher()
   122  
   123  	h.supportedCompletions = params.Capabilities.TextDocument.Completion.CompletionItemKind.ValueSet
   124  	h.init = &params
   125  
   126  	h.analyzer, err = newAnalyzer()
   127  	if err != nil {
   128  		return nil, &jsonrpc2.Error{
   129  			Code:    jsonrpc2.CodeParseError,
   130  			Message: fmt.Sprintf("error in parsing .plzconfig file: %s", err),
   131  		}
   132  	}
   133  
   134  	// Reset the requestStore, and get sub-context based on request ID
   135  	reqStore := newRequestStore()
   136  	h.requestStore = reqStore
   137  	ctx = h.requestStore.Store(ctx, req)
   138  
   139  	defer h.requestStore.Cancel(req.ID)
   140  
   141  	// start the goroutine for publishing diagnostics
   142  	go func() {
   143  		for {
   144  			h.publishDiagnostics(h.conn)
   145  		}
   146  	}()
   147  
   148  	// Fill in the response results
   149  	TDsync := lsp.SyncIncremental
   150  	completeOps := &lsp.CompletionOptions{
   151  		ResolveProvider:   false,
   152  		TriggerCharacters: []string{".", ":"},
   153  	}
   154  
   155  	sigHelpOps := &lsp.SignatureHelpOptions{
   156  		TriggerCharacters: []string{"(", ","},
   157  	}
   158  
   159  	log.Info("Initializing plz build file language server..")
   160  	return lsp.InitializeResult{
   161  
   162  		Capabilities: lsp.ServerCapabilities{
   163  			TextDocumentSync:           &TDsync,
   164  			HoverProvider:              true,
   165  			RenameProvider:             true,
   166  			CompletionProvider:         completeOps,
   167  			SignatureHelpProvider:      sigHelpOps,
   168  			DefinitionProvider:         true,
   169  			TypeDefinitionProvider:     true,
   170  			ImplementationProvider:     true,
   171  			ReferencesProvider:         true,
   172  			DocumentFormattingProvider: true,
   173  			DocumentHighlightProvider:  true,
   174  			DocumentSymbolProvider:     true,
   175  		},
   176  	}, nil
   177  }
   178  
   179  func (h *LsHandler) handleInitialized(ctx context.Context, req *jsonrpc2.Request) (result interface{}, err error) {
   180  	return nil, nil
   181  }
   182  
   183  func (h *LsHandler) handleShutDown(ctx context.Context, req *jsonrpc2.Request) (result interface{}, err error) {
   184  	h.mu.Lock()
   185  	if h.IsServerDown {
   186  		log.Warning("Server is already down!")
   187  	}
   188  	h.IsServerDown = true
   189  	defer h.mu.Unlock()
   190  	return nil, nil
   191  }
   192  
   193  func (h *LsHandler) handleExit(ctx context.Context, req *jsonrpc2.Request) (result interface{}, err error) {
   194  	h.handleShutDown(ctx, req)
   195  	h.conn.Close()
   196  	return nil, nil
   197  }
   198  
   199  func (h *LsHandler) handleCancel(ctx context.Context, req *jsonrpc2.Request) (result interface{}, err error) {
   200  	// Is there is no param with Id, or if there is no requests stored currently, return nothing
   201  	if req.Params == nil || h.requestStore.IsEmpty() {
   202  		return nil, nil
   203  	}
   204  
   205  	var params lsp.CancelParams
   206  	if err := json.Unmarshal(*req.Params, &params); err != nil {
   207  		return nil, &jsonrpc2.Error{
   208  			Code:    lsp.RequestCancelled,
   209  			Message: fmt.Sprintf("Cancellation of request(id: %s) failed", req.ID),
   210  		}
   211  	}
   212  
   213  	defer h.requestStore.Cancel(params.ID)
   214  
   215  	return nil, nil
   216  }
   217  
   218  // getParamFromTDPositionReq gets the lsp.TextDocumentPositionParams struct
   219  // if the method sends a TextDocumentPositionParams json object, e.g. "textDocument/definition", "textDocument/hover"
   220  func (h *LsHandler) getParamFromTDPositionReq(req *jsonrpc2.Request, methodName string) (*lsp.TextDocumentPositionParams, error) {
   221  	if req.Params == nil {
   222  		return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidParams}
   223  	}
   224  
   225  	log.Info("%s with params %s", methodName, req.Params)
   226  	var params *lsp.TextDocumentPositionParams
   227  	if err := json.Unmarshal(*req.Params, &params); err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	documentURI, err := getURIAndHandleErrors(params.TextDocument.URI, methodName)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	params.TextDocument.URI = documentURI
   237  
   238  	return params, nil
   239  }
   240  
   241  // ensureLineContent handle cases when the completion pos happens on the last line of the file, without any newline char
   242  func (h *LsHandler) ensureLineContent(uri lsp.DocumentURI, pos lsp.Position) string {
   243  	fileContent := h.workspace.documents[uri].textInEdit
   244  	// so we don't run into the problem of 'index out of range'
   245  	if len(fileContent)-1 < pos.Line {
   246  		return ""
   247  	}
   248  
   249  	lineContent := fileContent[pos.Line]
   250  
   251  	if len(lineContent)+1 == pos.Character && len(fileContent) == pos.Line+1 {
   252  		lineContent += "\n"
   253  	}
   254  
   255  	return lineContent
   256  }
   257  
   258  func getURIAndHandleErrors(uri lsp.DocumentURI, method string) (lsp.DocumentURI, error) {
   259  	documentURI, err := EnsureURL(uri, "file")
   260  	if err != nil {
   261  		message := fmt.Sprintf("invalid documentURI '%s' for method %s", documentURI, method)
   262  		log.Error(message)
   263  		return "", &jsonrpc2.Error{
   264  			Code:    jsonrpc2.CodeInvalidParams,
   265  			Message: message,
   266  		}
   267  	}
   268  	return documentURI, err
   269  }
   270  
   271  func isVisible(buildDef *BuildDef, currentPkg string) bool {
   272  	for _, i := range buildDef.Visibility {
   273  		if i == "PUBLIC" {
   274  			return true
   275  		}
   276  
   277  		label := core.ParseBuildLabel(i, currentPkg)
   278  		currentPkgLabel := core.ParseBuildLabel(currentPkg, currentPkg)
   279  		if label.Includes(currentPkgLabel) {
   280  			return true
   281  		}
   282  	}
   283  	return false
   284  }