mirror of
https://github.com/nicklockwood/SwiftFormat.git
synced 2026-06-16 10:34:34 +00:00
106 lines
4.6 KiB
Swift
106 lines
4.6 KiB
Swift
//
|
|
// FormatSelectedSourceCommand.swift
|
|
// Swift Formatter
|
|
//
|
|
// Created by Tony Arnold on 5/10/16.
|
|
// Copyright 2016 Charcoal Design
|
|
//
|
|
// Distributed under the permissive zlib license
|
|
// Get the latest version from here:
|
|
//
|
|
// https://github.com/nicklockwood/SwiftFormat
|
|
//
|
|
// This software is provided 'as-is', without any express or implied
|
|
// warranty. In no event will the authors be held liable for any damages
|
|
// arising from the use of this software.
|
|
//
|
|
// Permission is granted to anyone to use this software for any purpose,
|
|
// including commercial applications, and to alter it and redistribute it
|
|
// freely, subject to the following restrictions:
|
|
//
|
|
// 1. The origin of this software must not be misrepresented; you must not
|
|
// claim that you wrote the original software. If you use this software
|
|
// in a product, an acknowledgment in the product documentation would be
|
|
// appreciated but is not required.
|
|
//
|
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
|
// misrepresented as being the original software.
|
|
//
|
|
// 3. This notice may not be removed or altered from any source distribution.
|
|
//
|
|
|
|
import Foundation
|
|
import XcodeKit
|
|
|
|
class FormatSelectedSourceCommand: NSObject, XCSourceEditorCommand {
|
|
|
|
func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping(Error?) -> Void) -> Void {
|
|
guard invocation.buffer.contentUTI == "public.swift-source" else {
|
|
return completionHandler(FormatCommandError.notSwiftLanguage)
|
|
}
|
|
|
|
guard let selection = invocation.buffer.selections.firstObject as? XCSourceTextRange else {
|
|
return completionHandler(FormatCommandError.noSelection)
|
|
}
|
|
|
|
// Grab the selected source to format using entire lines of text
|
|
let selectionRange = selection.start.line ... min(selection.end.line, invocation.buffer.lines.count - 1)
|
|
let sourceToFormat = selectionRange.flatMap { invocation.buffer.lines[$0] as? String }.joined()
|
|
|
|
// Remove all selections to avoid a crash when changing the contents of the buffer.
|
|
invocation.buffer.selections.removeAllObjects()
|
|
|
|
do {
|
|
let indent = self.indentationString(for: invocation.buffer)
|
|
let options = FormatOptions(indent: indent, fragment: true)
|
|
let formattedSource = try format(sourceToFormat, rules: defaultRules, options: options)
|
|
|
|
for line in selectionRange.reversed() {
|
|
invocation.buffer.lines.removeObject(at: line)
|
|
}
|
|
|
|
invocation.buffer.lines.insert(formattedSource, at: selection.start.line)
|
|
|
|
let updatedSelectionRange = self.rangeForDifferences(in: selection, between: sourceToFormat, and: formattedSource)
|
|
|
|
invocation.buffer.selections.add(updatedSelectionRange)
|
|
} catch let error {
|
|
|
|
return completionHandler(error)
|
|
}
|
|
|
|
return completionHandler(nil)
|
|
}
|
|
|
|
/// Given a source text range, an original source string and a modified target string this method will calculate the differences, and return a usable XCSourceTextRange based upon the original.
|
|
///
|
|
/// - Parameters:
|
|
/// - textRange: Existing source text range
|
|
/// - sourceText: Original text
|
|
/// - targetText: Modified text
|
|
/// - Returns: Source text range that should be usable with the passed modified text
|
|
private func rangeForDifferences(in textRange: XCSourceTextRange, between sourceText: String, and targetText: String) -> XCSourceTextRange {
|
|
let lineCountOfTarget = targetText.components(separatedBy: CharacterSet.newlines).count
|
|
|
|
// Ensure that we're not greedy about end selections — this can cause empty lines to be removed
|
|
let finalLine = (textRange.end.column > 0) ? textRange.end.line : textRange.end.line - 1
|
|
let range = textRange.start.line ... finalLine
|
|
let difference = range.count - lineCountOfTarget
|
|
let start = XCSourceTextPosition(line: textRange.start.line, column: 0)
|
|
let end = XCSourceTextPosition(line: finalLine - difference, column: 0)
|
|
|
|
return XCSourceTextRange(start: start, end: end)
|
|
}
|
|
|
|
/// Calculates the indentation string representation for a given source text buffer.
|
|
///
|
|
/// - Parameter buffer: Source text buffer
|
|
/// - Returns: Indentation represented as a string
|
|
private func indentationString(for buffer: XCSourceTextBuffer) -> String {
|
|
let indentCharacter = buffer.usesTabsForIndentation ? "\t" : " "
|
|
let indentCount = buffer.usesTabsForIndentation ? buffer.indentationWidth / buffer.tabWidth : buffer.indentationWidth
|
|
|
|
return String(repeating: indentCharacter, count: indentCount)
|
|
}
|
|
}
|