Skip to content

Commit a045367

Browse files
committed
Don’t include internal parameter names in LSP snippets
When translating editor placeholders to snippets, we should not include internal parameter names into the snippet. The problem is that once you leave snippet editing mode (essentially by moving the cursor to any place that’s not the snippet, eg. by typing a string literal for one of the parameters or moving the cursor), all snippets become verbatim text. And then it’s impossible to tell whether `paramName: ` was an annotation inside the snippet describing the internal parameter’s name or the external parameter label. rdar://123772474
1 parent 331f16d commit a045367

File tree

8 files changed

+49
-138
lines changed

8 files changed

+49
-138
lines changed

Sources/SourceKitLSP/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ target_sources(SourceKitLSP PRIVATE
2727
Swift/DiagnosticReportManager.swift
2828
Swift/DocumentFormatting.swift
2929
Swift/DocumentSymbols.swift
30-
Swift/EditorPlaceholder.swift
3130
Swift/FoldingRange.swift
3231
Swift/OpenInterface.swift
3332
Swift/RelatedIdentifiers.swift
33+
Swift/RewriteSourceKitPlaceholders.swift
3434
Swift/SemanticRefactorCommand.swift
3535
Swift/SemanticRefactoring.swift
3636
Swift/SemanticTokens.swift

Sources/SourceKitLSP/Swift/CodeCompletionSession.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ class CodeCompletionSession {
357357
}
358358

359359
let text = insertText.map {
360-
rewriteSourceKitPlaceholders(inString: $0, clientSupportsSnippets: clientSupportsSnippets)
360+
rewriteSourceKitPlaceholders(in: $0, clientSupportsSnippets: clientSupportsSnippets)
361361
}
362362
let isInsertTextSnippet = clientSupportsSnippets && text != insertText
363363

Sources/SourceKitLSP/Swift/Diagnostic.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,7 @@ extension TextEdit {
129129
{
130130
// Snippets are only suppored in code completion.
131131
// Remove SourceKit placeholders from Fix-Its because they can't be represented in the editor properly.
132-
let replacementWithoutPlaceholders = rewriteSourceKitPlaceholders(
133-
inString: replacement,
134-
clientSupportsSnippets: false
135-
)
132+
let replacementWithoutPlaceholders = rewriteSourceKitPlaceholders(in: replacement, clientSupportsSnippets: false)
136133

137134
// If both the replacement without placeholders and the fixit are empty, no TextEdit should be created.
138135
if (replacementWithoutPlaceholders.isEmpty && length == 0) {

Sources/SourceKitLSP/Swift/EditorPlaceholder.swift

Lines changed: 0 additions & 78 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import LSPLogging
14+
@_spi(SourceKitLSP) import SwiftRefactor
15+
16+
func rewriteSourceKitPlaceholders(in string: String, clientSupportsSnippets: Bool) -> String {
17+
var result = string
18+
var index = 1
19+
while let start = result.range(of: "<#") {
20+
guard let end = result[start.upperBound...].range(of: "#>") else {
21+
logger.error("invalid placeholder in \(string)")
22+
return string
23+
}
24+
let rawPlaceholder = String(result[start.lowerBound..<end.upperBound])
25+
guard let displayName = EditorPlaceholderData(text: rawPlaceholder)?.nameForSnippet else {
26+
logger.error("failed to decode placeholder \(rawPlaceholder) in \(string)")
27+
return string
28+
}
29+
let snippet = clientSupportsSnippets ? "${\(index):\(displayName)}" : ""
30+
result.replaceSubrange(start.lowerBound..<end.upperBound, with: snippet)
31+
index += 1
32+
}
33+
return result
34+
}
35+
36+
fileprivate extension EditorPlaceholderData {
37+
var nameForSnippet: Substring {
38+
switch self {
39+
case .basic(text: let text): return text
40+
case .typed(text: let text, type: _): return text
41+
}
42+
}
43+
}

Sources/SourceKitLSP/Swift/SemanticRefactoring.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ struct SemanticRefactoring {
6464
{
6565
// Snippets are only suppored in code completion.
6666
// Remove SourceKit placeholders in refactoring actions because they can't be represented in the editor properly.
67-
let textWithSnippets = rewriteSourceKitPlaceholders(inString: text, clientSupportsSnippets: false)
67+
let textWithSnippets = rewriteSourceKitPlaceholders(in: text, clientSupportsSnippets: false)
6868
let edit = TextEdit(range: startPosition..<endPosition, newText: textWithSnippets)
6969
textEdits.append(edit)
7070
}

Tests/SourceKitLSPTests/LocalSwiftTests.swift

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,57 +1364,6 @@ final class LocalSwiftTests: XCTestCase {
13641364
}
13651365
}
13661366

1367-
func testEditorPlaceholderParsing() {
1368-
var text =
1369-
"<#basic placeholder" // Need to end this in another line so Xcode doesn't treat it as a real placeholder
1370-
+ "#>"
1371-
var data = EditorPlaceholder(text)
1372-
XCTAssertNotNil(data)
1373-
if let data = data {
1374-
XCTAssertEqual(data, .basic("basic placeholder"))
1375-
XCTAssertEqual(data.displayName, "basic placeholder")
1376-
}
1377-
text = "<#T##x: Int##Int" + "#>"
1378-
data = EditorPlaceholder(text)
1379-
XCTAssertNotNil(data)
1380-
if let data = data {
1381-
XCTAssertEqual(data, .typed(displayName: "x: Int", type: "Int", typeForExpansion: "Int"))
1382-
XCTAssertEqual(data.displayName, "x: Int")
1383-
}
1384-
text = "<#T##x: Int##Blah##()->Int" + "#>"
1385-
data = EditorPlaceholder(text)
1386-
XCTAssertNotNil(data)
1387-
if let data = data {
1388-
XCTAssertEqual(data, .typed(displayName: "x: Int", type: "Blah", typeForExpansion: "()->Int"))
1389-
XCTAssertEqual(data.displayName, "x: Int")
1390-
}
1391-
text = "<#T##Int" + "#>"
1392-
data = EditorPlaceholder(text)
1393-
XCTAssertNotNil(data)
1394-
if let data = data {
1395-
XCTAssertEqual(data, .typed(displayName: "Int", type: "Int", typeForExpansion: "Int"))
1396-
XCTAssertEqual(data.displayName, "Int")
1397-
}
1398-
text = "<#foo"
1399-
data = EditorPlaceholder(text)
1400-
XCTAssertNil(data)
1401-
text = " <#foo"
1402-
data = EditorPlaceholder(text)
1403-
XCTAssertNil(data)
1404-
text = "foo"
1405-
data = EditorPlaceholder(text)
1406-
XCTAssertNil(data)
1407-
text = "foo#>"
1408-
data = EditorPlaceholder(text)
1409-
XCTAssertNil(data)
1410-
text = "<#foo#"
1411-
data = EditorPlaceholder(text)
1412-
XCTAssertNil(data)
1413-
text = " <#foo" + "#>"
1414-
data = EditorPlaceholder(text)
1415-
XCTAssertNil(data)
1416-
}
1417-
14181367
func testIncrementalParse() async throws {
14191368
let testClient = try await TestSourceKitLSPClient()
14201369
let url = URL(fileURLWithPath: "/\(UUID())/a.swift")

Tests/SourceKitLSPTests/SwiftCompletionTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,11 @@ final class SwiftCompletionTests: XCTestCase {
172172
.textEdit(
173173
TextEdit(
174174
range: Position(line: 9, utf16index: 9)..<Position(line: 9, utf16index: 9),
175-
newText: "test(${1:b: Int})"
175+
newText: "test(${1:Int})"
176176
)
177177
)
178178
)
179-
XCTAssertEqual(test.insertText, "test(${1:b: Int})")
179+
XCTAssertEqual(test.insertText, "test(${1:Int})")
180180
XCTAssertEqual(test.insertTextFormat, .snippet)
181181
}
182182
}

0 commit comments

Comments
 (0)