File: edits.go

package info (click to toggle)
golang-golang-x-tools 1%3A0.25.0%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental, forky, sid, trixie
  • size: 22,724 kB
  • sloc: javascript: 2,027; asm: 1,645; sh: 166; yacc: 155; makefile: 49; ansic: 8
file content (175 lines) | stat: -rw-r--r-- 4,978 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package protocol

import (
	"fmt"

	"golang.org/x/tools/internal/diff"
)

// EditsFromDiffEdits converts diff.Edits to a non-nil slice of LSP TextEdits.
// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textEditArray
func EditsFromDiffEdits(m *Mapper, edits []diff.Edit) ([]TextEdit, error) {
	// LSP doesn't require TextEditArray to be sorted:
	// this is the receiver's concern. But govim, and perhaps
	// other clients have historically relied on the order.
	edits = append([]diff.Edit(nil), edits...)
	diff.SortEdits(edits)

	result := make([]TextEdit, len(edits))
	for i, edit := range edits {
		rng, err := m.OffsetRange(edit.Start, edit.End)
		if err != nil {
			return nil, err
		}
		result[i] = TextEdit{
			Range:   rng,
			NewText: edit.New,
		}
	}
	return result, nil
}

// EditsToDiffEdits converts LSP TextEdits to diff.Edits.
// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textEditArray
func EditsToDiffEdits(m *Mapper, edits []TextEdit) ([]diff.Edit, error) {
	if edits == nil {
		return nil, nil
	}
	result := make([]diff.Edit, len(edits))
	for i, edit := range edits {
		start, end, err := m.RangeOffsets(edit.Range)
		if err != nil {
			return nil, err
		}
		result[i] = diff.Edit{
			Start: start,
			End:   end,
			New:   edit.NewText,
		}
	}
	return result, nil
}

// ApplyEdits applies the patch (edits) to m.Content and returns the result.
// It also returns the edits converted to diff-package form.
func ApplyEdits(m *Mapper, edits []TextEdit) ([]byte, []diff.Edit, error) {
	diffEdits, err := EditsToDiffEdits(m, edits)
	if err != nil {
		return nil, nil, err
	}
	out, err := diff.ApplyBytes(m.Content, diffEdits)
	return out, diffEdits, err
}

// AsTextEdits converts a slice possibly containing AnnotatedTextEdits
// to a slice of TextEdits.
func AsTextEdits(edits []Or_TextDocumentEdit_edits_Elem) []TextEdit {
	var result []TextEdit
	for _, e := range edits {
		var te TextEdit
		if x, ok := e.Value.(AnnotatedTextEdit); ok {
			te = x.TextEdit
		} else if x, ok := e.Value.(TextEdit); ok {
			te = x
		} else {
			panic(fmt.Sprintf("unexpected type %T, expected AnnotatedTextEdit or TextEdit", e.Value))
		}
		result = append(result, te)
	}
	return result
}

// AsAnnotatedTextEdits converts a slice of TextEdits
// to a slice of Or_TextDocumentEdit_edits_Elem.
// (returning a typed nil is required in server: in code_action.go and command.go))
func AsAnnotatedTextEdits(edits []TextEdit) []Or_TextDocumentEdit_edits_Elem {
	if edits == nil {
		return []Or_TextDocumentEdit_edits_Elem{}
	}
	var result []Or_TextDocumentEdit_edits_Elem
	for _, e := range edits {
		result = append(result, Or_TextDocumentEdit_edits_Elem{
			Value: TextEdit{
				Range:   e.Range,
				NewText: e.NewText,
			},
		})
	}
	return result
}

// fileHandle abstracts file.Handle to avoid a cycle.
type fileHandle interface {
	URI() DocumentURI
	Version() int32
}

// NewWorkspaceEdit constructs a WorkspaceEdit from a list of document changes.
//
// Any ChangeAnnotations must be added after.
func NewWorkspaceEdit(changes ...DocumentChange) *WorkspaceEdit {
	return &WorkspaceEdit{DocumentChanges: changes}
}

// DocumentChangeEdit constructs a DocumentChange containing a
// TextDocumentEdit from a file.Handle and a list of TextEdits.
func DocumentChangeEdit(fh fileHandle, textedits []TextEdit) DocumentChange {
	return DocumentChange{
		TextDocumentEdit: &TextDocumentEdit{
			TextDocument: OptionalVersionedTextDocumentIdentifier{
				Version:                fh.Version(),
				TextDocumentIdentifier: TextDocumentIdentifier{URI: fh.URI()},
			},
			Edits: AsAnnotatedTextEdits(textedits),
		},
	}
}

// DocumentChangeCreate constructs a DocumentChange that creates a file.
func DocumentChangeCreate(uri DocumentURI) DocumentChange {
	return DocumentChange{
		CreateFile: &CreateFile{
			Kind: "create",
			URI:  uri,
		},
	}
}

// DocumentChangeRename constructs a DocumentChange that renames a file.
func DocumentChangeRename(src, dst DocumentURI) DocumentChange {
	return DocumentChange{
		RenameFile: &RenameFile{
			Kind:   "rename",
			OldURI: src,
			NewURI: dst,
		},
	}
}

// SelectCompletionTextEdit returns insert or replace mode TextEdit
// included in the completion item.
func SelectCompletionTextEdit(item CompletionItem, useReplaceMode bool) (TextEdit, error) {
	var edit TextEdit
	switch typ := item.TextEdit.Value.(type) {
	case TextEdit: // old style completion item.
		return typ, nil
	case InsertReplaceEdit:
		if useReplaceMode {
			return TextEdit{
				NewText: typ.NewText,
				Range:   typ.Replace,
			}, nil
		} else {
			return TextEdit{
				NewText: typ.NewText,
				Range:   typ.Insert,
			}, nil
		}
	default:
		return edit, fmt.Errorf("unsupported edit type %T", typ)
	}
}