diff --git a/.gitignore b/.gitignore index 5d84a1a..c29b7fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ +.DS_Store + # Xcode -# build/ *.pbxuser !default.pbxuser diff --git a/Common.swift b/Common.swift deleted file mode 100644 index 8f442df..0000000 --- a/Common.swift +++ /dev/null @@ -1,156 +0,0 @@ -// -// Common.swift -// Linex -// -// Created by Kaunteya Suryawanshi on 01/09/17. -// Copyright © 2017 Kaunteya Suryawanshi. All rights reserved. -// - -import Foundation -//MARK: - Basic -extension String { - - /// All multiple whitespaces are replaced by one whitespace - var condensedWhitespace: String { - let components = self.components(separatedBy: .whitespaces) - return components.filter { !$0.isEmpty }.joined(separator: " ") - } - - subscript (i: Int) -> Character { - return self[index(startIndex, offsetBy: i)] - } - - subscript (i: Int) -> String { - return String(self[i] as Character) - } - - subscript (range: Range) -> Substring { - let rangeIndex:Range = self.indexRangeFor(range: range) - return self[rangeIndex] - } - - subscript(range: NSRange) -> String { - return (self as NSString).substring(with: range) - } - - func indexAt(offset: Int) -> Index { - return self.index(self.startIndex, offsetBy: offset) - } - - func indexRangeFor(range: Range) -> Range { - return indexAt(offset: range.lowerBound)..) -> ClosedRange { - return indexAt(offset: range.lowerBound)...indexAt(offset: range.upperBound) - } - - /// Fetch indentation offset of lines in code - /// " var foo" -> 4 - func lineIndentationOffset() -> Int { - var i = 0 - for a in self.characters { - if a == " " { - i += 1 - } else { break } - } - return i - } - - func trimmedStart() -> String { - return self.replacingOccurrences(of: "^[ \t]+", - with: "", - options: CompareOptions.regularExpression) - } - - func trimmedEnd() -> String { - return self.replacingOccurrences(of: "[ \t]+$", - with: "", - options: CompareOptions.regularExpression) - } - - func repeating(count: Int) -> String { - return Array(repeating: self, count: count).joined() - } - - func replacedRegex(pattern: String, with template: String) -> String { - let regex = try! NSRegularExpression(pattern: pattern) - let range = NSMakeRange(0, characters.count) - let modString = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: template) - return modString - } -} - -//MARK: Advance -extension String { - func lineOneSpaceAt(pin: Int) -> (Int, String) { - - var start = pin - while start > 0 && self[start - 1] == " " { - start -= 1 - } - - var end = pin - while end < self.characters.count && self[end] == " " { - end += 1 - } - - var newString = self - if start == end {//No space - newString.replaceSubrange(self.indexAt(offset: start).. Range? { - guard let range:Range = selectWord(pin: pin) else { return nil } - return self.indexRangeFor(range: range) - } - - func selectWord(pin: Int) -> Range? { - var pin = pin - guard pin <= self.characters.count else { - return nil - } - guard self.characters.count > 1 else { - return nil - } - - // Move pin to one position left when it is after last character - let invalidLastChars = CharacterSet(charactersIn: " :!?,.") - var validChars = CharacterSet.alphanumerics - validChars.insert(charactersIn: "@_") - if (pin > 0), let _ = (self[pin] as String).rangeOfCharacter(from: invalidLastChars) { - if let _ = (self[pin - 1] as String).rangeOfCharacter(from: validChars) { - pin -= 1 - } - } - - var start = pin - while start >= 0 && (self[start] as String).rangeOfCharacter(from: validChars) != nil { - start -= 1 - } - - var end = pin - while end < characters.count && (self[end] as String).rangeOfCharacter(from: validChars) != nil { - end += 1 - } - if start == end { return nil } - return start + 1..CFBundleShortVersionString 1.0 CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSExtension diff --git a/Convert/SourceEditorCommand.swift b/Convert/SourceEditorCommand.swift index 8d04ec2..b16d2ec 100644 --- a/Convert/SourceEditorCommand.swift +++ b/Convert/SourceEditorCommand.swift @@ -18,32 +18,47 @@ class SourceEditorCommand: NSObject, XCSourceEditorCommand { func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void { let buffer = invocation.buffer - let selectedRanges: SelectionType = selectionRanges(of: buffer) - if let command = Options(command: invocation.commandIdentifier) { + let command = Options(command: invocation.commandIdentifier)! + buffer.selectionRanges.forEach { range in + switch range.selection { - switch selectedRanges { case .none(let line, let column): - let range = buffer.selections.firstObject as! XCSourceTextRange - var currentLine = buffer.lines[line] as! String - let currentChar = currentLine[column] as String + let currentLine = buffer[line] + let currentChar = currentLine[column] + if currentChar.presentIn(.decimalDigits), var num = Int(String(currentChar)) { + switch command { + case .increment: num += 1 + case .decrement: num -= 1//currentLine.replaceSubrange(currentRange, with: "\(num - 1)") + } + } + + default: break + } + } + +/* + switch selection { + case .none(let position): + var currentLine = buffer.lines[position.line] as! String + let currentChar = currentLine[position.column] //If caret is beside number - if let _ = currentChar.rangeOfCharacter(from: .decimalDigits), let num = Int(currentChar) { + if currentChar.presentIn(.decimalDigits), let num = Int(String(currentChar)) { let currentRange = currentLine.indexRangeFor(range: range.start.column...range.start.column) switch command { case .increment: currentLine.replaceSubrange(currentRange, with: "\(num + 1)") case .decrement: currentLine.replaceSubrange(currentRange, with: "\(num - 1)") } } else { - if let selectionRange:Range = currentLine.selectWord(pin: column) { + if let selectionRange:Range = currentLine.selectWord(pin: position.column, validChars: .validWordChars) { let selectedSubString = currentLine[selectionRange] if let newString = toggle(boolString: selectedSubString) { currentLine.replaceSubrange(selectionRange, with: newString) } } } - buffer.lines.replaceObject(at: line, with: currentLine) + buffer.lines.replaceObject(at: position.line, with: currentLine) case .words(let line, let colStart, let colEnd): var currentLine = buffer.lines[line] as! String @@ -76,9 +91,10 @@ class SourceEditorCommand: NSObject, XCSourceEditorCommand { } case .lines(_, _): break - case .multiLocation(_): break } - } + */ + +// } completionHandler(nil) } diff --git a/Line/Extras.swift b/Line/Extras.swift deleted file mode 100644 index 6310949..0000000 --- a/Line/Extras.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// Extras.swift -// Linex -// -// Created by Kaunteya Suryawanshi on 30/08/17. -// Copyright © 2017 Kaunteya Suryawanshi. All rights reserved. -// - -import Foundation -import XcodeKit - - -extension XCSourceEditorCommandInvocation { - - - func selectionRanges() -> [XCSourceTextRange]? { - assert(self.buffer.selections.count > 0, "Count can be zero. Wrong assumption") - - return self.buffer.selections.map { $0 as! XCSourceTextRange } - } - - func moveCursorTo(location: XCSourceTextPosition) { - let lineSelection = XCSourceTextRange(start: location, end: location) - buffer.selections.setArray([lineSelection]) - } -} diff --git a/Line/Info.plist b/Line/Info.plist index 162022c..812dd7f 100644 --- a/Line/Info.plist +++ b/Line/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleVersion - 1 + 10 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSExtension @@ -28,62 +28,62 @@ XCSourceEditorCommandDefinitions - - XCSourceEditorCommandClassName - $(PRODUCT_MODULE_NAME).SourceEditorCommand - XCSourceEditorCommandIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER).duplicate - XCSourceEditorCommandName - Duplicate Line - - - XCSourceEditorCommandClassName - $(PRODUCT_MODULE_NAME).SourceEditorCommand - XCSourceEditorCommandIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER).openNewLineBelow - XCSourceEditorCommandName - Open New Line Below - - - XCSourceEditorCommandClassName - $(PRODUCT_MODULE_NAME).SourceEditorCommand - XCSourceEditorCommandIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER).openNewLineAbove - XCSourceEditorCommandName - Open New Line Above - - - XCSourceEditorCommandClassName - $(PRODUCT_MODULE_NAME).SourceEditorCommand - XCSourceEditorCommandIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER).commentedDuplicate - XCSourceEditorCommandName - Commented Duplicate - - - XCSourceEditorCommandClassName - $(PRODUCT_MODULE_NAME).SourceEditorCommand - XCSourceEditorCommandIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER).deleteLine - XCSourceEditorCommandName - Delete Line - - - XCSourceEditorCommandClassName - $(PRODUCT_MODULE_NAME).SourceEditorCommand - XCSourceEditorCommandIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER).join - XCSourceEditorCommandName - Join Line - - - XCSourceEditorCommandClassName - $(PRODUCT_MODULE_NAME).SourceEditorCommand - XCSourceEditorCommandIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER).lineBeginning - XCSourceEditorCommandName - Line Beginning - + + XCSourceEditorCommandClassName + $(PRODUCT_MODULE_NAME).SourceEditorCommand + XCSourceEditorCommandIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER).duplicate + XCSourceEditorCommandName + Copy Lines + + + XCSourceEditorCommandClassName + $(PRODUCT_MODULE_NAME).SourceEditorCommand + XCSourceEditorCommandIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER).openNewLineBelow + XCSourceEditorCommandName + Open New Line Below + + + XCSourceEditorCommandClassName + $(PRODUCT_MODULE_NAME).SourceEditorCommand + XCSourceEditorCommandIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER).openNewLineAbove + XCSourceEditorCommandName + Open New Line Above + + + XCSourceEditorCommandClassName + $(PRODUCT_MODULE_NAME).SourceEditorCommand + XCSourceEditorCommandIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER).commentedDuplicate + XCSourceEditorCommandName + Commented Duplicate + + + XCSourceEditorCommandClassName + $(PRODUCT_MODULE_NAME).SourceEditorCommand + XCSourceEditorCommandIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER).deleteLine + XCSourceEditorCommandName + Delete Line + + + XCSourceEditorCommandClassName + $(PRODUCT_MODULE_NAME).SourceEditorCommand + XCSourceEditorCommandIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER).join + XCSourceEditorCommandName + Join Line + + + XCSourceEditorCommandClassName + $(PRODUCT_MODULE_NAME).SourceEditorCommand + XCSourceEditorCommandIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER).lineBeginning + XCSourceEditorCommandName + Line Beginning + XCSourceEditorExtensionPrincipalClass $(PRODUCT_MODULE_NAME).SourceEditorExtension diff --git a/Line/SourceEditorCommand.swift b/Line/SourceEditorCommand.swift index df22ee3..e0266dd 100644 --- a/Line/SourceEditorCommand.swift +++ b/Line/SourceEditorCommand.swift @@ -16,144 +16,138 @@ enum Options: String { class SourceEditorCommand: NSObject, XCSourceEditorCommand { - public func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Swift.Void) { - + public func perform(with invocation: XCSourceEditorCommandInvocation, + completionHandler: @escaping (Error?) -> Swift.Void) { let buffer = invocation.buffer - let selectedRanges: SelectionType = selectionRanges(of: buffer) - let selectionIndexSet: IndexSet = selectedLinesIndexSet(for: selectedRanges) switch Options(command: invocation.commandIdentifier)! { case .duplicate: - let range = buffer.selections.firstObject as! XCSourceTextRange - let copyOfLines = buffer.lines.objects(at: selectionIndexSet) - buffer.lines.insert(copyOfLines, at: selectionIndexSet) - - switch selectedRanges { - case .none(_, let column): - if column == 0 { - range.start.line += 1 - range.end.line += 1 + buffer.selectionRanges.forEach { range in + let end = range.end//Required for updating selection + let copyOfLines = buffer.lines.objects(at: range.selectedLines) + buffer.lines.insert(copyOfLines, at: range.selectedLines) + + switch range.selection { + case .none(_, let column): + if column == 0 { + range.start.line += 1 + range.end.line += 1 + } + case .words: break + case .lines: range.start = end } - case .words(_, _, _)://TODO: - break - case .lines(_, let endPosition): range.start = endPosition - case .multiLocation(_): break } case .commentedDuplicate: - let range = buffer.selections.firstObject as! XCSourceTextRange - let copyOfLines = buffer.lines.objects(at: selectionIndexSet) - let commentedLines = copyOfLines.map { "//" + ($0 as! String) } - buffer.lines.insert(commentedLines, at: selectionIndexSet) - - switch selectedRanges { - case .none(_, let column): - if column == 0 { - range.start.line += 1 - range.end.line += 1 + buffer.selectionRanges.forEach { range in + let end = range.end//Required for updating selection + let copyOfLines = buffer.lines.objects(at: range.selectedLines) + let commentedLines = copyOfLines.map { "//" + ($0 as! String) } + buffer.lines.insert(commentedLines, at: range.selectedLines) + + switch range.selection { + case .none(_, let column): + if column == 0 { + range.start.line += 1 + range.end.line += 1 + } + case .words: break + case .lines: range.start = end } - case .words(_, _, _)://TODO: - break - case .lines(_, let endPosition): range.start = endPosition - case .multiLocation(_): break } case .openNewLineBelow: - switch selectedRanges { - case .none(let line, _), - .words(let line, _, _): - let indentationOffset = (buffer.lines[line] as! String).lineIndentationOffset() - let offsetWhiteSpaces = Array(repeating: " ", count: indentationOffset).joined() - buffer.lines.insert(offsetWhiteSpaces, at: line + 1) - //Selection - let position = XCSourceTextPosition(line: line + 1, column: indentationOffset) - let lineSelection = XCSourceTextRange(start: position, end: position) - buffer.selections.setArray([lineSelection]) - - case .lines(_, _): break - case .multiLocation(_): break + buffer.selectionRanges.forEach { range in + if range.isSelectionEmpty { + let indentationOffset = buffer[range.start.line].indentationOffset + let offsetWhiteSpaces = String(repeating: " ", count: indentationOffset) + buffer.lines.insert(offsetWhiteSpaces, at: range.start.line + 1) + //Selection + let position = TextPosition(line: range.start.line + 1, column: indentationOffset) + let lineSelection = XCSourceTextRange(start: position, end: position) + buffer.selections.setArray([lineSelection]) + } } case .openNewLineAbove: - switch selectedRanges { - case .none(let line, _), - .words(let line, _, _): - let indentationOffset = (buffer.lines[line] as! String).lineIndentationOffset() - let offsetWhiteSpaces = Array(repeating: " ", count: indentationOffset).joined() - buffer.lines.insert(offsetWhiteSpaces, at: line) - //Selection - let position = XCSourceTextPosition(line: line, column: indentationOffset) - let lineSelection = XCSourceTextRange(start: position, end: position) - buffer.selections.setArray([lineSelection]) - - case .lines(_, _): break - case .multiLocation(_): break + buffer.selectionRanges.forEach { range in + if range.isSelectionEmpty { + let indentationOffset = buffer[range.start.line].indentationOffset + let offsetWhiteSpaces = String(repeating: " ", count: indentationOffset) + buffer.lines.insert(offsetWhiteSpaces, at: range.start.line) + //Selection + let position = TextPosition(line: range.start.line, column: indentationOffset) + let lineSelection = XCSourceTextRange(start: position, end: position) + buffer.selections.setArray([lineSelection]) + } } case .deleteLine: - switch selectedRanges { - case .none(let line, _), - .words(let line, _, _): - buffer.lines.removeObject(at: line) + buffer.selectionRanges.forEach { range in + switch range.selection { + case .none, .words: + buffer.lines.removeObject(at: range.start.line) - case .lines(_, _): break - case .multiLocation(_): break + case .lines: break + + } } case .join: - switch selectedRanges { - case .none(let line, _), - .words(let line, _, _): + buffer.selectionRanges.forEach { range in + + switch range.selection { + case .none(let line, _): if line == buffer.lines.count { return } - let firstLine = (buffer.lines[line] as! String).trimmingCharacters(in: .newlines) - let newLine = (buffer.lines[line + 1] as! String).trimmingCharacters(in: .whitespaces) + let caretOffset = buffer[line].count + 1 + let lineIndexSet = IndexSet(arrayLiteral: line, line + 1) + let lines = buffer[lineIndexSet] - buffer.lines.replaceObject(at: line, with: "\(firstLine) \(newLine)") + var joinedLine = lines.joined(separator: " ", trimming: .whitespacesAndNewlines) + joinedLine.indent(by: lines.first!.indentationOffset) + + buffer.lines.replaceObject(at: line, with: joinedLine) buffer.lines.removeObject(at: line + 1) //Selection/CaretPosition - let range = buffer.selections.lastObject as! XCSourceTextRange - range.start.column = firstLine.characters.count + 1 - range.end.column = firstLine.characters.count + 1 - - case .lines(_, _): - let range = buffer.selections.firstObject as! XCSourceTextRange - let selectedLines = buffer.lines.objects(at: selectionIndexSet) as! [String] - - var joinedLine = "" - for (i, line) in selectedLines.enumerated() { - if i == 0 { - joinedLine += line.trimmingCharacters(in: .newlines) - } else { - joinedLine += " " + line.trimmedStart().trimmingCharacters(in: .newlines) - } - } - buffer.lines.removeObjects(at: selectionIndexSet) + range.start.column = caretOffset + range.end.column = caretOffset + + case .words: break + + case .lines: + let lines = buffer[range.selectedLines] + var joinedLine = lines.joined(separator: " ", trimming: .whitespacesAndNewlines) + joinedLine.indent(by: lines.first!.indentationOffset) + + buffer.lines.removeObjects(at: range.selectedLines) buffer.lines.insert(joinedLine, at: range.start.line) //Selection/CaretPosition range.end.line = range.start.line - range.end.column = joinedLine.characters.count + range.end.column = joinedLine.count - case .multiLocation(_): break + } } case .lineBeginning: - switch selectedRanges { - case .none(let line, _), - .words(let line, _, _): - let range = buffer.selections.lastObject as! XCSourceTextRange - let indentationOffset = (buffer.lines[line] as! String).lineIndentationOffset() - if range.start.column == indentationOffset { - range.start.column = 0; range.end.column = 0; - } else { - range.start.column = indentationOffset; range.end.column = indentationOffset; + buffer.selectionRanges.forEach { range in + switch range.selection { + case .none(let line, let column): + let indentationOffset = buffer[line].indentationOffset + if column == indentationOffset { + range.start.column = 0; + range.end.column = 0; + } else { + range.start.column = indentationOffset; + range.end.column = indentationOffset; + } + case .words, .lines: break } - case .lines(_, _): break - case .multiLocation(_): break } } + completionHandler(nil) } } diff --git a/Linex.xcodeproj/project.pbxproj b/Linex.xcodeproj/project.pbxproj index 67db3b3..807a4c5 100644 --- a/Linex.xcodeproj/project.pbxproj +++ b/Linex.xcodeproj/project.pbxproj @@ -7,10 +7,25 @@ objects = { /* Begin PBXBuildFile section */ - E31FD4741F5712540044765A /* Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = E31FD4731F5712540044765A /* Extras.swift */; }; - E356FD7E1F66D281004558F1 /* XcodeKitExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = E356FD7C1F66D266004558F1 /* XcodeKitExt.swift */; }; - E356FD7F1F66D282004558F1 /* XcodeKitExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = E356FD7C1F66D266004558F1 /* XcodeKitExt.swift */; }; - E356FD801F66D284004558F1 /* XcodeKitExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = E356FD7C1F66D266004558F1 /* XcodeKitExt.swift */; }; + DD60A3FF256F15330053F85C /* XcodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD60A3FE256F15330053F85C /* XcodeKit.framework */; }; + DD60A400256F15330053F85C /* XcodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DD60A3FE256F15330053F85C /* XcodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + DDD528CB256F162200DF2491 /* XcodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD60A3FE256F15330053F85C /* XcodeKit.framework */; }; + DDD528CC256F162200DF2491 /* XcodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DD60A3FE256F15330053F85C /* XcodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + DDD528CE256F162900DF2491 /* XcodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD60A3FE256F15330053F85C /* XcodeKit.framework */; }; + DDD528CF256F162900DF2491 /* XcodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DD60A3FE256F15330053F85C /* XcodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + E30650FE20AC6E52006B0BBF /* CharacterSet+Linex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E30650FD20AC6E52006B0BBF /* CharacterSet+Linex.swift */; }; + E30650FF20AC6E52006B0BBF /* CharacterSet+Linex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E30650FD20AC6E52006B0BBF /* CharacterSet+Linex.swift */; }; + E306510020AC6E52006B0BBF /* CharacterSet+Linex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E30650FD20AC6E52006B0BBF /* CharacterSet+Linex.swift */; }; + E306510720AEEC48006B0BBF /* TextBuffer+Expand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E306510620AEEC48006B0BBF /* TextBuffer+Expand.swift */; }; + E306510820AEEC48006B0BBF /* TextBuffer+Expand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E306510620AEEC48006B0BBF /* TextBuffer+Expand.swift */; }; + E306510920AEEC48006B0BBF /* TextBuffer+Expand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E306510620AEEC48006B0BBF /* TextBuffer+Expand.swift */; }; + E306510B20B0388C006B0BBF /* TextRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = E306510A20B0388C006B0BBF /* TextRange.swift */; }; + E306510C20B0388C006B0BBF /* TextRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = E306510A20B0388C006B0BBF /* TextRange.swift */; }; + E306510D20B0388C006B0BBF /* TextRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = E306510A20B0388C006B0BBF /* TextRange.swift */; }; + E35783BE20B73B5A00D845A7 /* CharacterSet+Linex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E30650FD20AC6E52006B0BBF /* CharacterSet+Linex.swift */; }; + E35783C120B73BF700D845A7 /* TextBuffer+Basic.swift in Sources */ = {isa = PBXBuildFile; fileRef = E35783C020B73BF700D845A7 /* TextBuffer+Basic.swift */; }; + E35783C220B73BF700D845A7 /* TextBuffer+Basic.swift in Sources */ = {isa = PBXBuildFile; fileRef = E35783C020B73BF700D845A7 /* TextBuffer+Basic.swift */; }; + E35783C320B73BF700D845A7 /* TextBuffer+Basic.swift in Sources */ = {isa = PBXBuildFile; fileRef = E35783C020B73BF700D845A7 /* TextBuffer+Basic.swift */; }; E359421B1F55564C009EFA35 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E359421A1F55564C009EFA35 /* AppDelegate.swift */; }; E359421D1F55564C009EFA35 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E359421C1F55564C009EFA35 /* ViewController.swift */; }; E359421F1F55564C009EFA35 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E359421E1F55564C009EFA35 /* Assets.xcassets */; }; @@ -20,20 +35,43 @@ E35942431F55565E009EFA35 /* SourceEditorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E35942421F55565E009EFA35 /* SourceEditorExtension.swift */; }; E35942451F55565E009EFA35 /* SourceEditorCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E35942441F55565E009EFA35 /* SourceEditorCommand.swift */; }; E35942491F55565E009EFA35 /* Line.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E359423B1F55565E009EFA35 /* Line.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - E359BCF61F604E000071246B /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = E359BCF51F604E000071246B /* Util.swift */; }; - E359BD0A1F6063A70071246B /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = E359BCF51F604E000071246B /* Util.swift */; }; + E359BCF61F604E000071246B /* Alignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E359BCF51F604E000071246B /* Alignment.swift */; }; + E359BD0A1F6063A70071246B /* Alignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E359BCF51F604E000071246B /* Alignment.swift */; }; + E37DDA7D2099E65900D86394 /* SelectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E37DDA7C2099E65900D86394 /* SelectionTests.swift */; }; + E3938E35209DEC32007F69E3 /* String+Linex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E33209DEC2B007F69E3 /* String+Linex.swift */; }; + E3938E36209DEC32007F69E3 /* String+Linex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E33209DEC2B007F69E3 /* String+Linex.swift */; }; + E3938E37209DEC33007F69E3 /* String+Linex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E33209DEC2B007F69E3 /* String+Linex.swift */; }; + E3938E38209DEC34007F69E3 /* String+Linex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E33209DEC2B007F69E3 /* String+Linex.swift */; }; + E3938E3B209DF622007F69E3 /* Character+Linex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E39209DF605007F69E3 /* Character+Linex.swift */; }; + E3938E3C209DF623007F69E3 /* Character+Linex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E39209DF605007F69E3 /* Character+Linex.swift */; }; + E3938E3D209DF624007F69E3 /* Character+Linex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E39209DF605007F69E3 /* Character+Linex.swift */; }; + E3938E3E209DF626007F69E3 /* Character+Linex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E39209DF605007F69E3 /* Character+Linex.swift */; }; + E3938E41209DF665007F69E3 /* TextPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E3F209DF64F007F69E3 /* TextPosition.swift */; }; + E3938E42209DF665007F69E3 /* TextPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E3F209DF64F007F69E3 /* TextPosition.swift */; }; + E3938E43209DF666007F69E3 /* TextPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E3F209DF64F007F69E3 /* TextPosition.swift */; }; + E3938E47209DF772007F69E3 /* TextBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E45209DF772007F69E3 /* TextBuffer.swift */; }; + E3938E48209DF772007F69E3 /* TextBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E45209DF772007F69E3 /* TextBuffer.swift */; }; + E3938E49209DF772007F69E3 /* TextBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E45209DF772007F69E3 /* TextBuffer.swift */; }; + E3938E4B209DF834007F69E3 /* String+Basic.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E4A209DF834007F69E3 /* String+Basic.swift */; }; + E3938E4C209DF834007F69E3 /* String+Basic.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E4A209DF834007F69E3 /* String+Basic.swift */; }; + E3938E4D209DF834007F69E3 /* String+Basic.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E4A209DF834007F69E3 /* String+Basic.swift */; }; + E3938E4E209DF834007F69E3 /* String+Basic.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3938E4A209DF834007F69E3 /* String+Basic.swift */; }; E39E226E1F59876A00354A5F /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E359423D1F55565E009EFA35 /* Cocoa.framework */; }; E39E22731F59876A00354A5F /* SourceEditorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E39E22721F59876A00354A5F /* SourceEditorExtension.swift */; }; E39E22751F59876A00354A5F /* SourceEditorCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E39E22741F59876A00354A5F /* SourceEditorCommand.swift */; }; E39E22791F59876A00354A5F /* Selection.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E39E226D1F59876A00354A5F /* Selection.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - E39E22801F59C59100354A5F /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = E39E227E1F59C58E00354A5F /* Common.swift */; }; - E39E22811F59C59200354A5F /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = E39E227E1F59C58E00354A5F /* Common.swift */; }; - E39E22821F59C59300354A5F /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = E39E227E1F59C58E00354A5F /* Common.swift */; }; + E39E22801F59C59100354A5F /* RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E39E227E1F59C58E00354A5F /* RawRepresentable.swift */; }; + E39E22811F59C59200354A5F /* RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E39E227E1F59C58E00354A5F /* RawRepresentable.swift */; }; + E39E22821F59C59300354A5F /* RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E39E227E1F59C58E00354A5F /* RawRepresentable.swift */; }; + E3A56CCD20D82AD400A44165 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3A56CCC20D82AD400A44165 /* Line.swift */; }; + E3A56CCE20D82AD400A44165 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3A56CCC20D82AD400A44165 /* Line.swift */; }; + E3A56CCF20D82AD400A44165 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3A56CCC20D82AD400A44165 /* Line.swift */; }; + E3A56CD020D82AD400A44165 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3A56CCC20D82AD400A44165 /* Line.swift */; }; E3F543941F5A84FC008F426E /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E359423D1F55565E009EFA35 /* Cocoa.framework */; }; E3F543991F5A84FC008F426E /* SourceEditorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3F543981F5A84FC008F426E /* SourceEditorExtension.swift */; }; E3F5439B1F5A84FC008F426E /* SourceEditorCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3F5439A1F5A84FC008F426E /* SourceEditorCommand.swift */; }; E3F5439F1F5A84FC008F426E /* Convert.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E3F543931F5A84FC008F426E /* Convert.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - E3F543A61F5ABA48008F426E /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = E39E227E1F59C58E00354A5F /* Common.swift */; }; + E3F543A61F5ABA48008F426E /* RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E39E227E1F59C58E00354A5F /* RawRepresentable.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -68,6 +106,39 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + DD60A401256F15330053F85C /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + DD60A400256F15330053F85C /* XcodeKit.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + DDD528CD256F162200DF2491 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + DDD528CC256F162200DF2491 /* XcodeKit.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + DDD528D0256F162900DF2491 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + DDD528CF256F162900DF2491 /* XcodeKit.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; E359424D1F55565E009EFA35 /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -84,12 +155,15 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + DD60A3FE256F15330053F85C /* XcodeKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XcodeKit.framework; path = Library/Frameworks/XcodeKit.framework; sourceTree = DEVELOPER_DIR; }; + E30650FD20AC6E52006B0BBF /* CharacterSet+Linex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CharacterSet+Linex.swift"; sourceTree = ""; }; + E306510620AEEC48006B0BBF /* TextBuffer+Expand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextBuffer+Expand.swift"; sourceTree = ""; }; + E306510A20B0388C006B0BBF /* TextRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRange.swift; sourceTree = ""; }; E31714C31F8385F40036CF07 /* Linex.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Linex.entitlements; sourceTree = ""; }; - E31FD4731F5712540044765A /* Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extras.swift; sourceTree = ""; }; E330C82E1F796DE600ECC222 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; E330C82F1F796DF000ECC222 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = SOURCE_ROOT; }; - E356FD7C1F66D266004558F1 /* XcodeKitExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XcodeKitExt.swift; sourceTree = ""; }; E356FD811F66F55C004558F1 /* TestPad.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestPad.swift; sourceTree = ""; }; + E35783C020B73BF700D845A7 /* TextBuffer+Basic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextBuffer+Basic.swift"; sourceTree = ""; }; E35942171F55564C009EFA35 /* Linex.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Linex.app; sourceTree = BUILT_PRODUCTS_DIR; }; E359421A1F55564C009EFA35 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; E359421C1F55564C009EFA35 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -105,15 +179,21 @@ E35942421F55565E009EFA35 /* SourceEditorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditorExtension.swift; sourceTree = ""; }; E35942441F55565E009EFA35 /* SourceEditorCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditorCommand.swift; sourceTree = ""; }; E35942461F55565E009EFA35 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E359BCF51F604E000071246B /* Util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = ""; }; - E35F567A1F7BEFA100905225 /* propertyalign.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = propertyalign.gif; sourceTree = ""; }; + E359BCF51F604E000071246B /* Alignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Alignment.swift; sourceTree = ""; }; + E37DDA7C2099E65900D86394 /* SelectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectionTests.swift; sourceTree = ""; }; E383F00A1F7A13A900429B4D /* TestPad.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestPad.m; sourceTree = ""; }; + E3938E33209DEC2B007F69E3 /* String+Linex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Linex.swift"; sourceTree = ""; }; + E3938E39209DF605007F69E3 /* Character+Linex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Character+Linex.swift"; sourceTree = ""; }; + E3938E3F209DF64F007F69E3 /* TextPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextPosition.swift; sourceTree = ""; }; + E3938E45209DF772007F69E3 /* TextBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBuffer.swift; sourceTree = ""; }; + E3938E4A209DF834007F69E3 /* String+Basic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Basic.swift"; sourceTree = ""; }; E39E226D1F59876A00354A5F /* Selection.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Selection.appex; sourceTree = BUILT_PRODUCTS_DIR; }; E39E22711F59876A00354A5F /* Selection.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Selection.entitlements; sourceTree = ""; }; E39E22721F59876A00354A5F /* SourceEditorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditorExtension.swift; sourceTree = ""; }; E39E22741F59876A00354A5F /* SourceEditorCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditorCommand.swift; sourceTree = ""; }; E39E22761F59876A00354A5F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E39E227E1F59C58E00354A5F /* Common.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = ""; }; + E39E227E1F59C58E00354A5F /* RawRepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawRepresentable.swift; sourceTree = ""; }; + E3A56CCC20D82AD400A44165 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; E3F543931F5A84FC008F426E /* Convert.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Convert.appex; sourceTree = BUILT_PRODUCTS_DIR; }; E3F543971F5A84FC008F426E /* Convert.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Convert.entitlements; sourceTree = ""; }; E3F543981F5A84FC008F426E /* SourceEditorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceEditorExtension.swift; sourceTree = ""; }; @@ -141,6 +221,7 @@ buildActionMask = 2147483647; files = ( E359423E1F55565E009EFA35 /* Cocoa.framework in Frameworks */, + DD60A3FF256F15330053F85C /* XcodeKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -149,6 +230,7 @@ buildActionMask = 2147483647; files = ( E39E226E1F59876A00354A5F /* Cocoa.framework in Frameworks */, + DDD528CB256F162200DF2491 /* XcodeKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -157,23 +239,48 @@ buildActionMask = 2147483647; files = ( E3F543941F5A84FC008F426E /* Cocoa.framework in Frameworks */, + DDD528CE256F162900DF2491 /* XcodeKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + E35783BD20B73AB700D845A7 /* Standard Extensions */ = { + isa = PBXGroup; + children = ( + E39E227E1F59C58E00354A5F /* RawRepresentable.swift */, + E3A56CCC20D82AD400A44165 /* Line.swift */, + E3938E4A209DF834007F69E3 /* String+Basic.swift */, + E3938E39209DF605007F69E3 /* Character+Linex.swift */, + E30650FD20AC6E52006B0BBF /* CharacterSet+Linex.swift */, + ); + path = "Standard Extensions"; + sourceTree = ""; + }; + E35783BF20B73B9A00D845A7 /* XcodeKit */ = { + isa = PBXGroup; + children = ( + E3938E3F209DF64F007F69E3 /* TextPosition.swift */, + E306510A20B0388C006B0BBF /* TextRange.swift */, + E3938E45209DF772007F69E3 /* TextBuffer.swift */, + E35783C020B73BF700D845A7 /* TextBuffer+Basic.swift */, + E306510620AEEC48006B0BBF /* TextBuffer+Expand.swift */, + ); + path = XcodeKit; + sourceTree = ""; + }; E359420E1F55564C009EFA35 = { isa = PBXGroup; children = ( - E39E227E1F59C58E00354A5F /* Common.swift */, - E356FD7C1F66D266004558F1 /* XcodeKitExt.swift */, - E35F56791F7BEFA100905225 /* Images */, - E35942191F55564C009EFA35 /* Linex */, - E359422B1F55564D009EFA35 /* LinexTests */, + E35783BD20B73AB700D845A7 /* Standard Extensions */, + E3938E33209DEC2B007F69E3 /* String+Linex.swift */, + E35783BF20B73B9A00D845A7 /* XcodeKit */, E359423F1F55565E009EFA35 /* Line */, E39E226F1F59876A00354A5F /* Selection */, E3F543951F5A84FC008F426E /* Convert */, + E35942191F55564C009EFA35 /* Linex */, + E359422B1F55564D009EFA35 /* LinexTests */, E359423C1F55565E009EFA35 /* Frameworks */, E35942181F55564C009EFA35 /* Products */, ); @@ -210,6 +317,7 @@ isa = PBXGroup; children = ( E359422C1F55564D009EFA35 /* LinexTests.swift */, + E37DDA7C2099E65900D86394 /* SelectionTests.swift */, E356FD811F66F55C004558F1 /* TestPad.swift */, E383F00A1F7A13A900429B4D /* TestPad.m */, E359422E1F55564D009EFA35 /* Info.plist */, @@ -220,6 +328,7 @@ E359423C1F55565E009EFA35 /* Frameworks */ = { isa = PBXGroup; children = ( + DD60A3FE256F15330053F85C /* XcodeKit.framework */, E359423D1F55565E009EFA35 /* Cocoa.framework */, ); name = Frameworks; @@ -230,7 +339,6 @@ children = ( E35942421F55565E009EFA35 /* SourceEditorExtension.swift */, E35942441F55565E009EFA35 /* SourceEditorCommand.swift */, - E31FD4731F5712540044765A /* Extras.swift */, E35942461F55565E009EFA35 /* Info.plist */, E35942401F55565E009EFA35 /* Supporting Files */, ); @@ -245,20 +353,12 @@ name = "Supporting Files"; sourceTree = ""; }; - E35F56791F7BEFA100905225 /* Images */ = { - isa = PBXGroup; - children = ( - E35F567A1F7BEFA100905225 /* propertyalign.gif */, - ); - path = Images; - sourceTree = ""; - }; E39E226F1F59876A00354A5F /* Selection */ = { isa = PBXGroup; children = ( E39E22721F59876A00354A5F /* SourceEditorExtension.swift */, E39E22741F59876A00354A5F /* SourceEditorCommand.swift */, - E359BCF51F604E000071246B /* Util.swift */, + E359BCF51F604E000071246B /* Alignment.swift */, E39E22761F59876A00354A5F /* Info.plist */, E39E22701F59876A00354A5F /* Supporting Files */, ); @@ -341,6 +441,7 @@ E35942371F55565E009EFA35 /* Sources */, E35942381F55565E009EFA35 /* Frameworks */, E35942391F55565E009EFA35 /* Resources */, + DD60A401256F15330053F85C /* Embed Frameworks */, ); buildRules = ( ); @@ -358,6 +459,7 @@ E39E22691F59876A00354A5F /* Sources */, E39E226A1F59876A00354A5F /* Frameworks */, E39E226B1F59876A00354A5F /* Resources */, + DDD528CD256F162200DF2491 /* Embed Frameworks */, ); buildRules = ( ); @@ -375,6 +477,7 @@ E3F5438F1F5A84FC008F426E /* Sources */, E3F543901F5A84FC008F426E /* Frameworks */, E3F543911F5A84FC008F426E /* Resources */, + DDD528D0256F162900DF2491 /* Embed Frameworks */, ); buildRules = ( ); @@ -392,12 +495,13 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0830; - LastUpgradeCheck = 0900; + LastUpgradeCheck = 1220; ORGANIZATIONNAME = "Kaunteya Suryawanshi"; TargetAttributes = { E35942161F55564C009EFA35 = { CreatedOnToolsVersion = 8.3.3; DevelopmentTeam = KQQ77ZBE5E; + LastSwiftMigration = 1220; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { @@ -408,29 +512,33 @@ E35942271F55564C009EFA35 = { CreatedOnToolsVersion = 8.3.3; DevelopmentTeam = KQQ77ZBE5E; + LastSwiftMigration = 1220; ProvisioningStyle = Automatic; TestTargetID = E35942161F55564C009EFA35; }; E359423A1F55565E009EFA35 = { CreatedOnToolsVersion = 8.3.3; DevelopmentTeam = KQQ77ZBE5E; + LastSwiftMigration = 1220; ProvisioningStyle = Automatic; }; E39E226C1F59876A00354A5F = { CreatedOnToolsVersion = 8.3.3; DevelopmentTeam = KQQ77ZBE5E; + LastSwiftMigration = 1220; ProvisioningStyle = Automatic; }; E3F543921F5A84FC008F426E = { CreatedOnToolsVersion = 8.3.3; DevelopmentTeam = KQQ77ZBE5E; + LastSwiftMigration = 1220; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = E35942121F55564C009EFA35 /* Build configuration list for PBXProject "Linex" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -504,9 +612,15 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E39E22821F59C59300354A5F /* Common.swift in Sources */, + E3938E3E209DF626007F69E3 /* Character+Linex.swift in Sources */, + E39E22821F59C59300354A5F /* RawRepresentable.swift in Sources */, E359422D1F55564D009EFA35 /* LinexTests.swift in Sources */, - E359BD0A1F6063A70071246B /* Util.swift in Sources */, + E3938E35209DEC32007F69E3 /* String+Linex.swift in Sources */, + E359BD0A1F6063A70071246B /* Alignment.swift in Sources */, + E37DDA7D2099E65900D86394 /* SelectionTests.swift in Sources */, + E3938E4B209DF834007F69E3 /* String+Basic.swift in Sources */, + E35783BE20B73B5A00D845A7 /* CharacterSet+Linex.swift in Sources */, + E3A56CCD20D82AD400A44165 /* Line.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -514,11 +628,19 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E356FD7E1F66D281004558F1 /* XcodeKitExt.swift in Sources */, + E3938E3B209DF622007F69E3 /* Character+Linex.swift in Sources */, E35942431F55565E009EFA35 /* SourceEditorExtension.swift in Sources */, - E31FD4741F5712540044765A /* Extras.swift in Sources */, - E39E22801F59C59100354A5F /* Common.swift in Sources */, + E39E22801F59C59100354A5F /* RawRepresentable.swift in Sources */, + E35783C120B73BF700D845A7 /* TextBuffer+Basic.swift in Sources */, + E3938E47209DF772007F69E3 /* TextBuffer.swift in Sources */, + E306510720AEEC48006B0BBF /* TextBuffer+Expand.swift in Sources */, + E306510B20B0388C006B0BBF /* TextRange.swift in Sources */, + E30650FE20AC6E52006B0BBF /* CharacterSet+Linex.swift in Sources */, + E3938E41209DF665007F69E3 /* TextPosition.swift in Sources */, + E3938E4C209DF834007F69E3 /* String+Basic.swift in Sources */, + E3A56CCE20D82AD400A44165 /* Line.swift in Sources */, E35942451F55565E009EFA35 /* SourceEditorCommand.swift in Sources */, + E3938E36209DEC32007F69E3 /* String+Linex.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -526,11 +648,20 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E356FD7F1F66D282004558F1 /* XcodeKitExt.swift in Sources */, - E39E22811F59C59200354A5F /* Common.swift in Sources */, + E3938E3C209DF623007F69E3 /* Character+Linex.swift in Sources */, + E39E22811F59C59200354A5F /* RawRepresentable.swift in Sources */, E39E22731F59876A00354A5F /* SourceEditorExtension.swift in Sources */, E39E22751F59876A00354A5F /* SourceEditorCommand.swift in Sources */, - E359BCF61F604E000071246B /* Util.swift in Sources */, + E3938E48209DF772007F69E3 /* TextBuffer.swift in Sources */, + E3938E42209DF665007F69E3 /* TextPosition.swift in Sources */, + E3938E4D209DF834007F69E3 /* String+Basic.swift in Sources */, + E306510820AEEC48006B0BBF /* TextBuffer+Expand.swift in Sources */, + E359BCF61F604E000071246B /* Alignment.swift in Sources */, + E3938E37209DEC33007F69E3 /* String+Linex.swift in Sources */, + E30650FF20AC6E52006B0BBF /* CharacterSet+Linex.swift in Sources */, + E3A56CCF20D82AD400A44165 /* Line.swift in Sources */, + E306510C20B0388C006B0BBF /* TextRange.swift in Sources */, + E35783C220B73BF700D845A7 /* TextBuffer+Basic.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -538,10 +669,19 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E356FD801F66D284004558F1 /* XcodeKitExt.swift in Sources */, - E3F543A61F5ABA48008F426E /* Common.swift in Sources */, + E3F543A61F5ABA48008F426E /* RawRepresentable.swift in Sources */, E3F543991F5A84FC008F426E /* SourceEditorExtension.swift in Sources */, + E3938E49209DF772007F69E3 /* TextBuffer.swift in Sources */, E3F5439B1F5A84FC008F426E /* SourceEditorCommand.swift in Sources */, + E35783C320B73BF700D845A7 /* TextBuffer+Basic.swift in Sources */, + E3938E3D209DF624007F69E3 /* Character+Linex.swift in Sources */, + E306510920AEEC48006B0BBF /* TextBuffer+Expand.swift in Sources */, + E306510D20B0388C006B0BBF /* TextRange.swift in Sources */, + E306510020AC6E52006B0BBF /* CharacterSet+Linex.swift in Sources */, + E3A56CD020D82AD400A44165 /* Line.swift in Sources */, + E3938E38209DEC34007F69E3 /* String+Linex.swift in Sources */, + E3938E43209DF666007F69E3 /* TextPosition.swift in Sources */, + E3938E4E209DF834007F69E3 /* String+Basic.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -596,6 +736,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -603,8 +744,10 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -652,6 +795,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -659,8 +803,10 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -694,13 +840,17 @@ CODE_SIGN_ENTITLEMENTS = Linex/Linex.entitlements; CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = KQQ77ZBE5E; + CURRENT_PROJECT_VERSION = 10; + ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Linex/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.kaunteya.Linex; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -712,13 +862,17 @@ CODE_SIGN_ENTITLEMENTS = Linex/Linex.entitlements; CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = KQQ77ZBE5E; + CURRENT_PROJECT_VERSION = 10; + ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Linex/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.kaunteya.Linex; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -731,9 +885,10 @@ DEVELOPMENT_TEAM = KQQ77ZBE5E; INFOPLIST_FILE = LinexTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = com.kaunteya.LinexTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Linex.app/Contents/MacOS/Linex"; }; name = Debug; @@ -747,9 +902,10 @@ DEVELOPMENT_TEAM = KQQ77ZBE5E; INFOPLIST_FILE = LinexTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = com.kaunteya.LinexTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Linex.app/Contents/MacOS/Linex"; }; name = Release; @@ -761,14 +917,16 @@ CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = KQQ77ZBE5E; + ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Line/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.kaunteya.Linex.Line; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -779,14 +937,16 @@ CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = KQQ77ZBE5E; + ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Line/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.kaunteya.Linex.Line; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -797,13 +957,15 @@ CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = KQQ77ZBE5E; + CURRENT_PROJECT_VERSION = 10; + ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Selection/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = com.kaunteya.Linex.Selection; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -814,13 +976,15 @@ CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = KQQ77ZBE5E; + CURRENT_PROJECT_VERSION = 10; + ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Selection/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = com.kaunteya.Linex.Selection; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -831,13 +995,15 @@ CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = KQQ77ZBE5E; + CURRENT_PROJECT_VERSION = 10; + ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Convert/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = com.kaunteya.Linex.Convert; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -848,13 +1014,15 @@ CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = KQQ77ZBE5E; + CURRENT_PROJECT_VERSION = 10; + ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Convert/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = com.kaunteya.Linex.Convert; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/Linex.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Linex.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Linex.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Linex.xcodeproj/xcuserdata/kaunteya.xcuserdatad/xcschemes/Linex.xcscheme b/Linex.xcodeproj/xcuserdata/kaunteya.xcuserdatad/xcschemes/Linex.xcscheme index c6ee9e4..2729c9a 100644 --- a/Linex.xcodeproj/xcuserdata/kaunteya.xcuserdatad/xcschemes/Linex.xcscheme +++ b/Linex.xcodeproj/xcuserdata/kaunteya.xcuserdatad/xcschemes/Linex.xcscheme @@ -1,6 +1,6 @@ SchemeUserState + Convert.xcscheme + + orderHint + 3 + Line.xcscheme orderHint diff --git a/Linex/Info.plist b/Linex/Info.plist index 6a141d9..f64fd81 100644 --- a/Linex/Info.plist +++ b/Linex/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.4 + $(MARKETING_VERSION) CFBundleVersion - 5 + $(CURRENT_PROJECT_VERSION) LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/LinexTests/LinexTests.swift b/LinexTests/LinexTests.swift index e8211f6..d3325ba 100644 --- a/LinexTests/LinexTests.swift +++ b/LinexTests/LinexTests.swift @@ -10,70 +10,44 @@ import XCTest @testable import Linex class LinexTests: XCTestCase { - - override func setUp() { - super.setUp() + + func testLineJoined() { + let arr = [Line(" Kaunteya\n"), Line(" Suryawanshi\n")] + XCTAssertEqual( + arr.joined(separator: " ", trimming: .whitespacesAndNewlines), + Line("Kaunteya Suryawanshi") + ) } - override func tearDown() { - super.tearDown() - } - func testLineIndentatinOffset() { - XCTAssertEqual("".lineIndentationOffset(), 0) + XCTAssertEqual(Line("").indentationOffset, 0) // 123456 - XCTAssertEqual(" ".lineIndentationOffset(), 6) - XCTAssertEqual(" ABC".lineIndentationOffset(), 6) - + XCTAssertEqual(Line(" ").indentationOffset, 6) + XCTAssertEqual(Line(" ABC").indentationOffset, 6) } func testOneSpace() { - - XCTAssert(" ".lineOneSpaceAt(pin: 0) == (0, " ")) + XCTAssert(" ".lineOneSpaceAt(pin: 0) == (0, "")) XCTAssert(" ".lineOneSpaceAt(pin: 0) == (0, " ")) XCTAssert(" ".lineOneSpaceAt(pin: 1) == (0, " ")) - XCTAssert("ABC".lineOneSpaceAt(pin: 0) == (0, "ABC")) - XCTAssert("ABC".lineOneSpaceAt(pin: 1) == (1, "ABC")) - XCTAssert("ABC".lineOneSpaceAt(pin: 2) == (2, "ABC")) - - // 0123456789 - let t1 = "A BCD", t2 = "A BCD" - XCTAssert(t1.lineOneSpaceAt(pin: 0) == (0, t1)) - XCTAssert(t1.lineOneSpaceAt(pin: 1) == (1, t2)) - XCTAssert(t1.lineOneSpaceAt(pin: 2) == (1, t2)) - XCTAssert(t1.lineOneSpaceAt(pin: 3) == (1, t2)) - XCTAssert(t1.lineOneSpaceAt(pin: 4) == (1, t2)) - XCTAssert(t1.lineOneSpaceAt(pin: 5) == (1, t2)) - XCTAssert(t1.lineOneSpaceAt(pin: 6) == (6, t1)) - XCTAssert(t1.lineOneSpaceAt(pin: 7) == (7, t1)) - XCTAssert(t1.lineOneSpaceAt(pin: 8) == (8, t1)) + XCTAssert("ABC".lineOneSpaceAt(pin: 0) == (0, " ABC")) + XCTAssert("ABC".lineOneSpaceAt(pin: 1) == (1, "A BC")) + XCTAssert("ABC".lineOneSpaceAt(pin: 2) == (2, "AB C")) + XCTAssert("AB C".lineOneSpaceAt(pin: 2) == (2, "ABC")) + + // 0123456789 + XCTAssert("A BCD".lineOneSpaceAt(pin: 0) == (0, " A BCD")) + XCTAssert("A BCD".lineOneSpaceAt(pin: 1) == (1, "A BCD")) + XCTAssert("A BCD".lineOneSpaceAt(pin: 2) == (1, "A BCD")) + XCTAssert("A BCD".lineOneSpaceAt(pin: 3) == (1, "A BCD")) + XCTAssert("A BCD".lineOneSpaceAt(pin: 4) == (1, "A BCD")) + XCTAssert("A BCD".lineOneSpaceAt(pin: 5) == (1, "A BCD")) + XCTAssert("A BCD".lineOneSpaceAt(pin: 6) == (6, "A B CD")) + XCTAssert("A BCD".lineOneSpaceAt(pin: 7) == (7, "A BC D")) + XCTAssert("A BCD".lineOneSpaceAt(pin: 8) == (8, "A BCD ")) } - - func testFarthestDistance() { - XCTAssertEqual(["var name = Kaunteya",].farthestOffsetFor(subStr: "=")!, 8) - - XCTAssertEqual(["var name = Kaunteya", - "self.lastupdated = createdOn", - ].farthestOffsetFor(subStr: "=")!,16) - - XCTAssertEqual(["var name = Kaunteya", - "self.lastupdated = createdOn", - "self.name = name", - ].farthestOffsetFor(subStr: "=")!, 16) - - XCTAssertEqual(["var name = Kaunteya", - "self.lastupdated = createdOn", - "self.name = name", - ].farthestOffsetFor(subStr: "=")!, 16) - - XCTAssertEqual(["var name = Kaunteya", - "self.lastupdated = createdOn", - "self.name = name", - ].farthestOffsetFor(subStr: "=")!, 16) - - } - + func testAlign() { XCTAssertEqual(["let name = \"Kaunteya\""].autoAlign()!, ["let name = \"Kaunteya\""]) diff --git a/LinexTests/SelectionTests.swift b/LinexTests/SelectionTests.swift new file mode 100644 index 0000000..7492404 --- /dev/null +++ b/LinexTests/SelectionTests.swift @@ -0,0 +1,18 @@ +// +// SelectionTests.swift +// LinexTests +// +// Created by Kaunteya Suryawanshi on 02/05/18. +// Copyright © 2018 Kaunteya Suryawanshi. All rights reserved. +// + +import XCTest +@testable import Linex + +class SelectionTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } +} diff --git a/README.md b/README.md index 34acba8..41f41e4 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ # Content - [📦 Installation](#installation) -- [⭐️ Key Features](#key-features) +- [⭐️ Key Features](#features) - [⌨️ Shortcuts](#shortcuts) - [👩‍💻 Contributing](#contributing) - [📃 License](#license) @@ -28,7 +28,7 @@ ## Mac App Store -Linex can be downloaded free on the Mac App Store. +Linex can be downloaded from the Mac App Store at a very modest price. Please consider purchasing it. Mac App Store @@ -42,83 +42,79 @@ Linex can be downloaded free on the Mac App Store. Extension preferences -# Key Features -- [Line](#line) - - [Duplicate](#duplicate) - - [Open new Line below](#open-new-line-below) - - [Open New Line Above](#open-new-line-above) - - [Commented Duplicate](#commented-duplicate) - - [Delete Line](#delete-line) - - [Join Line](#join-line) - - [Line beginning](#line-beginning) -- [Selection](#selection) - - [Select Word](#select-word) - - [Select Line](#select-line) - - [Select Line up](#select-line-up) - - [One Space](#one-space) - - [Align](#align) -- [Convert](#convert) - - [Increment](#convert) - - [Decrement](#convert) - -## Line -### Duplicate -Duplicates current line or selected line - -### Open New Line Below +# Features + +Copy Line + +
Open new Line below + Inserts new blank line below current line. This allows you to create a new indented line irrespective of your current caret postion. ![](/Images/opennewline.gif) +
-### Open New Line Above -Inserts new blank line above current line +
Open New Line Above + + Inserts new blank line above current line ![](/Images/openlineabove.gif) +
-### Commented Duplicate -Duplicate+Comment current line or selected lines. It can be used to check variations in code +
Commented Duplicate + + Duplicate+Comment current line or selected lines. It can be used to check variations in code ![](/Images/commentedduplicate.gif) +
-### Delete Line -Delete current line or selected lines +Delete Line -### Join Line -Joins the line below or all the selected lines +
Join Line + + Joins the line below or all the selected lines ![](/Images/join.gif) -![](/Images/join-selection.gif) +
-### Line Beginning -Toggles the caret between indented beginning and the real begninning +
Line beginning + + Toggles the caret between indented beginning and the real begninning ![Line Beginning](/Images/togglehome.gif) +
-## Selection -### Select Word -Selects the word around the caret +Select Word -### Select Line -Selects line. After selecting current line starts selecting next lines +
Select Line + + Selects line. After selecting current line starts selecting next lines ![Select line](/Images/selectline.gif) +
-### Select Line up -Selects line above the caret one-by-one +
Select Line up + + Selects line above the caret one-by-one Combination of `Select line` and `Select line up` can be used to expand selection above and below as seen below ![Select line up](/Images/selectdownup.gif) +
+ +
One Space -### One Space -Replace consecutive spaces with one space. Press again to toggle between `one space` and `no space` + Replace consecutive spaces with one space. Press again to toggle between `one space` and `no space` ![One Space](/Images/onespace.gif) +
-### Align -Smart align code. +
Align + + Smart align code. ![Align](/Images/propertyalign.gif) +
-## Convert -### Increment & Decrement -Increment & Decrement using quick shortcuts. +
Increment / Decrement + + Increment & Decrement using quick shortcuts. ![Increment Decrement](/Images/incrementdecrement.gif) These shortcuts also toggles `true`, `false`, `YES` & `NO` ![True false](/Images/incdec.gif) +
# Shortcuts @@ -130,11 +126,11 @@ These shortcuts also toggles `true`, `false`, `YES` & `NO` | Action | Shortcut | | -------------------- | :--------------------------------------------------: | | `Line` | | -| Duplicate | | +| Copy Lines | OPTN+ | | Open New Line Below | CTRL+return | | Open New Line Above | CTRL+SHIFT+return | -| Commented Duplicate | Cmd+CTRL+/ | -| Delete Line | CTRL+OPTN+L | +| Commented Duplicate | CMD+CTRL+/ | +| Delete Line | CTRL+OPTN+L | | Join Line | CTRL+J | | Line Beginning | CTRL+A | | | | @@ -143,12 +139,15 @@ These shortcuts also toggles `true`, `false`, `YES` & `NO` | Select Line | CTRL+L | | Select Line up | CTRL+SHIFT+L | | One Space | OPTN+Space | -| Align | Cmd+CTRL+X | +| Align | CMD+CTRL+X | | | | | `Convert` | | | Increment | CTRL++ | | Decrement | CTRL+- | +# Todo +- Expand selection + # Contributing Pull requests with bug fixes or with new failing Test cases are welcomed. diff --git a/Selection/Util.swift b/Selection/Alignment.swift similarity index 90% rename from Selection/Util.swift rename to Selection/Alignment.swift index 57934c4..8cc3859 100644 --- a/Selection/Util.swift +++ b/Selection/Alignment.swift @@ -10,20 +10,20 @@ import Foundation extension Array where Element == String { // Return nil when offset is not found in any line - func farthestOffsetFor(subStr: String) -> Int? { + private func farthestOffsetFor(subStr: String) -> Int? { guard self.count > 0 else { return nil; } var farthest: Int = 0 for str in self { let seperated = str.components(separatedBy: subStr) if seperated.count == 2 { - let offset = seperated.first!.trimmedEnd().characters.count + let offset = seperated.first!.trimmedEnd.count if offset > farthest { farthest = offset } } } return farthest } - func alignedDefineStatements() -> [String] { + private func alignedDefineStatements() -> [String] { var farthestOffset = 0 var indexSet = [Int]() var alignedLines = self @@ -34,8 +34,8 @@ extension Array where Element == String { indexSet.append(i) let split = line.condensedWhitespace.components(separatedBy: " ") if split.count > 2 { - if farthestOffset < split[1].characters.count { - farthestOffset = split[1].characters.count + if farthestOffset < split[1].count { + farthestOffset = split[1].count } } } @@ -56,7 +56,7 @@ extension Array where Element == String { return alignedLines } - func alignedPropertyStatements() -> [String]? { + private func alignedPropertyStatements() -> [String]? { let pattern = "@property\\s*(\\([\\w,= ]+\\))*\\s*(\\w+)\\s*(\\*)*\\s*(\\w+);(.*)" let mainRegex = try! NSRegularExpression(pattern: pattern) @@ -67,7 +67,8 @@ extension Array where Element == String { let range = NSRange(location: 0, length: line.count) if let property = mainRegex.firstMatch(in: line, options: [], range: range) { let attributeRange = property.range(at: 1) - if attributeRange.lowerBound < attributeRange.upperBound && maxAttributeLength < line[attributeRange].count { + if attributeRange.lowerBound < attributeRange.upperBound && + maxAttributeLength < line[attributeRange].count { maxAttributeLength = line[attributeRange].count } let dataTypeRange = property.range(at: 2) @@ -105,7 +106,7 @@ extension Array where Element == String { } } - func aligned(seperator: String) -> [String]? { + private func aligned(seperator: String) -> [String]? { guard self.count > 1 else { assert(self.count == 1) return self @@ -119,7 +120,7 @@ extension Array where Element == String { let component = str.components(separatedBy: seperator) if component.count == 2 { let a = component.first!.padding(toLength: alignOffset, withPad: " ", startingAt: 0) - return "\(a)\(seperator)\(component[1].trimmedStart())" + return "\(a)\(seperator)\(component[1].trimmedStart)" } return str } diff --git a/Selection/Info.plist b/Selection/Info.plist index 857be7b..e694075 100644 --- a/Selection/Info.plist +++ b/Selection/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSExtension @@ -28,46 +28,46 @@ XCSourceEditorCommandDefinitions - - XCSourceEditorCommandClassName - $(PRODUCT_MODULE_NAME).SourceEditorCommand - XCSourceEditorCommandIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER).expand - XCSourceEditorCommandName - Select Word - - - XCSourceEditorCommandClassName - $(PRODUCT_MODULE_NAME).SourceEditorCommand - XCSourceEditorCommandIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER).selectLine - XCSourceEditorCommandName - Select Line - - - XCSourceEditorCommandClassName - $(PRODUCT_MODULE_NAME).SourceEditorCommand - XCSourceEditorCommandIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER).selectLineAbove - XCSourceEditorCommandName - Select Line Up - - - XCSourceEditorCommandClassName - $(PRODUCT_MODULE_NAME).SourceEditorCommand - XCSourceEditorCommandIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER).oneSpace - XCSourceEditorCommandName - One Space - - - XCSourceEditorCommandClassName - $(PRODUCT_MODULE_NAME).SourceEditorCommand - XCSourceEditorCommandIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER).align - XCSourceEditorCommandName - Align - + + XCSourceEditorCommandClassName + $(PRODUCT_MODULE_NAME).SourceEditorCommand + XCSourceEditorCommandIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER).expand + XCSourceEditorCommandName + Select Word + + + XCSourceEditorCommandClassName + $(PRODUCT_MODULE_NAME).SourceEditorCommand + XCSourceEditorCommandIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER).selectLine + XCSourceEditorCommandName + Select Line + + + XCSourceEditorCommandClassName + $(PRODUCT_MODULE_NAME).SourceEditorCommand + XCSourceEditorCommandIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER).selectLineAbove + XCSourceEditorCommandName + Select Line Up + + + XCSourceEditorCommandClassName + $(PRODUCT_MODULE_NAME).SourceEditorCommand + XCSourceEditorCommandIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER).oneSpace + XCSourceEditorCommandName + One Space + + + XCSourceEditorCommandClassName + $(PRODUCT_MODULE_NAME).SourceEditorCommand + XCSourceEditorCommandIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER).align + XCSourceEditorCommandName + Align + XCSourceEditorExtensionPrincipalClass $(PRODUCT_MODULE_NAME).SourceEditorExtension diff --git a/Selection/SourceEditorCommand.swift b/Selection/SourceEditorCommand.swift index f3e8cd7..f2fd38c 100644 --- a/Selection/SourceEditorCommand.swift +++ b/Selection/SourceEditorCommand.swift @@ -15,68 +15,57 @@ enum Options: String { class SourceEditorCommand: NSObject, XCSourceEditorCommand { - func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void { + func perform(with invocation: XCSourceEditorCommandInvocation, + completionHandler: @escaping (Error?) -> Void ) -> Void { let buffer = invocation.buffer - let selectedRanges: SelectionType = selectionRanges(of: buffer) - let selectionIndex: IndexSet = selectedLinesIndexSet(for: selectedRanges) switch Options(command: invocation.commandIdentifier)! { case .selectLine: - let range = buffer.selections.lastObject as! XCSourceTextRange - range.start.column = 0 - range.end.line += 1 - range.end.column = 0 + buffer.selectionRanges.forEach { range in + if range.isSelectionEmpty { + let indentationOffset = buffer[range.start.line].indentationOffset + range.start.column = indentationOffset + range.end.column = (buffer.lines[range.start.line] as! String).count - 1 + } else { + range.start.column = 0 + range.end.line += 1 + range.end.column = 0 + } + } case .selectLineAbove: - let range = buffer.selections.lastObject as! XCSourceTextRange - range.start.column = 0 - range.start.line = max(range.start.line - 1, 0) - range.end.column = 0 + buffer.selectionRanges.forEach { range in + range.start.column = 0 + range.start.line = max(range.start.line - 1, 0) + range.end.column = 0 + } case .oneSpace: - switch selectedRanges { - case .none(let line, let column): - let range = buffer.selections.lastObject as! XCSourceTextRange - let currentLine = buffer.lines[line] as! String - let (newOffset, newLine) = currentLine.lineOneSpaceAt(pin: column) - buffer.lines.replaceObject(at: line, with: newLine) - range.end.column = newOffset - range.start.column = newOffset - case .words(_, _, _): break - case .lines(_, _): break - case .multiLocation(_): break + buffer.selectionRanges.forEach { range in + if range.isSelectionEmpty { + let currentLine = buffer.lines[range.start.line] as! String + let (newOffset, newLine) = currentLine.lineOneSpaceAt(pin: range.start.column) + buffer.lines.replaceObject(at: range.start.line, with: newLine) + range.end.column = newOffset + range.start.column = newOffset + } } case .expand: - switch selectedRanges { - case .none(let line, let column): - let range = buffer.selections.lastObject as! XCSourceTextRange - let currentLine = buffer.lines[line] as! String - - if let selectionRange:Range = currentLine.selectWord(pin: column) { - range.start.column = selectionRange.lowerBound - range.end.column = selectionRange.upperBound - } - case .words(_, _, _): break - case .lines(_, _): break - case .multiLocation(_): break - } + buffer.outerExpand() case .align: - switch selectedRanges { - case .none(_, _): break - case .words(_, _, _): break - case .lines(_, _): - let selectedLines = buffer.lines.objects(at: selectionIndex) as! [String] - if let aligned = selectedLines.autoAlign() { - buffer.lines.replaceObjects(at: selectionIndex, with: aligned) + buffer.selectionRanges.forEach { range in + if range.start.line != range.end.line { + let lines = buffer.lines.objects(at: range.selectedLines) as! [String] + if let aligned = lines.autoAlign() { + buffer.lines.replaceObjects(at: range.selectedLines, with: aligned) + } } - - case .multiLocation(_): break } } + completionHandler(nil) } - } diff --git a/Standard Extensions/Character+Linex.swift b/Standard Extensions/Character+Linex.swift new file mode 100644 index 0000000..2a2aa22 --- /dev/null +++ b/Standard Extensions/Character+Linex.swift @@ -0,0 +1,37 @@ +// +// Character+Linex.swift +// Linex +// +// Created by Kaunteya Suryawanshi on 05/05/18. +// Copyright © 2018 Kaunteya Suryawanshi. All rights reserved. +// + +import Foundation + +extension Character { + func presentIn(_ string: String) -> Bool { + return self.presentIn(CharacterSet(string)) + } + + func presentIn(_ characterSet:CharacterSet) -> Bool { + return CharacterSet(String(self)).isSubset(of: characterSet) + } + + var isOpening: Bool { + return self.presentIn(CharacterSet("{[(")) + } + + var isClosing:Bool { + return self.presentIn(CharacterSet("}])")) + } + + var closing: Character { + assert(self.isOpening, "Only opening characters can have closing characters") + return Self(["{":"}", "(":")", "[":"]"][self]!) + } + + var opening: Character { + assert(self.isClosing, "Only closing characters can have opening characters") + return Self(["}":"{", ")":"(", "]":"["][self]!) + } +} diff --git a/Standard Extensions/CharacterSet+Linex.swift b/Standard Extensions/CharacterSet+Linex.swift new file mode 100644 index 0000000..2ecf032 --- /dev/null +++ b/Standard Extensions/CharacterSet+Linex.swift @@ -0,0 +1,18 @@ +// +// CharacterSet+Linex.swift +// Linex +// +// Created by Kaunteya Suryawanshi on 16/05/18. +// Copyright © 2018 Kaunteya Suryawanshi. All rights reserved. +// + +import Foundation + +extension CharacterSet { + init(_ string: String) { + self.init(charactersIn: string) + } + static var validWordChars: CharacterSet { + return CharacterSet("@$_").union(.alphanumerics) + } +} diff --git a/Standard Extensions/Line.swift b/Standard Extensions/Line.swift new file mode 100644 index 0000000..c318e24 --- /dev/null +++ b/Standard Extensions/Line.swift @@ -0,0 +1,74 @@ +// +// Line.swift +// Linex +// +// Created by Kaunteya Suryawanshi on 18/06/18. +// Copyright © 2018 Kaunteya Suryawanshi. All rights reserved. +// + +import Foundation + +struct Line: Equatable { + var stringValue: String + + init(_ stringValue: String) { + self.stringValue = stringValue + } + + var indentationOffset: Int { + //TODO: Replace with firstIndex { where } in Swift 4.2 + var i = 0 + for a in stringValue { + if a == " " { + i += 1 + } else { break } + } + return i + } + + mutating func indent(by offset: Int) { + let offsetString = String(repeating: " ", count: offset) + stringValue = offsetString + stringValue.trimmingCharacters(in: .whitespacesAndNewlines) + } + + mutating func replace(range: Range, with value: String) { + stringValue.replace(range: range, with: value) + } +} + +extension Line: Collection { + typealias Index = String.Index + typealias Element = Character + + // Subscript by String Index + subscript(position: String.Index) -> Character { + return stringValue[position] + } + + subscript(position: Int) -> Element { + let index = self.index(self.startIndex, offsetBy: position) + return self[index] + } + + func index(after i: String.Index) -> Index { + return stringValue.index(after: i) + } + + var startIndex: Line.Index { + return stringValue.startIndex + } + + var endIndex: Line.Index { + return stringValue.endIndex + } +} + +extension Array where Element == Line { + func joined(separator: String, trimming: CharacterSet) -> Line { + let joinedString = self + .map { $0.stringValue.trimmingCharacters(in: trimming)} + .joined(separator: separator) + + return Line(joinedString) + } +} diff --git a/Standard Extensions/RawRepresentable.swift b/Standard Extensions/RawRepresentable.swift new file mode 100644 index 0000000..e994b41 --- /dev/null +++ b/Standard Extensions/RawRepresentable.swift @@ -0,0 +1,18 @@ +// +// Common.swift +// Linex +// +// Created by Kaunteya Suryawanshi on 01/09/17. +// Copyright © 2017 Kaunteya Suryawanshi. All rights reserved. +// + +import Foundation + +extension RawRepresentable where RawValue == String { + init?(command: String) { + // Eg: com.kaunteya.Line.Duplicate + let value = command.components(separatedBy: ".").last! + self.init(rawValue: value) + } +} + diff --git a/Standard Extensions/String+Basic.swift b/Standard Extensions/String+Basic.swift new file mode 100644 index 0000000..fec4e80 --- /dev/null +++ b/Standard Extensions/String+Basic.swift @@ -0,0 +1,65 @@ +// +// String+Basic.swift +// Linex +// +// Created by Kaunteya Suryawanshi on 05/05/18. +// Copyright © 2018 Kaunteya Suryawanshi. All rights reserved. +// + +import Foundation +extension String { + + /// All multiple whitespaces are replaced by one whitespace + var condensedWhitespace: String { + let components = self.components(separatedBy: .whitespaces) + return components.filter { !$0.isEmpty }.joined(separator: " ") + } + + func index(at offset: Int) -> Index { + return index(startIndex, offsetBy: offset) + } + + subscript(i: Int) -> Character { + return self[index(at: i)] + } + + subscript(range: Range) -> Substring { + let rangeIndex:Range = self.indexRangeFor(range: range) + return self[rangeIndex] + } + + subscript(range: NSRange) -> String { + return (self as NSString).substring(with: range) + } + + func indexRangeFor(range: Range) -> Range { + return index(at: range.lowerBound)..) -> ClosedRange { + return index(at: range.lowerBound)...index(at: range.upperBound) + } + + mutating func replace(range: Range, with replacement: String) { + self.replaceSubrange(self.index(at: range.lowerBound).. String { + return Array(repeating: self, count: count).joined() + } + + func replacedRegex(pattern: String, with template: String) -> String { + let regex = try! NSRegularExpression(pattern: pattern) + let range = NSMakeRange(0, count) + let modString = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: template) + return modString + } +} diff --git a/String+Linex.swift b/String+Linex.swift new file mode 100644 index 0000000..02d17ce --- /dev/null +++ b/String+Linex.swift @@ -0,0 +1,66 @@ +// +// String+Basic.swift +// Linex +// +// Created by Kaunteya Suryawanshi on 05/05/18. +// Copyright © 2018 Kaunteya Suryawanshi. All rights reserved. +// + +import Foundation + +extension String { + + func lineOneSpaceAt(pin: Int) -> (Int, String) { + var start = pin + while start > 0 && self[start - 1] == " " { + start -= 1 + } + + var end = pin + while end < self.count && self[end] == " " { + end += 1 + } + + var newString = self + if start == end {//No space + newString.replace(range: start.. Range? { + guard let range:Range = selectWord(pin: pin, validChars: validChars) else { return nil } + return self.indexRangeFor(range: range) + } + + func selectWord(pin: Int, validChars: CharacterSet) -> Range? { + var pin = pin + guard pin <= self.count else { + return nil + } + guard self.count > 1 else { + return nil + } + + // Move pin to one position left when it is after last character + if (pin > 0), self[pin - 1].presentIn(validChars) { + pin -= 1 + } + + var start = pin + while start >= 0 && self[start].presentIn(validChars) { + start -= 1 + } + + var end = pin + while end < count && self[end].presentIn(validChars) { + end += 1 + } + if start == end { return nil } + return start + 1.. Line { + return Line(lines[offset] as! String) + } + + /// Fetch all lines at index present in indexSet + subscript(indexSet: IndexSet) -> [Line] { + return indexSet.map { self[$0] } + } + + var selectionRanges: [TextRange] { + return selections as! [TextRange] + } + + var lastPosition: TextPosition { + let lastLineIndex = self.lines.count - 1 + let lastLine = self[lastLineIndex] + + return TextPosition(line: lastLineIndex, column: lastLine.count - 1) + } + + func isStart(postion: TextPosition) -> Bool { + return postion.line == 0 && postion.column == 0 + } + + func isEnd(position: TextPosition) -> Bool { + return lastPosition == position + } + + func char(at position: TextPosition) -> Character { + let currentLine = self[position.line] + return currentLine[position.column] + } +} diff --git a/XcodeKit/TextBuffer+Expand.swift b/XcodeKit/TextBuffer+Expand.swift new file mode 100644 index 0000000..4906d1e --- /dev/null +++ b/XcodeKit/TextBuffer+Expand.swift @@ -0,0 +1,60 @@ +// +// TextBuffer+Expand.swift +// Linex +// +// Created by Kaunteya Suryawanshi on 18/05/18. +// Copyright © 2018 Kaunteya Suryawanshi. All rights reserved. +// + +import Foundation +import XcodeKit + +extension TextBuffer { + func outerExpand() { + selectionRanges.forEach { range in + expand(range) + } + } + + func expand(_ range: TextRange) { + guard lines.count != 0 else { return } + + + if range.isSelectionEmpty, + let newRange = rangeForWordSelection(for: .validWordChars, at: range) { + range.updateSelection(range: newRange) + return + } + + let borderStart: Character? = range.start.previous(in: self).map { char(at: $0)} + let borderEnd: Character? = char(at: range.end) + + if (borderStart == "." || borderEnd == ".") { + let validChars = CharacterSet("@$_.!?").union(.alphanumerics) + if let newRange = rangeForWordSelection(for: validChars, at: range) { + range.updateSelection(range: newRange) + } + return + } + if (borderEnd?.presentIn("!?:") ?? false) {// Optionals + range.end.column += 1 + return + } + if (borderEnd == "(") { + range.end = findClosing(for: "(", at: range.end) ?? range.end + return + } + + switch (borderStart, borderEnd) { + case ("\"","\""), ("{", "}"), ("[","]"), ("(",")"): + range.start = range.start.previous(in: self) ?? range.start + range.end = range.end.next(in: self) ?? range.end + return + default: break + } + + if let newRange = smartExpand(current: range) { + range.updateSelection(range: newRange) + } + } +} diff --git a/XcodeKit/TextBuffer.swift b/XcodeKit/TextBuffer.swift new file mode 100644 index 0000000..28fccbd --- /dev/null +++ b/XcodeKit/TextBuffer.swift @@ -0,0 +1,126 @@ +// +// TextBuffer.swift +// Linex +// +// Created by Kaunteya Suryawanshi on 05/05/18. +// Copyright © 2018 Kaunteya Suryawanshi. All rights reserved. +// + +import Foundation +import XcodeKit + +//MARK - Word selection +extension TextBuffer { + func rangeForWordSelection(for chars: CharacterSet, at range: TextRange) -> TextRange? { + guard let start = nextPosition(.backward, from: range.start, until: chars), + let end = nextPosition(.forward, from: range.end, until: chars) else { + return nil + } + return TextRange(start: start, end: end) + } + + private func nextPosition(_ direction: TextDirection, + from startPosition: TextPosition, + until charSet: CharacterSet) -> TextPosition? { + var currentPosition = Optional(startPosition) + if direction == .backward { currentPosition = startPosition.previous(in: self)} + + while currentPosition != nil { + let currentChar = self.char(at: currentPosition!) + if !currentChar.presentIn(charSet) { + return direction == .backward ? currentPosition!.next(in: self) : currentPosition + } + currentPosition = currentPosition!.move(direction, in: self) + } + return nil + } +} + +//MARK - Find specific opening/closing bracket +extension TextBuffer { + func findClosing(for openingChar: Character, at position: TextPosition) -> TextPosition? { + assert(openingChar.isOpening, "Char must be opening") + var stackCount = 0 + var currentPosition = position.next(in: self) + while currentPosition != nil { + let currentChar = self.char(at: currentPosition!) + if currentChar.isOpening { stackCount += 1} + else if currentChar.isClosing { + if stackCount == 0 { + return TextPosition(line: currentPosition!.line, + column: currentPosition!.column + 1) + } + stackCount -= 1 + } + + currentPosition = currentPosition!.next(in: self) + } + return nil + } + + /// Invoke this when cursor is on close bracket + func findOpening(for closingChar: Character, at position: TextPosition) -> TextPosition? { + assert(closingChar.isClosing, "'closingChar' must be '), }, ]'") + var stackCount = 0 + var currentPosition = position.previous(in: self)?.previous(in: self) + while currentPosition != nil { + let currentChar = self.char(at: currentPosition!) + if currentChar.isClosing { stackCount += 1 } + else if currentChar.isOpening { + if stackCount == 0 { return currentPosition! } + stackCount -= 1 + } + + currentPosition = currentPosition?.previous(in: self) + } + return nil + } +} + +//MARK - Smart expansion +extension TextBuffer { + func smartExpand(current range: TextRange) -> TextRange? { + guard let newStart = searchLeft(from: range.start), + let newEnd = searchRight(from: range.end) else { return nil } + return TextRange(start: newStart, end: newEnd) + } + + private func searchLeft(from position: TextPosition) -> TextPosition? { + var stackCount = 0 + var currentPosition = position.previous(in: self) + while currentPosition != nil { + let currentChar = self.char(at: currentPosition!) + if currentChar.isClosing { + stackCount += 1 + } else if currentChar.isOpening { + if stackCount == 0 { return currentPosition!.next(in: self)! } + stackCount -= 1 + } else if currentChar == "\"" { + if stackCount == 0 { return currentPosition! } + } + + currentPosition = currentPosition?.previous(in: self) + } + return nil + } + + private func searchRight(from position: TextPosition) -> TextPosition? { + var stackCount = 0 + var currentPosition: TextPosition? = position + while currentPosition != nil { + let currentChar = self.char(at: currentPosition!) + if currentChar.isOpening { + stackCount += 1 + } else if currentChar.isClosing { + if stackCount == 0 { return currentPosition! } + stackCount -= 1 + } else if currentChar == "\"" { + if stackCount == 0 { return currentPosition!.next(in: self) } + } + + currentPosition = currentPosition?.next(in: self) + } + return nil + } +} + diff --git a/XcodeKit/TextPosition.swift b/XcodeKit/TextPosition.swift new file mode 100644 index 0000000..6c53f04 --- /dev/null +++ b/XcodeKit/TextPosition.swift @@ -0,0 +1,56 @@ +// +// TextPosition.swift +// Linex +// +// Created by Kaunteya Suryawanshi on 05/05/18. +// Copyright © 2018 Kaunteya Suryawanshi. All rights reserved. +// + +import Foundation +import XcodeKit + +typealias TextPosition = XCSourceTextPosition + +enum TextDirection { + case forward, backward +} + +extension TextPosition { + var isStart: Bool { + return self.line == 0 && self.column == 0 + } + + func move(_ direction: TextDirection, in buffer: XCSourceTextBuffer) -> TextPosition? { + switch direction { + case .forward: return next(in: buffer) + case .backward: return previous(in: buffer) + } + } + + func next(in buffer: XCSourceTextBuffer) -> TextPosition? { + guard !buffer.isEnd(position: self) else {return nil} + + let currentLine = buffer.lines[self.line] as! String + if self.column == currentLine.count - 1 { + return TextPosition(line: self.line + 1, column: 0) + } + + return TextPosition(line: self.line, column: self.column + 1) + } + + func previous(in buffer: XCSourceTextBuffer) -> TextPosition? { + guard !self.isStart else { return nil} + + if self.column == 0 { + let currentLine = buffer.lines[self.line - 1] as! String + return TextPosition(line: self.line - 1, column: currentLine.count - 1) + } + return TextPosition(line: self.line, column: self.column - 1) + } +} + +extension TextPosition: Equatable { + public static func == (lhs: XCSourceTextPosition, rhs: XCSourceTextPosition) -> Bool { + return lhs.line == rhs.line && lhs.column == rhs.column + } +} diff --git a/XcodeKit/TextRange.swift b/XcodeKit/TextRange.swift new file mode 100644 index 0000000..1e4504b --- /dev/null +++ b/XcodeKit/TextRange.swift @@ -0,0 +1,52 @@ +// +// TextRange.swift +// Linex +// +// Created by Kaunteya Suryawanshi on 19/05/18. +// Copyright © 2018 Kaunteya Suryawanshi. All rights reserved. +// + +import Foundation +import XcodeKit + +typealias TextRange = XCSourceTextRange + +extension TextRange { + + enum Selection { + case none(line: Int, column: Int) + case words, lines + } + + var selectedLines: IndexSet { + switch selection { + case .none, .words: + return IndexSet(integer: start.line) + + //Complete line selection is counted multiline + case .lines: + return IndexSet(integersIn: start.line...(end.column == 0 ? end.line - 1 : end.line)) + } + } + + var selection: Selection { + if start == end { + return .none(line: start.line, column: start.column) + } else if start.line == end.line { + return .words + } + return .lines + } + + var isSelectionEmpty: Bool { + if case Selection.none(_, _) = selection { + return true + } + return false + } + + func updateSelection(range: TextRange) { + self.start = range.start + self.end = range.end + } +} diff --git a/XcodeKitExt.swift b/XcodeKitExt.swift deleted file mode 100644 index 62cb5d6..0000000 --- a/XcodeKitExt.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// XcodeKitExt.swift -// Linex -// -// Created by Kaunteya Suryawanshi on 11/09/17. -// Copyright © 2017 Kaunteya Suryawanshi. All rights reserved. -// - -import Foundation -import XcodeKit - -enum SelectionType { - case none(line: Int, col: Int) - case words(line: Int, colStart: Int, colEnd: Int) - case lines(start:XCSourceTextPosition, end: XCSourceTextPosition)//Complete line selection is counted multiline - case multiLocation([XCSourceTextRange]) -} - -/// Returns nil if nothing is selected -func selectionRanges(of buffer: XCSourceTextBuffer) -> SelectionType { - let selections = buffer.selections as! [XCSourceTextRange] - if selections.count == 1 { - let range = selections.first! - if range.start.line == range.end.line { - if range.start.column == range.end.column { - return .none(line: range.start.line, col: range.start.column) - } - return .words(line: range.start.line, colStart: range.start.column, colEnd: range.end.column) - } - return .lines(start: range.start, end: range.end) - } - let textRangeList = buffer.selections.map { $0 as! XCSourceTextRange } - return .multiLocation(textRangeList) -} - - -/// Indexes of all the lines -/// -/// - Returns: Returns nil if no selection -func selectedLinesIndexSet(for selectedRanges: SelectionType) -> IndexSet { - switch selectedRanges { - case .none(let line, _): return IndexSet(integer: line) - case .words(let line, _, _): return IndexSet(integer: line) - case .lines(let start, let end): - return IndexSet(integersIn: start.line...(end.column == 0 ? end.line - 1 : end.line)) - case .multiLocation(_): fatalError() - } -}