├── .gitignore ├── FormatImpl ├── FormatImpl-Bridging-Header.h ├── FormatImpl.swift ├── Info.plist ├── LICENCE.md ├── clang-format ├── clang_format.sh ├── swift_format.sh └── swiftformat ├── FormatRelay ├── FormatRelay-Bridging-Header.h ├── FormatRelay.entitlements └── Info.plist ├── GitBlameImpl ├── GitBlameImpl-Bridging-Header.h ├── Info.plist └── gitblame.py ├── GitBlameRelay ├── GitBlameRelay-Bridging-Header.h ├── GitBlameRelay.entitlements └── Info.plist ├── GitDiffImpl ├── DiffMatchPatch │ ├── DMDiff.h │ ├── DMDiff.m │ ├── DMPatch.h │ ├── DMPatch.m │ ├── DiffMatchPatch.h │ ├── DiffMatchPatch.m │ ├── DiffMatchPatchCFUtilities.h │ ├── DiffMatchPatchCFUtilities.m │ ├── DiffMatchPatchInternals.h │ ├── LICENSE │ ├── NSString+EscapeHTMLCharacters.h │ ├── NSString+EscapeHTMLCharacters.m │ ├── NSString+UriCompatibility.h │ └── NSString+UriCompatibility.m ├── DiffProcessor.swift ├── GitDiffImpl-Bridging-Header.h ├── GitDiffImpl.entitlements ├── GitDiffImpl.swift └── Info.plist ├── GitDiffRelay ├── GitDiffRelay-Bridging-Header.h ├── GitDiffRelay.entitlements └── Info.plist ├── InferImpl ├── InferImpl-Bridging-Header.h ├── InferImpl.swift ├── Info.plist ├── infer.mm ├── infer.sh └── sourcekitd.h ├── InferRelay ├── InferRelay-Bridging-Header.h ├── InferRelay.entitlements └── Info.plist ├── LICENSE ├── LNProvider.t2d ├── LNProvider.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── LNProvider ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16.tiff │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ └── Contents.json ├── Base.lproj │ └── MainMenu.xib ├── DefaultManager.swift ├── Info.plist ├── LNProvider-Bridging-Header.h └── LNProvider.entitlements ├── LNProviderTests ├── Info.plist ├── LNProviderTests-Bridging-Header.h ├── LNProviderTests.swift └── example_diff.txt ├── LNProviderUITests ├── Info.plist └── LNProviderUITests.swift ├── LNXcodeSupport ├── Info.plist ├── KeyPath.swift ├── LNExtensionClient.h ├── LNExtensionClient.m ├── LNExtensionClientDO.h ├── LNExtensionClientDO.m ├── LNExtensionProtocol.h ├── LNFileHighlights.h ├── LNFileHighlights.mm ├── LNHighlightGutter.h ├── LNHighlightGutter.m ├── LNXcodeSupport.h ├── LNXcodeSupport.mm ├── NSColor+NSString.h ├── NSColor+NSString.m ├── XcodePrivate.h └── undo.png ├── LineNumberPlugin.pages ├── README.md └── SharedXPC ├── LNExtensionBase.swift ├── LNExtensionRelay.swift ├── LNScriptImpl.swift ├── LineGenerators.swift └── main.swift /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | *Library/* 3 | *xcuserdata* 4 | -------------------------------------------------------------------------------- /FormatImpl/FormatImpl-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "LNExtensionProtocol.h" 6 | #import "LNFileHighlights.h" 7 | 8 | #import "DiffMatchPatch.h" 9 | #import "DMDiff.h" 10 | -------------------------------------------------------------------------------- /FormatImpl/FormatImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormatImpl.swift 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 03/04/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let implementationFactory = FormatImpl.self 12 | var diffgen = DiffProcessor() 13 | 14 | class FormatDefaults: DefaultManager { 15 | 16 | open override var modifiedKey: String { return formatKey } 17 | 18 | } 19 | 20 | open class FormatImpl: LNExtensionBase, LNExtensionService { 21 | 22 | open var defaults: DefaultManager { 23 | return FormatDefaults() 24 | } 25 | 26 | open override func getConfig(_ callback: @escaping LNConfigCallback) { 27 | callback([ 28 | LNApplyTitleKey: "Format Lint", 29 | LNApplyPromptKey: "Apply style suggestion to lines %d-%d", 30 | LNApplyConfirmKey: "Modify", 31 | ]) 32 | } 33 | 34 | open var scripts = [ 35 | "swift": "swift_format", 36 | "m": "clang_format", 37 | "mm": "clang_format", 38 | "h": "clang_format", 39 | "cpp": "clang_format", 40 | "c": "clang_format" 41 | ] 42 | 43 | open func requestHighlights(forFile filepath: String, callback: @escaping LNHighlightCallback) { 44 | let url = URL(fileURLWithPath: filepath) 45 | 46 | guard let diffScript = scripts[url.pathExtension] else { 47 | callback(nil, nil) 48 | return 49 | } 50 | 51 | guard let script = Bundle.main.path(forResource: diffScript, ofType: "sh") else { 52 | callback(nil, error(description: "script \(diffScript).sh not in XPC bundle")) 53 | return 54 | } 55 | 56 | DispatchQueue.global().async { 57 | let generator = TaskGenerator(launchPath: script, 58 | arguments: [url.lastPathComponent], 59 | directory: url.deletingLastPathComponent().path) 60 | 61 | for _ in 0 ..< 2 { 62 | _ = generator.next() 63 | } 64 | 65 | let highlights = diffgen.generateHighlights(sequence: generator.lineSequence, defaults: self.defaults) 66 | callback(highlights.jsonData(), nil) 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /FormatImpl/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | FormatImpl 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSHumanReadableCopyright 24 | Copyright © 2017 John Holdsworth. All rights reserved. 25 | XPCService 26 | 27 | ServiceType 28 | Application 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /FormatImpl/LICENCE.md: -------------------------------------------------------------------------------- 1 | SwiftFormat 2 | 3 | Copyright (c) 2016 Nick Lockwood 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 18 | 2. Altered source versions must be plainly marked as such, and must not be 19 | misrepresented as being the original software. 20 | 21 | 3. This notice may not be removed or altered from any source distribution. -------------------------------------------------------------------------------- /FormatImpl/clang-format: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/GitDiff/1089f1f4eadbb7297ffb598983402ac383ae3ddd/FormatImpl/clang-format -------------------------------------------------------------------------------- /FormatImpl/clang_format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # clang_format.sh 4 | # LNProvider 5 | # 6 | # Created by John Holdsworth on 04/04/2017. 7 | # Copyright © 2017 John Holdsworth. All rights reserved. 8 | 9 | INDENT=`defaults read LineNumber FormatIndent` 10 | XCODE_STYLE="{ IndentWidth: $INDENT, TabWidth: $INDENT, ObjCBlockIndentWidth: $INDENT, ColumnLimit: 0 }" 11 | 12 | diff -Naur <("$(dirname "$0")/clang-format" -style="$XCODE_STYLE" <"$1") "$1" 13 | -------------------------------------------------------------------------------- /FormatImpl/swift_format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # swift_format.sh 4 | # LNProvider 5 | # 6 | # Created by John Holdsworth on 03/04/2017. 7 | # Copyright © 2017 John Holdsworth. All rights reserved. 8 | 9 | # https://github.com/nicklockwood/SwiftFormat/releases/tag/0.28.2 10 | 11 | diff -Naur <("$(dirname "$0")/swiftformat" <"$1") "$1" 12 | -------------------------------------------------------------------------------- /FormatImpl/swiftformat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/GitDiff/1089f1f4eadbb7297ffb598983402ac383ae3ddd/FormatImpl/swiftformat -------------------------------------------------------------------------------- /FormatRelay/FormatRelay-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // FormatRelay-Bridging-Header.h 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 03/04/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import "LNExtensionProtocol.h" 10 | 11 | #define EXTENSION_IMPL_SERVICE "com.johnholdsworth.FormatImpl" 12 | -------------------------------------------------------------------------------- /FormatRelay/FormatRelay.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /FormatRelay/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | FormatRelay 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSHumanReadableCopyright 24 | Copyright © 2017 John Holdsworth. All rights reserved. 25 | XPCService 26 | 27 | ServiceType 28 | Application 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /GitBlameImpl/GitBlameImpl-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "LNExtensionProtocol.h" 6 | 7 | #define EXTENSION_IMPL_SCRIPT "gitblame" 8 | -------------------------------------------------------------------------------- /GitBlameImpl/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | GitBlameImpl 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSHumanReadableCopyright 24 | Copyright © 2017 John Holdsworth. All rights reserved. 25 | XPCService 26 | 27 | ServiceType 28 | Application 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /GitBlameImpl/gitblame.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # gitblame.py 5 | # LNProvider 6 | # 7 | # Created by John Holdsworth on 31/03/2017. 8 | # Copyright © 2017 John Holdsworth. All rights reserved. 9 | 10 | import subprocess 11 | import json 12 | import time 13 | import math 14 | import sys 15 | import re 16 | 17 | file = sys.argv[1] 18 | parser = re.compile(r"^\^?(\S+) (?:\S+\s+)*\((.+) +(\d+) \S+ +(\d+)\)") 19 | 20 | def readdefault( key, default ): 21 | value = subprocess.Popen(['/usr/bin/env', 'defaults', 'read', 'LineNumber', key], 22 | stdout=subprocess.PIPE).stdout.read() 23 | return value if value != '' else default 24 | 25 | recent = float(readdefault('RecentDays', '7'))*24*60*60 26 | decay = recent 27 | 28 | color = readdefault('RecentColor', "0.5 1.0 0.5 1") 29 | color = re.sub(r' [\d.]+\n?$', ' %f', color) 30 | 31 | lastcommit = None 32 | commits = {} 33 | output = {} 34 | 35 | proc = subprocess.Popen(['/usr/bin/env', 'git', 'blame', '-t', file],stdout=subprocess.PIPE) 36 | for line in iter(proc.stdout.readline,''): 37 | match = parser.match(line) 38 | commit = match.group(1) 39 | if commit.startswith('00000000'): 40 | continue 41 | 42 | who = match.group(2) 43 | when = match.group(3) 44 | lineno = match.group(4) 45 | 46 | if commit != lastcommit: 47 | start = lineno 48 | lastcommit = commit 49 | 50 | age = time.time() - int(when) 51 | seen = commits.get(commit) 52 | if seen: 53 | alias = {'alias': seen['lineno']} 54 | if lineno == start: 55 | alias['start'] = start 56 | seen['lineno'] = start 57 | output[lineno] = alias 58 | elif age < recent: 59 | log = subprocess.Popen(['/usr/bin/env', 'git', 'show', '--pretty=medium', '-s', commit], 60 | stdout=subprocess.PIPE).stdout.read() 61 | commits[commit] = {'lineno': lineno, 'log': log} 62 | output[lineno] = {'text': log, 'start': start, 'color': color % math.exp(-age/decay)} 63 | 64 | print json.dumps(output) 65 | -------------------------------------------------------------------------------- /GitBlameRelay/GitBlameRelay-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "LNExtensionProtocol.h" 6 | 7 | #define EXTENSION_IMPL_SERVICE "com.johnholdsworth.GitBlameImpl" 8 | -------------------------------------------------------------------------------- /GitBlameRelay/GitBlameRelay.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /GitBlameRelay/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | GitBlameRelay 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSHumanReadableCopyright 24 | Copyright © 2017 John Holdsworth. All rights reserved. 25 | XPCService 26 | 27 | ServiceType 28 | Application 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /GitDiffImpl/DiffMatchPatch/DMDiff.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Diff Match and Patch 3 | * 4 | * Copyright 2010 geheimwerk.de. 5 | * http://code.google.com/p/google-diff-match-patch/ 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * Author: fraser@google.com (Neil Fraser) 20 | * ObjC port: jan@geheimwerk.de (Jan Weiß) 21 | * Refactoring & mangling: @inquisitivesoft (Harry Jordan) 22 | */ 23 | 24 | 25 | /* 26 | * The data structure representing a diff is an array of Diff objects: 27 | * [ 28 | * Diff(Operation.DIFF_DELETE, "Hello"), 29 | * Diff(Operation.DIFF_INSERT, "Goodbye"), 30 | * Diff(Operation.DIFF_EQUAL, " world.") 31 | * ] 32 | * which means: delete "Hello", add "Goodbye" and keep " world." 33 | */ 34 | 35 | 36 | typedef enum { 37 | DIFF_DELETE = 1, 38 | DIFF_INSERT = 2, 39 | DIFF_EQUAL = 3 40 | } DMDiffOperation; 41 | 42 | 43 | #import 44 | 45 | @interface DMDiff :NSObject { 46 | } 47 | 48 | @property (nonatomic, assign) DMDiffOperation operation; 49 | @property (nonatomic, copy) NSString *text; 50 | 51 | + (id)diffWithOperation:(DMDiffOperation)anOperation andText:(NSString *)aText; 52 | - (id)initWithOperation:(DMDiffOperation)anOperation andText:(NSString *)aText; 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /GitDiffImpl/DiffMatchPatch/DMDiff.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Diff Match and Patch 3 | * 4 | * Copyright 2010 geheimwerk.de. 5 | * http://code.google.com/p/google-diff-match-patch/ 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * Author: fraser@google.com (Neil Fraser) 20 | * ObjC port: jan@geheimwerk.de (Jan Weiß) 21 | * Refactoring & mangling: @inquisitivesoft (Harry Jordan) 22 | */ 23 | 24 | #pragma clang diagnostic push 25 | #pragma clang diagnostic ignored "-Wdocumentation" 26 | 27 | #import "DMDiff.h" 28 | 29 | 30 | @implementation DMDiff 31 | 32 | /** 33 | * Constructor. Initializes the diff with the provided values. 34 | * @param operation One of DIFF_INSERT, DIFF_DELETE or DIFF_EQUAL. 35 | * @param text The text being applied. 36 | */ 37 | + (id)diffWithOperation:(DMDiffOperation)anOperation andText:(NSString *)aText 38 | { 39 | return [[self alloc] initWithOperation:anOperation andText:aText]; 40 | } 41 | 42 | 43 | - (id)initWithOperation:(DMDiffOperation)anOperation andText:(NSString *)aText 44 | { 45 | self = [super init]; 46 | 47 | if(self) { 48 | self.operation = anOperation; 49 | self.text = aText; 50 | } 51 | 52 | return self; 53 | } 54 | 55 | 56 | - (id)copyWithZone:(NSZone *)zone 57 | { 58 | return [[[self class] allocWithZone:zone] initWithOperation:self.operation andText:self.text]; 59 | } 60 | 61 | 62 | /** 63 | * Display a human-readable version of this Diff. 64 | * @return text version. 65 | */ 66 | - (NSString *)description 67 | { 68 | NSString *prettyText = [self.text stringByReplacingOccurrencesOfString:@"\n" withString:@"\u00b6"]; 69 | NSString *operationName = nil; 70 | 71 | switch(self.operation) { 72 | case DIFF_DELETE: 73 | operationName = @"DELETE"; 74 | break; 75 | 76 | case DIFF_INSERT: 77 | operationName = @"INSERT"; 78 | break; 79 | 80 | case DIFF_EQUAL: 81 | operationName = @"EQUAL"; 82 | break; 83 | 84 | default: 85 | break; 86 | } 87 | 88 | return [NSString stringWithFormat:@"%@ (%@,\"%@\")", [super description], operationName, prettyText]; 89 | } 90 | 91 | 92 | /** 93 | * Is this Diff equivalent to another Diff? 94 | * @param obj Another Diff to compare against. 95 | * @return YES or NO. 96 | */ 97 | - (BOOL)isEqual:(id)obj 98 | { 99 | // If parameter is nil return NO. 100 | if(obj == nil) 101 | return NO; 102 | 103 | // If parameter cannot be cast to Diff return NO. 104 | if(![obj isKindOfClass:[DMDiff class]]) 105 | return NO; 106 | 107 | // Return YES if the fields match. 108 | DMDiff *p = (DMDiff *)obj; 109 | return p.operation == self.operation && [p.text isEqualToString:self.text]; 110 | } 111 | 112 | - (BOOL)isEqualToDiff:(DMDiff *)obj 113 | { 114 | // If parameter is nil return NO. 115 | if(obj == nil) 116 | return NO; 117 | 118 | // Return YES if the fields match. 119 | return obj.operation == self.operation && [obj.text isEqualToString:self.text]; 120 | } 121 | 122 | - (NSUInteger)hash 123 | { 124 | return [_text hash] ^ (NSUInteger)_operation; 125 | } 126 | 127 | @end 128 | -------------------------------------------------------------------------------- /GitDiffImpl/DiffMatchPatch/DMPatch.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Diff Match and Patch 3 | * 4 | * Copyright 2010 geheimwerk.de. 5 | * http://code.google.com/p/google-diff-match-patch/ 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * Author: fraser@google.com (Neil Fraser) 20 | * ObjC port: jan@geheimwerk.de (Jan Weiß) 21 | * Refactoring & mangling: @inquisitivesoft (Harry Jordan) 22 | */ 23 | 24 | 25 | #import 26 | 27 | 28 | @interface DMPatch : NSObject { 29 | } 30 | 31 | @property (nonatomic, strong) NSMutableArray *diffs; 32 | @property (nonatomic, assign) NSUInteger start1; 33 | @property (nonatomic, assign) NSUInteger start2; 34 | @property (nonatomic, assign) NSUInteger length1; 35 | @property (nonatomic, assign) NSUInteger length2; 36 | 37 | - (void)addContext:(NSString *)text withMargin:(NSInteger)patchMargin maximumBits:(NSUInteger)maximumBits; 38 | - (NSString *)patchText; 39 | 40 | @end -------------------------------------------------------------------------------- /GitDiffImpl/DiffMatchPatch/DMPatch.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Diff Match and Patch 3 | * 4 | * Copyright 2010 geheimwerk.de. 5 | * http://code.google.com/p/google-diff-match-patch/ 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * Author: fraser@google.com (Neil Fraser) 20 | * ObjC port: jan@geheimwerk.de (Jan Weiß) 21 | * Refactoring & mangling: @inquisitivesoft (Harry Jordan) 22 | */ 23 | 24 | #pragma clang diagnostic push 25 | #pragma clang diagnostic ignored "-Wdocumentation" 26 | 27 | #import "DMPatch.h" 28 | #import "DMDiff.h" 29 | 30 | #import "DiffMatchPatchInternals.h" // DMPatch uses the MAX_OF_CONST_AND_DIFF macro 31 | #import "DiffMatchPatchCFUtilities.h" 32 | #import "NSString+UriCompatibility.h" 33 | 34 | 35 | @implementation DMPatch 36 | 37 | 38 | - (id)init 39 | { 40 | self = [super init]; 41 | 42 | if(self) { 43 | self.diffs = [NSMutableArray array]; 44 | } 45 | 46 | return self; 47 | } 48 | 49 | 50 | - (id)copyWithZone:(NSZone *)zone 51 | { 52 | DMPatch *newPatch = [[[self class] allocWithZone:zone] init]; 53 | 54 | newPatch.diffs = [[NSMutableArray alloc] initWithArray:self.diffs copyItems:YES]; 55 | newPatch.start1 = self.start1; 56 | newPatch.start2 = self.start2; 57 | newPatch.length1 = self.length1; 58 | newPatch.length2 = self.length2; 59 | 60 | return newPatch; 61 | } 62 | 63 | 64 | - (NSString *)description 65 | { 66 | return [[super description] stringByAppendingFormat:@" %@", [self patchText]]; 67 | } 68 | 69 | 70 | /** 71 | * Emulate GNU diff's format. 72 | * Header: @@ -382,8 +481,9 @@ 73 | * Indicies are printed as 1-based, not 0-based. 74 | * @return The GNU diff NSString. 75 | */ 76 | - (NSString *)patchText 77 | { 78 | NSString *coords1; 79 | NSString *coords2; 80 | 81 | if(self.length1 == 0) { 82 | coords1 = [NSString stringWithFormat:@"%lu,0", 83 | (unsigned long)self.start1]; 84 | } else if(self.length1 == 1) { 85 | coords1 = [NSString stringWithFormat:@"%lu", 86 | (unsigned long)self.start1 + 1]; 87 | } else { 88 | coords1 = [NSString stringWithFormat:@"%lu,%lu", 89 | (unsigned long)self.start1 + 1, (unsigned long)self.length1]; 90 | } 91 | 92 | if(self.length2 == 0) { 93 | coords2 = [NSString stringWithFormat:@"%lu,0", 94 | (unsigned long)self.start2]; 95 | } else if(self.length2 == 1) { 96 | coords2 = [NSString stringWithFormat:@"%lu", 97 | (unsigned long)self.start2 + 1]; 98 | } else { 99 | coords2 = [NSString stringWithFormat:@"%lu,%lu", 100 | (unsigned long)self.start2 + 1, (unsigned long)self.length2]; 101 | } 102 | 103 | NSMutableString *text = [NSMutableString stringWithFormat:@"@@ -%@ +%@ @@\n", 104 | coords1, coords2]; 105 | 106 | // Escape the body of the patch with %xx notation. 107 | for(DMDiff *aDiff in self.diffs) { 108 | switch(aDiff.operation) { 109 | case DIFF_INSERT: 110 | [text appendString:@"+"]; 111 | break; 112 | 113 | case DIFF_DELETE: 114 | [text appendString:@"-"]; 115 | break; 116 | 117 | case DIFF_EQUAL: 118 | [text appendString:@" "]; 119 | break; 120 | } 121 | 122 | [text appendString:[aDiff.text encodedURIString]]; 123 | [text appendString:@"\n"]; 124 | } 125 | 126 | return text; 127 | } 128 | 129 | 130 | 131 | /** 132 | * Increase the context until it is unique, 133 | * but don't let the pattern expand beyond DIFF_MATCH_MAX_BITS. 134 | * @param patch The patch to grow. 135 | * @param text Source text. 136 | */ 137 | 138 | - (void)addContext:(NSString *)text withMargin:(NSInteger)patchMargin maximumBits:(NSUInteger)maximumBits 139 | { 140 | if(text.length == 0) 141 | return; 142 | 143 | NSString *pattern = [text substringWithRange:NSMakeRange(self.start2, self.length1)]; 144 | NSUInteger padding = 0; 145 | 146 | // Look for the first and last matches of pattern in text. If two 147 | // different matches are found, increase the pattern length. 148 | while([text rangeOfString:pattern options:NSLiteralSearch].location 149 | != [text rangeOfString:pattern options:(NSLiteralSearch | NSBackwardsSearch)].location 150 | && pattern.length < (maximumBits - patchMargin - patchMargin)) { 151 | padding += patchMargin; 152 | 153 | NSRange patternRange = NSMakeRange(MAX_OF_CONST_AND_DIFF(0, self.start2, padding), MIN(text.length, self.start2 + self.length1 + padding)); 154 | patternRange.length -= patternRange.location; 155 | pattern = [text substringWithRange:patternRange]; 156 | } 157 | 158 | // Add one chunk for good luck. 159 | padding += patchMargin; 160 | 161 | // Add the prefix. 162 | NSRange prefixRange = NSMakeRange(MAX_OF_CONST_AND_DIFF(0, self.start2, padding), 0); 163 | prefixRange.length = self.start2 - prefixRange.location; 164 | NSString *prefix = [text substringWithRange:prefixRange]; 165 | 166 | if(prefix.length != 0) { 167 | [self.diffs insertObject:[DMDiff diffWithOperation:DIFF_EQUAL andText:prefix] atIndex:0]; 168 | } 169 | 170 | 171 | // Add the suffix. 172 | NSRange suffixRange = NSMakeRange((self.start2 + self.length1), MIN(text.length - self.start2 - self.length1, padding)); 173 | NSString *suffix = [text substringWithRange:suffixRange]; 174 | 175 | if(suffix.length != 0) { 176 | [self.diffs addObject:[DMDiff diffWithOperation:DIFF_EQUAL andText:suffix]]; 177 | } 178 | 179 | // Roll back the start points. 180 | self.start1 -= prefix.length; 181 | self.start2 -= prefix.length; 182 | // Extend the lengths. 183 | self.length1 += prefix.length + suffix.length; 184 | self.length2 += prefix.length + suffix.length; 185 | } 186 | 187 | 188 | @end 189 | -------------------------------------------------------------------------------- /GitDiffImpl/DiffMatchPatch/DiffMatchPatch.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Diff Match and Patch 3 | * 4 | * Copyright 2010 geheimwerk.de. 5 | * http://code.google.com/p/google-diff-match-patch/ 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * Author: fraser@google.com (Neil Fraser) 20 | * ObjC port: jan@geheimwerk.de (Jan Weiß) 21 | * Refactoring & mangling: @inquisitivesoft (Harry Jordan) 22 | * 23 | * If you need more fine grained options 24 | * have a look at DiffMatchPatchInternals.h 25 | * 26 | */ 27 | 28 | #pragma clang diagnostic push 29 | #pragma clang diagnostic ignored "-Wdocumentation" 30 | 31 | #import 32 | 33 | #pragma mark - 34 | #pragma mark Generating Diffs 35 | 36 | /** 37 | * Find the differences between two texts. 38 | * 39 | * @param text1 Old NSString to be diffed. 40 | * @param text2 New NSString to be diffed. 41 | * @return Returns an NSArray of DMDiff objects. 42 | */ 43 | 44 | NSArray *diff_diffsBetweenTexts(NSString *text1, NSString *text2); 45 | 46 | 47 | /** 48 | * Find the differences between two texts, with a few options 49 | * 50 | * @param text1 Old NSString to be diffed. 51 | * @param text2 New NSString to be diffed. 52 | * @param highQuality Set to FALSE for a faster but less optimal diff 53 | * Setting this to high quality will be around 2 to 3 times slower 54 | * 55 | * @param timeLimit The number in seconds (from the current time) to allow the function to process the diff. 56 | * Enter 0.0 to allow the function an unlimited period. 57 | * 58 | * @return Returns an array of DMDiff objects. 59 | */ 60 | 61 | NSArray *diff_diffsBetweenTextsWithOptions(NSString *text1, NSString *text2, BOOL highQuality, NSTimeInterval timeLimit); 62 | 63 | 64 | #pragma mark - 65 | #pragma mark Formatting Diffs into a human readable output 66 | 67 | 68 | /** 69 | * Calculate the first text from an array of DMDiff objects 70 | * 71 | * @param diffs The array of DMDiff objects. 72 | * @return Returns the first text 73 | */ 74 | 75 | NSString *diff_text1(NSArray *diffs); 76 | 77 | 78 | /** 79 | * Calculate the second text from an array of DMDiff objects 80 | * 81 | * @param diffs The array of DMDiff objects. 82 | * @return Returns the first text 83 | */ 84 | 85 | NSString *diff_text2(NSArray *diffs); 86 | 87 | 88 | /** 89 | * Create a HTML output from an array of DMDiff objects 90 | * 91 | * @param diffs The array of DMDiff objects. 92 | * @return A HTML string 93 | */ 94 | 95 | NSString *diff_prettyHTMLFromDiffs(NSArray *diffs); 96 | 97 | 98 | /** 99 | * Create a delta string from an array of DMDiff objects 100 | * 101 | * @param diffs The array of DMDiff objects. 102 | * @return A delta string 103 | */ 104 | 105 | NSString *diff_deltaFromDiffs(NSArray *diffs); 106 | 107 | 108 | /** 109 | * Given the original text1, and an encoded NSString which describes the 110 | * operations required to transform text1 into text2, compute the full diff. 111 | * 112 | * @param text1 Source NSString for the diff. 113 | * @param delta Delta text. 114 | * @param error NSError if invalid input. 115 | * @return NSArray of DMDiff objects or nil if invalid. 116 | */ 117 | 118 | NSArray *diff_diffsFromOriginalTextAndDelta(NSString *text1, NSString *delta, NSError **error); 119 | 120 | 121 | /** 122 | * Calculate the levenshtein distance for an array of DMDiff objects 123 | * See http://en.wikipedia.org/wiki/Levenshtein_distance#Definition for more info 124 | * 125 | * @param diffs The array of DMDiff objects. 126 | * @return The levenshtein score for the diffs. 127 | */ 128 | 129 | NSUInteger diff_levenshtein(NSArray *diffs); 130 | 131 | 132 | #pragma mark - 133 | #pragma mark Searching text using fuzzy matching 134 | 135 | /** 136 | * Locate the best instance of 'pattern' in 'text' near 'nearestLocation'. 137 | * Returns NSNotFound if no match found. 138 | * 139 | * @param text The text to search. 140 | * @param pattern The pattern to search for. 141 | * @param approximateLocation The location to search around. 142 | * @return Index of the best match or NSNotFound. 143 | */ 144 | 145 | NSUInteger match_locationOfMatchInText(NSString *text, NSString *pattern, NSUInteger approximateLocation); 146 | 147 | 148 | /** 149 | * Locate the best instance of 'pattern' in 'text' near 'nearestLocation'. 150 | * Returns NSNotFound if no match found. 151 | * 152 | * @param text The text to search. 153 | * @param pattern The pattern to search for. 154 | * @param approximateLocation The location to search around. 155 | * @param matchThreshold How closely the minimum matching text should match the search pattern. The default is 0.5f 156 | * @param matchDistance How far away from the approximateLocation to search. The default is 1000 characters 157 | * @return Index of the best match or NSNotFound. 158 | */ 159 | 160 | NSUInteger match_locationOfMatchInTextWithOptions(NSString *text, NSString *pattern, NSUInteger approximateLocation, CGFloat matchThreshold, NSUInteger matchDistance); 161 | 162 | 163 | #pragma mark - 164 | #pragma mark Patching text 165 | 166 | /** 167 | * Generate an array of DMPatches from two texts 168 | * 169 | * @param text1 The first text 170 | * @param text2 The second text 171 | * @return An array of DMPatches 172 | */ 173 | 174 | NSArray *patch_patchesFromTexts(NSString *text1, NSString *text2); 175 | 176 | 177 | /** 178 | * Take a list of patches and return a textual representation. 179 | * 180 | * @param patches NSMutableArray of Patch objects. 181 | * @return Text representation of patches. 182 | */ 183 | 184 | NSString *patch_patchesToText(NSArray *patches); 185 | 186 | 187 | /** 188 | * Parse a textual representation of patches and return a NSMutableArray of DMPatch objects. 189 | * 190 | * @param textline Text representation of patches. 191 | * @param error NSError if invalid input. 192 | * @return NSArray of Patch objects. 193 | */ 194 | 195 | NSArray *patch_parsePatchesFromText(NSString *text, NSError **error); 196 | 197 | 198 | /** 199 | * Merge a set of patches onto the text. Return a patched text, as well 200 | * as an index set of for each value for which patches were applied. 201 | * 202 | * @param patches An NSArray of DMPatch objects 203 | * @param text The old text 204 | * @param indexesOfAppliedPatches An NSIndexSet of the patches, passed by reference (optional) 205 | * Pass NULL if not required 206 | * @return The patched text 207 | */ 208 | 209 | NSString *patch_applyPatchesToText(NSArray *sourcePatches, NSString *text, NSIndexSet **indexesOfAppliedPatches); 210 | 211 | 212 | -------------------------------------------------------------------------------- /GitDiffImpl/DiffMatchPatch/DiffMatchPatchCFUtilities.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Diff Match and Patch 3 | * 4 | * Copyright 2010 geheimwerk.de. 5 | * http://code.google.com/p/google-diff-match-patch/ 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * Author: fraser@google.com (Neil Fraser) 20 | * ObjC port: jan@geheimwerk.de (Jan Weiß) 21 | * Refactoring & mangling: @inquisitivesoft (Harry Jordan) 22 | */ 23 | 24 | 25 | 26 | CF_INLINE CFStringRef diff_CFStringCreateSubstring(CFStringRef text, CFIndex start_index, CFIndex length) 27 | { 28 | CFStringRef substring = CFStringCreateWithSubstring(kCFAllocatorDefault, text, CFRangeMake(start_index, length)); 29 | return substring; 30 | } 31 | 32 | CF_INLINE CFStringRef diff_CFStringCreateRightSubstring(CFStringRef text, CFIndex text_length, CFIndex new_length) 33 | { 34 | return diff_CFStringCreateSubstring(text, text_length - new_length, new_length); 35 | } 36 | 37 | CF_INLINE CFStringRef diff_CFStringCreateLeftSubstring(CFStringRef text, CFIndex new_length) 38 | { 39 | return diff_CFStringCreateSubstring(text, 0, new_length); 40 | } 41 | 42 | CF_INLINE CFStringRef diff_CFStringCreateSubstringWithStartIndex(CFStringRef text, CFIndex start_index) 43 | { 44 | return diff_CFStringCreateSubstring(text, start_index, (CFStringGetLength(text) - start_index) ); 45 | } 46 | 47 | 48 | CF_INLINE void diff_CFStringPrepareUniCharBuffer(CFStringRef string, const UniChar **string_chars, UniChar **string_buffer, CFRange string_range) 49 | { 50 | *string_chars = CFStringGetCharactersPtr(string); 51 | 52 | if(*string_chars == NULL) { 53 | // Fallback in case CFStringGetCharactersPtr() didn’t work. 54 | *string_buffer = malloc(string_range.length * sizeof(UniChar) ); 55 | CFStringGetCharacters(string, string_range, *string_buffer); 56 | *string_chars = *string_buffer; 57 | } 58 | } 59 | 60 | CF_INLINE CFIndex diff_CFArrayLastValueAsCFIndex(CFMutableArrayRef theArray) 61 | { 62 | return (CFIndex)CFArrayGetValueAtIndex(theArray, CFArrayGetCount(theArray) - 1); 63 | } 64 | 65 | CF_INLINE void diff_CFArrayRemoveLastValue(CFMutableArrayRef theArray) 66 | { 67 | CFArrayRemoveValueAtIndex(theArray, CFArrayGetCount(theArray) - 1); 68 | } 69 | 70 | 71 | CFIndex diff_commonPrefix(CFStringRef text1, CFStringRef text2); 72 | CFIndex diff_commonSuffix(CFStringRef text1, CFStringRef text2); 73 | CFIndex diff_commonOverlap(CFStringRef text1, CFStringRef text2); 74 | 75 | CFArrayRef diff_halfMatchCreate(CFStringRef text1, CFStringRef text2); 76 | CFArrayRef diff_halfMatchICreate(CFStringRef longtext, CFStringRef shorttext, CFIndex i); 77 | CFStringRef diff_linesToCharsMungeCFStringCreate(CFStringRef text, CFMutableArrayRef lineArray, CFMutableDictionaryRef lineHash); 78 | CFStringRef diff_tokensToCharsMungeCFStringCreate(CFStringRef text, CFMutableArrayRef tokenArray, CFMutableDictionaryRef tokenHash, CFOptionFlags tokenizerOptions); 79 | CFStringRef diff_wordsToCharsMungeCFStringCreate(CFStringRef text, CFMutableArrayRef tokenArray, CFMutableDictionaryRef tokenHash); 80 | CFStringRef diff_sentencesToCharsMungeCFStringCreate(CFStringRef text, CFMutableArrayRef tokenArray, CFMutableDictionaryRef tokenHash); 81 | CFStringRef diff_paragraphsToCharsMungeCFStringCreate(CFStringRef text, CFMutableArrayRef tokenArray, CFMutableDictionaryRef tokenHash); 82 | CFStringRef diff_lineBreakDelimiteredToCharsMungeCFStringCreate(CFStringRef text, CFMutableArrayRef tokenArray, CFMutableDictionaryRef tokenHash); 83 | CFStringRef diff_rangesToCharsMungeCFStringCreate(CFStringRef text, CFMutableArrayRef substringArray, CFMutableDictionaryRef substringHash, CFRange *ranges, size_t ranges_count); 84 | CFStringRef diff_charsToTokenCFStringCreate(CFStringRef charsString, CFArrayRef tokenArray); 85 | CFIndex diff_cleanupSemanticScore(CFStringRef one, CFStringRef two); -------------------------------------------------------------------------------- /GitDiffImpl/DiffMatchPatch/DiffMatchPatchInternals.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Diff Match and Patch 3 | * 4 | * Copyright 2010 geheimwerk.de. 5 | * http://code.google.com/p/google-diff-match-patch/ 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * Author: fraser@google.com (Neil Fraser) 20 | * ObjC port: jan@geheimwerk.de (Jan Weiß) 21 | * Refactoring & mangling: @inquisitivesoft (Harry Jordan) 22 | * 23 | * 24 | * This file contains declares the functions that DiffMatchPatch 25 | * uses internally. You might want to use these for more fine grained 26 | * control and particularly for testing. 27 | * 28 | * As a convention, functions which take an object by reference 29 | * e.g. diff_cleanupSemantic(NSMutableArray **diffs) will change the 30 | * objects content in some way. 31 | * 32 | */ 33 | 34 | 35 | // Structs which are used internally to define properties 36 | 37 | struct DiffProperties { 38 | BOOL checkLines; // Set to YES for a faster but less optimal diff 39 | NSTimeInterval deadline; 40 | }; 41 | 42 | typedef struct DiffProperties DiffProperties; 43 | 44 | 45 | struct MatchProperties { 46 | CGFloat matchThreshold; 47 | NSUInteger matchDistance; 48 | NSUInteger matchMaximumBits; // The number of bits in an int 49 | }; 50 | 51 | typedef struct MatchProperties MatchProperties; 52 | 53 | 54 | struct PatchProperties { 55 | DiffProperties diffProperties; 56 | MatchProperties matchProperties; 57 | 58 | CGFloat patchMargin; 59 | CGFloat patchDeleteThreshold; 60 | NSUInteger diffEditingCost; 61 | }; 62 | 63 | typedef struct PatchProperties PatchProperties; 64 | 65 | 66 | typedef enum { 67 | DiffWordTokens = 1, 68 | DiffParagraphTokens = 2, 69 | DiffSentenceTokens = 3, 70 | DiffLineBreakDelimiteredTokens = 4 71 | } DiffTokenMode; 72 | 73 | 74 | // Define default properties 75 | //DiffProperties diff_defaultDiffProperties(); 76 | //MatchProperties match_defaultMatchProperties(); 77 | //PatchProperties patch_defaultPatchProperties(); 78 | 79 | 80 | // Internal functions for diffing 81 | NSMutableArray *diff_diffsBetweenTextsWithProperties(NSString *oldText, NSString *newText, DiffProperties properties); 82 | NSUInteger diff_translateLocationFromText1ToText2(NSArray *diffs, NSUInteger location); 83 | NSMutableArray *diff_computeDiffsBetweenTexts(NSString *text1, NSString *text2, DiffProperties properties); 84 | NSMutableArray *diff_computeDiffsUsingLineMode(NSString *text1, NSString *text2, DiffProperties properties); 85 | 86 | NSArray *diff_linesToCharsForStrings(NSString *text1, NSString * text2); 87 | NSArray *diff_tokensToCharsForStrings(NSString *text1, NSString *text2, DiffTokenMode mode); 88 | void diff_charsToLines(NSArray **diffs, NSArray *lineArray); 89 | void diff_charsToTokens(NSArray **diffs, NSArray *tokenArray); 90 | 91 | NSMutableArray *diff_bisectOfStrings(NSString *text1, NSString *text2, DiffProperties properties); 92 | NSMutableArray *diff_bisectSplitOfStrings(NSString *text1, NSString *text2, NSUInteger x, NSUInteger y, DiffProperties properties); 93 | 94 | void diff_cleanupSemantic(NSMutableArray **diffs); 95 | void diff_cleanupMerge(NSMutableArray **diffs); 96 | void diff_cleanupSemanticLossless(NSMutableArray **diffs); 97 | NSInteger diff_cleanupSemanticScoreOfStrings(NSString *text1, NSString *text2); 98 | 99 | 100 | // Internal functions for matching 101 | NSUInteger match_locationOfMatchInTextWithProperties(NSString *text, NSString *pattern, NSUInteger nearLocation, MatchProperties properties); 102 | NSUInteger match_bitapOfTextAndPattern(NSString *text, NSString *pattern, NSUInteger nearLocation, MatchProperties properties); 103 | NSMutableDictionary *match_alphabetFromPattern(NSString *pattern); 104 | double match_bitapScoreForErrorCount(NSUInteger e, NSUInteger x, NSUInteger nearLocation, NSString *pattern, MatchProperties properties); 105 | 106 | 107 | // Internal functions for patching 108 | NSArray *patch_patchesFromTextsWithProperties(NSString *text1, NSString *text2, PatchProperties properties); 109 | NSArray *patch_patchesFromDiffs(NSArray *diffs, PatchProperties properties); 110 | NSArray *patch_patchesFromTextAndDiffs(NSString *text1, NSArray *diffs, PatchProperties properties); 111 | NSString *patch_applyPatchesToTextWithProperties(NSArray *sourcePatches, NSString *text, NSIndexSet **indexesOfAppliedPatches, PatchProperties properties); 112 | 113 | NSString *patch_addPaddingToPatches(NSMutableArray **patches, PatchProperties properties); 114 | void patch_splitMax(NSMutableArray **patches, PatchProperties properties); 115 | void patch_cleanupDiffsForEfficiency(NSMutableArray **diffs, PatchProperties properties); 116 | 117 | 118 | // A convenience function to splice two arrays, likely of DMDiffs or DMPatches 119 | void diff_spliceTwoArrays(NSMutableArray **input, NSUInteger start, NSUInteger count, NSArray *objects); 120 | 121 | 122 | // MAX_OF_CONST_AND_DIFF determines the maximum of two expressions: 123 | // The first is a constant (first parameter) while the second expression is 124 | // the difference between the second and third parameter. The way this is 125 | // calculated prevents integer overflow in the result of the difference. 126 | 127 | #if !defined(MAX_OF_CONST_AND_DIFF) 128 | #define MAX_OF_CONST_AND_DIFF(A, B, C) ((B) <= (C) ? (A) : (B)-(C) + (A)) 129 | #endif 130 | -------------------------------------------------------------------------------- /GitDiffImpl/DiffMatchPatch/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /GitDiffImpl/DiffMatchPatch/NSString+EscapeHTMLCharacters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * NSString+EscapeHTMLCharacters 3 | * Copyright 2010 Harry Jordan. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * Authored by @inquisitiveSoft (Harry Jordan) 18 | * 19 | * Heavily inspired by http://google-toolbox-for-mac.googlecode.com/svn/trunk/Foundation/GTMNSString+HTML.m 20 | * in fact the mapOfHTMLEquivalentsForCharacters table is a directly copy 21 | */ 22 | 23 | 24 | @interface NSString (DMEscapeHTMLCharacters) 25 | 26 | - (NSString *)stringByEscapingHTML; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /GitDiffImpl/DiffMatchPatch/NSString+EscapeHTMLCharacters.m: -------------------------------------------------------------------------------- 1 | /* 2 | * NSString+EscapeHTMLCharacters 3 | * Copyright 2010 Harry Jordan. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | * Authored by @inquisitiveSoft (Harry Jordan) 18 | * 19 | * Inspired by http://google-toolbox-for-mac.googlecode.com/svn/trunk/Foundation/GTMNSString+HTML.m 20 | * in fact the mapOfHTMLEquivalentsForCharacters table is a directly copy 21 | */ 22 | 23 | 24 | #import "NSString+EscapeHTMLCharacters.h" 25 | 26 | 27 | typedef struct { 28 | char *name; 29 | unichar character; 30 | } DMCharacterDefinition; 31 | 32 | 33 | static DMCharacterDefinition mapOfHTMLEquivalentsForCharacters[] = { 34 | { " ", 9 }, // Tab character 35 | 36 | // Originally from http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_Special_characters 37 | { """, 34 }, 38 | { "&", 38 }, 39 | { "'", 39 }, 40 | { "<", 60 }, 41 | { ">", 62 }, 42 | { "Œ", 338 }, 43 | { "œ", 339 }, 44 | { "Š", 352 }, 45 | { "š", 353 }, 46 | { "Ÿ", 376 }, 47 | { "ˆ", 710 }, 48 | { "˜", 732 }, 49 | { " ", 8194 }, 50 | { " ", 8195 }, 51 | { " ", 8201 }, 52 | { "‌", 8204 }, 53 | { "‍", 8205 }, 54 | { "‎", 8206 }, 55 | { "‏", 8207 }, 56 | { "–", 8211 }, 57 | { "—", 8212 }, 58 | { "‘", 8216 }, 59 | { "’", 8217 }, 60 | { "‚", 8218 }, 61 | { "“", 8220 }, 62 | { "”", 8221 }, 63 | { "„", 8222 }, 64 | { "†", 8224 }, 65 | { "‡", 8225 }, 66 | { "‰", 8240 }, 67 | { "‹", 8249 }, 68 | { "›", 8250 }, 69 | { "€", 8364 }, 70 | }; 71 | 72 | static const size_t numberOfHTMLEquivalents = 34; // ToDo: expand the range of characters 73 | 74 | 75 | int compareCharacterDefinitions(void const *firstEquivalent, void const *secondEquivalent) { 76 | const DMCharacterDefinition firstCharacter = *(const DMCharacterDefinition *)firstEquivalent; 77 | const DMCharacterDefinition secondCharacter = *(const DMCharacterDefinition *)secondEquivalent; 78 | 79 | if(firstCharacter.character < secondCharacter.character) 80 | return -1; 81 | else if(firstCharacter.character == secondCharacter.character) 82 | return 0; 83 | 84 | return 1; 85 | } 86 | 87 | 88 | 89 | @implementation NSString (DMEscapeHTMLCharacters) 90 | 91 | 92 | - (NSString *)stringByEscapingHTML 93 | { 94 | NSInteger length = self.length; 95 | if(length <= 0) 96 | return self; 97 | 98 | __block NSMutableString *result = [[NSMutableString alloc] init]; 99 | 100 | // Iteration state 101 | __block BOOL previousCharacterIsWhiteSpace = FALSE; 102 | __block BOOL previousCharacterIsEscapedWhiteSpace = FALSE; 103 | 104 | [self enumerateSubstringsInRange:NSMakeRange(0, self.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { 105 | // First, handle spaces as a special case 106 | if([substring isEqualToString:@" "]) { 107 | // If there are more than one space characters in a row then add  's 108 | if(previousCharacterIsWhiteSpace) { 109 | if(!previousCharacterIsEscapedWhiteSpace) { 110 | // Replace the previous normal space character 111 | [result replaceCharactersInRange:NSMakeRange([result length] - 1, 1) withString:@" "]; 112 | } 113 | 114 | [result appendString:@" "]; 115 | previousCharacterIsEscapedWhiteSpace = TRUE; 116 | } else 117 | [result appendString:@" "]; 118 | 119 | previousCharacterIsWhiteSpace = TRUE; 120 | } else { 121 | if(substringRange.length == 1) { 122 | // If the substring can be represented as a single unicode code point 123 | unichar currentCharacter = [substring characterAtIndex:0]; 124 | 125 | if([[NSCharacterSet newlineCharacterSet] characterIsMember:currentCharacter]) { 126 | // If the character represents a new line then add a
tag 127 | // Doesn't do any clever parsing of paragraphs 128 | [result appendString:@"
\n"]; 129 | } else { 130 | // If character is not a whitespace or newline character then search 131 | // mapOfHTMLEquivalentsForCharacters to see if we can find a replacement for it 132 | DMCharacterDefinition currentCharacterDefinition; 133 | currentCharacterDefinition.character = currentCharacter; 134 | DMCharacterDefinition *searchResult = bsearch(¤tCharacterDefinition, &mapOfHTMLEquivalentsForCharacters, numberOfHTMLEquivalents, sizeof(DMCharacterDefinition), compareCharacterDefinitions); 135 | 136 | if(searchResult != NULL) { 137 | // Append the resulting encoded HTML character 138 | [result appendFormat:@"%s", searchResult->name]; 139 | } else { 140 | // Otherwise just append the character 141 | [result appendString:substring]; 142 | } 143 | } 144 | } else if(substringRange.length > 1) { 145 | // Otherwise just append the complex character sequence 146 | [result appendString:substring]; 147 | } 148 | 149 | previousCharacterIsWhiteSpace = FALSE; 150 | previousCharacterIsEscapedWhiteSpace = FALSE; 151 | } 152 | }]; 153 | 154 | return result; 155 | } 156 | 157 | 158 | @end 159 | -------------------------------------------------------------------------------- /GitDiffImpl/DiffMatchPatch/NSString+UriCompatibility.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Diff Match and Patch 3 | * 4 | * Copyright 2010 geheimwerk.de. 5 | * http://code.google.com/p/google-diff-match-patch/ 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * Author: fraser@google.com (Neil Fraser) 20 | * ObjC port: jan@geheimwerk.de (Jan Weiß) 21 | */ 22 | 23 | 24 | #import 25 | 26 | 27 | @interface NSString (UriCompatibility) 28 | 29 | - (NSString *)encodedURIString; 30 | - (NSString *)decodedURIString; 31 | 32 | @end -------------------------------------------------------------------------------- /GitDiffImpl/DiffMatchPatch/NSString+UriCompatibility.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Diff Match and Patch 3 | * 4 | * Copyright 2010 geheimwerk.de. 5 | * http://code.google.com/p/google-diff-match-patch/ 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * Author: fraser@google.com (Neil Fraser) 20 | * ObjC port: jan@geheimwerk.de (Jan Weiß) 21 | */ 22 | 23 | #pragma clang diagnostic push 24 | #pragma clang diagnostic ignored "-Wdocumentation" 25 | #pragma clang diagnostic ignored "-Wdeprecated" 26 | 27 | #import "NSString+UriCompatibility.h" 28 | 29 | 30 | @implementation NSString (UriCompatibility) 31 | 32 | /** 33 | * Escape excluding selected chars for compatability with JavaScript's encodeURI. 34 | * This method produces uppercase hex. 35 | * 36 | * @param str The CFStringRef to escape. 37 | * @return The escaped CFStringRef. 38 | */ 39 | - (NSString *)encodedURIString 40 | { 41 | return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)self, CFSTR(" !~*'();/?:@&=+$,#"), NULL, kCFStringEncodingUTF8); 42 | } 43 | 44 | /** 45 | * Unescape all percent escapes. 46 | * 47 | * Example: "%3f" -> "?", "%24" -> "$", etc. 48 | * 49 | * @return The unescaped NSString. 50 | */ 51 | - (NSString *)decodedURIString 52 | { 53 | return (__bridge_transfer NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL, (CFStringRef)self, CFSTR(""), kCFStringEncodingUTF8); 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /GitDiffImpl/DiffProcessor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiffProessor.swift 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 03/04/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class DiffProcessor { 12 | 13 | let regex = try! NSRegularExpression(pattern: "^(?:(?:@@ -\\d+,\\d+ \\+(\\d+),\\d+ @@)|([-+])(.*))", options: []) 14 | 15 | enum Delta { 16 | case start(lineno: Int) 17 | case delete(text: String) 18 | case insert(text: String) 19 | case other 20 | } 21 | 22 | func delta(line: String) -> Delta { 23 | if let match = regex.firstMatch(in: line, options: [], range: NSMakeRange(0, line.utf16.count)) { 24 | if let lineno = match.group(1, in: line) { 25 | return .start(lineno: Int(lineno) ?? -1) 26 | } else if let delta = match.group(2, in: line), let text = match.group(3, in: line) { 27 | if delta == "-" { 28 | return .delete(text: text) 29 | } else { 30 | return .insert(text: text) 31 | } 32 | } 33 | } 34 | return .other 35 | } 36 | 37 | func textDiff(_ inserted: String, against deleted: String, defaults: DefaultManager) -> NSAttributedString { 38 | let attributes = [NSAttributedStringKey.foregroundColor: defaults.extraColor] 39 | let attributed = NSMutableAttributedString() 40 | 41 | for diff in diff_diffsBetweenTexts(deleted, inserted) { 42 | let diff = diff as! DMDiff 43 | if diff.operation == DIFF_INSERT { 44 | continue 45 | } 46 | 47 | let next = NSMutableAttributedString(string: diff.text ?? "") 48 | if diff.operation == DIFF_DELETE { 49 | next.setAttributes(attributes, range: NSMakeRange(0, next.length)) 50 | } 51 | 52 | attributed.append(next) 53 | } 54 | 55 | return attributed 56 | } 57 | 58 | open func generateHighlights(sequence: AnySequence, defaults: DefaultManager) -> LNFileHighlights { 59 | let deletedColor = defaults.deletedColor 60 | let modifiedColor = defaults.modifiedColor 61 | let addedColor = defaults.addedColor 62 | 63 | var currentLine = 0, startLine = 0, deletedCount = 0, insertedText = "" 64 | let fileHighlights = LNFileHighlights() 65 | var element: LNHighlightElement? 66 | 67 | func closeRange() { 68 | element?.range = "\(startLine) \(currentLine - startLine)" 69 | if let deleted = element?.text { 70 | element?.setAttributedText(textDiff(insertedText, against: deleted, defaults: defaults)) 71 | } 72 | } 73 | 74 | for line in sequence { 75 | 76 | switch delta(line: line) { 77 | case .start(let lineno): 78 | currentLine = lineno 79 | break 80 | case .delete(let text): 81 | if element == nil { 82 | startLine = currentLine 83 | element = LNHighlightElement() 84 | element?.start = currentLine 85 | element?.color = modifiedColor 86 | element?.text = "" 87 | fileHighlights[currentLine] = element 88 | } 89 | element?.text = (element?.text ?? "") + text + "\n" 90 | deletedCount += 1 91 | break 92 | case .insert(let text): 93 | if element == nil || currentLine - startLine >= deletedCount && element?.color != addedColor { 94 | if element == nil { 95 | startLine = currentLine 96 | } 97 | closeRange() 98 | element = LNHighlightElement() 99 | element?.start = currentLine 100 | element?.color = addedColor 101 | } 102 | fileHighlights[currentLine] = element 103 | insertedText += text 104 | currentLine += 1 105 | break 106 | case .other: 107 | if element?.color == modifiedColor && currentLine == startLine { 108 | element?.color = deletedColor 109 | } 110 | closeRange() 111 | currentLine += 1 112 | insertedText = "" 113 | deletedCount = 0 114 | element = nil 115 | break 116 | } 117 | } 118 | 119 | return fileHighlights 120 | } 121 | 122 | } 123 | 124 | extension NSTextCheckingResult { 125 | 126 | func group(_ group: Int, in string: String) -> String? { 127 | if range(at: group).location != NSNotFound { 128 | return string[range(at: group)] 129 | } 130 | return nil 131 | } 132 | 133 | } 134 | 135 | extension String { 136 | 137 | public subscript(i: Int) -> String { 138 | return self[i ..< i + 1] 139 | } 140 | 141 | public subscript(range: NSRange) -> String { 142 | return Range(range, in: self).flatMap { String(self[$0]) } ?? "??" 143 | } 144 | 145 | public subscript(r: Range) -> String { 146 | return self[NSMakeRange(r.lowerBound, r.upperBound - r.upperBound)] 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /GitDiffImpl/GitDiffImpl-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "LNExtensionProtocol.h" 6 | #import "LNFileHighlights.h" 7 | 8 | #import "DiffMatchPatch.h" 9 | #import "DMDiff.h" 10 | -------------------------------------------------------------------------------- /GitDiffImpl/GitDiffImpl.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /GitDiffImpl/GitDiffImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitDiffImpl.swift 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let implementationFactory = GitDiffImpl.self 12 | var lineNumberDefaults = DefaultManager() 13 | var diffgen = DiffProcessor() 14 | 15 | open class GitDiffImpl: LNExtensionBase, LNExtensionService { 16 | 17 | open override func getConfig(_ callback: @escaping LNConfigCallback) { 18 | callback([ 19 | LNPopoverColorKey: lineNumberDefaults.popoverColor.stringRepresentation, 20 | LNApplyTitleKey: "GitDiff", 21 | LNApplyPromptKey: "Revert code at lines %d-%d to staged version?", 22 | LNApplyConfirmKey: "Revert", 23 | ]) 24 | } 25 | 26 | open func requestHighlights(forFile filepath: String, callback: @escaping LNHighlightCallback) { 27 | DispatchQueue.global().async { 28 | let url = URL(fileURLWithPath: filepath) 29 | var arguments = ["git", "diff", "--no-ext-diff", "--no-color"] 30 | if lineNumberDefaults.showHead { 31 | arguments.append("HEAD") 32 | } 33 | arguments.append(url.lastPathComponent) 34 | let generator = TaskGenerator(launchPath: "/usr/bin/env", arguments: arguments, 35 | directory: url.deletingLastPathComponent().path) 36 | 37 | for _ in 0 ..< 4 { 38 | _ = generator.next() 39 | } 40 | 41 | let highlights = diffgen.generateHighlights(sequence: generator.lineSequence, defaults: lineNumberDefaults) 42 | callback(highlights.jsonData(), nil) 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /GitDiffImpl/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | GitDiffImpl 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSHumanReadableCopyright 24 | Copyright © 2017 John Holdsworth. All rights reserved. 25 | XPCService 26 | 27 | ServiceType 28 | Application 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /GitDiffRelay/GitDiffRelay-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "LNExtensionProtocol.h" 6 | 7 | #define EXTENSION_IMPL_SERVICE "com.johnholdsworth.GitDiffImpl" 8 | -------------------------------------------------------------------------------- /GitDiffRelay/GitDiffRelay.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /GitDiffRelay/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | GitDiffRelay 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSHumanReadableCopyright 24 | Copyright © 2017 John Holdsworth. All rights reserved. 25 | XPCService 26 | 27 | ServiceType 28 | Application 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /InferImpl/InferImpl-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "LNExtensionProtocol.h" 6 | #import "LNFileHighlights.h" 7 | 8 | #import "DiffMatchPatch.h" 9 | #import "DMDiff.h" 10 | -------------------------------------------------------------------------------- /InferImpl/InferImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormatImpl.swift 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 03/04/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let implementationFactory = InferImpl.self 12 | var diffgen = DiffProcessor() 13 | 14 | class InferDefaults: DefaultManager { 15 | 16 | open override var modifiedKey: String { return inferKey } 17 | 18 | } 19 | 20 | open class InferImpl: LNExtensionBase, LNExtensionService { 21 | 22 | open var defaults: DefaultManager { 23 | return InferDefaults() 24 | } 25 | 26 | open override func getConfig(_ callback: @escaping LNConfigCallback) { 27 | callback([ 28 | LNApplyTitleKey: "Infer Types", 29 | LNApplyPromptKey: "Make type explicit", 30 | LNApplyConfirmKey: "Modify", 31 | ]) 32 | } 33 | 34 | open func requestHighlights(forFile filepath: String, callback: @escaping LNHighlightCallback) { 35 | let url = URL(fileURLWithPath: filepath) 36 | 37 | guard url.pathExtension == "swift" else { 38 | callback(nil, nil) 39 | return 40 | } 41 | 42 | guard let script = Bundle.main.path(forResource: "infer", ofType: "sh") else { 43 | callback(nil, error(description: "script infer.sh not in XPC bundle")) 44 | return 45 | } 46 | 47 | DispatchQueue.global().async { 48 | let generator = TaskGenerator(launchPath: script, arguments: [filepath], 49 | directory: url.deletingLastPathComponent().path) 50 | 51 | for _ in 0 ..< 2 { 52 | _ = generator.next() 53 | } 54 | 55 | let highlights = diffgen.generateHighlights(sequence: generator.lineSequence, defaults: self.defaults) 56 | callback(highlights.jsonData(), nil) 57 | } 58 | } 59 | 60 | } 61 | 62 | -------------------------------------------------------------------------------- /InferImpl/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | InferImpl 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSHumanReadableCopyright 24 | Copyright © 2017 John Holdsworth. All rights reserved. 25 | XPCService 26 | 27 | ServiceType 28 | Application 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /InferImpl/infer.mm: -------------------------------------------------------------------------------- 1 | // 2 | // infer.mm 3 | // infer 4 | // 5 | // Created by John Holdsworth on 21/08/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | // Makes explicit, inferred types in a Swift source. 9 | // Requires project to have been built & indexed. 10 | // 11 | 12 | #import 13 | #import 14 | #import 15 | 16 | #import "sourcekitd.h" 17 | 18 | @interface PhaseOneFindArguemnts : NSObject 19 | - (NSString * _Nullable)projectForSourceFile:(NSString *)sourceFile; 20 | - (NSString * _Nullable)logDirectoryForProject:(NSString *)projectPath; 21 | - (NSString * _Nullable)commandLineForPrimaryFile:(NSString *)sourceFile 22 | inLogDirectory:(NSString *)logDirectory; 23 | @end 24 | 25 | #define INError(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__) 26 | //#define INError(fmt, ...) NSLog(@fmt, __VA_ARGS__) 27 | 28 | @interface PhaseTwoInferAssignments : NSObject 29 | - (int)inferAssignmentsFor:(const char *)sourceFile 30 | arguments:(const char **)argv into:(FILE *)output; 31 | @end 32 | 33 | int main(int argc, const char * argv[]) { 34 | const char *inferBinary = argv[0]; 35 | if (argc < 2) { 36 | INError("Usage: %s \n", inferBinary); 37 | exit(1); 38 | } 39 | else if(argc == 2) { 40 | auto logReader = [PhaseOneFindArguemnts new]; 41 | NSString *sourceFile = [NSString stringWithUTF8String:argv[1]]; 42 | NSString *projectPath = [logReader projectForSourceFile:sourceFile]; 43 | if (!projectPath) { 44 | INError("Could not find project for source file: %s\n", argv[1]); 45 | exit(1); 46 | } 47 | 48 | NSString *logDirectory = [logReader logDirectoryForProject:projectPath]; 49 | NSString *compileCommand = [logReader commandLineForPrimaryFile:sourceFile 50 | inLogDirectory:logDirectory]; 51 | if (!compileCommand) { 52 | INError("Could not find compile command for '%s' in log directory: %s\n", 53 | argv[1], logDirectory.UTF8String); 54 | exit(1); 55 | } 56 | 57 | NSTask *task = [NSTask new]; 58 | task.launchPath = @"/bin/bash"; 59 | task.arguments = @[@"-c", [NSString stringWithFormat:@"\"%@\" \"%@\" %@", 60 | [NSString stringWithUTF8String:inferBinary], 61 | sourceFile, compileCommand]]; 62 | [task launch]; 63 | [task waitUntilExit]; 64 | exit(task.terminationStatus); 65 | } 66 | else { 67 | auto inferer = [PhaseTwoInferAssignments new]; 68 | exit([inferer inferAssignmentsFor:argv[1] arguments:argv + 4 into:stdout]); 69 | } 70 | } 71 | 72 | @implementation NSTask(FILE) 73 | 74 | - (FILE *)stdout { 75 | NSPipe *pipe = [NSPipe pipe]; 76 | self.standardOutput = pipe; 77 | int fd = [self.standardOutput fileHandleForReading].fileDescriptor; 78 | [self launch]; 79 | return fdopen(fd, "r"); 80 | } 81 | 82 | @end 83 | 84 | @implementation PhaseOneFindArguemnts { 85 | char logFile[PATH_MAX], commandBuffer[1024*1024]; // max command is 256k on Darwin 86 | } 87 | 88 | - (NSString *)fileWithExtension:(NSString *)extension inFiles:(NSArray *)files { 89 | for (NSString *file in files) 90 | if ([[file pathExtension] isEqualToString:extension]) 91 | return file; 92 | return nil; 93 | } 94 | 95 | - (NSString *)projectForSourceFile:(NSString *)sourceFile { 96 | NSString *directory = sourceFile.stringByDeletingLastPathComponent; 97 | if ([directory isEqualToString:@"/"]) 98 | return nil; 99 | 100 | NSFileManager *manager = [NSFileManager defaultManager]; 101 | NSArray *fileList = [manager contentsOfDirectoryAtPath:directory error:NULL]; 102 | 103 | if (NSString *projectFile = 104 | [self fileWithExtension:@"xcworkspace" inFiles:fileList] ?: 105 | [self fileWithExtension:@"xcodeproj" inFiles:fileList]) { 106 | NSString *projectPath = [directory stringByAppendingPathComponent:projectFile]; 107 | NSString *logDir = [self logDirectoryForProject:projectPath]; 108 | if ([manager fileExistsAtPath:logDir]) 109 | return projectPath; 110 | } 111 | 112 | return [self projectForSourceFile:directory]; 113 | } 114 | 115 | - (NSString *)logDirectoryForProject:(NSString *)projectPath { 116 | NSString *projectName = [projectPath.lastPathComponent stringByDeletingPathExtension]; 117 | return [NSString stringWithFormat:@"%@/Library/Developer/Xcode/DerivedData/%@-%@/Logs/Build", 118 | NSHomeDirectory(), [projectName stringByReplacingOccurrencesOfString:@" " withString:@"_"], 119 | [self hashStringForPath:projectPath]]; 120 | } 121 | 122 | - (NSString *)commandLineForPrimaryFile:(NSString *)sourceFile 123 | inLogDirectory:(NSString *)logDirectory { 124 | NSTask *lsTask = [NSTask new]; 125 | lsTask.launchPath = @"/bin/bash"; 126 | lsTask.arguments = @[@"-c", [NSString stringWithFormat:@"/bin/ls -t \"%@\"/*.xcactivitylog", 127 | logDirectory]]; 128 | FILE *logsFILE = [lsTask stdout]; 129 | 130 | while (fgets(logFile, sizeof logFile, logsFILE)) { 131 | logFile[strlen(logFile)-1] = '\000'; 132 | 133 | NSTask *grepTask = [NSTask new]; 134 | grepTask.launchPath = @"/bin/bash"; 135 | grepTask.arguments = @[@"-c", [NSString stringWithFormat:@"/usr/bin/gunzip <'%@' | " 136 | "tr '\\r' '\\n' | /usr/bin/grep -E ' -primary-file \"?%@\"? '", 137 | [NSString stringWithUTF8String:logFile], 138 | [sourceFile stringByReplacingOccurrencesOfString:@"+" withString:@"\\+"]]]; 139 | FILE *grepFILE = [grepTask stdout]; 140 | 141 | if (fgets(commandBuffer, sizeof commandBuffer, grepFILE)) { 142 | *strstr(commandBuffer, " -o ") = '\000'; 143 | [grepTask terminate]; 144 | [lsTask terminate]; 145 | return [NSString stringWithUTF8String:commandBuffer]; 146 | } 147 | 148 | [grepTask waitUntilExit]; 149 | pclose(grepFILE); 150 | } 151 | 152 | [lsTask waitUntilExit]; 153 | pclose(logsFILE); 154 | return nil; 155 | } 156 | 157 | // Thanks to: http://samdmarshall.com/blog/xcode_deriveddata_hashes.html 158 | 159 | // this function is used to swap byte ordering of a 64bit integer 160 | uint64_t swap_uint64(uint64_t val) { 161 | val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL); 162 | val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL); 163 | return (val << 32) | (val >> 32); 164 | } 165 | 166 | /*! 167 | @method hashStringForPath 168 | 169 | Create the unique identifier string for a Xcode project path 170 | 171 | @param path (input) string path to the ".xcodeproj" or ".xcworkspace" file 172 | 173 | @result NSString* of the identifier 174 | */ 175 | - (NSString *)hashStringForPath:(NSString *)path; 176 | { 177 | // using uint64_t[2] for ease of use, since it is the same size as char[CC_MD5_DIGEST_LENGTH] 178 | uint64_t digest[CC_MD2_DIGEST_LENGTH] = {0}; 179 | 180 | // char array that will contain the identifier 181 | unsigned char resultStr[28] = {0}; 182 | 183 | // setup md5 context 184 | CC_MD5_CTX md5; 185 | CC_MD5_Init(&md5); 186 | 187 | // get the UTF8 string of the path 188 | const char *c_path = [path UTF8String]; 189 | 190 | // get length of the path string 191 | unsigned long length = strlen(c_path); 192 | 193 | // update the md5 context with the full path string 194 | CC_MD5_Update (&md5, c_path, (CC_LONG)length); 195 | 196 | // finalize working with the md5 context and store into the digest 197 | CC_MD5_Final ((unsigned char *)digest, &md5); 198 | 199 | // take the first 8 bytes of the digest and swap byte order 200 | uint64_t startValue = swap_uint64(digest[0]); 201 | 202 | // for indexes 13->0 203 | int index = 13; 204 | do { 205 | // take 'startValue' mod 26 (restrict to alphabetic) and add based 'a' 206 | resultStr[index] = (char)((startValue % 26) + 'a'); 207 | 208 | // divide 'startValue' by 26 209 | startValue /= 26; 210 | 211 | index--; 212 | } while (index >= 0); 213 | 214 | // The second loop, this time using the last 8 bytes 215 | // repeating the same process as before but over indexes 27->14 216 | startValue = swap_uint64(digest[1]); 217 | index = 27; 218 | do { 219 | resultStr[index] = (char)((startValue % 26) + 'a'); 220 | startValue /= 26; 221 | index--; 222 | } while (index > 13); 223 | 224 | // create a new string from the 'resultStr' char array and return 225 | return [[NSString alloc] initWithBytes:resultStr length:28 encoding:NSUTF8StringEncoding]; 226 | } 227 | 228 | @end 229 | 230 | @implementation PhaseTwoInferAssignments 231 | 232 | - (int)inferAssignmentsFor:(const char *)sourceFile arguments:(const char **)argv into:(FILE *)output { 233 | NSError *error; 234 | NSMutableData *sourceData = [NSMutableData dataWithContentsOfFile:[NSString stringWithUTF8String:sourceFile] 235 | options:0 error:&error]; 236 | if (!sourceData) { 237 | INError("Could not load source file '%s': %s\n", 238 | sourceFile, error.localizedDescription.UTF8String); 239 | return 1; 240 | } 241 | 242 | const char *input = (const char *)[sourceData bytes], eos = '\000', *next = input; 243 | [sourceData appendBytes:&eos length:sizeof eos]; 244 | 245 | sourcekitd_initialize(); 246 | 247 | int argc = 0, argo = 0; 248 | sourcekitd_object_t objects[1000]; 249 | NSDictionary *skips = @{ 250 | @"-primary-file": @1, 251 | @"-emit-module-doc-path": @2, 252 | @"-emit-dependencies-path": @2, 253 | @"-emit-reference-dependencies-path": @2, 254 | @"-enable-objc-interop": @1, 255 | @"-warn-long-function-bodies": @1, 256 | @"-warn-long-expression-type-checking": @1, 257 | @"-serialize-debugging-options": @1, 258 | @"-enable-anonymous-context-mangled-names": @1, 259 | @"-pch-disable-validation": @1, 260 | @"-serialize-diagnostics-path": @2, 261 | @"-target-sdk-version": @2, 262 | }; 263 | while (argv[argc]) { 264 | NSString *option = [NSString stringWithUTF8String:argv[argc]]; 265 | option = [option stringByReplacingOccurrencesOfString:@"=.*" withString:@"" 266 | options:NSRegularExpressionSearch range:NSMakeRange(0, option.length)]; 267 | int skip = [skips[option] intValue]; 268 | if (!skip) 269 | objects[argo++] = 270 | sourcekitd_request_string_create(argv[argc]); 271 | argc += skip ?: 1; 272 | } 273 | 274 | sourcekitd_object_t compilerArgs = 275 | sourcekitd_request_array_create(objects, argo); 276 | 277 | sourcekitd_uid_t nameID = sourcekitd_uid_get_from_cstr("key.name"); 278 | sourcekitd_uid_t requestID = sourcekitd_uid_get_from_cstr("key.request"); 279 | sourcekitd_uid_t sourceFileID = sourcekitd_uid_get_from_cstr("key.sourcefile"); 280 | sourcekitd_uid_t compilerArgsID = sourcekitd_uid_get_from_cstr("key.compilerargs"); 281 | sourcekitd_uid_t cursorRequestID = sourcekitd_uid_get_from_cstr("source.request.cursorinfo"); 282 | 283 | sourcekitd_object_t cursorRequest = sourcekitd_request_dictionary_create(nil, nil, 0); 284 | sourcekitd_request_dictionary_set_uid(cursorRequest, requestID, cursorRequestID); 285 | sourcekitd_request_dictionary_set_string(cursorRequest, sourceFileID, sourceFile); 286 | sourcekitd_request_dictionary_set_string(cursorRequest, nameID, sourceFile); 287 | sourcekitd_request_dictionary_set_value(cursorRequest, compilerArgsID, compilerArgs); 288 | 289 | sourcekitd_uid_t offsetID = sourcekitd_uid_get_from_cstr("key.offset"); 290 | sourcekitd_uid_t declID = sourcekitd_uid_get_from_cstr("key.fully_annotated_decl"); 291 | 292 | // sourcekit cursor ops deal in byte offsets 293 | regex_t assigns; 294 | if (int err = regcomp(&assigns, 295 | "[ \t\n](let|var)[ \t]+([^\n,)]+?)[ \t]=[ \t]", 296 | REG_EXTENDED|REG_ENHANCED)) { 297 | char errbuff[1000]; 298 | regerror(err, &assigns, errbuff, sizeof errbuff); 299 | INError("Regex compilation error: %s\n", errbuff); 300 | return 1; 301 | } 302 | 303 | regmatch_t matches[3]; 304 | 305 | while(regexec(&assigns, next, sizeof matches/sizeof matches[0], matches, 0) != REG_NOMATCH) { 306 | const char *varStart = next + matches[2].rm_so, *equals = next + matches[2].rm_eo; 307 | ptrdiff_t byteOffset = varStart - input; 308 | 309 | next += fwrite((void *)next, 1, matches[1].rm_so, output); 310 | 311 | sourcekitd_request_dictionary_set_int64(cursorRequest, offsetID, byteOffset); 312 | 313 | sourcekitd_response_t response = sourcekitd_send_request_sync(cursorRequest); 314 | if (sourcekitd_response_is_error(response)) { 315 | NSLog(@"Cursor request %s", 316 | sourcekitd_response_error_get_description(response)); 317 | sourcekitd_request_description_dump(cursorRequest); 318 | continue; 319 | } 320 | else { 321 | sourcekitd_variant_t dict = sourcekitd_response_get_value(response); 322 | if (const char *declaration = sourcekitd_variant_dictionary_get_string(dict, declID)) { 323 | const char *replacement = strstr(declaration, "let") ?: 324 | strstr(declaration, "var") ?: "NODECL"; 325 | int inTag = 0; 326 | while (char ch = *replacement++) { 327 | switch (ch) { 328 | case '<': case '{': 329 | inTag++; 330 | break; 331 | case '>': case '}': 332 | inTag--; 333 | break; 334 | case '&': 335 | if (strncmp(replacement, "lt;", 3) == 0) { 336 | fputc('<', output); 337 | replacement += 3; 338 | break; 339 | } 340 | else if (strncmp(replacement, "gt;", 3) == 0) { 341 | fputc('>', output); 342 | replacement += 3; 343 | break; 344 | } 345 | default: 346 | if (!inTag && !(ch == ' ' && 347 | (*replacement == ':' || *replacement == ' ' || *replacement == '{'))) 348 | fputc(ch, output); 349 | } 350 | } 351 | } 352 | else { 353 | fwrite((void *)next, 1, equals - next, output); 354 | } 355 | } 356 | 357 | sourcekitd_response_dispose(response); 358 | next = equals; 359 | } 360 | 361 | fwrite((void *)next, 1, strlen(next), output); 362 | fflush(output); 363 | return 0; 364 | } 365 | 366 | @end 367 | -------------------------------------------------------------------------------- /InferImpl/infer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # infer.sh 4 | # LNProvider 5 | # 6 | # Created by User on 22/08/2017. 7 | # Copyright © 2017 John Holdsworth. All rights reserved. 8 | 9 | diff -Naur <("$(dirname "$0")/infer" "$1") "$1" 10 | 11 | -------------------------------------------------------------------------------- /InferImpl/sourcekitd.h: -------------------------------------------------------------------------------- 1 | //===--- sourcekitd.h - -----------------------------------------*- C++ -*-===// 2 | // 3 | // This source file is part of the Swift.org open source project 4 | // 5 | // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 10 | // 11 | //===----------------------------------------------------------------------===// 12 | 13 | #ifndef LLVM_SOURCEKITD_SOURCEKITD_H 14 | #define LLVM_SOURCEKITD_SOURCEKITD_H 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | /// \brief The version constants for the sourcekitd API. 21 | /// SOURCEKITD_VERSION_MINOR should increase when there are API additions. 22 | /// SOURCEKITD_VERSION_MAJOR is intended for "major" source/ABI breaking 23 | /// changes. 24 | /// 25 | /// The policy about the sourcekitd API is to keep it source and ABI compatible, 26 | /// thus SOURCEKITD_VERSION_MAJOR is expected to remain stable. 27 | #define SOURCEKITD_VERSION_MAJOR 0 28 | #define SOURCEKITD_VERSION_MINOR 3 29 | 30 | #define SOURCEKITD_VERSION_ENCODE(major, minor) ( \ 31 | ((major) * 10000) \ 32 | + ((minor) * 1)) 33 | 34 | #define SOURCEKITD_VERSION SOURCEKITD_VERSION_ENCODE( \ 35 | SOURCEKITD_VERSION_MAJOR, \ 36 | SOURCEKITD_VERSION_MINOR) 37 | 38 | #define SOURCEKITD_VERSION_STRINGIZE_(major, minor) \ 39 | #major"."#minor 40 | #define SOURCEKITD_VERSION_STRINGIZE(major, minor) \ 41 | SOURCEKITD_VERSION_STRINGIZE_(major, minor) 42 | 43 | #define SOURCEKITD_VERSION_STRING SOURCEKITD_VERSION_STRINGIZE( \ 44 | SOURCEKITD_VERSION_MAJOR, \ 45 | SOURCEKITD_VERSION_MINOR) 46 | 47 | #ifdef __cplusplus 48 | # define SOURCEKITD_BEGIN_DECLS extern "C" { 49 | # define SOURCEKITD_END_DECLS } 50 | #else 51 | # define SOURCEKITD_BEGIN_DECLS 52 | # define SOURCEKITD_END_DECLS 53 | #endif 54 | 55 | #ifndef SOURCEKITD_PUBLIC 56 | # if defined (_MSC_VER) 57 | # define SOURCEKITD_PUBLIC __declspec(dllimport) 58 | # else 59 | # define SOURCEKITD_PUBLIC 60 | # endif 61 | #endif 62 | 63 | #ifndef __has_feature 64 | # define __has_feature(x) 0 65 | #endif 66 | 67 | #if __has_feature(blocks) 68 | # define SOURCEKITD_HAS_BLOCKS 1 69 | #else 70 | # define SOURCEKITD_HAS_BLOCKS 0 71 | #endif 72 | 73 | #if defined(__clang__) || defined(__GNUC__) 74 | # define SOURCEKITD_WARN_RESULT __attribute__((__warn_unused_result__)) 75 | # define SOURCEKITD_NONNULL1 __attribute__((__nonnull__(1))) 76 | # define SOURCEKITD_NONNULL2 __attribute__((__nonnull__(2))) 77 | # define SOURCEKITD_NONNULL3 __attribute__((__nonnull__(3))) 78 | # define SOURCEKITD_NONNULL_ALL __attribute__((__nonnull__)) 79 | # define SOURCEKITD_DEPRECATED(m) __attribute__((deprecated(m))) 80 | #else 81 | # define SOURCEKITD_WARN_RESULT 82 | # define SOURCEKITD_NONNULL1 83 | # define SOURCEKITD_NONNULL2 84 | # define SOURCEKITD_NONNULL3 85 | # define SOURCEKITD_NONNULL_ALL 86 | #endif 87 | 88 | SOURCEKITD_BEGIN_DECLS 89 | 90 | /// \brief Initializes structures needed across the rest of the sourcekitd API. 91 | /// 92 | /// Must be called before any other sourcekitd call. 93 | /// Can be called multiple times as long as it is matched with a 94 | /// \c sourcekitd_shutdown call. 95 | /// Calling \c sourcekitd_initialize a second time without an intervening 96 | /// \c sourcekitd_shutdown is undefined. 97 | /// \c sourcekitd_initialize does not need to be called again even if the 98 | /// service crashes. 99 | SOURCEKITD_PUBLIC 100 | void 101 | sourcekitd_initialize(void); 102 | 103 | /// \brief Deallocates structures needed across the rest of the sourcekitd API. 104 | /// 105 | /// If there are response handlers still waiting for a response, they will 106 | /// receive a SOURCEKITD_ERROR_REQUEST_CANCELLED response. 107 | /// 108 | /// Calling \c sourcekitd_shutdown without a matching \c sourcekitd_initialize 109 | /// is undefined. 110 | SOURCEKITD_PUBLIC 111 | void 112 | sourcekitd_shutdown(void); 113 | 114 | #if SOURCEKITD_HAS_BLOCKS 115 | 116 | typedef void(^sourcekitd_interrupted_connection_handler_t)(void); 117 | 118 | /// \brief Sets the handler which should be called whenever the connection to 119 | /// SourceKit is interrupted. 120 | /// 121 | /// The handler should reestablish any necessary state, such as re-opening any 122 | /// documents which were open before the connection was interrupted. 123 | /// 124 | /// It is not necessary to call \c sourcekitd_initialize; the connection will 125 | /// automatically be reestablished when sending the next request. 126 | /// 127 | /// \param handler Interrupted connection handler to use. Pass NULL to remove 128 | /// the handler. 129 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL 130 | void 131 | sourcekitd_set_interrupted_connection_handler( 132 | sourcekitd_interrupted_connection_handler_t handler); 133 | 134 | #endif 135 | 136 | /// \brief A "unique identifier" utilized by the request/response protocol. 137 | /// 138 | /// A \c sourcekitd_uid_t object is associated with a string and is uniqued for 139 | /// the lifetime of the process. Its usefulness is in providing an "infinite 140 | /// namespace" of identifiers. 141 | /// A \c sourcekitd_uid_t object remains valid even if the service crashes. 142 | typedef struct sourcekitd_uid_s *sourcekitd_uid_t; 143 | 144 | /// \brief Create a \c sourcekitd_uid_t from a C string. 145 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 SOURCEKITD_WARN_RESULT 146 | sourcekitd_uid_t 147 | sourcekitd_uid_get_from_cstr(const char *string); 148 | 149 | /// \brief Create a \c sourcekitd_uid_t from a string buffer. 150 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 SOURCEKITD_WARN_RESULT 151 | sourcekitd_uid_t 152 | sourcekitd_uid_get_from_buf(const char *buf, size_t length); 153 | 154 | /// \brief Get the length of the string associated with a \c sourcekitd_uid_t. 155 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 SOURCEKITD_WARN_RESULT 156 | size_t 157 | sourcekitd_uid_get_length(sourcekitd_uid_t obj); 158 | 159 | /// \brief Get the C string pointer associated with a \c sourcekitd_uid_t. 160 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 SOURCEKITD_WARN_RESULT 161 | const char * 162 | sourcekitd_uid_get_string_ptr(sourcekitd_uid_t obj); 163 | 164 | /// \defgroup Request API 165 | /// 166 | /// @{ 167 | /// 168 | /// \brief Used for constructing a request object. 169 | typedef void *sourcekitd_object_t; 170 | 171 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 172 | sourcekitd_object_t 173 | sourcekitd_request_retain(sourcekitd_object_t object); 174 | 175 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 176 | void 177 | sourcekitd_request_release(sourcekitd_object_t object); 178 | 179 | SOURCEKITD_PUBLIC SOURCEKITD_WARN_RESULT 180 | sourcekitd_object_t 181 | sourcekitd_request_dictionary_create(const sourcekitd_uid_t *keys, 182 | const sourcekitd_object_t *values, 183 | size_t count); 184 | 185 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL 186 | void 187 | sourcekitd_request_dictionary_set_value(sourcekitd_object_t dict, 188 | sourcekitd_uid_t key, 189 | sourcekitd_object_t value); 190 | 191 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL 192 | void 193 | sourcekitd_request_dictionary_set_string(sourcekitd_object_t dict, 194 | sourcekitd_uid_t key, 195 | const char *string); 196 | 197 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL 198 | void 199 | sourcekitd_request_dictionary_set_stringbuf(sourcekitd_object_t dict, 200 | sourcekitd_uid_t key, 201 | const char *buf, size_t length); 202 | 203 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 SOURCEKITD_NONNULL2 204 | void 205 | sourcekitd_request_dictionary_set_int64(sourcekitd_object_t dict, 206 | sourcekitd_uid_t key, int64_t val); 207 | 208 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL 209 | void 210 | sourcekitd_request_dictionary_set_uid(sourcekitd_object_t dict, 211 | sourcekitd_uid_t key, 212 | sourcekitd_uid_t uid); 213 | 214 | #define SOURCEKITD_ARRAY_APPEND ((size_t)(-1)) 215 | 216 | SOURCEKITD_PUBLIC SOURCEKITD_WARN_RESULT 217 | sourcekitd_object_t 218 | sourcekitd_request_array_create(const sourcekitd_object_t *objects, 219 | size_t count); 220 | 221 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 SOURCEKITD_NONNULL3 222 | void 223 | sourcekitd_request_array_set_value(sourcekitd_object_t array, size_t index, 224 | sourcekitd_object_t value); 225 | 226 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 SOURCEKITD_NONNULL3 227 | void 228 | sourcekitd_request_array_set_string(sourcekitd_object_t array, size_t index, 229 | const char *string); 230 | 231 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 SOURCEKITD_NONNULL3 232 | void 233 | sourcekitd_request_array_set_stringbuf(sourcekitd_object_t array, size_t index, 234 | const char *buf, size_t length); 235 | 236 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 237 | void 238 | sourcekitd_request_array_set_int64(sourcekitd_object_t array, size_t index, 239 | int64_t val); 240 | 241 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 SOURCEKITD_NONNULL3 242 | void 243 | sourcekitd_request_array_set_uid(sourcekitd_object_t array, size_t index, 244 | sourcekitd_uid_t uid); 245 | 246 | SOURCEKITD_PUBLIC SOURCEKITD_WARN_RESULT 247 | sourcekitd_object_t 248 | sourcekitd_request_int64_create(int64_t val); 249 | 250 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 SOURCEKITD_WARN_RESULT 251 | sourcekitd_object_t 252 | sourcekitd_request_string_create(const char *string); 253 | 254 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 SOURCEKITD_WARN_RESULT 255 | sourcekitd_object_t 256 | sourcekitd_request_uid_create(sourcekitd_uid_t uid); 257 | 258 | /// \brief Creates a request object by parsing the provided string in YAML 259 | /// format. 260 | /// 261 | /// \param yaml The string in YAML format. 262 | /// 263 | /// \param error A pointer to store a C string of the error description if 264 | /// parsing fails. This string should be disposed of with \c free when done. 265 | /// Can be NULL. 266 | /// 267 | /// \returns A sourcekitd_object_t instance or NULL if parsing fails. 268 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 SOURCEKITD_WARN_RESULT 269 | sourcekitd_object_t 270 | sourcekitd_request_create_from_yaml(const char *yaml, char **error); 271 | 272 | /// \brief Prints to stderr a string representation of the request object in 273 | /// YAML format. 274 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 275 | void 276 | sourcekitd_request_description_dump(sourcekitd_object_t obj); 277 | 278 | /// \brief Copies a string representation of the request object in YAML format. 279 | /// \returns A string representation of the request object. This string should 280 | /// be disposed of with \c free when done. 281 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 282 | char * 283 | sourcekitd_request_description_copy(sourcekitd_object_t obj); 284 | 285 | /// @} 286 | 287 | /// \defgroup Response API 288 | /// 289 | /// @{ 290 | 291 | /// \brief The result of a request. 292 | /// 293 | /// If the request failed \c sourcekitd_response_t will be an error response and 294 | /// will contain information about the error, otherwise it will contain the 295 | /// resulting values of the request. 296 | typedef void *sourcekitd_response_t; 297 | 298 | /// \brief A value of the response object. 299 | /// 300 | /// Its lifetime is tied to the sourcekitd_response_t object that it came from. 301 | typedef struct { 302 | uint64_t data[3]; 303 | } sourcekitd_variant_t; 304 | 305 | typedef enum { 306 | SOURCEKITD_VARIANT_TYPE_NULL = 0, 307 | SOURCEKITD_VARIANT_TYPE_DICTIONARY = 1, 308 | SOURCEKITD_VARIANT_TYPE_ARRAY = 2, 309 | SOURCEKITD_VARIANT_TYPE_INT64 = 3, 310 | SOURCEKITD_VARIANT_TYPE_STRING = 4, 311 | SOURCEKITD_VARIANT_TYPE_UID = 5, 312 | SOURCEKITD_VARIANT_TYPE_BOOL = 6 313 | } sourcekitd_variant_type_t; 314 | 315 | typedef enum { 316 | SOURCEKITD_ERROR_CONNECTION_INTERRUPTED = 1, 317 | SOURCEKITD_ERROR_REQUEST_INVALID = 2, 318 | SOURCEKITD_ERROR_REQUEST_FAILED = 3, 319 | SOURCEKITD_ERROR_REQUEST_CANCELLED = 4 320 | } sourcekitd_error_t; 321 | 322 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 323 | void 324 | sourcekitd_response_dispose(sourcekitd_response_t obj); 325 | 326 | /// \brief Returns true if the given response is an error. 327 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL SOURCEKITD_WARN_RESULT 328 | bool 329 | sourcekitd_response_is_error(sourcekitd_response_t obj); 330 | 331 | /// \brief Returns the error kind given a response error. 332 | /// 333 | /// Passing a response object that is not an error will result in undefined 334 | /// behavior. 335 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL SOURCEKITD_WARN_RESULT 336 | sourcekitd_error_t 337 | sourcekitd_response_error_get_kind(sourcekitd_response_t err); 338 | 339 | /// \brief Returns a C string of the error description. 340 | /// 341 | /// Passing a response object that is not an error will result in undefined 342 | /// behavior. 343 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL SOURCEKITD_WARN_RESULT 344 | const char * 345 | sourcekitd_response_error_get_description(sourcekitd_response_t err); 346 | 347 | /// \brief Returns the value contained in the response. 348 | /// 349 | /// If the response is an error it will return a null variant. 350 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 SOURCEKITD_WARN_RESULT 351 | sourcekitd_variant_t 352 | sourcekitd_response_get_value(sourcekitd_response_t resp); 353 | 354 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL SOURCEKITD_WARN_RESULT 355 | sourcekitd_variant_type_t 356 | sourcekitd_variant_get_type(sourcekitd_variant_t obj); 357 | 358 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL2 SOURCEKITD_WARN_RESULT 359 | sourcekitd_variant_t 360 | sourcekitd_variant_dictionary_get_value(sourcekitd_variant_t dict, 361 | sourcekitd_uid_t key); 362 | 363 | /// The underlying C string for the specified key. NULL if the value for the 364 | /// specified key is not a C string value or if there is no value for the 365 | /// specified key. 366 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL SOURCEKITD_WARN_RESULT 367 | const char * 368 | sourcekitd_variant_dictionary_get_string(sourcekitd_variant_t dict, 369 | sourcekitd_uid_t key); 370 | 371 | /// The underlying \c int64 value for the specified key. 0 if the 372 | /// value for the specified key is not an integer value or if there is no 373 | /// value for the specified key. 374 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL SOURCEKITD_WARN_RESULT 375 | int64_t 376 | sourcekitd_variant_dictionary_get_int64(sourcekitd_variant_t dict, 377 | sourcekitd_uid_t key); 378 | 379 | /// The underlying \c bool value for the specified key. false if the 380 | /// value for the specified key is not a Boolean value or if there is no 381 | /// value for the specified key. 382 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL SOURCEKITD_WARN_RESULT 383 | bool 384 | sourcekitd_variant_dictionary_get_bool(sourcekitd_variant_t dict, 385 | sourcekitd_uid_t key); 386 | 387 | /// The underlying \c sourcekitd_uid_t value for the specified key. NULL if the 388 | /// value for the specified key is not a uid value or if there is no 389 | /// value for the specified key. 390 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL SOURCEKITD_WARN_RESULT 391 | sourcekitd_uid_t 392 | sourcekitd_variant_dictionary_get_uid(sourcekitd_variant_t dict, 393 | sourcekitd_uid_t key); 394 | 395 | #if SOURCEKITD_HAS_BLOCKS 396 | /// \brief A block to be invoked for every key/value pair in the dictionary. 397 | /// 398 | /// \param key The current key in the iteration. 399 | /// 400 | /// \param value The current value in the iteration. 401 | /// 402 | /// \returns true to indicate that iteration should continue. 403 | typedef bool (^sourcekitd_variant_dictionary_applier_t)(sourcekitd_uid_t key, 404 | sourcekitd_variant_t value); 405 | 406 | /// \brief Invokes the given block for every key/value pair in the dictionary. 407 | /// 408 | /// \returns true to indicate that iteration of the dictionary completed 409 | /// successfully. Iteration will only fail if the applier block returns false. 410 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL 411 | bool 412 | sourcekitd_variant_dictionary_apply(sourcekitd_variant_t dict, 413 | sourcekitd_variant_dictionary_applier_t applier); 414 | #endif 415 | 416 | /// \brief A function to be invoked for every key/value pair in the dictionary. 417 | /// 418 | /// \param key The current key in the iteration. 419 | /// 420 | /// \param value The current value in the iteration. 421 | /// 422 | /// \returns true to indicate that iteration should continue. 423 | typedef bool (*sourcekitd_variant_dictionary_applier_f_t)(sourcekitd_uid_t key, 424 | sourcekitd_variant_t value, 425 | void *context); 426 | 427 | /// \brief Invokes the given function for every key/value pair in the 428 | /// dictionary. 429 | /// 430 | /// \returns true to indicate that iteration of the dictionary completed 431 | /// successfully. Iteration will only fail if the applier block returns 0. 432 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL2 433 | bool 434 | sourcekitd_variant_dictionary_apply_f(sourcekitd_variant_t dict, 435 | sourcekitd_variant_dictionary_applier_f_t applier, 436 | void *context); 437 | 438 | SOURCEKITD_PUBLIC SOURCEKITD_WARN_RESULT 439 | size_t 440 | sourcekitd_variant_array_get_count(sourcekitd_variant_t array); 441 | 442 | SOURCEKITD_PUBLIC SOURCEKITD_WARN_RESULT 443 | sourcekitd_variant_t 444 | sourcekitd_variant_array_get_value(sourcekitd_variant_t array, size_t index); 445 | 446 | SOURCEKITD_PUBLIC SOURCEKITD_WARN_RESULT 447 | const char * 448 | sourcekitd_variant_array_get_string(sourcekitd_variant_t array, size_t index); 449 | 450 | SOURCEKITD_PUBLIC SOURCEKITD_WARN_RESULT 451 | int64_t 452 | sourcekitd_variant_array_get_int64(sourcekitd_variant_t array, size_t index); 453 | 454 | SOURCEKITD_PUBLIC SOURCEKITD_WARN_RESULT 455 | bool 456 | sourcekitd_variant_array_get_bool(sourcekitd_variant_t array, size_t index); 457 | 458 | SOURCEKITD_PUBLIC SOURCEKITD_WARN_RESULT 459 | sourcekitd_uid_t 460 | sourcekitd_variant_array_get_uid(sourcekitd_variant_t array, size_t index); 461 | 462 | #if SOURCEKITD_HAS_BLOCKS 463 | /// \brief A block to be invoked for every value in the array. 464 | /// 465 | /// \param index The current index in the iteration. 466 | /// 467 | /// \param value The current value in the iteration. 468 | /// 469 | /// \returns true to indicate that iteration should continue. 470 | typedef bool (^sourcekitd_variant_array_applier_t)(size_t index, 471 | sourcekitd_variant_t value); 472 | 473 | /// \brief Invokes the given block for every value in the array. 474 | /// 475 | /// \returns true to indicate that iteration of the array completed 476 | /// successfully. Iteration will only fail if the applier block returns false. 477 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL 478 | bool 479 | sourcekitd_variant_array_apply(sourcekitd_variant_t array, 480 | sourcekitd_variant_array_applier_t applier); 481 | #endif 482 | 483 | /// \brief A function to be invoked for every value in the array. 484 | /// 485 | /// \param index The current index in the iteration. 486 | /// 487 | /// \param value The current value in the iteration. 488 | /// 489 | /// \returns true to indicate that iteration should continue. 490 | typedef bool (*sourcekitd_variant_array_applier_f_t)(size_t index, 491 | sourcekitd_variant_t value, 492 | void *context); 493 | 494 | /// \brief Invokes the given function for every value in the array. 495 | /// 496 | /// \returns true to indicate that iteration of the array completed 497 | /// successfully. Iteration will only fail if the applier block returns false. 498 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL2 499 | bool 500 | sourcekitd_variant_array_apply_f(sourcekitd_variant_t array, 501 | sourcekitd_variant_array_applier_f_t applier, 502 | void *context); 503 | 504 | SOURCEKITD_PUBLIC SOURCEKITD_WARN_RESULT 505 | int64_t 506 | sourcekitd_variant_int64_get_value(sourcekitd_variant_t obj); 507 | 508 | SOURCEKITD_PUBLIC SOURCEKITD_WARN_RESULT 509 | bool 510 | sourcekitd_variant_bool_get_value(sourcekitd_variant_t obj); 511 | 512 | SOURCEKITD_PUBLIC SOURCEKITD_WARN_RESULT 513 | size_t 514 | sourcekitd_variant_string_get_length(sourcekitd_variant_t obj); 515 | 516 | SOURCEKITD_PUBLIC SOURCEKITD_WARN_RESULT 517 | const char * 518 | sourcekitd_variant_string_get_ptr(sourcekitd_variant_t obj); 519 | 520 | SOURCEKITD_PUBLIC SOURCEKITD_WARN_RESULT 521 | sourcekitd_uid_t 522 | sourcekitd_variant_uid_get_value(sourcekitd_variant_t obj); 523 | 524 | /// \brief Prints to stderr a string representation of the response object in 525 | /// YAML format. 526 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 527 | void 528 | sourcekitd_response_description_dump(sourcekitd_response_t resp); 529 | 530 | /// \brief Prints to the given file descriptor a string representation of the 531 | /// response object. 532 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 533 | void 534 | sourcekitd_response_description_dump_filedesc(sourcekitd_response_t resp, 535 | int fd); 536 | 537 | /// \brief Copies a string representation of the response object in YAML format. 538 | /// \returns A string representation of the response object. This string should 539 | /// be disposed of with \c free when done. 540 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 541 | char * 542 | sourcekitd_response_description_copy(sourcekitd_response_t resp); 543 | 544 | /// \brief Prints to stderr a string representation of the variant object in 545 | /// YAML format. 546 | SOURCEKITD_PUBLIC 547 | void 548 | sourcekitd_variant_description_dump(sourcekitd_variant_t obj); 549 | 550 | /// \brief Prints to the given file descriptor a string representation of the 551 | /// variant object. 552 | SOURCEKITD_PUBLIC 553 | void 554 | sourcekitd_variant_description_dump_filedesc(sourcekitd_variant_t obj, int fd); 555 | 556 | /// \brief Copies a string representation of the variant object in YAML format. 557 | /// \returns A string representation of the variant object. This string should 558 | /// be disposed of with \c free when done. 559 | SOURCEKITD_PUBLIC 560 | char * 561 | sourcekitd_variant_description_copy(sourcekitd_variant_t obj); 562 | 563 | /// \brief Copies a string representation of the variant object in JSON format. 564 | /// \returns A string representation of the variant object. This string should 565 | /// be disposed of with \c free when done. 566 | SOURCEKITD_PUBLIC 567 | char * 568 | sourcekitd_variant_json_description_copy(sourcekitd_variant_t obj); 569 | 570 | /// @} 571 | 572 | /// \brief Invoke a request synchronously. 573 | /// 574 | /// The caller accepts ownership of the returned sourcekitd_response_t object 575 | /// and should invoke \c sourcekitd_response_dispose on it when it is done with 576 | /// it. 577 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL_ALL SOURCEKITD_WARN_RESULT 578 | sourcekitd_response_t 579 | sourcekitd_send_request_sync(sourcekitd_object_t req); 580 | 581 | /// \brief Used to cancel a request that has been invoked asynchronously. 582 | typedef void *sourcekitd_request_handle_t; 583 | 584 | #if SOURCEKITD_HAS_BLOCKS 585 | /// \brief Receives the response of an asynchronous request or notification. 586 | /// 587 | /// The receiver accepts ownership of the response object and should invoke 588 | /// \c sourcekitd_response_dispose on it when it is done with it. 589 | typedef void (^sourcekitd_response_receiver_t)(sourcekitd_response_t resp); 590 | 591 | /// \brief Invoke a request asynchronously. 592 | /// 593 | /// \param req the request object. 594 | /// 595 | /// \param out_handle the address where the associated 596 | /// \c sourcekitd_request_handle_t will be stored. Can be NULL. 597 | /// 598 | /// \param receiver the block that will receive the response object. 599 | SOURCEKITD_PUBLIC SOURCEKITD_NONNULL1 600 | void 601 | sourcekitd_send_request(sourcekitd_object_t req, 602 | sourcekitd_request_handle_t *out_handle, 603 | sourcekitd_response_receiver_t receiver); 604 | #endif 605 | 606 | /// \brief Cancel a request using the associated request handle returned by 607 | /// \c sourcekitd_send_request. 608 | /// 609 | /// It is not guaranteed that invoking \c sourcekitd_cancel_request will cancel 610 | /// the request. If the request gets cancelled, the receiver will get a 611 | /// \c SOURCEKITD_ERROR_REQUEST_CANCELLED response error. 612 | /// 613 | /// Calling \c sourcekitd_cancel_request after the response object has been 614 | /// delivered will have no effect. 615 | SOURCEKITD_PUBLIC 616 | void 617 | sourcekitd_cancel_request(sourcekitd_request_handle_t handle); 618 | 619 | #if SOURCEKITD_HAS_BLOCKS 620 | 621 | /// \brief Sets the handler which should be called to receive notifications. 622 | /// The block will be set to be executed in the main thread queue. 623 | /// 624 | /// If the connection to SourceKit is interrupted the handler will receive an 625 | /// error response object of kind \c SOURCEKITD_ERROR_CONNECTION_INTERRUPTED. 626 | /// Any subsequent requests will immediately fail with the same error until 627 | /// the service is restored. 628 | /// When the service is restored the handler will receive an empty response 629 | /// object. 630 | /// 631 | /// \param receiver Notification handler block to use. Pass NULL to remove the 632 | /// previous handler that was set. 633 | SOURCEKITD_PUBLIC 634 | void 635 | sourcekitd_set_notification_handler(sourcekitd_response_receiver_t receiver); 636 | 637 | typedef sourcekitd_uid_t(^sourcekitd_uid_handler_t)(const char* uidStr); 638 | 639 | SOURCEKITD_PUBLIC SOURCEKITD_DEPRECATED("use sourcekitd_set_uid_handlers") 640 | void sourcekitd_set_uid_handler(sourcekitd_uid_handler_t handler); 641 | 642 | typedef sourcekitd_uid_t(^sourcekitd_uid_from_str_handler_t)(const char* uidStr); 643 | typedef const char *(^sourcekitd_str_from_uid_handler_t)(sourcekitd_uid_t uid); 644 | 645 | SOURCEKITD_PUBLIC 646 | void 647 | sourcekitd_set_uid_handlers(sourcekitd_uid_from_str_handler_t uid_from_str, 648 | sourcekitd_str_from_uid_handler_t str_from_uid); 649 | 650 | #endif 651 | 652 | SOURCEKITD_END_DECLS 653 | 654 | #endif 655 | 656 | -------------------------------------------------------------------------------- /InferRelay/InferRelay-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "LNExtensionProtocol.h" 6 | 7 | #define EXTENSION_IMPL_SERVICE "com.johnholdsworth.InferImpl" 8 | -------------------------------------------------------------------------------- /InferRelay/InferRelay.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /InferRelay/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | UnusedRelay 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSHumanReadableCopyright 24 | Copyright © 2017 John Holdsworth. All rights reserved. 25 | XPCService 26 | 27 | ServiceType 28 | Application 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 John Holdsworth 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /LNProvider.t2d: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /LNProvider.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LNProvider.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LNProvider/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | @IBOutlet weak var window: NSWindow! 15 | @IBOutlet var defaults: DefaultManager! 16 | 17 | @IBOutlet var formatChecked: NSButton! 18 | @IBOutlet var gitDiffChecked: NSButton! 19 | @IBOutlet var gitBlameChecked: NSButton! 20 | @IBOutlet var inferChecked: NSButton! 21 | 22 | var services = [LNExtensionClient]() 23 | private var statusItem: NSStatusItem! 24 | 25 | lazy var buttonMap: [NSButton: String] = [ 26 | self.formatChecked: "com.johnholdsworth.FormatRelay", 27 | self.gitDiffChecked: "com.johnholdsworth.GitDiffRelay", 28 | self.gitBlameChecked: "com.johnholdsworth.GitBlameRelay", 29 | self.inferChecked: "com.johnholdsworth.InferRelay", 30 | ] 31 | 32 | func applicationDidFinishLaunching(_: Notification) { 33 | startServiceAndRegister(checkButton: formatChecked) 34 | startServiceAndRegister(checkButton: gitDiffChecked) 35 | startServiceAndRegister(checkButton: gitBlameChecked) 36 | startServiceAndRegister(checkButton: inferChecked) 37 | let statusBar = NSStatusBar.system 38 | statusItem = statusBar.statusItem(withLength: statusBar.thickness) 39 | statusItem.toolTip = "GitDiff Preferences" 40 | statusItem.highlightMode = true 41 | statusItem.target = self 42 | statusItem.action = #selector(show(sender:)) 43 | statusItem.isEnabled = true 44 | statusItem.title = "" 45 | setMenuIcon(tiffName: "icon_16x16") 46 | NSColorPanel.shared.showsAlpha = true 47 | window.appearance = NSAppearance(named: NSAppearance.Name.vibrantDark) 48 | } 49 | 50 | func setMenuIcon(tiffName: String) { 51 | if let path = Bundle.main.path(forResource: tiffName, ofType: "tiff"), 52 | let image = NSImage(contentsOfFile: path) { 53 | image.isTemplate = true 54 | statusItem.image = image 55 | statusItem.alternateImage = statusItem.image 56 | } 57 | } 58 | 59 | @IBAction func show(sender: Any) { 60 | window.makeKeyAndOrderFront(sender) 61 | NSApp.activate(ignoringOtherApps: true) 62 | } 63 | 64 | func startServiceAndRegister(checkButton: NSButton) { 65 | if checkButton.state == .on, let serviceName = buttonMap[checkButton] { 66 | services.append(LNExtensionClient(serviceName: serviceName, delegate: nil)) 67 | } 68 | } 69 | 70 | @IBAction func serviceDidChange(checkButton: NSButton) { 71 | if checkButton.state == .on { 72 | startServiceAndRegister(checkButton: checkButton) 73 | } else if let serviceName = buttonMap[checkButton] { 74 | services.first(where: { $0.serviceName == serviceName })?.deregister() 75 | services = services.filter { $0.serviceName != serviceName } 76 | } 77 | } 78 | 79 | func applicationWillTerminate(_: Notification) { 80 | _ = services.map { $0.deregister() } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /LNProvider/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon_16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon_32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon_32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon_128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon_128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon_256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon_256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon_512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon_512x512@2x.png", 61 | "scale" : "2x" 62 | }, 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /LNProvider/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/GitDiff/1089f1f4eadbb7297ffb598983402ac383ae3ddd/LNProvider/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /LNProvider/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/GitDiff/1089f1f4eadbb7297ffb598983402ac383ae3ddd/LNProvider/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /LNProvider/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/GitDiff/1089f1f4eadbb7297ffb598983402ac383ae3ddd/LNProvider/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /LNProvider/Assets.xcassets/AppIcon.appiconset/icon_16x16.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/GitDiff/1089f1f4eadbb7297ffb598983402ac383ae3ddd/LNProvider/Assets.xcassets/AppIcon.appiconset/icon_16x16.tiff -------------------------------------------------------------------------------- /LNProvider/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/GitDiff/1089f1f4eadbb7297ffb598983402ac383ae3ddd/LNProvider/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /LNProvider/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/GitDiff/1089f1f4eadbb7297ffb598983402ac383ae3ddd/LNProvider/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /LNProvider/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/GitDiff/1089f1f4eadbb7297ffb598983402ac383ae3ddd/LNProvider/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /LNProvider/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/GitDiff/1089f1f4eadbb7297ffb598983402ac383ae3ddd/LNProvider/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /LNProvider/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/GitDiff/1089f1f4eadbb7297ffb598983402ac383ae3ddd/LNProvider/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /LNProvider/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/GitDiff/1089f1f4eadbb7297ffb598983402ac383ae3ddd/LNProvider/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /LNProvider/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/GitDiff/1089f1f4eadbb7297ffb598983402ac383ae3ddd/LNProvider/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /LNProvider/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /LNProvider/DefaultManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultManager.swift 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 03/04/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | open class DefaultManager: NSObject { 12 | 13 | open var defaults = UserDefaults(suiteName: "LineNumber")! 14 | 15 | @IBOutlet weak var popoverColorWell: NSColorWell! 16 | @IBOutlet weak var deletedColorWell: NSColorWell! 17 | @IBOutlet weak var modifiedColorWell: NSColorWell! 18 | @IBOutlet weak var addedColorWell: NSColorWell! 19 | @IBOutlet weak var extraColorWell: NSColorWell! 20 | @IBOutlet weak var recentColorWell: NSColorWell! 21 | @IBOutlet weak var formatColorWell: NSColorWell! 22 | @IBOutlet weak var inferColorWell: NSColorWell! 23 | 24 | @IBOutlet weak var recentDaysField: NSTextField! 25 | @IBOutlet weak var formatIndentField: NSTextField! 26 | 27 | open var popoverKey: String { return "PopoverColor" } 28 | open var deletedKey: String { return "DeletedColor" } 29 | open var modifiedKey: String { return "ModifiedColor" } 30 | open var addedKey: String { return "AddedColor" } 31 | open var extraKey: String { return "ExtraColor" } 32 | open var recentKey: String { return "RecentColor" } 33 | open var formatKey: String { return "FormatColor" } 34 | open var inferKey: String { return "InferColor" } 35 | 36 | open var showHeadKey: String { return "ShowHead" } 37 | open var recentDaysKey: String { return "RecentDays" } 38 | open var formatIndentKey: String { return "FormatIndent" } 39 | 40 | open lazy var wellKeys: [NSColorWell: String] = [ 41 | self.popoverColorWell: self.popoverKey, 42 | self.deletedColorWell: self.deletedKey, 43 | self.modifiedColorWell: self.modifiedKey, 44 | self.addedColorWell: self.addedKey, 45 | self.extraColorWell: self.extraKey, 46 | self.recentColorWell: self.recentKey, 47 | self.formatColorWell: self.formatKey, 48 | self.inferColorWell: self.inferKey, 49 | ] 50 | 51 | open override func awakeFromNib() { 52 | for (colorWell, key) in wellKeys { 53 | setup(colorWell, key: key) 54 | } 55 | if let existing = defaults.value(forKey: recentDaysKey) { 56 | recentDaysField?.stringValue = existing as! String 57 | } else if recentDaysField != nil { 58 | defaults.set(recentDaysField.stringValue, forKey: recentDaysKey) 59 | } 60 | if let existing = defaults.value(forKey: formatIndentKey) { 61 | formatIndentField?.stringValue = existing as! String 62 | } else if formatIndentField != nil { 63 | defaults.set(formatIndentField.stringValue, forKey: formatIndentKey) 64 | } 65 | defaults.synchronize() 66 | } 67 | 68 | open func setup(_ colorWell: NSColorWell?, key: String) { 69 | if let existing = defaults.value(forKey: key) { 70 | colorWell?.color = NSColor(string: existing as! String) 71 | } else if colorWell != nil { 72 | defaults.set(colorWell!.color.stringRepresentation, forKey: key) 73 | } 74 | } 75 | 76 | @IBAction func colorChanged(sender: NSColorWell) { 77 | if let key = wellKeys[sender] { 78 | defaults.set(sender.color.stringRepresentation, forKey: key) 79 | } 80 | } 81 | 82 | @IBAction func reset(sender: NSButton) { 83 | for (_, key) in wellKeys { 84 | defaults.removeObject(forKey: key) 85 | } 86 | popoverColorWell?.color = popoverColor 87 | deletedColorWell?.color = deletedColor 88 | modifiedColorWell?.color = modifiedColor 89 | addedColorWell?.color = addedColor 90 | extraColorWell?.color = extraColor 91 | recentColorWell?.color = recentColor 92 | formatColorWell?.color = formatColor 93 | inferColorWell?.color = inferColor 94 | awakeFromNib() 95 | } 96 | 97 | open func defaultColor(for key: String, default value: String) -> NSColor { 98 | return NSColor(string: defaults.value(forKey: key) as? String ?? value) 99 | } 100 | 101 | open var popoverColor: NSColor { 102 | return defaultColor(for: popoverKey, default: "1 0.914 0.662 1") 103 | } 104 | 105 | open var deletedColor: NSColor { 106 | return defaultColor(for: deletedKey, default: "1 0.584 0.571 1") 107 | } 108 | 109 | open var modifiedColor: NSColor { 110 | return defaultColor(for: modifiedKey, default: "1 0.576 0 1") 111 | } 112 | 113 | open var addedColor: NSColor { 114 | return defaultColor(for: addedKey, default: "0.253 0.659 0.694 1") 115 | } 116 | 117 | open var extraColor: NSColor { 118 | return defaultColor(for: extraKey, default: "0.5 0 0 1") 119 | } 120 | 121 | open var recentColor: NSColor { 122 | return defaultColor(for: recentKey, default: "0.5 1.0 0.5 1") 123 | } 124 | 125 | open var formatColor: NSColor { 126 | return defaultColor(for: formatKey, default: "0.129 0.313 1 1") 127 | } 128 | 129 | open var inferColor: NSColor { 130 | return defaultColor(for: inferKey, default: "0.646293 0.919667 1 1") 131 | } 132 | 133 | open var showHead: Bool { 134 | return defaults.bool(forKey: showHeadKey) 135 | } 136 | 137 | @IBAction open func showHeadChanged(sender: NSButton) { 138 | defaults.set(sender.state == .on, forKey: showHeadKey) 139 | } 140 | 141 | @IBAction open func recentChanged(sender: NSTextField) { 142 | defaults.setValue(sender.stringValue, forKey: recentDaysKey) 143 | } 144 | 145 | @IBAction open func indentChanged(sender: NSTextField) { 146 | defaults.setValue(sender.stringValue, forKey: formatIndentKey) 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /LNProvider/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSApplicationCategoryType 24 | public.app-category.developer-tools 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2017 John Holdsworth. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | LSUIElement 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /LNProvider/LNProvider-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "LNExtensionClient.h" 6 | 7 | -------------------------------------------------------------------------------- /LNProvider/LNProvider.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LNProviderTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LNProviderTests/LNProviderTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "LNExtensionClient.h" 6 | 7 | #import "DiffMatchPatch.h" 8 | #import "DMDiff.h" 9 | -------------------------------------------------------------------------------- /LNProviderTests/LNProviderTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LNProviderTests.swift 3 | // LNProviderTests 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LNProvider 11 | 12 | class LNProviderTests: XCTestCase { 13 | 14 | var service: LNExtensionClient! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | // service = LNExtensionClient(serviceName: "com.johnholdsworth.GitBlameRelay", delegate: nil) 20 | } 21 | 22 | override func tearDown() { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | super.tearDown() 25 | } 26 | 27 | func testBlame() { 28 | if let service = service { 29 | // This is an example of a functional test case. 30 | // Use XCTAssert and related functions to verify your tests produce the correct results. 31 | service.requestHighlights(forFile: #file) 32 | RunLoop.main.run(until: Date(timeIntervalSinceNow: 2.0)) 33 | if let highlights = service[(#file)] { 34 | let element = highlights[(#line)] 35 | XCTAssertNotNil(element, "Got blame") 36 | NSLog("element: \(String(describing: element))") 37 | } else { 38 | XCTFail("No highlights") 39 | } 40 | } 41 | } 42 | 43 | func testSerializing() { 44 | let reference = LNFileHighlights() 45 | for i in stride(from: 1, to: 100, by: 10) { 46 | let element = LNHighlightElement() 47 | element.start = i 48 | element.color = NSColor(string: ".1 .3 .3 .4") 49 | element.text = "\(i)" 50 | element.range = "\(i) \(i + 1)" 51 | reference[i] = element 52 | for j in 1 ... 9 { 53 | reference[i + j] = element 54 | } 55 | } 56 | 57 | let highlights = LNFileHighlights(data: reference.jsonData(), service:"none")! 58 | XCTAssertTrue(highlights[1] == reference[1], "basic") 59 | XCTAssertTrue(highlights[1] != reference[11], "other") 60 | XCTAssertTrue(highlights[1] === highlights[2], "alias") 61 | } 62 | 63 | func testDiff() { 64 | let path = Bundle(for: type(of: self)).path(forResource: "example_diff", ofType: "txt") 65 | let sequence = FileGenerator(path: path!)!.lineSequence 66 | let highlights = DiffProcessor().generateHighlights(sequence: sequence, defaults: DefaultManager()) 67 | print(String(data: highlights.jsonData(), encoding: .utf8)!) 68 | highlights.foreachHighlightRange { 69 | (range, element) in 70 | print(range, element) 71 | } 72 | } 73 | 74 | func testFormat() { 75 | FormatImpl(connection: nil)?.requestHighlights(forFile: #file, callback: { 76 | json, _ in 77 | if let json = json { 78 | print(String(data: json, encoding: .utf8)!) 79 | } 80 | }) 81 | } 82 | 83 | func testAttributed() { 84 | let element = LNHighlightElement() 85 | let string = NSMutableAttributedString(string: "hello world") 86 | string.addAttributes([NSFontAttributeName: NSFont(name: "Arial", size: 10)!], range: NSMakeRange(0, 4)) 87 | element.setAttributedText(string) 88 | print("\(String(describing: element.attributedText()))") 89 | } 90 | 91 | func testPerformanceExample() { 92 | // This is an example of a performance test case. 93 | measure { 94 | // Put the code you want to measure the time of here. 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /LNProviderTests/example_diff.txt: -------------------------------------------------------------------------------- 1 | diff --git a/LNXcodeSupport/LNFileHighlights.mm b/LNXcodeSupport/LNFileHighlights.mm 2 | index 894020a..e75cc24 100644 3 | --- a/LNXcodeSupport/LNFileHighlights.mm 4 | +++ b/LNXcodeSupport/LNFileHighlights.mm 5 | @@ -7,6 +7,7 @@ 6 | // 7 | 8 | #import "LNFileHighlights.h" 9 | +#import "NSColor+NSString.h" 10 | #import 11 | 12 | // JSON wire format 13 | @@ -19,7 +20,7 @@ - (void)updadateFrom:(LNHighlightMap *)map { 14 | if (NSString *start = map[@"start"]) 15 | self.start = start.integerValue; 16 | if (NSString *color = map[@"color"]) 17 | - self.color = [NSColor colorWithCIColor:[CIColor colorWithString:color]]; 18 | + self.color = [NSColor colorWithString:color]; 19 | // hover text 20 | if (NSString *text = map[@"text"]) 21 | self.text = text; 22 | @@ -37,13 +38,21 @@ - (instancetype)copy { 23 | return copy; 24 | } 25 | 26 | +- (BOOL)isEqual:(LNHighlightElement *)object { 27 | + return 28 | + self.start == object.start && 29 | + [self.color isEqual:object.color] && 30 | + [self.text isEqualToString:object.text] && 31 | + [self.range isEqualToString:object.range]; 32 | +} 33 | + 34 | @end 35 | 36 | @implementation LNFileHighlights { 37 | std::map elemants; 38 | } 39 | 40 | -- (instancetype)initHighlights:(NSData *)data { 41 | +- (instancetype)initWithData:(NSData *)data { 42 | if ( (self = [super init]) ) { 43 | NSError *error; 44 | LNHighlightInfo *info = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; 45 | @@ -80,11 +89,11 @@ - (instancetype)initHighlights:(NSData *)data { 46 | return self; 47 | } 48 | 49 | -- (void)setObject:(LNHighlightElement *)obj atIndexedSubscript:(NSUInteger)line { 50 | +- (void)setObject:(LNHighlightElement *)obj atIndexedSubscript:(NSInteger)line { 51 | elemants[line] = obj; 52 | } 53 | 54 | -- (LNHighlightElement *)objectAtIndexedSubscript:(NSUInteger)line { 55 | +- (LNHighlightElement *)objectAtIndexedSubscript:(NSInteger)line { 56 | return elemants.find(line) != elemants.end() ? elemants[line] : nil; 57 | } 58 | 59 | @@ -95,25 +104,21 @@ - (void)foreachHighlight:(void (^)(NSInteger line, LNHighlightElement *element)) 60 | 61 | - (NSData *)jsonData { 62 | NSMutableDictionary *info = [NSMutableDictionary new]; 63 | - LNHighlightElement *last = nil; 64 | - NSUInteger lastLine = 0; 65 | - 66 | - for ( NSInteger line = 1, found = 0 ; found < elemants.size() ; found++ ) { 67 | - while (elemants.find(line) == elemants.end()) 68 | - line++; 69 | + __block LNHighlightElement *last = nil; 70 | + __block NSUInteger lastLine = 0; 71 | 72 | - if (elemants[line] == last) { 73 | + [self foreachHighlight:^(NSInteger line, LNHighlightElement * _Nonnull element) { 74 | + if (elemants[line] == last) 75 | info[@(line).stringValue] = @{@"alias": @(lastLine).stringValue}; 76 | - continue; 77 | + else { 78 | + lastLine = line; 79 | + last = elemants[line]; 80 | + info[@(line).stringValue] = @{@"start": @(last.start).stringValue, 81 | + @"color": last.color.stringRepresentation ?: NULL_COLOR, 82 | + @"text": last.text, 83 | + @"range": last.range}; 84 | } 85 | - 86 | - lastLine = line; 87 | - last = elemants[line]; 88 | - info[@(line).stringValue] = @{@"start": @(last.start).stringValue, 89 | - @"color": [CIColor colorWithCGColor:last.color.CGColor].stringRepresentation, 90 | - @"text": last.text, 91 | - @"range": last.range}; 92 | - } 93 | + }]; 94 | 95 | return [NSJSONSerialization dataWithJSONObject:info options:0 error:NULL]; 96 | } 97 | -------------------------------------------------------------------------------- /LNProviderUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LNProviderUITests/LNProviderUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LNProviderUITests.swift 3 | // LNProviderUITests 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class LNProviderUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /LNXcodeSupport/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | ${CURRENT_PROJECT_VERSION} 19 | CFBundleVersion 20 | ${CURRENT_PROJECT_VERSION} 21 | DVTPlugInCompatibilityUUIDs 22 | 23 | CA351AD8-3176-41CB-875C-42A05C7CDEC7 24 | DF11C142-1584-4A99-87AC-1925D5F5652A 25 | 89CB8A86-8683-4928-AF66-11A6CE26A829 26 | C3998872-68CC-42C2-847C-B44D96AB2691 27 | B395D63E-9166-4CD6-9287-6889D507AD6A 28 | 8CC8AD71-C38D-4C1A-A34B-DC78BCE76F57 29 | EE23884D-A5C0-4163-94CF-DBBF3A5ED8D6 30 | 8B6F3D98-F36D-4F64-870A-CC6BD1B0740D 31 | 426A087B-D3AA-431A-AFDF-F135EC00DE1C 32 | 26355AE5-C605-4A56-A79B-AD4207EA18DD 33 | 92CB09D8-3B74-4EF7-849C-99816039F0E7 34 | 35 | NSPrincipalClass 36 | LNXcodeSupport 37 | XC4Compatible 38 | 39 | XCGCReady 40 | 41 | XCPluginHasUI 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /LNXcodeSupport/KeyPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyPath.swift 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 09/06/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @objc public class KeyPath: NSObject { 12 | 13 | @objc public class func object(for keyPath: String, from: AnyObject) -> AnyObject? { 14 | var out = from 15 | for key in keyPath.components(separatedBy: ".") { 16 | for (name, value) in Mirror(reflecting: out).children { 17 | if name == key || name == key + ".storage" { 18 | let mirror = Mirror(reflecting: value) 19 | if name == "lineNumberLayers" { 20 | let dict = NSMutableDictionary() 21 | for (_, pair) in mirror.children { 22 | let children = Mirror(reflecting: pair).children 23 | let key = children.first!.value as! Int 24 | let value = children.dropFirst().first!.value 25 | dict[NSNumber(value: key)] = value 26 | } 27 | out = dict 28 | } else if name == "name" { 29 | out = value as! String as NSString 30 | } else if mirror.displayStyle == .optional, 31 | let value = mirror.children.first?.value { 32 | out = value as AnyObject 33 | } else { 34 | out = value as AnyObject 35 | } 36 | break 37 | } 38 | } 39 | } 40 | return out === from ? nil : out 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LNXcodeSupport/LNExtensionClient.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNExtensionClient.h 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import "LNExtensionProtocol.h" 10 | #import "LNFileHighlights.h" 11 | 12 | @protocol LNConnectionDelegate 13 | @end 14 | 15 | @interface LNExtensionClient : NSObject 16 | 17 | @property NSString *_Nonnull serviceName; 18 | @property id _Nonnull service; 19 | 20 | @property LNConfig config; 21 | @property NSMutableDictionary *_Nonnull highightsByFile; 22 | 23 | - (instancetype _Nonnull)initServiceName:(NSString *_Nonnull)serviceName 24 | delegate:(id _Nullable)delegate; 25 | - (LNFileHighlights *_Nullable)objectForKeyedSubscript:(NSString *_Nonnull)key; 26 | - (NSString *_Nonnull)serviceNameDO; 27 | - (void)deregister; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /LNXcodeSupport/LNExtensionClient.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNExtensionClient.m 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import "LNExtensionClient.h" 10 | 11 | @interface LNExtensionClient () 12 | 13 | @property NSXPCConnection *connection; 14 | @property NSConnection *connectionDO; 15 | @property id pluginDO; 16 | @property id registrationDO; 17 | @property __weak id delegate; 18 | 19 | @end 20 | 21 | @implementation LNExtensionClient 22 | 23 | - (instancetype)initServiceName:(NSString *)serviceName delegate:(id)delegate { 24 | if ((self = [super init])) { 25 | self.highightsByFile = [NSMutableDictionary new]; 26 | self.serviceName = serviceName; 27 | self.delegate = delegate; 28 | [self setup]; 29 | } 30 | return self; 31 | } 32 | 33 | - (LNFileHighlights *)objectForKeyedSubscript:(NSString *)key { 34 | @synchronized(self.highightsByFile) { 35 | return self.highightsByFile[key]; 36 | } 37 | } 38 | 39 | - (NSString *)serviceNameDO { 40 | return [self.serviceName stringByAppendingString:@".DO"]; 41 | } 42 | 43 | - (void)setup { 44 | self.connection = [[NSXPCConnection alloc] initWithServiceName:self.serviceName]; 45 | self.connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(LNExtensionService)]; 46 | self.service = self.connection.remoteObjectProxy; 47 | 48 | self.connection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(LNExtensionPlugin)]; 49 | self.connection.exportedObject = self; 50 | [self.connection resume]; 51 | 52 | self.connectionDO = [NSConnection new]; 53 | [self.connectionDO setRootObject:self]; 54 | [self.connectionDO registerName:self.serviceNameDO]; 55 | 56 | [self.service ping:1 callback:^(int test){ 57 | NSLog(@"Connected to %@ -> %d", self.serviceName, test); 58 | }]; 59 | 60 | [self regsiterWithXcode]; 61 | } 62 | 63 | - (void)regsiterWithXcode { 64 | @try { 65 | [self.registrationDO ping]; 66 | } 67 | @catch (NSException *e) { 68 | NSLog(@"Disconnected %@ %@", self.serviceName, e); 69 | self.registrationDO = nil; 70 | } 71 | 72 | if (!self.registrationDO) { 73 | self.registrationDO = 74 | (id)[NSConnection rootProxyForConnectionWithRegisteredName:XCODE_LINE_NUMBER_REGISTRATION host:nil]; 75 | [(id)self.registrationDO setProtocolForProxy:@protocol(LNRegistration)]; 76 | [(id)self.registrationDO registerLineNumberService:self.serviceName]; 77 | } 78 | 79 | [self performSelector:@selector(regsiterWithXcode) withObject:nil afterDelay:5.]; 80 | } 81 | 82 | - (void)getConfig:(LNConfigCallback)callback { 83 | NSLog(@"-[%@ getConfig: %p]", self, callback); 84 | } 85 | 86 | - (void)getConfig { 87 | [self.service getConfig:^(LNConfig config) { 88 | [self updateConfig:config forService:self.serviceName]; 89 | }]; 90 | } 91 | 92 | - (void)updateConfig:(LNConfig)config forService:(NSString *)serviceName { 93 | self.config = config; 94 | [self.pluginDO updateConfig:config forService:self.serviceName]; 95 | [self.delegate updateConfig:config forService:self.serviceName]; 96 | } 97 | 98 | - (void)_requestHighlightsForFile:(NSString *)filepath callback:(LNHighlightCallback)callback { 99 | [self.service requestHighlightsForFile:filepath callback:^(NSData *json, NSError *error) { 100 | [self updateHighlights:json error:error forFile:filepath]; 101 | if (callback) 102 | callback(json, error); 103 | }]; 104 | } 105 | 106 | - (void)requestHighlightsForFile:(NSString *)filepath callback:(LNHighlightCallback)callback { 107 | @try { 108 | [self _requestHighlightsForFile:filepath callback:callback]; 109 | } 110 | @catch (NSException *e) { 111 | @try { 112 | [self setup]; 113 | [self _requestHighlightsForFile:filepath callback:callback]; 114 | } 115 | @catch (NSException *e) { 116 | NSLog(@"-[LNExtensionClient requestHighlightsForFile: %@", e); 117 | } 118 | } 119 | } 120 | 121 | - (void)requestHighlightsForFile:(NSString *)filepath { 122 | [self requestHighlightsForFile:filepath callback:^(NSData *json, NSError *error) {}]; 123 | } 124 | 125 | - (void)updateHighlights:(NSData *)json error:(NSError *)error forFile:(NSString *)filepath { 126 | if (self.highightsByFile) 127 | @synchronized(self.highightsByFile) { 128 | self.highightsByFile[filepath] = [[LNFileHighlights alloc] initWithData:json service:self.serviceName]; 129 | } 130 | #if 0 131 | NSLog(@"%@: %@ %@ %@ %@ %@ %@", 132 | self.serviceName, filepath, self.pluginDO, error, self.highightsByFile, 133 | [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding], error); 134 | #endif 135 | [self.pluginDO updateHighlights:json error:error forFile:filepath]; 136 | [self.delegate updateHighlights:json error:error forFile:filepath]; 137 | } 138 | 139 | - (void)ping:(int)test callback:(void (^)(int))callback { 140 | NSLog(@"-[%@ ping:callback:]", self); 141 | } 142 | 143 | - (void)deregister { 144 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 145 | [self.registrationDO deregisterService:self.serviceName]; 146 | } 147 | 148 | @end 149 | -------------------------------------------------------------------------------- /LNXcodeSupport/LNExtensionClientDO.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNExtensionClientDO.h 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 01/04/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import "LNExtensionClient.h" 10 | 11 | @interface LNExtensionClientDO : LNExtensionClient 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /LNXcodeSupport/LNExtensionClientDO.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNExtensionClientDO.m 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 01/04/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import "LNExtensionClientDO.h" 10 | 11 | @implementation LNExtensionClientDO 12 | 13 | - (void)setup { 14 | self.service = (id)[NSConnection rootProxyForConnectionWithRegisteredName:self.serviceNameDO host:nil]; 15 | [(id)self.service setProtocolForProxy:@protocol(LNExtensionServiceDO)]; 16 | [self.service setPluginDO:self]; 17 | [self.service getConfig]; 18 | } 19 | 20 | - (void)requestHighlightsForFile:(NSString *)filepath { 21 | @try { 22 | if (!self.service) 23 | [self setup]; 24 | [self.service requestHighlightsForFile:filepath]; 25 | } 26 | @catch (NSException *e) { 27 | @try { 28 | [self setup]; 29 | [self.service requestHighlightsForFile:filepath]; 30 | } 31 | @catch (NSException *e) { 32 | NSLog(@"-[LNExtensionClientDO requestHighlightsForFile: %@]", e); 33 | for (LNFileHighlights *highlights in self.highightsByFile.allValues) 34 | [highlights invalidate]; 35 | } 36 | } 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /LNXcodeSupport/LNExtensionProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNExtensionService.h 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #ifndef LNExtensionService_h 10 | #define LNExtensionService_h 11 | 12 | #import 13 | 14 | typedef NSDictionary *_Nullable LNConfig; 15 | typedef void (^_Nonnull LNConfigCallback)(LNConfig config); 16 | 17 | #define LNPopoverColorKey @"LNPopoverColor" 18 | #define LNApplyTitleKey @"LNApplyTitle" 19 | #define LNApplyPromptKey @"LNApplyPrompt" 20 | #define LNApplyConfirmKey @"LNApplyConfirm" 21 | 22 | typedef void (^LNHighlightCallback)(NSData *_Nullable json, NSError *_Nullable error); 23 | 24 | @protocol LNExtensionService 25 | 26 | - (void)getConfig:(LNConfigCallback)callback; 27 | 28 | // closures work for XPC but not Distributed Objects 29 | - (void)requestHighlightsForFile:(NSString *_Nonnull)filepath 30 | callback:(LNHighlightCallback _Nonnull)callback 31 | NS_SWIFT_NAME(requestHighlights(forFile:callback:)); 32 | 33 | - (void)ping:(int)test callback:(void (^_Nonnull)(int test))callback; 34 | 35 | @end 36 | 37 | // DO communication back to Xcode plugin 38 | @protocol LNExtensionPlugin 39 | 40 | - (void)updateConfig:(LNConfig)config forService:(NSString *_Nonnull)serviceName; 41 | - (void)updateHighlights:(NSData *_Nullable)json error:(NSError *_Nullable)error forFile:(NSString *_Nonnull)filepath; 42 | 43 | @end 44 | 45 | @protocol LNExtensionServiceDO 46 | 47 | - (void)setPluginDO:(id _Nonnull)pluginDO; 48 | - (void)requestHighlightsForFile:(NSString *_Nonnull)filepath; 49 | - (void)getConfig; 50 | 51 | @end 52 | 53 | #define XCODE_LINE_NUMBER_REGISTRATION @"com.johnholdsworth.LineNumberPlugin" 54 | 55 | @protocol LNRegistration 56 | 57 | - (oneway void)registerLineNumberService:(NSString *_Nonnull)serviceName; 58 | - (oneway void)deregisterService:(NSString *_Nonnull)serviceName; 59 | - (oneway void)ping; 60 | 61 | @end 62 | 63 | #endif /* LNExtensionService_h */ 64 | -------------------------------------------------------------------------------- /LNXcodeSupport/LNFileHighlights.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNFileHighlights.h 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "NSColor+NSString.h" 11 | 12 | @interface LNHighlightElement : NSObject 13 | 14 | @property NSInteger start; 15 | @property NSColor *_Nonnull color; 16 | @property NSString *_Nullable text; 17 | @property NSString *_Nullable range; 18 | 19 | - (void)setAttributedText:(NSAttributedString *_Nonnull)text; 20 | - (NSAttributedString *_Nullable)attributedText; 21 | 22 | @end 23 | 24 | @interface LNFileHighlights : NSObject 25 | 26 | @property NSTimeInterval updated; 27 | 28 | - (instancetype _Nullable)initWithData:(NSData *_Nullable)json service:(NSString *_Nonnull)serviceName; 29 | 30 | - (LNHighlightElement *_Nullable)objectAtIndexedSubscript:(NSInteger)line; 31 | - (void)setObject:(LNHighlightElement *_Nullable)element atIndexedSubscript:(NSInteger)line; 32 | 33 | - (void)foreachHighlight:(void (^_Nonnull)(NSInteger line, LNHighlightElement *_Nonnull element))block; 34 | - (void)foreachHighlightRange:(void (^_Nonnull)(NSRange range, LNHighlightElement *_Nonnull element))block; 35 | 36 | - (NSData *_Nonnull)jsonData; 37 | - (void)invalidate; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /LNXcodeSupport/LNFileHighlights.mm: -------------------------------------------------------------------------------- 1 | // 2 | // LNFileHighlights.m 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import "LNFileHighlights.h" 10 | 11 | #import 12 | 13 | // JSON wire format 14 | typedef NSDictionary LNHighlightMap; 15 | typedef NSDictionary LNHighlightInfo; 16 | 17 | @implementation LNHighlightElement 18 | 19 | - (void)updadateFrom:(LNHighlightMap *)map { 20 | if (NSString *start = map[@"start"]) 21 | self.start = start.integerValue; 22 | if (NSString *color = map[@"color"]) 23 | self.color = [NSColor colorWithString:color]; 24 | // hover text 25 | if (NSString *text = map[@"text"]) 26 | self.text = text == (id)[NSNull null] ? nil : text; 27 | // undo range 28 | if (NSString *range = map[@"range"]) 29 | self.range = range == (id)[NSNull null] ? nil : range; 30 | } 31 | 32 | - (id)copyWithZone:(NSZone *)zone { 33 | LNHighlightElement *copy = [[self class] new]; 34 | copy.start = self.start; 35 | copy.color = self.color; 36 | copy.text = self.text; 37 | copy.range = self.range; 38 | return copy; 39 | } 40 | 41 | - (BOOL)isEqual:(LNHighlightElement *)object { 42 | return self.start == object.start && 43 | [self.color isEqual:object.color] && 44 | [self.text isEqualToString:object.text] && 45 | [self.range isEqualToString:object.range]; 46 | } 47 | 48 | // http://stackoverflow.com/questions/22620615/cocoa-how-to-save-nsattributedstring-to-json 49 | 50 | - (void)setAttributedText:(NSAttributedString *_Nonnull)text { 51 | NSMutableData *data = [NSMutableData new]; 52 | NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; 53 | archiver.outputFormat = NSPropertyListXMLFormat_v1_0; 54 | [archiver encodeObject:text forKey:NSKeyedArchiveRootObjectKey]; 55 | self.text = [[NSString alloc] initWithData:archiver.encodedData encoding:NSUTF8StringEncoding]; 56 | } 57 | 58 | - (NSAttributedString *_Nullable)attributedText { 59 | if (!self.text) 60 | return nil; 61 | if ([self.text hasPrefix:@"\n" 62 | ""]) 63 | return [NSKeyedUnarchiver unarchiveObjectWithData:[self.text dataUsingEncoding:NSUTF8StringEncoding]]; 64 | else 65 | return [[NSAttributedString alloc] initWithString:self.text]; 66 | } 67 | 68 | @end 69 | 70 | @implementation LNFileHighlights { 71 | std::map elements; 72 | } 73 | 74 | - (instancetype)initWithData:(NSData *)json service:(NSString *)serviceName { 75 | if ((self = [super init]) && json) { 76 | NSError *error; 77 | LNHighlightInfo *info = [NSJSONSerialization JSONObjectWithData:json options:0 error:&error]; 78 | if (error) 79 | NSLog(@"%@ -[LNFileHighlights initWithData: %@]", serviceName, error); 80 | 81 | for (NSString *line in [info.allKeys 82 | sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { 83 | return [obj1 intValue] < [obj2 intValue] ? NSOrderedAscending : NSOrderedDescending; 84 | }]) { 85 | LNHighlightMap *map = info[line]; 86 | LNHighlightElement *element; 87 | 88 | if (NSString *alias = map[@"alias"]) { 89 | LNHighlightElement *original = elements[alias.intValue]; 90 | if (map.count == 1) 91 | element = original; 92 | else { 93 | element = [original copy]; 94 | [element updadateFrom:map]; 95 | } 96 | } else { 97 | element = [LNHighlightElement new]; 98 | [element updadateFrom:map]; 99 | } 100 | 101 | elements[line.intValue] = element; 102 | } 103 | NSLog(@"Updated map: %ld elements", elements.size()); 104 | } 105 | 106 | self.updated = [NSDate timeIntervalSinceReferenceDate]; 107 | return self; 108 | } 109 | 110 | - (void)setObject:(LNHighlightElement *)element atIndexedSubscript:(NSInteger)line { 111 | elements[line] = element; 112 | } 113 | 114 | - (LNHighlightElement *)objectAtIndexedSubscript:(NSInteger)line { 115 | return elements.find(line) != elements.end() ? elements[line] : nil; 116 | } 117 | 118 | - (void)foreachHighlight:(void (^)(NSInteger line, LNHighlightElement *element))block { 119 | for (auto it = elements.begin(); it != elements.end(); ++it) 120 | block(it->first, it->second); 121 | } 122 | 123 | - (void)foreachHighlightRange:(void (^)(NSRange range, LNHighlightElement *element))block { 124 | __block LNHighlightElement *lastElement = nil; 125 | __block NSInteger lastLine = -1; 126 | 127 | auto callbackOnNewElement = ^(LNHighlightElement *element) { 128 | if (lastElement && element != lastElement) 129 | block(NSMakeRange(lastElement.start, lastLine - lastElement.start + 1), lastElement); 130 | }; 131 | 132 | for (auto it = elements.begin(); it != elements.end(); ++it) { 133 | callbackOnNewElement(it->second); 134 | lastElement = it->second; 135 | lastLine = it->first; 136 | } 137 | 138 | callbackOnNewElement(nil); 139 | } 140 | 141 | - (NSData *)jsonData { 142 | NSMutableDictionary *highlights = [NSMutableDictionary new]; 143 | __block LNHighlightElement *lastElement = nil; 144 | __block NSInteger lastLine = 0; 145 | 146 | for (auto it = elements.begin(); it != elements.end(); ++it) { 147 | NSInteger line = it->first; 148 | if (it->second == lastElement) 149 | highlights[@(line).stringValue] = @{ @"alias" : @(lastLine).stringValue }; 150 | else { 151 | lastLine = line; 152 | lastElement = it->second; 153 | highlights[@(line).stringValue] = @{ @"start" : @(lastElement.start).stringValue, 154 | @"color" : lastElement.color.stringRepresentation ?: NULL_COLOR_STRING, 155 | @"text" : lastElement.text ?: [NSNull null], 156 | @"range" : lastElement.range ?: [NSNull null] }; 157 | } 158 | }; 159 | 160 | return [NSJSONSerialization dataWithJSONObject:highlights options:0 error:NULL]; 161 | } 162 | 163 | - (void)invalidate { 164 | elements.clear(); 165 | } 166 | 167 | @end 168 | -------------------------------------------------------------------------------- /LNXcodeSupport/LNHighlightGutter.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNHighlightGutter.h 3 | // LNXcodeSupport 4 | // 5 | // Created by User on 08/06/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import "LNExtensionClient.h" 10 | 11 | #define LNFLECK_WIDTH 6. 12 | #define LNFLECK_VISIBLE 2. 13 | 14 | @interface LNHighlightGutter : NSView 15 | @end 16 | 17 | @interface LNHighlightFleck : NSView 18 | @property LNHighlightElement *element; 19 | @property LNExtensionClient *extension; 20 | @property CGFloat yoffset; 21 | + (LNHighlightFleck *)fleck; 22 | + (void)recycle:(NSArray *)used; 23 | @end 24 | -------------------------------------------------------------------------------- /LNXcodeSupport/LNHighlightGutter.m: -------------------------------------------------------------------------------- 1 | // 2 | // LNHighlightGutter.m 3 | // LNXcodeSupport 4 | // 5 | // Created by User on 08/06/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import "LNHighlightGutter.h" 10 | 11 | @implementation LNHighlightGutter 12 | 13 | @end 14 | 15 | @implementation LNHighlightFleck { 16 | NSTrackingArea *trackingArea; 17 | } 18 | 19 | static NSMutableArray *queue; 20 | 21 | + (LNHighlightFleck *)fleck { 22 | if (!queue) 23 | queue = [NSMutableArray new]; 24 | LNHighlightFleck *fleck = queue.lastObject; 25 | [queue removeLastObject]; 26 | return fleck ?: [[LNHighlightFleck alloc] initWithFrame:NSZeroRect]; 27 | } 28 | 29 | + (void)recycle:(NSArray *)used { 30 | [queue addObjectsFromArray:used]; 31 | } 32 | 33 | - (BOOL)isEqual:(LNHighlightFleck *)object { 34 | return ![object isKindOfClass:[self class]] ? [super isEqual:object] : 35 | self.element == object.element && NSEqualRects(self.frame, object.frame); 36 | } 37 | 38 | - (void)drawRect:(NSRect)dirtyRect { 39 | [super drawRect:dirtyRect]; 40 | 41 | // Drawing code here. 42 | dirtyRect.origin.x = NSWidth(dirtyRect) - LNFLECK_VISIBLE; 43 | dirtyRect.size.width = LNFLECK_VISIBLE; 44 | [self.element.color setFill]; 45 | NSRectFill(dirtyRect); 46 | } 47 | 48 | // https://stackoverflow.com/questions/11188034/mouseentered-and-mouseexited-not-called-in-nsimageview-subclass 49 | 50 | - (void)updateTrackingAreas { 51 | if (trackingArea != nil) 52 | [self removeTrackingArea:trackingArea]; 53 | 54 | int opts = (NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways); 55 | trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] 56 | options:opts 57 | owner:self 58 | userInfo:nil]; 59 | [self addTrackingArea:trackingArea]; 60 | } 61 | 62 | // mouseEntered: & mouseExited: implemented in category in LNXcodeSupport.mm 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /LNXcodeSupport/LNXcodeSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // LNXcodeSupport.h 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LNXcodeSupport : NSObject 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /LNXcodeSupport/LNXcodeSupport.mm: -------------------------------------------------------------------------------- 1 | // 2 | // LNXcodeSupport.m 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | // Repo: https://github.com/johnno1962/GitDiff 9 | // 10 | // $Id: //depot/GitDiff/LNXcodeSupport/LNXcodeSupport.mm#11 $ 11 | // 12 | 13 | #import "LNXcodeSupport.h" 14 | #import "LNXcodeSupport-Swift.h" 15 | #import "LNExtensionClientDO.h" 16 | #import "LNHighlightGutter.h" 17 | 18 | #import "XcodePrivate.h" 19 | #import 20 | 21 | #define REFRESH_INTERVAL 60. 22 | #define REVERT_DELAY 1.5 23 | 24 | static LNXcodeSupport *lineNumberPlugin; 25 | static NSString *lastSaved; 26 | 27 | @interface LNXcodeSupport () 28 | 29 | @property NSMutableArray *extensions; 30 | @property NSMutableDictionary *onupdate; 31 | 32 | @property Class sourceDocClass, scrollerClass; 33 | @property NSTextView *popover; 34 | @property NSButton *undoButton; 35 | 36 | @end 37 | 38 | @implementation LNXcodeSupport 39 | 40 | + (void)pluginDidLoad:(NSBundle *)pluginBundle { 41 | NSString *currentApplicationName = [NSBundle mainBundle].infoDictionary[@"CFBundleName"]; 42 | static dispatch_once_t onceToken; 43 | 44 | if ([currentApplicationName isEqualToString:@"Xcode"]) { 45 | dispatch_once(&onceToken, ^{ 46 | LNXcodeSupport *plugin = lineNumberPlugin = [[self alloc] init]; 47 | plugin.extensions = [NSMutableArray new]; 48 | plugin.onupdate = [NSMutableDictionary new]; 49 | 50 | #pragma clang diagnostic push 51 | #pragma clang diagnostic ignored "-Wundeclared-selector" 52 | [self swizzleClass:[NSDocument class] 53 | exchange:@selector(_finishSavingToURL:ofType:forSaveOperation:changeCount:) 54 | with:@selector(ln_finishSavingToURL:ofType:forSaveOperation:changeCount:)]; 55 | 56 | [self swizzleClass:objc_getClass("IDEEditorDocument") 57 | exchange:@selector(closeToRevert) 58 | with:@selector(ln_closeToRevert)]; 59 | 60 | [self swizzleClass:objc_getClass("DVTMarkedScroller") 61 | exchange:@selector(drawKnobSlotInRect:highlight:) 62 | with:@selector(ln_drawKnobSlotInRect:highlight:)]; 63 | #pragma clang diagnostic pop 64 | 65 | dispatch_async(dispatch_get_main_queue(), ^{ 66 | plugin.sourceDocClass = objc_getClass("IDEEditorDocument"); //IDESourceCodeDocument"); 67 | plugin.scrollerClass = objc_getClass("SourceEditorScrollView"); 68 | 69 | plugin.undoButton = [[NSButton alloc] initWithFrame:NSZeroRect]; 70 | plugin.undoButton.bordered = FALSE; 71 | 72 | NSBundle *pluginBundle = [NSBundle bundleForClass:self]; 73 | NSString *path = [pluginBundle pathForResource:@"undo" ofType:@"png"]; 74 | plugin.undoButton.image = [[NSImage alloc] initWithContentsOfFile:path]; 75 | plugin.undoButton.imageScaling = NSImageScaleProportionallyDown; 76 | 77 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ 78 | NSConnection *registrationDO = [[NSConnection alloc] init]; 79 | [registrationDO setRootObject:plugin]; 80 | [registrationDO registerName:XCODE_LINE_NUMBER_REGISTRATION]; 81 | 82 | NSURL *appURL = [pluginBundle URLForResource:@"LNProvider" withExtension:@"app"]; 83 | [[NSWorkspace sharedWorkspace] openURL:appURL]; 84 | 85 | [[NSRunLoop currentRunLoop] run]; 86 | }); 87 | }); 88 | }); 89 | } 90 | } 91 | 92 | + (void)swizzleClass:(Class)aClass exchange:(SEL)origMethod with:(SEL)altMethod { 93 | method_exchangeImplementations(class_getInstanceMethod(aClass, origMethod), 94 | class_getInstanceMethod(aClass, altMethod)); 95 | } 96 | 97 | - (oneway void)registerLineNumberService:(NSString *)serviceName { 98 | [self deregisterService:serviceName]; 99 | NSLog(@"Registering %@ ...", serviceName); 100 | [self.extensions addObject:[[LNExtensionClientDO alloc] initServiceName:serviceName delegate:self]]; 101 | } 102 | 103 | - (oneway void)ping { 104 | } 105 | 106 | - (oneway void)deregisterService:(NSString *_Nonnull)serviceName { 107 | for (LNExtensionClient *extension in self.extensions) { 108 | if ([extension.serviceName isEqualToString:serviceName]) { 109 | [self.extensions removeObject:extension]; 110 | break; 111 | } 112 | } 113 | } 114 | 115 | - (void)updateHighlights:(NSData *)json error:(NSError *)error forFile:(NSString *)filepath { 116 | dispatch_async(dispatch_get_main_queue(), ^{ 117 | if (error) 118 | [[NSAlert alertWithError:error] runModal]; 119 | else if (auto update = self.onupdate[filepath]) 120 | update(); 121 | }); 122 | } 123 | 124 | - (void)updateGutter:(NSString *)filepath { 125 | self.onupdate[filepath](); 126 | } 127 | 128 | - (void)updateLinenumberHighlightsForFile:(NSString *)filepath { 129 | for (LNExtensionClient *extension in self.extensions) 130 | [extension requestHighlightsForFile:filepath]; 131 | } 132 | 133 | - (void)updateConfig:(LNConfig)config forService:(NSString *_Nonnull)serviceName { 134 | NSLog(@"%@ updateConfig: %@", serviceName, config); 135 | } 136 | 137 | - (void)initialOrOccaisionalLineNumberUpdate:(NSString *)filepath { 138 | // update if not already in memory or 60 seconds has passed 139 | NSTimeInterval stale = [NSDate timeIntervalSinceReferenceDate] - REFRESH_INTERVAL; 140 | for (LNExtensionClient *extension in self.extensions) { 141 | if (filepath && extension[filepath].updated < stale) { 142 | if (!extension[filepath]) 143 | extension.highightsByFile[filepath] = [[LNFileHighlights alloc] initWithData:nil 144 | service:extension.serviceName]; 145 | [extension requestHighlightsForFile:filepath]; 146 | } 147 | } 148 | } 149 | 150 | @end 151 | 152 | @implementation NSDocument (IDESourceCodeDocument) 153 | 154 | - (void)forceLineNumberUpdate { 155 | if ([self isKindOfClass:lineNumberPlugin.sourceDocClass]) 156 | [lineNumberPlugin updateLinenumberHighlightsForFile:lastSaved = [[self fileURL] path]]; 157 | } 158 | 159 | // source file is being saved 160 | - (void)ln_finishSavingToURL:(id)a0 ofType:(id)a1 forSaveOperation:(NSUInteger)a2 changeCount:(id)a3 { 161 | [self ln_finishSavingToURL:a0 ofType:a1 forSaveOperation:a2 changeCount:a3]; 162 | [self forceLineNumberUpdate]; 163 | } 164 | 165 | // revert on change on disk 166 | - (void)ln_closeToRevert { 167 | [self ln_closeToRevert]; 168 | [self forceLineNumberUpdate]; 169 | } 170 | 171 | @end 172 | 173 | @implementation NSString (LineNumber) 174 | 175 | // https://stackoverflow.com/questions/1085524/how-to-count-the-number-of-lines-in-an-objective-c-string-nsstring 176 | 177 | - (NSUInteger)numberOfLines { 178 | NSUInteger numberOfLines, index, stringLength = [self length]; 179 | 180 | for (index = 0, numberOfLines = 0; index < stringLength; numberOfLines++) 181 | index = NSMaxRange([self lineRangeForRange:NSMakeRange(index, 0)]); 182 | 183 | return numberOfLines; 184 | } 185 | 186 | - (NSUInteger)indexForLine:(NSUInteger)lineNumber { 187 | NSUInteger numberOfLines, index, stringLength = [self length]; 188 | 189 | for (index = 0, numberOfLines = 0; index < stringLength && numberOfLines < lineNumber; numberOfLines++) 190 | index = NSMaxRange([self lineRangeForRange:NSMakeRange(index, 0)]); 191 | 192 | return index; 193 | } 194 | 195 | @end 196 | 197 | @implementation NSScroller (LineNumber) 198 | 199 | - (NSString *)editedDocPath { 200 | return lastSaved ?: @""; 201 | id name = [KeyPath objectFor:@"hostingEditor.dataSource.name" from:self.superview.superview]; 202 | return [name isKindOfClass:[NSString class]] ? name : @""; 203 | } 204 | 205 | // scroll bar overview 206 | - (void)ln_drawKnobSlotInRect:(CGRect)rect highlight:(BOOL)highlight { 207 | [self ln_drawKnobSlotInRect:rect highlight:highlight]; 208 | if (![self.superview isKindOfClass:lineNumberPlugin.scrollerClass]) 209 | return; 210 | 211 | NSString *filepath = [self editedDocPath]; 212 | [lineNumberPlugin initialOrOccaisionalLineNumberUpdate:filepath]; 213 | NSLog(@"ln_drawKnobSlotInRect: %@ %@", self, filepath); 214 | 215 | __weak NSScroller *weakSelf = self; 216 | lineNumberPlugin.onupdate[filepath] = ^{ 217 | [weakSelf updateLineNumberFlecksFor:filepath]; 218 | [weakSelf updateScrollbarMarkersFor:filepath in:rect]; 219 | }; 220 | 221 | [NSObject cancelPreviousPerformRequestsWithTarget:lineNumberPlugin]; 222 | [lineNumberPlugin performSelector:@selector(updateGutter:) withObject:filepath afterDelay:.1]; 223 | } 224 | 225 | static CGFloat gutterWidth; 226 | 227 | - (void)updateLineNumberFlecksFor:(NSString *)filepath { 228 | NSView *floatingContainer = self.superview.subviews[1]; 229 | NSArray *floating = floatingContainer.subviews; 230 | LNHighlightGutter *highlightGutter = floating.lastObject; 231 | SourceEditorGutterMarginContentView *lineNumberGutter; 232 | 233 | if (![highlightGutter isKindOfClass:[LNHighlightGutter class]]) { 234 | lineNumberGutter = floating.lastObject; 235 | highlightGutter = [[LNHighlightGutter alloc] initWithFrame:NSZeroRect]; 236 | [floatingContainer addSubview:highlightGutter]; 237 | } else 238 | lineNumberGutter = floating.firstObject; 239 | 240 | NSLog(@"updateLineNumberFlecksFor: %@ %@ %@ %@", filepath, highlightGutter, 241 | NSStringFromRect(highlightGutter.frame), lineNumberGutter); 242 | 243 | static Class gutterContentClasss; 244 | if (!gutterContentClasss) 245 | gutterContentClasss = objc_getClass("SourceEditor.SourceEditorGutterMarginContentView"); 246 | if (![lineNumberGutter isKindOfClass:gutterContentClasss] && 247 | !(floating.count > 1 && 248 | [lineNumberGutter = floating[1] isKindOfClass:gutterContentClasss])) 249 | return; 250 | 251 | NSRect rect = lineNumberGutter.frame; 252 | gutterWidth = rect.size.width; 253 | 254 | rect.origin.y = 0.; 255 | rect.origin.x += gutterWidth - 3.; 256 | rect.size.width = 8.; 257 | rect.size.height += 5000.; 258 | if (!NSEqualRects(highlightGutter.frame, rect)) 259 | highlightGutter.frame = rect; 260 | 261 | NSDictionary *lineNumberLayers = [KeyPath objectFor:@"lineNumberLayers" from:lineNumberGutter]; 262 | NSMutableArray *next = [NSMutableArray new]; 263 | SourceEditorContentView *sourceTextView = self.superview.subviews[0].subviews[0]; 264 | CGFloat lineHeight = [sourceTextView defaultLineHeight]; 265 | 266 | for (NSNumber *line in lineNumberLayers) { 267 | SourceEditorFontSmoothingTextLayer *layer = lineNumberLayers[line]; 268 | NSRect rect = layer.frame; 269 | rect.size.width = LNFLECK_WIDTH; 270 | rect.size.height = lineHeight; 271 | rect.origin.x = NSWidth(highlightGutter.frame) - NSWidth(rect); 272 | rect.origin.y = NSHeight(highlightGutter.frame) - 273 | lineNumberGutter.frame.origin.y - rect.origin.y - lineHeight + 4.; 274 | for (LNExtensionClient *extension in lineNumberPlugin.extensions.reverseObjectEnumerator) { 275 | if (LNFileHighlights *diffs = extension[filepath]) { 276 | if (LNHighlightElement *element = diffs[line.integerValue + 1]) { 277 | LNHighlightFleck *fleck = [LNHighlightFleck fleck]; 278 | fleck.frame = rect; 279 | fleck.element = element; 280 | fleck.extension = extension; 281 | fleck.yoffset = [lineNumberLayers[@(element.start - 1)] frame].origin.y; 282 | [next addObject:fleck]; 283 | rect.origin.x -= LNFLECK_VISIBLE; 284 | } 285 | } 286 | } 287 | } 288 | 289 | #define NSOrderedCompare(a, b) ab ? NSOrderedDescending : NSOrderedSame 290 | [next sortUsingComparator:^NSComparisonResult(LNHighlightFleck *obj1, LNHighlightFleck *obj2) { 291 | return NSOrderedCompare(obj1.yoffset, obj2.yoffset); 292 | }]; 293 | 294 | if (![[highlightGutter subviews] isEqualToArray:next]) { 295 | [[[highlightGutter subviews] copy] makeObjectsPerformSelector:@selector(removeFromSuperview)]; 296 | for (LNHighlightFleck *fleck in next) 297 | [highlightGutter addSubview:fleck]; 298 | } else 299 | [LNHighlightFleck recycle:next]; 300 | } 301 | 302 | - (void)updateScrollbarMarkersFor:(NSString *)filepath in:(NSRect)rect { 303 | static NSMutableDictionary *lineCountCache; 304 | if (!lineCountCache) 305 | lineCountCache = [NSMutableDictionary new]; 306 | 307 | SourceEditorContentView *sourceTextView = self.superview.subviews[0].subviews[0]; 308 | NSInteger lines = lineCountCache[filepath].intValue; 309 | if (!lines) 310 | lineCountCache[filepath] = @(lines = [sourceTextView.accessibilityValue numberOfLines] ?: 1); 311 | 312 | CGFloat lineHeight = [sourceTextView defaultLineHeight]; 313 | CGFloat scale = lines * lineHeight < NSHeight(self.frame) ? lineHeight : NSHeight(self.frame) / lines; 314 | NSMutableArray *marks = [NSMutableArray new], *markRects = [NSMutableArray new]; 315 | 316 | static Class markerListClass9_2; 317 | if (!markerListClass9_2 && (markerListClass9_2 = objc_getClass("_DVTMarkerList"))) { 318 | markerListClass9_2 = objc_allocateClassPair(markerListClass9_2, "ln_DVTMarkerList", 0); 319 | class_addMethod(markerListClass9_2, @selector(_recomputeMarkRects), 320 | imp_implementationWithBlock(^{}), "v16@0:8"); 321 | objc_registerClassPair(markerListClass9_2); 322 | } 323 | 324 | if (!markerListClass9_2) 325 | [self clearDiffMarks]; 326 | 327 | for (LNExtensionClient *extension in lineNumberPlugin.extensions) { 328 | if (LNFileHighlights *diffs = extension[filepath]) { 329 | [diffs foreachHighlightRange:^(NSRange range, LNHighlightElement *element) { 330 | if (markerListClass9_2) { 331 | NSRect rect = NSMakeRect(4., (range.location - 1) * scale, 2., MAX(range.length * scale, 2.)); 332 | [marks addObject:@((range.location - 1.) / lines)]; 333 | [markRects addObject:[NSValue valueWithRect:rect]]; 334 | } else 335 | [self addMark:(range.location - 1.) / lines onLine:range.location ofType:2]; 336 | }]; 337 | } 338 | } 339 | 340 | if (markerListClass9_2) { 341 | _DVTMarkerList *markers = [[markerListClass9_2 alloc] initWithSlotRect:rect]; 342 | [markers setValue:marks forKey:@"_marks"]; 343 | [markers setValue:markRects forKey:@"_markRects"]; 344 | [self setValue:markers forKey:@"_diffMarks"]; 345 | } 346 | } 347 | 348 | @end 349 | 350 | @implementation LNHighlightFleck (LNXcodeSupport) 351 | 352 | - (SourceEditorContentView *)editorContentView { 353 | return self.superview.superview.superview.subviews[0].subviews[0]; 354 | } 355 | 356 | - (void)mouseEntered:(NSEvent *)theEvent { 357 | if (!self.element.text) 358 | return; 359 | NSLog(@"mouseEntered: %@", self); 360 | // NSUInteger start = self.element.start; 361 | NSMutableAttributedString *attString = [[self.element attributedText] mutableCopy]; 362 | 363 | // https://panupan.com/2012/06/04/trim-leading-and-trailing-whitespaces-from-nsmutableattributedstring/ 364 | 365 | // Trim trailing whitespace and newlines. 366 | NSCharacterSet *charSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; 367 | NSRange range = [attString.string rangeOfCharacterFromSet:charSet 368 | options:NSBackwardsSearch]; 369 | while (range.length != 0 && NSMaxRange(range) == attString.length) { 370 | [attString replaceCharactersInRange:range 371 | withString:@""]; 372 | range = [attString.string rangeOfCharacterFromSet:charSet 373 | options:NSBackwardsSearch]; 374 | } 375 | 376 | SourceEditorContentView *sourceTextView = [self editorContentView]; 377 | CGFloat lineHeight = [sourceTextView defaultLineHeight]; 378 | 379 | NSMutableParagraphStyle *myStyle = [NSMutableParagraphStyle new]; 380 | [myStyle setMinimumLineHeight:lineHeight]; 381 | [attString setAttributes:@{NSParagraphStyleAttributeName : myStyle} 382 | range:NSMakeRange(0, attString.length)]; 383 | 384 | [lineNumberPlugin.popover removeFromSuperview]; 385 | NSTextView *popover = 386 | lineNumberPlugin.popover = [[NSTextView alloc] initWithFrame:NSZeroRect]; 387 | 388 | [[popover textStorage] setAttributedString:attString]; 389 | popover.font = [KeyPath objectFor:@"layoutManager.fontTheme.plainTextFont" from:sourceTextView]; 390 | 391 | CGFloat width = NSWidth(sourceTextView.frame); 392 | CGFloat height = lineHeight * [popover.string numberOfLines]; 393 | 394 | popover.frame = NSMakeRect(gutterWidth + 5., self.yoffset - 4., width, height); 395 | 396 | NSLog(@"%@ %f %f - %@ %@", NSStringFromRect(popover.frame), lineHeight, height, self.element.range, sourceTextView); 397 | 398 | NSString *popoverColor = self.extension.config[LNPopoverColorKey] ?: @"1 0.914 0.662 1"; 399 | popover.backgroundColor = [NSColor colorWithString:popoverColor]; 400 | [sourceTextView addSubview:popover]; 401 | 402 | if (self.element.range) 403 | [self performSelector:@selector(showUndoButton) withObject:nil afterDelay:REVERT_DELAY]; 404 | } 405 | 406 | - (void)mouseExited:(NSEvent *)theEvent { 407 | [lineNumberPlugin.popover removeFromSuperview]; 408 | [lineNumberPlugin.undoButton removeFromSuperview]; 409 | } 410 | 411 | - (void)showUndoButton { 412 | if (lineNumberPlugin.popover.superview) { 413 | NSButton *undoButton = lineNumberPlugin.undoButton; 414 | undoButton.action = @selector(performUndo:); 415 | undoButton.target = self; 416 | 417 | CGFloat width = NSWidth(self.superview.frame); 418 | undoButton.frame = NSMakeRect(0, self.frame.origin.y + width, width, width); 419 | [self.superview addSubview:undoButton]; 420 | } 421 | } 422 | 423 | - (void)performUndo:(NSButton *)sender { 424 | SourceEditorContentView *sourceTextView = [self editorContentView]; 425 | NSRange lineRange, charRange; 426 | 427 | #pragma clang diagnostic push 428 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 429 | LNConfig config = self.extension.config; 430 | if (sscanf(self.element.range.UTF8String ?: "", "%ld %ld", &lineRange.location, &lineRange.length) != 2 || 431 | [[NSAlert alertWithMessageText:config[LNApplyTitleKey] ?: @"Line Number Plugin:" 432 | defaultButton:config[LNApplyConfirmKey] ?: @"Modify" 433 | alternateButton:@"Cancel" 434 | otherButton:nil 435 | informativeTextWithFormat:config[LNApplyPromptKey] ?: @"Apply suggested changes at line %d-%d?", 436 | (int)lineRange.location, (int)(lineRange.location + MAX(lineRange.length, 1) - 1)] 437 | runModal] == NSAlertAlternateReturn) 438 | return; 439 | #pragma clang diagnostic pop 440 | 441 | lineRange.location--; 442 | NSString *buffer = sourceTextView.accessibilityValue; 443 | charRange.location = [buffer indexForLine:lineRange.location]; 444 | charRange.length = [buffer indexForLine:lineRange.location + lineRange.length] - charRange.location; 445 | NSLog(@"performUndo: %@ %@", sourceTextView, [buffer substringWithRange:charRange]); 446 | 447 | [sourceTextView setAccessibilitySelectedTextRange:charRange]; 448 | [sourceTextView setAccessibilitySelectedText:self.element.attributedText.string]; 449 | } 450 | 451 | @end 452 | -------------------------------------------------------------------------------- /LNXcodeSupport/NSColor+NSString.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSColor+NSString.h 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #define NULL_COLOR_STRING @"0 0 0 1" 12 | 13 | @interface NSColor (NSString) 14 | 15 | + (NSColor *_Nonnull)colorWithString:(NSString *_Nonnull)string; 16 | 17 | @property(readonly) NSString *_Nonnull stringRepresentation; 18 | @property(readonly) NSColor *_Nonnull stripAlpha; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /LNXcodeSupport/NSColor+NSString.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSColor+NSString.m 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | #import "NSColor+NSString.h" 10 | 11 | static NSMutableDictionary *cache; 12 | 13 | @implementation NSColor (NSString) 14 | 15 | + (NSColor *)colorWithString:(NSString *)string { 16 | if (!string) 17 | string = NULL_COLOR_STRING; 18 | 19 | if (!cache) 20 | cache = [NSMutableDictionary new]; 21 | else { 22 | NSColor *existing = cache[string]; 23 | if (existing) 24 | return existing; 25 | } 26 | 27 | return cache[string] = [NSColor colorWithCIColor:[CIColor colorWithString:string]]; 28 | } 29 | 30 | - (NSString *)stringRepresentation { 31 | return [CIColor colorWithCGColor:self.CGColor].stringRepresentation; 32 | } 33 | 34 | - (NSColor *)stripAlpha { 35 | const CGFloat *components = CGColorGetComponents(self.CGColor); 36 | return components ? [NSColor colorWithRed:components[0] 37 | green:components[1] 38 | blue:components[2] 39 | alpha:1.] 40 | : [NSColor redColor]; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /LNXcodeSupport/XcodePrivate.h: -------------------------------------------------------------------------------- 1 | // 2 | // XcodePrivate.h 3 | // GitDiff 4 | // 5 | //  Created by Christoffer Winterkvist on 30/10/14. 6 | // Copyright (c) 2014 zenangst. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DVTTextDocumentLocation : NSObject 12 | @property(readonly) NSRange characterRange; 13 | @property(readonly) NSRange lineRange; 14 | - (id)initWithDocumentURL:(id)a0 timestamp:(id)a1 lineRange:(NSRange)a2; 15 | @end 16 | 17 | @interface IDESourceCodeComparisonEditor : NSObject 18 | @property(readonly) NSTextView *keyTextView; 19 | @end 20 | 21 | @interface IDESourceCodeEditor : NSObject 22 | 23 | @property(retain) NSDocument *document; // T@"IDEEditorDocument",&,V_document 24 | @property(retain) NSTextView *textView; 25 | 26 | - (long)_currentOneBasedLineNubmer; 27 | - (long)_currentOneBasedLineNumber; 28 | - (id)_documentLocationForLineNumber:(long)a0; 29 | - (void)selectDocumentLocations:(id)a0 highlightSelection:(BOOL)a1; 30 | - (void)selectAndHighlightDocumentLocations:(id)a1; 31 | - (void)selectDocumentLocations:(id)a1; 32 | @end 33 | 34 | @interface IDEEditorContext : NSObject 35 | - (id)editor; 36 | @end 37 | 38 | @interface IDEEditorArea : NSObject 39 | - (id)lastActiveEditorContext; 40 | @end 41 | 42 | @interface IDEWorkspaceWindowController : NSWindowController 43 | - (id)editorArea; 44 | @end 45 | 46 | @interface NSRulerView (DVTTextSidebarView) 47 | - (void)getParagraphRect:(CGRect *)a0 firstLineRect:(CGRect *)a1 forLineNumber:(NSUInteger)a2; 48 | - (NSUInteger)lineNumberForPoint:(CGPoint)a0; 49 | - (double)sidebarWidth; 50 | @end 51 | 52 | @interface _DVTMarkerList : NSObject 53 | - (void)setMarkRect:(CGRect)a0; 54 | - (CGRect)_rectForMark:(double)a0; 55 | - (void)_mergeMarkRect:(CGRect)a0; 56 | - (void)_recomputeMarkRects; 57 | - (id)initWithSlotRect:(CGRect)a0; 58 | - (CGRect)markRect; 59 | - (void)clearMarks; 60 | - (CGRect)addMark:(double)a0; 61 | - (unsigned long)numMarkRects; 62 | - (id)markRectList; 63 | @end 64 | 65 | // Xcode 9 Swift classes 66 | 67 | @interface SourceCodeEditor : NSViewController 68 | - (NSFont *)lineNumberFont; 69 | - (NSDocument *)document; 70 | @end 71 | 72 | @interface SourceCodeEditorContainerView : NSView 73 | - (SourceCodeEditor *)editor; 74 | @end 75 | 76 | @interface SourceEditorContentView : NSTextView 77 | - (CGFloat)defaultLineHeight; 78 | - (NSUInteger)numberOfLines; 79 | - (CGRect)layoutBounds; 80 | @end 81 | 82 | @interface SourceEditorFontSmoothingTextLayer : CALayer 83 | @end 84 | 85 | @interface SourceEditorGutterMarginContentView : NSView 86 | - (NSDictionary *)lineNumberLayers; 87 | @end 88 | 89 | @interface NSScroller(DVTMarkedScroller) 90 | - (void)clearDiffMarks ; 91 | - (void)addMark:(double)a0 onLine:(long)a1 ofType:(unsigned long)a2 ; 92 | @end 93 | -------------------------------------------------------------------------------- /LNXcodeSupport/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/GitDiff/1089f1f4eadbb7297ffb598983402ac383ae3ddd/LNXcodeSupport/undo.png -------------------------------------------------------------------------------- /LineNumberPlugin.pages: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/GitDiff/1089f1f4eadbb7297ffb598983402ac383ae3ddd/LineNumberPlugin.pages -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # GitDiff9 - GitDiff for Xcode 9 3 | 4 | As this plugin has rather been "Sherlocked" it is no longer supported and only just works in Xcode 11. You need to save the file before the highlights are visible. 5 | 6 | A port of the "GitDiff" Xcode plugin to the Xcode 9 beta now that the Source editor has been implemented in Swift. It uses an extensible framework of generalised providers of line number gutter highlights with which it communicates using JSON IPC. This version of GitDiff includes the implementations for four types of line number highlighters: 7 | 8 | * Unstaged differences against a project's git repo 9 | * Highlight of changes committed in the last week 10 | * Format linting hints provided by swiftformat and clang-format 11 | * A viewer that makes explicit inferred types in declarations. 12 | 13 | To use, clone this project and build target "LNXcodeSupport". You'll need to [unsign your Xcode binary](https://github.com/fpg1503/MakeXcodeGr8Again) for the Xcode side of the plugin to load. The user interface is largely as it was before. 14 | 15 | ![Icon](http://johnholdsworth.com/gitdiff9.png) 16 | 17 | Lines that have been changed relative to the repo are highlighted in amber and new lines highlighted in blue. Code lint suggestions are highlighted in dark blue and lines with a recent commit to the repo (the last 7 days by default) are highlighted in light green, fading with time. 18 | 19 | Hovering over a change or lint highlight will overlay the previous or suggested version over the source editor and if you would like to revert the code change or apply the lint suggestion, continue hovering over the highlight until a very small button appears and click on it. The plugin runs a menubar app that contains colour preferences and allows you to turn on and off individual highlights. 20 | 21 | ![Icon](http://johnholdsworth.com/lnprovider9a.png) 22 | 23 | ### Expandability 24 | 25 | The new implementation has been generalised to provide line number highlighting as a service from inside a new Legacy Xcode plugin. The project includes an menubar app "LNProvider" which is run to provide the default implementations using XPC. Any application can register with the plugin to provide line number highlights if it follows the Distributed Objects messaging protocol documented in "LNExtensionProtocol.h". Whenever a file is saved or reloaded, a call is made from the plugin to your application to provide JSON describing the intended highlights, their colours and any associated text. See the document "LineNumberPlugin.pages" for details about the architecture. 26 | 27 | ### Code linting 28 | 29 | This repo includes binary releases of [swiftformat](https://github.com/nicklockwood/SwiftFormat) and [clang-format](https://clang.llvm.org/docs/ClangFormatStyleOptions.html) under their respective licenses. To modify code linting preferences, edit the files swift_format.sh and clang_format.sh in the "FormatImpl" directory and rebuild the plugin. 30 | -------------------------------------------------------------------------------- /SharedXPC/LNExtensionBase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LNExtensionBase.swift 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | open class LNExtensionBase: NSObject { 12 | 13 | var owner: LNExtensionPlugin! 14 | 15 | @objc open func getConfig(_ callback: @escaping LNConfigCallback) { 16 | callback(["config": "here"]) 17 | } 18 | 19 | @objc open func ping(_ test: Int32, callback: @escaping (Int32) -> Void) { 20 | callback(test + 1000) 21 | } 22 | 23 | open func error(description: String) -> NSError { 24 | return NSError(domain: "Line Number Extension", code: -1000, userInfo: [ 25 | NSLocalizedDescriptionKey: description, 26 | ]) 27 | } 28 | 29 | public required init?(connection: NSXPCConnection?) { 30 | if connection != nil { 31 | connection!.remoteObjectInterface = NSXPCInterface(with: LNExtensionPlugin.self) 32 | owner = connection!.remoteObjectProxy as! LNExtensionPlugin 33 | } 34 | 35 | super.init() 36 | 37 | connection?.exportedInterface = NSXPCInterface(with: LNExtensionService.self) 38 | connection?.exportedObject = self 39 | connection?.resume() 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /SharedXPC/LNExtensionRelay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LNExtensionRelay.swift 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let implementationFactory = LNExtensionRelay.self 12 | 13 | open class LNExtensionRelay: LNExtensionBase, LNExtensionService, LNExtensionPlugin { 14 | 15 | private let implXPCService: NSXPCConnection = { 16 | let connection = NSXPCConnection(serviceName: EXTENSION_IMPL_SERVICE) 17 | connection.remoteObjectInterface = NSXPCInterface(with: LNExtensionService.self) 18 | connection.exportedInterface = NSXPCInterface(with: LNExtensionPlugin.self) 19 | connection.exportedObject = self 20 | connection.resume() 21 | return connection 22 | }() 23 | 24 | open var impl: LNExtensionService? { 25 | return implXPCService.remoteObjectProxy as? LNExtensionService 26 | } 27 | 28 | open override func getConfig(_ callback: @escaping LNConfigCallback) { 29 | impl?.getConfig({ 30 | callback($0) 31 | }) 32 | } 33 | 34 | public func updateConfig(_ config: [String: String]?, forService serviceName: String) { 35 | owner.updateConfig(config, forService: serviceName) 36 | } 37 | 38 | open func requestHighlights(forFile filepath: String, callback: @escaping LNHighlightCallback) { 39 | guard let impl = implXPCService.remoteObjectProxy as? LNExtensionService else { return } 40 | impl.requestHighlights(forFile: filepath, callback: { 41 | callback($0, $1) 42 | }) 43 | } 44 | 45 | open func updateHighlights(_ json: Data?, error: Error?, forFile filepath: String) { 46 | owner.updateHighlights(json, error: error, forFile: filepath) 47 | } 48 | 49 | open override func ping(_ test: Int32, callback: @escaping (Int32) -> Void) { 50 | impl?.ping(test, callback: { 51 | callback($0 + 1_000_000) 52 | }) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /SharedXPC/LNScriptImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LNScriptImpl.swift 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 31/03/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let implementationFactory = LNScriptImpl.self 12 | 13 | open class LNScriptImpl: LNExtensionBase, LNExtensionService { 14 | 15 | open var scriptExt: String { 16 | return "py" 17 | } 18 | 19 | open func requestHighlights(forFile filepath: String, callback: @escaping LNHighlightCallback) { 20 | guard let script = Bundle.main.path(forResource: EXTENSION_IMPL_SCRIPT, ofType: scriptExt) else { 21 | callback(nil, error(description: "script \(EXTENSION_IMPL_SCRIPT).\(scriptExt) not in XPC bundle")) 22 | return 23 | } 24 | 25 | DispatchQueue.global().async { 26 | let url = URL(fileURLWithPath: filepath) 27 | let task = Process() 28 | 29 | task.launchPath = script 30 | task.arguments = [url.lastPathComponent] 31 | task.currentDirectoryPath = url.deletingLastPathComponent().path 32 | 33 | let pipe = Pipe() 34 | task.standardOutput = pipe.fileHandleForWriting 35 | task.standardError = pipe.fileHandleForWriting 36 | 37 | task.launch() 38 | pipe.fileHandleForWriting.closeFile() 39 | let json = pipe.fileHandleForReading.readDataToEndOfFile() 40 | task.waitUntilExit() 41 | 42 | if task.terminationStatus != 0 { 43 | let alert = "Script \(EXTENSION_IMPL_SCRIPT).\(self.scriptExt) exit status " + 44 | "\(task.terminationStatus):\n" + (String(data: json, encoding: .utf8) ?? "No output") 45 | callback(json, self.error(description: alert)) 46 | } else { 47 | callback(json, nil) 48 | } 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /SharedXPC/LineGenerators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LineGenerators.swift 3 | // refactord 4 | // 5 | // Created by John Holdsworth on 19/12/2015. 6 | // Copyright © 2015 John Holdsworth. All rights reserved. 7 | // 8 | // $Id: //depot/Refactorator/refactord/LineGenerators.swift#7 $ 9 | // 10 | // Repo: https://github.com/johnno1962/Refactorator 11 | // 12 | 13 | import Foundation 14 | 15 | open class TaskGenerator: FileGenerator { 16 | 17 | let task: Process 18 | 19 | convenience init(command: String, directory: String? = nil, lineSeparator: String? = nil) { 20 | self.init(launchPath: "/bin/bash", arguments: ["-c", "exec \(command)"], 21 | directory: directory, lineSeparator: lineSeparator) 22 | } 23 | 24 | convenience init(launchPath: String, arguments: [String] = [], directory: String? = nil, lineSeparator: String? = nil) { 25 | 26 | let task = Process() 27 | task.launchPath = launchPath 28 | task.arguments = arguments 29 | task.currentDirectoryPath = directory ?? "." 30 | 31 | self.init(task: task, lineSeparator: lineSeparator) 32 | } 33 | 34 | init(task: Process, lineSeparator: String? = nil) { 35 | 36 | self.task = task 37 | 38 | let pipe = Pipe() 39 | task.standardOutput = pipe.fileHandleForWriting 40 | task.launch() 41 | 42 | pipe.fileHandleForWriting.closeFile() 43 | super.init(handle: pipe.fileHandleForReading, lineSeparator: lineSeparator) 44 | } 45 | 46 | deinit { 47 | task.terminate() 48 | } 49 | 50 | } 51 | 52 | open class FileGenerator: IteratorProtocol { 53 | 54 | let eol: Int32 55 | let handle: FileHandle 56 | let readBuffer = NSMutableData() 57 | 58 | convenience init?(path: String, lineSeparator: String? = nil) { 59 | guard let handle = FileHandle(forReadingAtPath: path) else { return nil } 60 | self.init(handle: handle, lineSeparator: lineSeparator) 61 | } 62 | 63 | init(handle: FileHandle, lineSeparator: String? = nil) { 64 | eol = Int32((lineSeparator ?? "\n").utf16.first!) 65 | self.handle = handle 66 | } 67 | 68 | open func next() -> String? { 69 | while true { 70 | if let endOfLine = memchr(readBuffer.bytes, eol, readBuffer.length) { 71 | let endOfLine = endOfLine.assumingMemoryBound(to: Int8.self) 72 | endOfLine[0] = 0 73 | 74 | let start = readBuffer.bytes.assumingMemoryBound(to: Int8.self) 75 | let line = String(cString: start) 76 | 77 | let consumed = NSMakeRange(0, UnsafePointer(endOfLine) + 1 - start) 78 | readBuffer.replaceBytes(in: consumed, withBytes: nil, length: 0) 79 | 80 | return line 81 | } 82 | 83 | let bytesRead = handle.availableData 84 | if bytesRead.count <= 0 { 85 | if readBuffer.length != 0 { 86 | let last = String.fromData(data: readBuffer) 87 | readBuffer.length = 0 88 | return last 89 | } else { 90 | break 91 | } 92 | } 93 | 94 | readBuffer.append(bytesRead) 95 | } 96 | return nil 97 | } 98 | 99 | open var lineSequence: AnySequence { 100 | return AnySequence({ self }) 101 | } 102 | 103 | deinit { 104 | handle.closeFile() 105 | } 106 | } 107 | 108 | extension String { 109 | 110 | static func fromData(data: NSData, encoding: String.Encoding = .utf8) -> String? { 111 | return String(data: data as Data, encoding: encoding) 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /SharedXPC/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // LNProvider 4 | // 5 | // Created by John Holdsworth on 02/04/2017. 6 | // Copyright © 2017 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ServiceDelegate: NSObject, NSXPCListenerDelegate { 12 | 13 | func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { 14 | return implementationFactory.init(connection: newConnection) != nil 15 | } 16 | 17 | } 18 | 19 | // Create the delegate for the service. 20 | let delegate = ServiceDelegate() 21 | 22 | // can't get this to work :( 23 | // let globalListener = NSXPCListener(machServiceName: Bundle.main.bundleIdentifier!) 24 | // globalListener.delegate = delegate 25 | // globalListener.resume() 26 | 27 | // Set up the one NSXPCListener for this service. It will handle all incoming connections. 28 | let listener = NSXPCListener.service() 29 | listener.delegate = delegate 30 | 31 | // Resuming the serviceListener starts this service. This method does not return. 32 | listener.resume() 33 | --------------------------------------------------------------------------------