├── Assets
├── App.png
├── Banner.png
├── UsageCode.png
├── UsageMenu.png
├── Preferences.png
├── UsageResult.png
├── Multiliner-1.0.mp4
├── Multiliner-1.1.mp4
├── Social Preview.jpg
└── UsageShortcut.png
├── Multiliner.zip
├── .gitignore
├── Sources
├── Multiliner
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── Banner.imageset
│ │ │ ├── Banner.png
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ ├── AppIcon-16.png
│ │ │ ├── AppIcon-32.png
│ │ │ ├── AppIcon-64.png
│ │ │ ├── AppIcon-1024.png
│ │ │ ├── AppIcon-128.png
│ │ │ ├── AppIcon-256.png
│ │ │ ├── AppIcon-512.png
│ │ │ └── Contents.json
│ │ ├── UsageCode.imageset
│ │ │ ├── UsageCode.png
│ │ │ └── Contents.json
│ │ ├── UsageMenu.imageset
│ │ │ ├── UsageMenu.png
│ │ │ └── Contents.json
│ │ ├── Preferences.imageset
│ │ │ ├── Preferences.png
│ │ │ └── Contents.json
│ │ ├── UsageResult.imageset
│ │ │ ├── UsageResult.png
│ │ │ └── Contents.json
│ │ ├── UsageShortcut.imageset
│ │ │ ├── UsageShortcut.png
│ │ │ └── Contents.json
│ │ └── AccentColor.colorset
│ │ │ └── Contents.json
│ ├── Multiliner.entitlements
│ ├── MultilinerApp.swift
│ └── ContentView.swift
├── Multiliner.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── xcuserdata
│ │ └── aheze.xcuserdatad
│ │ │ ├── xcdebugger
│ │ │ └── Breakpoints_v2.xcbkptlist
│ │ │ └── xcschemes
│ │ │ └── xcschememanagement.plist
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ ├── Multiliner.xcscheme
│ │ │ └── MultilinerExtension.xcscheme
│ └── project.pbxproj
└── MultilinerExtension
│ ├── MultilinerExtension.entitlements
│ ├── SelectionKind.swift
│ ├── SourceEditorExtension.swift
│ ├── FormatError.swift
│ ├── Extensions.swift
│ ├── Info.plist
│ └── FormatSelectedCodeCommand.swift
├── LICENSE
└── README.md
/Assets/App.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Assets/App.png
--------------------------------------------------------------------------------
/Multiliner.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Multiliner.zip
--------------------------------------------------------------------------------
/Assets/Banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Assets/Banner.png
--------------------------------------------------------------------------------
/Assets/UsageCode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Assets/UsageCode.png
--------------------------------------------------------------------------------
/Assets/UsageMenu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Assets/UsageMenu.png
--------------------------------------------------------------------------------
/Assets/Preferences.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Assets/Preferences.png
--------------------------------------------------------------------------------
/Assets/UsageResult.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Assets/UsageResult.png
--------------------------------------------------------------------------------
/Assets/Multiliner-1.0.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Assets/Multiliner-1.0.mp4
--------------------------------------------------------------------------------
/Assets/Multiliner-1.1.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Assets/Multiliner-1.1.mp4
--------------------------------------------------------------------------------
/Assets/Social Preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Assets/Social Preview.jpg
--------------------------------------------------------------------------------
/Assets/UsageShortcut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Assets/UsageShortcut.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # macOS
2 | .DS_Store
3 |
4 | ## Xcode-related
5 | xcuserdata/
6 | .build/
7 | build/
8 | DerivedData/
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/Banner.imageset/Banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Sources/Multiliner/Assets.xcassets/Banner.imageset/Banner.png
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/AppIcon.appiconset/AppIcon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Sources/Multiliner/Assets.xcassets/AppIcon.appiconset/AppIcon-16.png
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Sources/Multiliner/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/AppIcon.appiconset/AppIcon-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Sources/Multiliner/Assets.xcassets/AppIcon.appiconset/AppIcon-64.png
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/UsageCode.imageset/UsageCode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Sources/Multiliner/Assets.xcassets/UsageCode.imageset/UsageCode.png
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/UsageMenu.imageset/UsageMenu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Sources/Multiliner/Assets.xcassets/UsageMenu.imageset/UsageMenu.png
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Sources/Multiliner/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/AppIcon.appiconset/AppIcon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Sources/Multiliner/Assets.xcassets/AppIcon.appiconset/AppIcon-128.png
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Sources/Multiliner/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Sources/Multiliner/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/Preferences.imageset/Preferences.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Sources/Multiliner/Assets.xcassets/Preferences.imageset/Preferences.png
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/UsageResult.imageset/UsageResult.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Sources/Multiliner/Assets.xcassets/UsageResult.imageset/UsageResult.png
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/UsageShortcut.imageset/UsageShortcut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aheze/Multiliner/HEAD/Sources/Multiliner/Assets.xcassets/UsageShortcut.imageset/UsageShortcut.png
--------------------------------------------------------------------------------
/Sources/Multiliner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Sources/Multiliner/Multiliner.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Sources/Multiliner.xcodeproj/xcuserdata/aheze.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/Banner.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Banner.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/UsageCode.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "UsageCode.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/UsageMenu.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "UsageMenu.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/Preferences.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Preferences.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/UsageResult.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "UsageResult.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/UsageShortcut.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "UsageShortcut.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/MultilinerExtension/MultilinerExtension.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Sources/MultilinerExtension/SelectionKind.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SelectionKind.swift
3 | // Multiliner
4 | //
5 | // Created by A. Zheng (github.com/aheze) on 7/5/22.
6 | // Copyright © 2022 A. Zheng. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum SelectionKind {
12 | case parameters
13 | case array
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/Multiliner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Sources/MultilinerExtension/SourceEditorExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SourceEditorExtension.swift
3 | // Multiliner
4 | //
5 | // Created by A. Zheng (github.com/aheze) on 6/27/22.
6 | // Copyright © 2022 A. Zheng. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import XcodeKit
11 |
12 | /// Boilerplate code.
13 | class SourceEditorExtension: NSObject, XCSourceEditorExtension {}
14 |
--------------------------------------------------------------------------------
/Sources/Multiliner/MultilinerApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MultilinerApp.swift
3 | // Multiliner
4 | //
5 | // Created by A. Zheng (github.com/aheze) on 6/27/22.
6 | // Copyright © 2022 A. Zheng. All rights reserved.
7 | //
8 |
9 |
10 | import SwiftUI
11 |
12 | @main
13 | struct MultilinerApp: App {
14 | var body: some Scene {
15 | WindowGroup {
16 | ContentView()
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/MultilinerExtension/FormatError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormatError.swift
3 | // Multiliner
4 | //
5 | // Created by A. Zheng (github.com/aheze) on 7/5/22.
6 | // Copyright © 2022 A. Zheng. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum FormatError: Error, CustomStringConvertible, LocalizedError, CustomNSError {
12 | case noSelection
13 | case invalidSelection
14 |
15 | var description: String {
16 | switch self {
17 | case .noSelection:
18 | return "No selection."
19 | case .invalidSelection:
20 | return "Selection must be bounded by `()` or `[]`."
21 | }
22 | }
23 |
24 | var localizedDescription: String {
25 | return "Error: \(description)."
26 | }
27 |
28 | var errorUserInfo: [String: Any] {
29 | return [NSLocalizedDescriptionKey: localizedDescription]
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/MultilinerExtension/Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Extensions.swift
3 | // Multiliner
4 | //
5 | // Created by A. Zheng (github.com/aheze) on 7/5/22.
6 | // Copyright © 2022 A. Zheng. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import XcodeKit
11 |
12 | extension XCSourceEditorCommand {
13 | /// Get the lines of an entire files as an array of `String`s.
14 | func getLines(from buffer: XCSourceTextBuffer) -> [String] {
15 | guard let lines = buffer.lines as? [String] else { return [] }
16 | return lines
17 | }
18 |
19 | /// Get a single string from a range.
20 | func getText(from range: XCSourceTextRange, buffer: XCSourceTextBuffer) -> String {
21 | let allLines = getLines(from: buffer)
22 | let lines = allLines[range.start.line ... range.end.line]
23 | let text = lines.map { String($0) }.joined()
24 | return text
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/Multiliner.xcodeproj/xcuserdata/aheze.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Multiliner.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 1
11 |
12 | MultilinerExtension.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 0
16 |
17 |
18 | SuppressBuildableAutocreation
19 |
20 | 3C08D429286A3F160068FA83
21 |
22 | primary
23 |
24 |
25 | 3C08D43F286A3F3D0068FA83
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Sources/MultilinerExtension/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSExtension
6 |
7 | NSExtensionAttributes
8 |
9 | XCSourceEditorCommandDefinitions
10 |
11 |
12 | XCSourceEditorCommandClassName
13 | $(PRODUCT_MODULE_NAME).FormatSelectedCodeCommand
14 | XCSourceEditorCommandIdentifier
15 | $(PRODUCT_BUNDLE_IDENTIFIER).FormatSelectedCodeCommand
16 | XCSourceEditorCommandName
17 | Format Selected Code
18 |
19 |
20 | XCSourceEditorExtensionPrincipalClass
21 | $(PRODUCT_MODULE_NAME).SourceEditorExtension
22 |
23 | NSExtensionPointIdentifier
24 | com.apple.dt.Xcode.extension.source-editor
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 A. Zheng
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Sources/Multiliner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "AppIcon-16.png",
5 | "idiom" : "mac",
6 | "scale" : "1x",
7 | "size" : "16x16"
8 | },
9 | {
10 | "filename" : "AppIcon-32.png",
11 | "idiom" : "mac",
12 | "scale" : "2x",
13 | "size" : "16x16"
14 | },
15 | {
16 | "filename" : "AppIcon-32.png",
17 | "idiom" : "mac",
18 | "scale" : "1x",
19 | "size" : "32x32"
20 | },
21 | {
22 | "filename" : "AppIcon-64.png",
23 | "idiom" : "mac",
24 | "scale" : "2x",
25 | "size" : "32x32"
26 | },
27 | {
28 | "filename" : "AppIcon-128.png",
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "filename" : "AppIcon-256.png",
35 | "idiom" : "mac",
36 | "scale" : "2x",
37 | "size" : "128x128"
38 | },
39 | {
40 | "filename" : "AppIcon-256.png",
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "256x256"
44 | },
45 | {
46 | "filename" : "AppIcon-512.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "AppIcon-512.png",
53 | "idiom" : "mac",
54 | "scale" : "1x",
55 | "size" : "512x512"
56 | },
57 | {
58 | "filename" : "AppIcon-1024.png",
59 | "idiom" : "mac",
60 | "scale" : "2x",
61 | "size" : "512x512"
62 | }
63 | ],
64 | "info" : {
65 | "author" : "xcode",
66 | "version" : 1
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Multiliner
4 |
5 | An Xcode source extension to expand lengthy lines.
6 |
7 | - Super lightweight, it's like a script.
8 | - It expands long lines. That's it!
9 | - Works with:
10 | - Initializers
11 | - Function calls
12 | - Array literals
13 | - SwiftUI modifiers
14 |
15 | ### Showcase
16 |
17 | https://user-images.githubusercontent.com/49819455/176060861-4bab03cc-a953-4839-b04e-cf05864f879e.mp4
18 |
19 | ### Installation
20 |
21 | 1. Download the app [directly](https://github.com/aheze/Multiliner/raw/main/Multiliner.zip) or through Homebrew
22 |
23 | ```bash
24 | brew install hkamran80/things/multiliner
25 | ```
26 |
27 | **Note:** Multiliner requires macOS Monterey (12.3) or higher.
28 |
29 | 2. Open the app
30 | 3. Go to System Preferences → Extensions and check `Multiliner`
31 |
32 |
33 |
34 | ### Usage
35 |
36 | It's simple, just highlight the code that you want to format, then press Editor → Multiliner → Format Selected Code. More details in the app.
37 |
38 |
39 |
40 | ### Shortcut
41 |
42 | If you'd like to access Multiliner quicker, try adding a key binding. Go to Xcode → Preferences → Key Bindings, then search for "Multiliner":
43 |
44 |
45 |
46 |
47 | ### Author
48 |
49 | Multiliner is made by [aheze](https://github.com/aheze).
50 |
51 | ### Contributing
52 |
53 | All contributions are welcome. Just [fork](https://github.com/aheze/Multiliner/fork) the repo, then make a pull request.
54 |
55 | ### Need Help?
56 |
57 | Open an [issue](https://github.com/aheze/Multiliner/issues) or join the [Discord server](https://discord.com/invite/Pmq8fYcus2). You can also ping me on [Twitter](https://twitter.com/aheze0). Or read the source code, I added a bunch of comments.
58 |
59 | ### License
60 |
61 | ```text
62 | MIT License
63 |
64 | Copyright (c) 2022 A. Zheng
65 |
66 | Permission is hereby granted, free of charge, to any person obtaining a copy
67 | of this software and associated documentation files (the "Software"), to deal
68 | in the Software without restriction, including without limitation the rights
69 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
70 | copies of the Software, and to permit persons to whom the Software is
71 | furnished to do so, subject to the following conditions:
72 |
73 | The above copyright notice and this permission notice shall be included in all
74 | copies or substantial portions of the Software.
75 |
76 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
77 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
78 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
79 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
80 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
81 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
82 | SOFTWARE.
83 | ```
84 |
--------------------------------------------------------------------------------
/Sources/Multiliner.xcodeproj/xcshareddata/xcschemes/Multiliner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/Sources/Multiliner.xcodeproj/xcshareddata/xcschemes/MultilinerExtension.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
9 |
10 |
16 |
22 |
23 |
24 |
30 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
47 |
48 |
60 |
62 |
68 |
69 |
70 |
71 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/Sources/MultilinerExtension/FormatSelectedCodeCommand.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SourceEditorCommand.swift
3 | // Multiliner
4 | //
5 | // Created by A. Zheng (github.com/aheze) on 6/27/22.
6 | // Copyright © 2022 A. Zheng. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import XcodeKit
11 |
12 | class FormatSelectedCodeCommand: NSObject, XCSourceEditorCommand {
13 | /// The `Format Selected Code` command.
14 | func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void) {
15 | /// Get the selection first.
16 | guard
17 | let selection = invocation.buffer.selections.firstObject,
18 | let range = selection as? XCSourceTextRange
19 | else {
20 | completionHandler(FormatError.noSelection)
21 | return
22 | }
23 |
24 | /// It's possible that the user selected the last "extra" line too.
25 | if range.start.line > invocation.buffer.lines.count - 1 { range.start.line -= 1 }
26 | if range.end.line > invocation.buffer.lines.count - 1 { range.end.line -= 1 }
27 |
28 | /// Store the current lines of the entire file.
29 | let oldLines = getLines(from: invocation.buffer)
30 |
31 | /// The width of a single tab, usually ` `.
32 | let tab: String
33 |
34 | /// The selection's starting tab.
35 | /// Example:
36 | // **input** ` init()`
37 | // **output** ` `
38 | let startTab: Substring
39 |
40 | let startColumn: Int
41 |
42 | if invocation.buffer.usesTabsForIndentation {
43 | tab = "\u{0009}" /// Tab character.
44 |
45 | startTab = oldLines[range.start.line]
46 | .prefix { $0 == "\u{0009}" }
47 |
48 | startColumn = startTab.count
49 |
50 | } else {
51 | startTab = oldLines[range.start.line]
52 | .prefix { $0 == " " }
53 |
54 | tab = String(repeating: " ", count: invocation.buffer.indentationWidth)
55 |
56 | startColumn = startTab.count / invocation.buffer.indentationWidth
57 | }
58 |
59 | /// The tab that prefixes each parameter/array element.
60 | let contentTab = startTab + tab
61 |
62 | /// The entire text of the file.
63 | let text = getText(from: range, buffer: invocation.buffer)
64 |
65 | /// Get the opening and closing indices if the selected text contains parameters.
66 | let openingParenthesisIndex = text.firstIndex(of: "(")
67 | let closingParenthesisIndex = text.lastIndex(of: ")")
68 |
69 | /// Get the opening and closing array element if the selected text is an array.
70 | let openingArrayIndex = text.firstIndex(of: "[")
71 | let closingArrayIndex = text.lastIndex(of: "]")
72 |
73 | /// Determine if the selection was an array or a set of parameters.
74 | /// Only use the opening brace for comparison.
75 | var selectionKind: SelectionKind
76 | switch (openingParenthesisIndex, openingArrayIndex) {
77 | case let (.some(openingParenthesisIndex), .some(openingArrayIndex)):
78 | if openingParenthesisIndex < openingArrayIndex {
79 | selectionKind = .parameters
80 | } else {
81 | selectionKind = .array
82 | }
83 | case (.some, .none):
84 | selectionKind = .parameters
85 | case (.none, .some):
86 | selectionKind = .array
87 | default:
88 | completionHandler(FormatError.noSelection)
89 | return
90 | }
91 |
92 | /// Determine if the selection was an array or a set of parameters.
93 | let openingBracesIndex: String.Index? = selectionKind == .parameters
94 | ? openingParenthesisIndex
95 | : openingArrayIndex
96 |
97 | let closingBracesIndex: String.Index? = selectionKind == .parameters
98 | ? closingParenthesisIndex
99 | : closingArrayIndex
100 |
101 | /// Make sure there's an opening and closing index.
102 | guard let openingBracesIndex = openingBracesIndex, let closingBracesIndex = closingBracesIndex else {
103 | completionHandler(FormatError.invalidSelection)
104 | return
105 | }
106 |
107 | /// Skip the opening `(` or `[`.
108 | let openingContentIndex = text.index(after: openingBracesIndex)
109 | let closingContentIndex = closingBracesIndex
110 |
111 | /// The text inside the braces.
112 | let contentsString = text[openingContentIndex ..< closingContentIndex]
113 | let contents = contentsString
114 | .components(separatedBy: ",")
115 |
116 | /// Format the content by adding spaces and commas.
117 | let contentsFormatted: [String] = contents.enumerated()
118 | .map { index, element in
119 | let line = element.trimmingCharacters(in: .whitespacesAndNewlines)
120 | if index == contents.indices.last {
121 | return contentTab + line
122 | } else {
123 | return contentTab + line + ","
124 | }
125 | }
126 |
127 | /// The string that comes before the selection.
128 | let openingString = text[..