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