@@ -14,166 +14,34 @@ import Foundation
14
14
import SKLogging
15
15
@_spi ( RawSyntax) import SwiftSyntax
16
16
17
- /// Translate SourceKit placeholder syntax — `<#foo#>` — in `input` to LSP
18
- /// placeholder syntax: `${n:foo}`.
19
- ///
20
- /// If `clientSupportsSnippets` is `false`, the placeholder is rendered as an
21
- /// empty string, to prevent the client from inserting special placeholder
22
- /// characters as if they were literal text.
23
- @_spi ( Testing)
24
- public func rewriteSourceKitPlaceholders( in input: String , clientSupportsSnippets: Bool ) -> String {
25
- var result = " "
26
- var nextPlaceholderNumber = 1
27
- // Current stack of nested placeholders, most nested last. Each element needs
28
- // to be rendered inside the element before it.
29
- var placeholders : [ ( number: Int , contents: String ) ] = [ ]
30
- let tokens = tokenize ( input)
31
- for token in tokens {
32
- switch token {
33
- case let . text( text) :
34
- if placeholders. isEmpty {
35
- result += text
36
- } else {
37
- placeholders. latest. contents += text
38
- }
39
-
40
- case let . curlyBrace( brace) :
41
- if placeholders. isEmpty {
42
- result. append ( brace)
43
- } else {
44
- // Braces are only escaped _inside_ a placeholder; otherwise the client
45
- // would include the backslashes literally.
46
- placeholders. latest. contents. append ( contentsOf: [ " \\ " , brace] )
47
- }
48
-
49
- case . placeholderOpen:
50
- placeholders. append ( ( number: nextPlaceholderNumber, contents: " " ) )
51
- nextPlaceholderNumber += 1
52
-
53
- case . placeholderClose:
54
- guard let ( number, placeholderBody) = placeholders. popLast ( ) else {
55
- logger. fault ( " Invalid placeholder in \( input) " )
56
- return input
57
- }
58
- guard let displayName = nameForSnippet ( placeholderBody) else {
59
- logger. fault ( " Failed to decode placeholder \( placeholderBody) in \( input) " )
60
- return input
61
- }
62
- let placeholder =
63
- clientSupportsSnippets
64
- ? formatLSPPlaceholder ( displayName, number: number)
65
- : " "
66
- if placeholders. isEmpty {
67
- result += placeholder
68
- } else {
69
- placeholders. latest. contents += placeholder
70
- }
71
- }
72
- }
73
-
74
- return result
75
- }
76
-
77
- /// Scan `input` to identify special elements within: curly braces, which may
78
- /// need to be escaped; and SourceKit placeholder open/close delimiters.
79
- private func tokenize( _ input: String ) -> [ SnippetToken ] {
80
- var index = input. startIndex
81
- var isAtEnd : Bool { index == input. endIndex }
82
- func match( _ char: Character ) -> Bool {
83
- if isAtEnd || input [ index] != char {
84
- return false
85
- } else {
86
- input. formIndex ( after: & index)
87
- return true
17
+ func rewriteSourceKitPlaceholders( in string: String , clientSupportsSnippets: Bool ) -> String {
18
+ var result = string
19
+ var index = 1
20
+ while let start = result. range ( of: " <# " ) {
21
+ guard let end = result [ start. upperBound... ] . range ( of: " #> " ) else {
22
+ logger. fault ( " Invalid placeholder in \( string) " )
23
+ return string
88
24
}
89
- }
90
- func next( ) -> Character ? {
91
- guard !isAtEnd else { return nil }
92
- defer { input. formIndex ( after: & index) }
93
- return input [ index]
94
- }
95
-
96
- var tokens : [ SnippetToken ] = [ ]
97
- var text = " "
98
- while let char = next ( ) {
99
- switch char {
100
- case " < " :
101
- if match ( " # " ) {
102
- tokens. append ( . text( text) )
103
- text. removeAll ( )
104
- tokens. append ( . placeholderOpen)
105
- } else {
106
- text. append ( char)
107
- }
108
-
109
- case " # " :
110
- if match ( " > " ) {
111
- tokens. append ( . text( text) )
112
- text. removeAll ( )
113
- tokens. append ( . placeholderClose)
114
- } else {
115
- text. append ( char)
116
- }
117
-
118
- case " { " , " } " :
119
- tokens. append ( . text( text) )
120
- text. removeAll ( )
121
- tokens. append ( . curlyBrace( char) )
122
-
123
- case let c:
124
- text. append ( c)
25
+ let rawPlaceholder = String ( result [ start. lowerBound..< end. upperBound] )
26
+ guard let displayName = nameForSnippet ( rawPlaceholder) else {
27
+ logger. fault ( " Failed to decode placeholder \( rawPlaceholder) in \( string) " )
28
+ return string
125
29
}
30
+ let snippet = clientSupportsSnippets ? " ${ \( index) : \( displayName) } " : " "
31
+ result. replaceSubrange ( start. lowerBound..< end. upperBound, with: snippet)
32
+ index += 1
126
33
}
127
-
128
- tokens. append ( . text( text) )
129
-
130
- return tokens
131
- }
132
-
133
- /// A syntactical element inside a SourceKit snippet.
134
- private enum SnippetToken {
135
- /// A placeholder delimiter.
136
- case placeholderOpen, placeholderClose
137
- /// One of '{' or '}', which may need to be escaped in the output.
138
- case curlyBrace( Character )
139
- /// Any other consecutive run of characters from the input, which needs no
140
- /// special treatment.
141
- case text( String )
34
+ return result
142
35
}
143
36
144
- /// Given the interior text of a SourceKit placeholder, extract a display name
145
- /// suitable for a LSP snippet.
146
- private func nameForSnippet( _ body : String ) -> String ? {
147
- var text = rewrappedAsPlaceholder ( body )
37
+ /// Parse a SourceKit placeholder and extract the display name suitable for a
38
+ /// LSP snippet.
39
+ fileprivate func nameForSnippet( _ text : String ) -> String ? {
40
+ var text = text
148
41
return text. withSyntaxText {
149
42
guard let data = RawEditorPlaceholderData ( syntaxText: $0) else {
150
43
return nil
151
44
}
152
45
return String ( syntaxText: data. typeForExpansionText ?? data. displayText)
153
46
}
154
47
}
155
-
156
- private let placeholderStart = " <# "
157
- private let placeholderEnd = " #> "
158
- private func rewrappedAsPlaceholder( _ body: String ) -> String {
159
- return placeholderStart + body + placeholderEnd
160
- }
161
-
162
- /// Wrap `body` in LSP snippet placeholder syntax, using `number` as the
163
- /// placeholder's index in the snippet.
164
- private func formatLSPPlaceholder( _ body: String , number: Int ) -> String {
165
- " ${ \( number) : \( body) } "
166
- }
167
-
168
- private extension Array {
169
- /// Mutable access to the final element of an array.
170
- ///
171
- /// - precondition: The array must not be empty.
172
- var latest : Element {
173
- get { self . last! }
174
- _modify {
175
- let index = self . index ( before: self . endIndex)
176
- yield & self [ index]
177
- }
178
- }
179
- }
0 commit comments