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

     1  package langserver
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"github.com/thought-machine/please/tools/build_langserver/lsp"
     8  
     9  	"github.com/bazelbuild/buildtools/build"
    10  	"github.com/sourcegraph/jsonrpc2"
    11  )
    12  
    13  const reformatMethod = "textDocument/formatting"
    14  
    15  func (h *LsHandler) handleReformatting(ctx context.Context, req *jsonrpc2.Request) (result interface{}, err error) {
    16  	if req.Params == nil {
    17  		return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidParams}
    18  	}
    19  
    20  	var params lsp.DocumentFormattingParams
    21  	if err := json.Unmarshal(*req.Params, &params); err != nil {
    22  		return nil, err
    23  	}
    24  
    25  	documentURI, err := getURIAndHandleErrors(params.TextDocument.URI, reformatMethod)
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  
    30  	h.mu.Lock()
    31  	defer h.mu.Unlock()
    32  
    33  	edits, err := h.getFormatEdits(documentURI)
    34  	if err != nil {
    35  		log.Warning("error occurred when formatting file: %s, skipping", err)
    36  	}
    37  
    38  	log.Info("formatting document with edits: %s", edits)
    39  	return edits, err
    40  }
    41  
    42  func (h *LsHandler) getFormatEdits(uri lsp.DocumentURI) ([]*lsp.TextEdit, error) {
    43  
    44  	filePath, err := GetPathFromURL(uri, "file")
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	doc, ok := h.workspace.documents[uri]
    50  	if !ok {
    51  		return nil, fmt.Errorf("document not opened: %s", uri)
    52  	}
    53  
    54  	content := JoinLines(doc.textInEdit, true)
    55  	bytecontent := []byte(content)
    56  
    57  	var f *build.File
    58  	if h.analyzer.IsBuildFile(uri) {
    59  		f, err = build.ParseBuild(filePath, bytecontent)
    60  		if err != nil {
    61  			return nil, err
    62  		}
    63  	} else {
    64  		f, err = build.ParseDefault(filePath, bytecontent)
    65  		if err != nil {
    66  			return nil, err
    67  		}
    68  	}
    69  
    70  	reformatted := build.Format(f)
    71  
    72  	return getEdits(content, string(reformatted)), nil
    73  }
    74  
    75  func getEdits(before string, after string) []*lsp.TextEdit {
    76  	beforeLines := SplitLines(before, true)
    77  	afterLines := SplitLines(after, true)
    78  
    79  	var edits []*lsp.TextEdit
    80  	for i, line := range afterLines {
    81  
    82  		eRange := lsp.Range{
    83  			Start: lsp.Position{
    84  				Line:      i,
    85  				Character: 0,
    86  			},
    87  			End: lsp.Position{
    88  				Line:      i,
    89  				Character: 0,
    90  			},
    91  		}
    92  
    93  		if i <= len(beforeLines)-1 {
    94  			if line == beforeLines[i] {
    95  				continue
    96  			}
    97  
    98  			if i < len(beforeLines)-1 {
    99  				eRange.End.Line = i + 1
   100  			} else if beforeLines[i] != "" {
   101  				// This is to ensure the original line gets overridden if there is no newline after:
   102  				// usually if the last line is new line, it gets formatted correctly with startline:currentline, endline:currline + 1
   103  				// however this does not apply we want to format the last line
   104  				eRange.End.Character = len(beforeLines[i]) - 1
   105  			}
   106  		}
   107  
   108  		edit := &lsp.TextEdit{
   109  			Range:   eRange,
   110  			NewText: line,
   111  		}
   112  
   113  		edits = append(edits, edit)
   114  	}
   115  
   116  	return edits
   117  }