Skip to content

Commit

Permalink
Merge pull request #212 from jpsim/jp-format-command
Browse files Browse the repository at this point in the history
add format command
  • Loading branch information
jpsim committed May 28, 2016
2 parents 8e5e38c + 61a42ba commit 8d88fef
Show file tree
Hide file tree
Showing 16 changed files with 319 additions and 85 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@

##### Enhancements

* Add `format` command that re-indents a Swift file much like pasting into
Xcode would. This command optionally takes the following parameters:

* `--file (string)`: relative or absolute path of Swift file to format
* `--no-trim-whitespace`: trim trailing whitespace
* `--use-tabs`: use tabs to indent
* `--indent-width (integer)`: number of spaces to indent
[JP Simard](https://github.com/jpsim)

* Add support `TOOLCHAINS` environment variable to selecting alternative
toolchains for loading SourceKitService.
[Norio Nomura](https://github.com/norio-nomura)
Expand Down
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ let package = Package(
.Package(url: "https://github.com/norio-nomura/Clang_C.git", majorVersion: 1),
.Package(url: "https://github.com/drmohundro/SWXMLHash.git", majorVersion: 2, minor: 3),
.Package(url: "https://github.com/Carthage/Commandant.git", majorVersion: 0, minor: 9),
]
],
exclude: ["Tests/SourceKittenFramework/Fixtures"]
)
40 changes: 40 additions & 0 deletions Source/SourceKittenFramework/File.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,46 @@ public final class File {
lines = contents.lines()
}

/**
Formats the file.
*/
public func format(trimmingTrailingWhitespace trimmingTrailingWhitespace: Bool,
useTabs: Bool,
indentWidth: Int) -> String {
guard let path = path else {
return contents
}
_ = Structure(file: self)
var newContents = [String]()
var offset = 0
for line in lines {
let formatResponse = Request.Format(file: path,
line: Int64(line.index),
useTabs: useTabs,
indentWidth: Int64(indentWidth)).send()
let newText = formatResponse["key.sourcetext"] as! String
newContents.append(newText)

guard newText != line.content else { continue }

Request.ReplaceText(file: path,
offset: Int64(line.byteRange.location + offset),
length: Int64(line.byteRange.length - 1),
sourceText: newText).send()
let oldLength = line.byteRange.length
let newLength = newText.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)
offset += 1 + newLength - oldLength
}

if trimmingTrailingWhitespace {
newContents = newContents.map {
($0 as NSString).stringByTrimmingTrailingCharactersInSet(.whitespaceCharacterSet())
}
}

return newContents.joinWithSeparator("\n") + "\n"
}

