github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/fake/edit.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package fake
     6  
     7  import (
     8  	"fmt"
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/jhump/golang-x-tools/internal/lsp/protocol"
    13  )
    14  
    15  // Pos represents a position in a text buffer. Both Line and Column are
    16  // 0-indexed.
    17  type Pos struct {
    18  	Line, Column int
    19  }
    20  
    21  func (p Pos) String() string {
    22  	return fmt.Sprintf("%v:%v", p.Line, p.Column)
    23  }
    24  
    25  // Range corresponds to protocol.Range, but uses the editor friend Pos
    26  // instead of UTF-16 oriented protocol.Position
    27  type Range struct {
    28  	Start Pos
    29  	End   Pos
    30  }
    31  
    32  func (p Pos) ToProtocolPosition() protocol.Position {
    33  	return protocol.Position{
    34  		Line:      uint32(p.Line),
    35  		Character: uint32(p.Column),
    36  	}
    37  }
    38  
    39  func fromProtocolPosition(pos protocol.Position) Pos {
    40  	return Pos{
    41  		Line:   int(pos.Line),
    42  		Column: int(pos.Character),
    43  	}
    44  }
    45  
    46  // Edit represents a single (contiguous) buffer edit.
    47  type Edit struct {
    48  	Start, End Pos
    49  	Text       string
    50  }
    51  
    52  // Location is the editor friendly equivalent of protocol.Location
    53  type Location struct {
    54  	Path  string
    55  	Range Range
    56  }
    57  
    58  // SymbolInformation is an editor friendly version of
    59  // protocol.SymbolInformation, with location information transformed to byte
    60  // offsets. Field names correspond to the protocol type.
    61  type SymbolInformation struct {
    62  	Name     string
    63  	Kind     protocol.SymbolKind
    64  	Location Location
    65  }
    66  
    67  // NewEdit creates an edit replacing all content between
    68  // (startLine, startColumn) and (endLine, endColumn) with text.
    69  func NewEdit(startLine, startColumn, endLine, endColumn int, text string) Edit {
    70  	return Edit{
    71  		Start: Pos{Line: startLine, Column: startColumn},
    72  		End:   Pos{Line: endLine, Column: endColumn},
    73  		Text:  text,
    74  	}
    75  }
    76  
    77  func (e Edit) toProtocolChangeEvent() protocol.TextDocumentContentChangeEvent {
    78  	return protocol.TextDocumentContentChangeEvent{
    79  		Range: &protocol.Range{
    80  			Start: e.Start.ToProtocolPosition(),
    81  			End:   e.End.ToProtocolPosition(),
    82  		},
    83  		Text: e.Text,
    84  	}
    85  }
    86  
    87  func fromProtocolTextEdit(textEdit protocol.TextEdit) Edit {
    88  	return Edit{
    89  		Start: fromProtocolPosition(textEdit.Range.Start),
    90  		End:   fromProtocolPosition(textEdit.Range.End),
    91  		Text:  textEdit.NewText,
    92  	}
    93  }
    94  
    95  // inText reports whether p is a valid position in the text buffer.
    96  func inText(p Pos, content []string) bool {
    97  	if p.Line < 0 || p.Line >= len(content) {
    98  		return false
    99  	}
   100  	// Note the strict right bound: the column indexes character _separators_,
   101  	// not characters.
   102  	if p.Column < 0 || p.Column > len([]rune(content[p.Line])) {
   103  		return false
   104  	}
   105  	return true
   106  }
   107  
   108  // editContent implements a simplistic, inefficient algorithm for applying text
   109  // edits to our buffer representation. It returns an error if the edit is
   110  // invalid for the current content.
   111  func editContent(content []string, edits []Edit) ([]string, error) {
   112  	newEdits := make([]Edit, len(edits))
   113  	copy(newEdits, edits)
   114  	sort.Slice(newEdits, func(i, j int) bool {
   115  		if newEdits[i].Start.Line < newEdits[j].Start.Line {
   116  			return true
   117  		}
   118  		if newEdits[i].Start.Line > newEdits[j].Start.Line {
   119  			return false
   120  		}
   121  		return newEdits[i].Start.Column < newEdits[j].Start.Column
   122  	})
   123  
   124  	// Validate edits.
   125  	for _, edit := range newEdits {
   126  		if edit.End.Line < edit.Start.Line || (edit.End.Line == edit.Start.Line && edit.End.Column < edit.Start.Column) {
   127  			return nil, fmt.Errorf("invalid edit: end %v before start %v", edit.End, edit.Start)
   128  		}
   129  		if !inText(edit.Start, content) {
   130  			return nil, fmt.Errorf("start position %v is out of bounds", edit.Start)
   131  		}
   132  		if !inText(edit.End, content) {
   133  			return nil, fmt.Errorf("end position %v is out of bounds", edit.End)
   134  		}
   135  	}
   136  
   137  	var (
   138  		b            strings.Builder
   139  		line, column int
   140  	)
   141  	advance := func(toLine, toColumn int) {
   142  		for ; line < toLine; line++ {
   143  			b.WriteString(string([]rune(content[line])[column:]) + "\n")
   144  			column = 0
   145  		}
   146  		b.WriteString(string([]rune(content[line])[column:toColumn]))
   147  		column = toColumn
   148  	}
   149  	for _, edit := range newEdits {
   150  		advance(edit.Start.Line, edit.Start.Column)
   151  		b.WriteString(edit.Text)
   152  		line = edit.End.Line
   153  		column = edit.End.Column
   154  	}
   155  	advance(len(content)-1, len([]rune(content[len(content)-1])))
   156  	return strings.Split(b.String(), "\n"), nil
   157  }