Skip to content

Commit

Permalink
Fix indentation handling
Browse files Browse the repository at this point in the history
The previous implementation did not support tab indentations or deeply
nested types. Instead of using indentationWidth to pad the generated
code, we deduce the current indentation depth from the first line in the
selection and check if the user prefers tabs for the initializer body.

This commit also inserts a new line between the variables and generated
initializers, as this seems to be a common Swift design choice.
  • Loading branch information
karlpuusepp committed Nov 8, 2017
1 parent 0345a56 commit 51ab77c
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 20 deletions.
8 changes: 3 additions & 5 deletions SourceEditorExtension/SIG.swift
Expand Up @@ -17,7 +17,7 @@ enum SIGError: Swift.Error {

let accessModifiers = ["open", "public", "internal", "private", "fileprivate"]

func generate(selection: [String], tabWidth: Int, indentationWidth: Int) throws -> [String] {
func generate(selection: [String], indentation: String, leadingIndent: String) throws -> [String] {
var variables = [(String, String)]()

for line in selection {
Expand Down Expand Up @@ -51,11 +51,9 @@ func generate(selection: [String], tabWidth: Int, indentationWidth: Int) throws

let arguments = variables.map { "\($0.0): \(addEscapingAttributeIfNeeded(to: $0.1))" }.joined(separator: ", ")

let indentExpressions = String(repeating: " ", count: tabWidth)
let expressions = variables.map { "\(indentExpressions)self.\($0.0) = \($0.0)" }
let expressions = variables.map { "\(indentation)self.\($0.0) = \($0.0)" }

let indentLines = String(repeating: " ", count: indentationWidth)
let lines = (["public init(\(arguments)) {"] + expressions + ["}"]).map { "\(indentLines)\($0)" }
let lines = (["public init(\(arguments)) {"] + expressions + ["}"]).map { "\(leadingIndent)\($0)" }

return lines
}
Expand Down
51 changes: 37 additions & 14 deletions SourceEditorExtension/SourceEditorCommand.swift
Expand Up @@ -10,12 +10,22 @@ import Foundation
import XcodeKit

class SourceEditorCommand: NSObject, XCSourceEditorCommand {
func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Swift.Error?) -> Void ) -> Void {

func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Swift.Error?) -> Void) {
do {
try generateInitializer(invocation: invocation)
completionHandler(nil)
} catch {
completionHandler(error)
}
}

private func generateInitializer(invocation: XCSourceEditorCommandInvocation) throws {
guard invocation.buffer.contentUTI == "public.swift-source" else {
return completionHandler(SIGError.notSwiftLanguage)
throw SIGError.notSwiftLanguage
}
guard let selection = invocation.buffer.selections.firstObject as? XCSourceTextRange else {
return completionHandler(SIGError.noSelection)
throw SIGError.noSelection
}

print(selection.start.line, selection.start.column)
Expand All @@ -34,18 +44,31 @@ class SourceEditorCommand: NSObject, XCSourceEditorCommand {
+ [String((invocation.buffer.lines[selection.end.line] as! String).utf8.prefix(selection.end.column))!]
}

let initializer: [String]
do {
initializer = try generate(selection: selectedText,
tabWidth: invocation.buffer.tabWidth,
indentationWidth: invocation.buffer.indentationWidth)
} catch {
return completionHandler(error)
}
var initializer = try generate(
selection: selectedText,
indentation: indentSequence(for: invocation.buffer),
leadingIndent: leadingIndentation(from: selection, in: invocation.buffer)
)

initializer.insert("", at: 0) // separate from selection with empty line

let targetRange = selection.end.line + 1..<selection.end.line + 1 + initializer.count
let targetRange = selection.end.line + 1 ..< selection.end.line + 1 + initializer.count
invocation.buffer.lines.insert(initializer, at: IndexSet(integersIn: targetRange))

completionHandler(nil)
}
}

private func indentSequence(for buffer: XCSourceTextBuffer) -> String {
return buffer.usesTabsForIndentation
? "\t"
: String(repeating: " ", count: buffer.indentationWidth)
}

private func leadingIndentation(from selection: XCSourceTextRange, in buffer: XCSourceTextBuffer) -> String {
let firstLineOfSelection = buffer.lines[selection.start.line] as! String

if let nonWhitespace = firstLineOfSelection.rangeOfCharacter(from: CharacterSet.whitespaces.inverted) {
return String(firstLineOfSelection.prefix(upTo: nonWhitespace.lowerBound))
} else {
return ""
}
}
Expand Up @@ -11,7 +11,7 @@ import XCTest
class SwiftInitializerGeneratorTests: XCTestCase {
func assert(input: [String], output: [String], file: StaticString = #file, line: UInt = #line) {
do {
let lines = try generate(selection: input, tabWidth: 4, indentationWidth: 0)
let lines = try generate(selection: input, indentation: " ", leadingIndent: "")
if(lines != output) {
XCTFail("Output is not correct; expected:\n\(output.joined(separator: "\n"))\n\ngot:\n\(lines.joined(separator: "\n"))", file: file, line: line)
}
Expand Down

0 comments on commit 51ab77c

Please sign in to comment.