/**
Parse source declaration string from SourceKit dictionary.

Expand Down
26 changes: 26 additions & 0 deletions Source/SourceKittenFramework/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ public enum Request {
case FindUSR(file: String, usr: String)
/// Index
case Index(file: String)
/// Format
case Format(file: String, line: Int64, useTabs: Bool, indentWidth: Int64)
/// ReplaceText
case ReplaceText(file: String, offset: Int64, length: Int64, sourceText: String)

private var sourcekitObject: sourcekitd_object_t {
var dict: [sourcekitd_uid_t : sourcekitd_object_t]
Expand Down Expand Up @@ -254,6 +258,28 @@ public enum Request {
sourcekitd_uid_get_from_cstr("key.sourcefile"): sourcekitd_request_string_create(file),
sourcekitd_uid_get_from_cstr("key.compilerargs"): sourcekitd_request_array_create(&compilerargs, compilerargs.count)
]
case .Format(let file, let line, let useTabs, let indentWidth):
let formatOptions = [
sourcekitd_uid_get_from_cstr("key.editor.format.indentwidth"): sourcekitd_request_int64_create(indentWidth),
sourcekitd_uid_get_from_cstr("key.editor.format.tabwidth"): sourcekitd_request_int64_create(indentWidth),
sourcekitd_uid_get_from_cstr("key.editor.format.usetabs"): sourcekitd_request_int64_create(Int64(Int(useTabs))),
]
var formatOptionsKeys = Array(formatOptions.keys)
var formatOptionsValues = Array(formatOptions.values)
dict = [
sourcekitd_uid_get_from_cstr("key.request"): sourcekitd_request_uid_create(sourcekitd_uid_get_from_cstr("source.request.editor.formattext")),
sourcekitd_uid_get_from_cstr("key.name"): sourcekitd_request_string_create(file),
sourcekitd_uid_get_from_cstr("key.line"): sourcekitd_request_int64_create(line),
sourcekitd_uid_get_from_cstr("key.editor.format.options"): sourcekitd_request_dictionary_create(&formatOptionsKeys, &formatOptionsValues, formatOptions.count)
]
case .ReplaceText(let file, let offset, let length, let sourceText):
dict = [
sourcekitd_uid_get_from_cstr("key.request"): sourcekitd_request_uid_create(sourcekitd_uid_get_from_cstr("source.request.editor.replacetext")),
sourcekitd_uid_get_from_cstr("key.name"): sourcekitd_request_string_create(file),
sourcekitd_uid_get_from_cstr("key.offset"): sourcekitd_request_int64_create(offset),
sourcekitd_uid_get_from_cstr("key.length"): sourcekitd_request_int64_create(length),
sourcekitd_uid_get_from_cstr("key.sourcetext"): sourcekitd_request_string_create(sourceText),
]
}
var keys = Array(dict.keys)
var values = Array(dict.values)
Expand Down
2 changes: 1 addition & 1 deletion Source/SourceKittenFramework/library_wrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ private extension String {
return (self as NSString).stringByAppendingPathComponent(str)
}

private func deletingLastPathComponents(_ n: Int) -> String {
private func deletingLastPathComponents(n: Int) -> String {
let pathComponents = NSString(string: self).pathComponents.dropLast(n)
return NSString.pathWithComponents(Array(pathComponents))
}
Expand Down
52 changes: 52 additions & 0 deletions Source/sourcekitten/FormatCommand.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// FormatCommand.swift
// SourceKitten
//
// Created by JP Simard on 2016-05-28.
// Copyright (c) 2016 SourceKitten. All rights reserved.
//

import Commandant
import Foundation
import Result
import SourceKittenFramework

struct FormatCommand: CommandType {
let verb = "format"
let function = "Format Swift file"

struct Options: OptionsType {
let file: String
let trimWhitespace: Bool
let useTabs: Bool
let indentWidth: Int

static func create(file: String) -> (trimWhitespace: Bool) -> (useTabs: Bool) -> (indentWidth: Int) -> Options {
return { trimWhitespace in { useTabs in { indentWidth in
self.init(file: file, trimWhitespace: trimWhitespace, useTabs: useTabs, indentWidth: indentWidth)
}}}
}

static func evaluate(m: CommandMode) -> Result<Options, CommandantError<SourceKittenError>> {
return create
<*> m <| Option(key: "file", defaultValue: "", usage: "relative or absolute path of Swift file to format")
<*> m <| Option(key: "trim-whitespace", defaultValue: true, usage: "trim trailing whitespace")
<*> m <| Option(key: "use-tabs", defaultValue: false, usage: "use tabs to indent")
<*> m <| Option(key: "indent-width", defaultValue: 4, usage: "number of spaces to indent")
}
}

func run(options: Options) -> Result<(), SourceKittenError> {
guard !options.file.isEmpty else {
return .Failure(.InvalidArgument(description: "file must be set when calling format"))
}
let absolutePath = (options.file as NSString).absolutePathRepresentation()
try! File(path: absolutePath)?
.format(trimmingTrailingWhitespace: options.trimWhitespace,
useTabs: options.useTabs,
indentWidth: options.indentWidth)
.dataUsingEncoding(NSUTF8StringEncoding)?
.writeToFile(absolutePath, options: [])
return .Success()
}
}
2 changes: 1 addition & 1 deletion Source/sourcekitten/IndexCommand.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Structure.swift
// IndexCommand.swift
// SourceKitten
//
// Created by JP Simard on 2015-04-24.
Expand Down
2 changes: 1 addition & 1 deletion Source/sourcekitten/StructureCommand.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Structure.swift
// StructureCommand.swift
// SourceKitten
//
// Created by JP Simard on 2015-01-07.
Expand Down
2 changes: 1 addition & 1 deletion Source/sourcekitten/SyntaxCommand.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Syntax.swift
// SyntaxCommand.swift
// SourceKitten
//
// Created by JP Simard on 2015-01-07.
Expand Down
2 changes: 1 addition & 1 deletion Source/sourcekitten/VersionCommand.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Version.swift
// VersionCommand.swift
// SourceKitten
//
// Created by JP Simard on 2015-01-07.
Expand Down
1 change: 1 addition & 0 deletions Source/sourcekitten/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let registry = CommandRegistry<SourceKittenError>()
registry.register(CompleteCommand())
registry.register(DocCommand())
registry.register(FormatCommand())
registry.register(IndexCommand())
registry.register(SyntaxCommand())
registry.register(StructureCommand())
Expand Down
8 changes: 8 additions & 0 deletions Tests/SourceKittenFramework/FileTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,12 @@ class FileTests: XCTestCase {
func testUnreadablePath() {
XCTAssert(File(path: "/dev/null") == nil)
}

func testFormat() {
let file = File(path: fixturesDirectory + "BicycleUnformatted.swift")
let formattedFile = file?.format(trimmingTrailingWhitespace: true,
useTabs: false,
indentWidth: 4)
XCTAssertEqual(formattedFile!, try! String(contentsOfFile: fixturesDirectory + "Bicycle.swift"))
}
}
Loading

0 comments on commit 8d88fef

Please sign in to comment.