File: RefactoringResponse.swift

package info (click to toggle)
swiftlang 6.1.3-4
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 2,791,644 kB
  • sloc: cpp: 9,901,738; ansic: 2,201,433; asm: 1,091,827; python: 308,252; objc: 82,166; f90: 80,126; lisp: 38,358; pascal: 25,559; sh: 20,429; ml: 5,058; perl: 4,745; makefile: 4,484; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (140 lines) | stat: -rw-r--r-- 5,430 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
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Csourcekitd
import LanguageServerProtocol
import SKLogging
import SKUtilities
import SourceKitD

protocol RefactoringResponse {
  init(title: String, uri: DocumentURI, refactoringEdits: [RefactoringEdit])
}

extension Array<RefactoringEdit> {
  init?(_ dict: SKDResponseDictionary, _ snapshot: DocumentSnapshot, _ keys: sourcekitd_api_keys) {
    guard let categorizedEdits: SKDResponseArray = dict[keys.categorizedEdits] else {
      logger.fault("categorizedEdits doesn't exist in response dictionary")
      return nil
    }

    self = []

    categorizedEdits.forEach { _, categorizedEdit in
      guard let edits: SKDResponseArray = categorizedEdit[keys.edits] else {
        logger.fault("edits doesn't exist in categorizedEdit dictionary")
        return true
      }
      edits.forEach { _, edit in
        guard let startLine: Int = edit[keys.line],
          let startColumn: Int = edit[keys.column],
          let endLine: Int = edit[keys.endLine],
          let endColumn: Int = edit[keys.endColumn],
          let text: String = edit[keys.text]
        else {
          logger.fault("Failed to deserialise edit dictionary containing values: \(edit)")
          return true  // continue
        }

        // The LSP is zero based, but semantic_refactoring is one based.
        let startPosition = snapshot.positionOf(
          zeroBasedLine: startLine - 1,
          utf8Column: startColumn - 1
        )
        let endPosition = snapshot.positionOf(
          zeroBasedLine: endLine - 1,
          utf8Column: endColumn - 1
        )
        // Snippets are only supported in code completion.
        // Remove SourceKit placeholders in refactoring actions because they
        // can't be represented in the editor properly.
        let textWithSnippets = rewriteSourceKitPlaceholders(in: text, clientSupportsSnippets: false)
        self.append(
          RefactoringEdit(
            range: startPosition..<endPosition,
            newText: textWithSnippets,
            bufferName: edit[keys.bufferName]
          )
        )
        return true
      }
      return true
    }

    guard !self.isEmpty else {
      logger.error("No refactoring edits found")
      return nil
    }
  }
}

extension RefactoringResponse {
  /// Create an instance of `RefactoringResponse` from a sourcekitd semantic
  /// refactoring response dictionary, if possible.
  ///
  /// - Parameters:
  ///   - title: The title of the refactoring action.
  ///   - dict: Response dictionary to extract information from.
  ///   - snapshot: The snapshot that triggered the `semantic_refactoring` request.
  ///   - keys: The sourcekitd key set to use for looking up into `dict`.
  init?(_ title: String, _ dict: SKDResponseDictionary, _ snapshot: DocumentSnapshot, _ keys: sourcekitd_api_keys) {
    guard let refactoringEdits = [RefactoringEdit](dict, snapshot, keys) else {
      return nil
    }

    self.init(title: title, uri: snapshot.uri, refactoringEdits: refactoringEdits)
  }
}

extension SwiftLanguageService {
  /// Provides detailed information about the result of a specific refactoring
  /// operation.
  ///
  /// Wraps the information returned by sourcekitd's `semantic_refactoring`
  /// request, such as the necessary edits and placeholder locations.
  ///
  /// - Parameters:
  ///   - refactorCommand: The semantic `RefactorCommand` that triggered this request.
  /// - Returns: The response of the refactoring
  func refactoring(
    _ refactorCommand: SemanticRefactorCommand
  ) async throws -> SemanticRefactoring {
    let keys = self.keys

    let uri = refactorCommand.textDocument.uri
    let snapshot = try self.documentManager.latestSnapshot(uri)
    let line = refactorCommand.positionRange.lowerBound.line
    let utf16Column = refactorCommand.positionRange.lowerBound.utf16index
    let utf8Column = snapshot.lineTable.utf8ColumnAt(line: line, utf16Column: utf16Column)

    let skreq = sourcekitd.dictionary([
      keys.request: self.requests.semanticRefactoring,
      // Preferred name for e.g. an extracted variable.
      // Empty string means sourcekitd chooses a name automatically.
      keys.name: "",
      keys.sourceFile: uri.pseudoPath,
      // LSP is zero based, but this request is 1 based.
      keys.line: line + 1,
      keys.column: utf8Column + 1,
      keys.length: snapshot.utf8OffsetRange(of: refactorCommand.positionRange).count,
      keys.actionUID: self.sourcekitd.api.uid_get_from_cstr(refactorCommand.actionString)!,
      keys.compilerArgs: await self.buildSettings(for: snapshot.uri, fallbackAfterTimeout: true)?.compilerArgs
        as [SKDRequestValue]?,
    ])

    let dict = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
    guard let refactor = SemanticRefactoring(refactorCommand.title, dict, snapshot, self.keys) else {
      throw SemanticRefactoringError.noEditsNeeded(uri)
    }
    return refactor
  }
}