├── .gitignore ├── Application ├── Application.xcodeproj │ └── project.pbxproj ├── Classes │ ├── AppDelegate.h │ └── AppDelegate.m ├── Info.plist ├── Resources │ ├── English.lproj │ │ └── MainMenu.xib │ └── Source.lua └── main.m ├── Keyword-Colors ├── JavaScript.plist └── Lua.plist ├── LICENSE ├── README.md └── Source ├── SourceTextView.h └── SourceTextView.m /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | xcuserdata 3 | project.xcworkspace 4 | -------------------------------------------------------------------------------- /Application/Application.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 47; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E200E17F158C1FD90078EAF8 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; 11 | E22A4F0818711BCD00575281 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E22A4F0718711BCD00575281 /* main.m */; }; 12 | E22A4F0C18711BDD00575281 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E22A4F0B18711BDD00575281 /* AppDelegate.m */; }; 13 | E22A4F1318711BE700575281 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = E22A4F1018711BE700575281 /* MainMenu.xib */; }; 14 | E22A4F1718711D5000575281 /* SourceTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = E22A4F1618711D5000575281 /* SourceTextView.m */; }; 15 | E22A4F191871207A00575281 /* Keyword-Colors in Resources */ = {isa = PBXBuildFile; fileRef = E22A4F181871207A00575281 /* Keyword-Colors */; }; 16 | E22A4F1D1871227200575281 /* Source.lua in Resources */ = {isa = PBXBuildFile; fileRef = E22A4F1C1871227200575281 /* Source.lua */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 21 | 8D1107320486CEB800E47090 /* Application.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Application.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | E22A4F0718711BCD00575281 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 23 | E22A4F0A18711BDD00575281 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 24 | E22A4F0B18711BDD00575281 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 25 | E22A4F1118711BE700575281 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = ""; }; 26 | E22A4F1518711D5000575281 /* SourceTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SourceTextView.h; sourceTree = ""; }; 27 | E22A4F1618711D5000575281 /* SourceTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SourceTextView.m; sourceTree = ""; }; 28 | E22A4F181871207A00575281 /* Keyword-Colors */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "Keyword-Colors"; path = "../Keyword-Colors"; sourceTree = ""; }; 29 | E22A4F1C1871227200575281 /* Source.lua */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Source.lua; path = Resources/Source.lua; sourceTree = SOURCE_ROOT; }; 30 | E2CF2CF418711B8300ED5355 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 8D11072E0486CEB800E47090 /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | E200E17F158C1FD90078EAF8 /* Cocoa.framework in Frameworks */, 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | 19C28FACFE9D520D11CA2CBB /* Products */ = { 46 | isa = PBXGroup; 47 | children = ( 48 | 8D1107320486CEB800E47090 /* Application.app */, 49 | ); 50 | name = Products; 51 | sourceTree = ""; 52 | }; 53 | 29B97314FDCFA39411CA2CEA /* Mac */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | E2CF2CF418711B8300ED5355 /* Info.plist */, 57 | E22A4F0718711BCD00575281 /* main.m */, 58 | E22A4F0918711BDD00575281 /* Classes */, 59 | E22A4F0D18711BE700575281 /* Resources */, 60 | E22A4F1418711D5000575281 /* Source */, 61 | E22A4F181871207A00575281 /* Keyword-Colors */, 62 | 29B97323FDCFA39411CA2CEA /* Frameworks and Libraries */, 63 | 19C28FACFE9D520D11CA2CBB /* Products */, 64 | ); 65 | name = Mac; 66 | sourceTree = ""; 67 | }; 68 | 29B97323FDCFA39411CA2CEA /* Frameworks and Libraries */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 72 | ); 73 | name = "Frameworks and Libraries"; 74 | sourceTree = ""; 75 | }; 76 | E22A4F0918711BDD00575281 /* Classes */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | E22A4F0A18711BDD00575281 /* AppDelegate.h */, 80 | E22A4F0B18711BDD00575281 /* AppDelegate.m */, 81 | ); 82 | path = Classes; 83 | sourceTree = ""; 84 | }; 85 | E22A4F0D18711BE700575281 /* Resources */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | E22A4F1018711BE700575281 /* MainMenu.xib */, 89 | E22A4F1C1871227200575281 /* Source.lua */, 90 | ); 91 | path = Resources; 92 | sourceTree = ""; 93 | }; 94 | E22A4F1418711D5000575281 /* Source */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | E22A4F1518711D5000575281 /* SourceTextView.h */, 98 | E22A4F1618711D5000575281 /* SourceTextView.m */, 99 | ); 100 | name = Source; 101 | path = ../Source; 102 | sourceTree = ""; 103 | }; 104 | /* End PBXGroup section */ 105 | 106 | /* Begin PBXNativeTarget section */ 107 | 8D1107260486CEB800E47090 /* Application */ = { 108 | isa = PBXNativeTarget; 109 | buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Application" */; 110 | buildPhases = ( 111 | 8D1107290486CEB800E47090 /* Resources */, 112 | 8D11072C0486CEB800E47090 /* Sources */, 113 | 8D11072E0486CEB800E47090 /* Frameworks */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = Application; 120 | productInstallPath = "$(HOME)/Applications"; 121 | productName = Mac; 122 | productReference = 8D1107320486CEB800E47090 /* Application.app */; 123 | productType = "com.apple.product-type.application"; 124 | }; 125 | /* End PBXNativeTarget section */ 126 | 127 | /* Begin PBXProject section */ 128 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 129 | isa = PBXProject; 130 | attributes = { 131 | LastUpgradeCheck = 0730; 132 | }; 133 | buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Application" */; 134 | compatibilityVersion = "Xcode 6.3"; 135 | developmentRegion = English; 136 | hasScannedForEncodings = 1; 137 | knownRegions = ( 138 | English, 139 | Japanese, 140 | French, 141 | German, 142 | ); 143 | mainGroup = 29B97314FDCFA39411CA2CEA /* Mac */; 144 | projectDirPath = ""; 145 | projectRoot = ""; 146 | targets = ( 147 | 8D1107260486CEB800E47090 /* Application */, 148 | ); 149 | }; 150 | /* End PBXProject section */ 151 | 152 | /* Begin PBXResourcesBuildPhase section */ 153 | 8D1107290486CEB800E47090 /* Resources */ = { 154 | isa = PBXResourcesBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | E22A4F1D1871227200575281 /* Source.lua in Resources */, 158 | E22A4F191871207A00575281 /* Keyword-Colors in Resources */, 159 | E22A4F1318711BE700575281 /* MainMenu.xib in Resources */, 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | /* End PBXResourcesBuildPhase section */ 164 | 165 | /* Begin PBXSourcesBuildPhase section */ 166 | 8D11072C0486CEB800E47090 /* Sources */ = { 167 | isa = PBXSourcesBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | E22A4F0818711BCD00575281 /* main.m in Sources */, 171 | E22A4F0C18711BDD00575281 /* AppDelegate.m in Sources */, 172 | E22A4F1718711D5000575281 /* SourceTextView.m in Sources */, 173 | ); 174 | runOnlyForDeploymentPostprocessing = 0; 175 | }; 176 | /* End PBXSourcesBuildPhase section */ 177 | 178 | /* Begin PBXVariantGroup section */ 179 | E22A4F1018711BE700575281 /* MainMenu.xib */ = { 180 | isa = PBXVariantGroup; 181 | children = ( 182 | E22A4F1118711BE700575281 /* English */, 183 | ); 184 | name = MainMenu.xib; 185 | sourceTree = ""; 186 | }; 187 | /* End PBXVariantGroup section */ 188 | 189 | /* Begin XCBuildConfiguration section */ 190 | C01FCF4B08A954540054247B /* Debug */ = { 191 | isa = XCBuildConfiguration; 192 | buildSettings = { 193 | INFOPLIST_FILE = Info.plist; 194 | PRODUCT_NAME = Application; 195 | }; 196 | name = Debug; 197 | }; 198 | C01FCF4C08A954540054247B /* Release */ = { 199 | isa = XCBuildConfiguration; 200 | buildSettings = { 201 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 202 | INFOPLIST_FILE = Info.plist; 203 | PRODUCT_NAME = Application; 204 | }; 205 | name = Release; 206 | }; 207 | C01FCF4F08A954540054247B /* Debug */ = { 208 | isa = XCBuildConfiguration; 209 | buildSettings = { 210 | ALWAYS_SEARCH_USER_PATHS = NO; 211 | ARCHS = "$(ARCHS_STANDARD)"; 212 | CLANG_ENABLE_OBJC_ARC = YES; 213 | GCC_C_LANGUAGE_STANDARD = c99; 214 | GCC_OPTIMIZATION_LEVEL = 0; 215 | MACOSX_DEPLOYMENT_TARGET = 10.8; 216 | ONLY_ACTIVE_ARCH = YES; 217 | SDKROOT = macosx; 218 | WARNING_CFLAGS = "-Wall"; 219 | }; 220 | name = Debug; 221 | }; 222 | C01FCF5008A954540054247B /* Release */ = { 223 | isa = XCBuildConfiguration; 224 | buildSettings = { 225 | ALWAYS_SEARCH_USER_PATHS = NO; 226 | ARCHS = "$(ARCHS_STANDARD)"; 227 | CLANG_ENABLE_OBJC_ARC = YES; 228 | GCC_C_LANGUAGE_STANDARD = c99; 229 | MACOSX_DEPLOYMENT_TARGET = 10.8; 230 | SDKROOT = macosx; 231 | WARNING_CFLAGS = "-Wall"; 232 | }; 233 | name = Release; 234 | }; 235 | /* End XCBuildConfiguration section */ 236 | 237 | /* Begin XCConfigurationList section */ 238 | C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Application" */ = { 239 | isa = XCConfigurationList; 240 | buildConfigurations = ( 241 | C01FCF4B08A954540054247B /* Debug */, 242 | C01FCF4C08A954540054247B /* Release */, 243 | ); 244 | defaultConfigurationIsVisible = 0; 245 | defaultConfigurationName = Release; 246 | }; 247 | C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Application" */ = { 248 | isa = XCConfigurationList; 249 | buildConfigurations = ( 250 | C01FCF4F08A954540054247B /* Debug */, 251 | C01FCF5008A954540054247B /* Release */, 252 | ); 253 | defaultConfigurationIsVisible = 0; 254 | defaultConfigurationName = Release; 255 | }; 256 | /* End XCConfigurationList section */ 257 | }; 258 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 259 | } 260 | -------------------------------------------------------------------------------- /Application/Classes/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013-2016, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "SourceTextView.h" 29 | 30 | @interface AppDelegate : NSObject 31 | @property(nonatomic, weak) IBOutlet NSWindow* mainWindow; 32 | @property(nonatomic, strong) IBOutlet SourceTextView* sourceTextView; // Does not support weak references 33 | @end 34 | -------------------------------------------------------------------------------- /Application/Classes/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013-2016, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "AppDelegate.h" 29 | 30 | @implementation AppDelegate 31 | 32 | - (void)_sourceDidUpdate:(NSNotification*)notification { 33 | NSLog(@"Updated source!"); 34 | } 35 | 36 | - (void)awakeFromNib { 37 | [_sourceTextView setLanguage:kSourceTextViewLanguage_Lua]; 38 | NSMutableDictionary* keywords = [SourceTextView keywordColorsFromKeywordsPropertyList:[[NSBundle mainBundle] pathForResource:@"Keyword-Colors/Lua" ofType:@"plist"]]; 39 | [keywords setObject:[NSColor redColor] forKey:@"lua"]; 40 | [_sourceTextView setKeywordColors:keywords]; 41 | 42 | [_sourceTextView setSource:[NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Source" ofType:@"lua"] encoding:NSUTF8StringEncoding error:NULL]]; 43 | [_sourceTextView setErrorLine:3]; 44 | 45 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_sourceDidUpdate:) name:NSTextDidChangeNotification object:_sourceTextView]; 46 | } 47 | 48 | - (void)applicationDidFinishLaunching:(NSNotification*)notification { 49 | [_mainWindow makeKeyAndOrderFront:nil]; 50 | } 51 | 52 | - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)application { 53 | return YES; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /Application/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | net.pol-online.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | APPL 17 | LSMinimumSystemVersion 18 | ${MACOSX_DEPLOYMENT_TARGET} 19 | NSHumanReadableCopyright 20 | Copyright © 2013-2016 Pierre-Olivier Latour. All Rights Reserved. 21 | NSMainNibFile 22 | MainMenu 23 | NSPrincipalClass 24 | NSApplication 25 | 26 | 27 | -------------------------------------------------------------------------------- /Application/Resources/English.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /Application/Resources/Source.lua: -------------------------------------------------------------------------------- 1 | print "Hello World!" 2 | 3 | function factorial(n) 4 | local x = 1 5 | for i = 2,n do 6 | x = x * i 7 | end 8 | return x -- We're done! 9 | end 10 | -------------------------------------------------------------------------------- /Application/main.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013-2016, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import 29 | 30 | int main(int argc, char *argv[]) { 31 | return NSApplicationMain(argc, (const char**)argv); 32 | } 33 | -------------------------------------------------------------------------------- /Keyword-Colors/JavaScript.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | color-red 7 | 0 8 | color-green 9 | 0.21 10 | color-blue 11 | 0.8 12 | keywords 13 | 14 | this undefined null 15 | true false Infinity 16 | 17 | 18 | 19 | color-red 20 | 0.14 21 | color-green 22 | 0.43 23 | color-bllue 24 | 0.15 25 | keywords 26 | 27 | function switch do for while in if else return break continue case default with try catch finally throw 28 | 29 | 30 | 31 | color-red 32 | 0.33 33 | color-green 34 | 0.26 35 | color-blue 36 | 0.72 37 | keywords 38 | 39 | const var new typeof parseInt parseFloat constructor prototype 40 | Array concat join length pop push reverse shift slice sort toSource unshift 41 | Boolean 42 | Date getFullYear getYear getMonth getDate getDay getHours getMinutes getSeconds getMilliseconds getTime getTimezoneOffset getUTCFullYear getUTCMonth getUTCDate getUTCDay getUTCHours getUTCMinutes getUTCSeconds getUTCMilliseconds setFullYear setYear setMonth setDate setHours setMinutes setSeconds setMilliseconds setTime setUTCFullYear setUTCMonth setUTCDate setUTCHours setUTCMinutes setUTCSeconds setUTCMilliseconds toGMTString toLocaleString toLocaleDateString toLocaleTimeString toUTCString parse UTC 43 | Function Arguments caller length apply call 44 | Math abs acos asin atan atan2 ceil cos exp floor log max min pow random round sin sqrt tan LN2 LN10 LOG2E LOG10E PI SQRT1_2 SQRT2 45 | Number toExponential toFixed toPrecision MAX_VALUE MIN_VALUE NEGATIVE_INFINITY POSITIVE_INFINITY NaN 46 | Object hasOwnProperty isPrototypeOf propertyIsEnumerable toLocalString toString valueOf 47 | String length charAt charChodeAt concat fromCharCode indexOf lastIndexOf match replace search slice split substr substring toLowerCase to UpperCase 48 | RegExp global ignoreCase lastIndex multiline source exec test 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Keyword-Colors/Lua.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | color-red 7 | 0.14 8 | color-green 9 | 0.43 10 | color-blue 11 | 0.15 12 | keywords 13 | 14 | function while do end repeat until if then else elseif return break for 15 | true false nil 16 | number string boolean table thread userdata 17 | local 18 | and or not 19 | 20 | 21 | 22 | color-red 23 | 0.46 24 | color-green 25 | 0.06 26 | color-blue 27 | 0.31 28 | keywords 29 | 30 | patch time arguments mouse setmetadata getmetadata 31 | event type subtype modifier clickcount characters keycode LeftMouseDown LeftMouseUp RightMouseDown RightMouseUp MouseMoved LeftMouseDragged RightMouseDragged MouseEntered MouseExited KeyDown KeyUp ScrollWheel TabletPoint TabletProximity OtherMouseDown OtherMouseUp OtherMouseDragged MouseEventSubtype TabletPointEventSubtype TabletProximityEventSubtype AlphaShiftKey ShiftKey ControlKey AlternateKey CommandKey NumericPadKey HelpKey FunctionKey LeftArrowKey RightArrowKey UpArrowKey DownArrowKey PageUpKey PageDownKey HomeKey EndKey EscapeKey TabKey BackSpaceKey DeleteKey F1Key F2Key F3Key F4Key F5Key F6Key F7Key F8Key F9Key F10Key F11Key F12Key F13Key F14Key F15Key 32 | structure count 33 | width height ratio 34 | 35 | 36 | 37 | color-red 38 | 0.33 39 | color-green 40 | 0.26 41 | color-blue 42 | 0.72 43 | keywords 44 | 45 | error print assert 46 | getmetatable setmetatable 47 | getfenv setfenv 48 | next ipairs pairs tonumber tostring 49 | type unpack rawequal rawget rawset 50 | pcall xpcall 51 | collectgarbage gcinfo 52 | coroutine create resume status wrap yield 53 | string len sub lower upper char rep byte format dump find gfind gsub 54 | table concat foreach foreachi getn setn sort insert remove 55 | math pi abs sin cos tan asin acos atan atan2 ceil floor mod frexp ldexp sqrt min max log log10 exp deg pow rad random randomseed 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2016, Pierre-Olivier Latour 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * The name of Pierre-Olivier Latour may not be used to endorse 12 | or promote products derived from this software without specific 13 | prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | SourceTextView is an NSTextView subclass with basic syntax highlighting for C, C++, JavaScript and Lua languages. It can also display line numbers as well as highlight a given source line for error reporting. 5 | 6 | License 7 | ======= 8 | 9 | SourceTextView is available under [New BSD License](LICENSE). 10 | -------------------------------------------------------------------------------- /Source/SourceTextView.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013-2016, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import 29 | 30 | typedef enum { 31 | kSourceTextViewLanguage_Undefined = 0, 32 | kSourceTextViewLanguage_C, 33 | kSourceTextViewLanguage_CPP, 34 | kSourceTextViewLanguage_JavaScript, 35 | kSourceTextViewLanguage_Lua, 36 | kSourceTextViewLanguage_ShellScript 37 | } SourceTextViewLanguage; 38 | 39 | @interface SourceTextView : NSTextView 40 | + (NSMutableDictionary*)keywordColorsFromKeywordsPropertyList:(NSString*)path; 41 | 42 | @property(nonatomic) SourceTextViewLanguage language; 43 | @property(nonatomic, copy) NSString* source; // Observe NSTextDidChangeNotification to know when source has been edited 44 | @property(nonatomic) BOOL showLineNumbers; // YES by default (this also controls wrapping) 45 | 46 | @property(nonatomic, copy) NSDictionary* keywordColors; // Maps keywords to NSColors 47 | @property(nonatomic, copy) NSColor* stringColor; // Strings are assumed to be '...' or "..." 48 | @property(nonatomic, copy) NSColor* commentColor; // Comments are assumed to be //... or /* ... */ 49 | @property(nonatomic, copy) NSColor* preprocessorColor; // Preprocessor is assumed to be #... 50 | 51 | - (void)setErrorLine:(NSUInteger)line; // Starts at 1 (automatically cleared on edit or pass 0 to clear manually) 52 | @property(nonatomic, copy) NSColor* errorHighlightColor; 53 | @end 54 | 55 | @interface SourceTextView (Actions) 56 | - (void)shiftLeft:(id)sender; 57 | - (void)shiftRight:(id)sender; 58 | @end 59 | -------------------------------------------------------------------------------- /Source/SourceTextView.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013-2016, Pierre-Olivier Latour 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * The name of Pierre-Olivier Latour may not be used to endorse 13 | or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL PIERRE-OLIVIER LATOUR BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #import "SourceTextView.h" 29 | 30 | #define kFontName @"Menlo Regular" 31 | #define kFontSize 11 32 | #define kCharacterWidth 7 33 | #define kLineHeight 13 34 | #define kRulerThickness 38 35 | 36 | typedef enum { 37 | kSourceToken_Code = 0, 38 | kSourceToken_SingleQuoteString, 39 | kSourceToken_DoubleQuoteString, 40 | kSourceToken_LineComment, 41 | kSourceToken_BlockComment, 42 | kSourceToken_Preprocessor 43 | } SourceToken; 44 | 45 | typedef void (*SourceTokenCallback)(NSString* source, SourceToken token, NSRange range, void* userInfo); 46 | 47 | @interface SourceTextView () 48 | @end 49 | 50 | @interface SourceRulerView : NSRulerView 51 | @property(nonatomic, assign) SourceTextView* sourceView; 52 | @end 53 | 54 | static void _SourceColorizeCallback(NSString* source, SourceToken token, NSRange range, void* userInfo) { 55 | SourceTextView* view = (__bridge SourceTextView*)userInfo; 56 | static NSCharacterSet* characters = nil; 57 | static NSCharacterSet* charactersInverted = nil; 58 | NSColor* color; 59 | 60 | if (characters == nil) { 61 | characters = [NSCharacterSet characterSetWithCharactersInString:@"0123456789#ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"]; 62 | } 63 | if (charactersInverted == nil) { 64 | charactersInverted = [characters invertedSet]; 65 | } 66 | 67 | switch (token) { 68 | 69 | case kSourceToken_Code: 70 | if ([view keywordColors]) { 71 | NSUInteger start2 = range.location; 72 | NSUInteger end2 = range.location + range.length; 73 | while (1) { 74 | range = [source rangeOfCharacterFromSet:characters options:0 range:NSMakeRange(start2, end2 - start2)]; 75 | if (range.location != NSNotFound) { 76 | start2 = range.location; 77 | if (start2 == end2) { 78 | break; 79 | } 80 | } 81 | 82 | range = [source rangeOfCharacterFromSet:charactersInverted options:0 range:NSMakeRange(start2, end2 - start2)]; 83 | if (range.location == NSNotFound) { 84 | range.location = end2; 85 | } 86 | if (range.location != start2) { 87 | NSRange subRange = NSMakeRange(start2, range.location - start2); 88 | if ((color = [[view keywordColors] objectForKey:[source substringWithRange:subRange]])) { 89 | [view setTextColor:color range:subRange]; 90 | } 91 | start2 = range.location; 92 | if (start2 == end2) { 93 | break; 94 | } 95 | } else { 96 | break; 97 | } 98 | } 99 | } 100 | break; 101 | 102 | case kSourceToken_SingleQuoteString: 103 | case kSourceToken_DoubleQuoteString: 104 | if ((color = [view stringColor])) { 105 | [view setTextColor:color range:range]; 106 | } 107 | break; 108 | 109 | case kSourceToken_LineComment: 110 | case kSourceToken_BlockComment: 111 | if ((color = [view commentColor])) { 112 | [view setTextColor:color range:range]; 113 | } 114 | break; 115 | 116 | case kSourceToken_Preprocessor: 117 | if ((color = [view preprocessorColor])) { 118 | [view setTextColor:color range:range]; 119 | } 120 | break; 121 | 122 | } 123 | } 124 | 125 | @implementation SourceRulerView 126 | 127 | - (CGFloat)requiredThickness { 128 | return kRulerThickness; 129 | } 130 | 131 | - (void)setSourceView:(SourceTextView*)view { 132 | _sourceView = view; 133 | [self setNeedsDisplay:YES]; 134 | } 135 | 136 | - (void)drawRect:(NSRect)aRect { 137 | static NSDictionary* attributes = nil; 138 | static NSColor* backColor = nil; 139 | static NSColor* lineColor = nil; 140 | NSRect bounds = [self bounds]; 141 | 142 | if (backColor == nil) { 143 | backColor = [NSColor colorWithDeviceRed:0.90 green:0.90 blue:0.90 alpha:1.0]; 144 | } 145 | if (lineColor == nil) { 146 | lineColor = [NSColor grayColor]; 147 | } 148 | if (attributes == nil) { 149 | attributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSColor darkGrayColor], NSForegroundColorAttributeName, [NSFont fontWithName:kFontName size:kFontSize], NSFontAttributeName, nil]; 150 | } 151 | 152 | [backColor set]; 153 | NSRectFill(aRect); 154 | [lineColor set]; 155 | NSFrameRect(NSMakeRect(bounds.origin.x + bounds.size.width - 1, aRect.origin.y, 1, aRect.size.height)); 156 | 157 | NSUInteger start = ([_sourceView visibleRect].origin.y + aRect.origin.y) / kLineHeight + 1; 158 | #if defined(__LP64__) && __LP64__ 159 | CGFloat offset = fmod([_sourceView visibleRect].origin.y + aRect.origin.y, kLineHeight); 160 | #else 161 | CGFloat offset = fmodf([_sourceView visibleRect].origin.y + aRect.origin.y, kLineHeight); 162 | #endif 163 | for (NSUInteger i = 0; i < aRect.size.height / kLineHeight + 1; ++i) { 164 | NSPoint point; 165 | point.x = (start + i < 10 ? bounds.origin.x + 4 * kCharacterWidth - 1 : (start + i < 100 ? bounds.origin.x + 3 * kCharacterWidth - 1 : (start + i < 1000 ? bounds.origin.x + 2 * kCharacterWidth - 1 : bounds.origin.x + 1 * kCharacterWidth - 1))); 166 | point.y = (aRect.origin.y / kLineHeight + i) * kLineHeight - offset - 4; 167 | [[NSString stringWithFormat:@"%lu", (long)(start + i)] drawAtPoint:point withAttributes:attributes]; 168 | } 169 | } 170 | 171 | @end 172 | 173 | @implementation SourceTextView 174 | 175 | + (NSMutableDictionary*) keywordColorsFromKeywordsPropertyList:(NSString*)path { 176 | NSMutableDictionary* dictionary = [NSMutableDictionary dictionary]; 177 | 178 | // Read the plist file 179 | NSArray* array = [NSPropertyListSerialization propertyListWithData:[NSData dataWithContentsOfFile:path] options:NSPropertyListImmutable format:NULL error:NULL]; 180 | if (![array isKindOfClass:[NSArray class]]) { 181 | return nil; 182 | } 183 | 184 | // Extract colors and keywords 185 | for (NSDictionary* entry in array) { 186 | NSColor* color = [NSColor colorWithDeviceRed:[[entry objectForKey:@"color-red"] floatValue] green:[[entry objectForKey:@"color-green"] floatValue] blue:[[entry objectForKey:@"color-blue"] floatValue] alpha:1.0]; 187 | NSScanner* scanner = [NSScanner scannerWithString:[entry objectForKey:@"keywords"]]; 188 | while (![scanner isAtEnd]) { 189 | NSString* keyword; 190 | [scanner scanCharactersFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] intoString:NULL]; 191 | if ([scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] intoString:&keyword]) { 192 | [dictionary setObject:color forKey:keyword]; 193 | } 194 | } 195 | } 196 | 197 | return dictionary; 198 | } 199 | 200 | + (void)_parseSource:(NSString*)source range:(NSRange)range language:(SourceTextViewLanguage)language callback:(SourceTokenCallback)callback userInfo:(void*)info { 201 | SourceToken state = kSourceToken_Code; 202 | NSRange subRange; 203 | 204 | // Safe checks 205 | if ((range.location + range.length > [source length]) || (range.length == 0) || (callback == NULL)) { 206 | return; 207 | } 208 | 209 | // Copy string contents into buffer 210 | unichar* buffer = malloc(range.length * sizeof(unichar)); 211 | if (buffer == NULL) { 212 | return; 213 | } 214 | [source getCharacters:buffer range:range]; 215 | 216 | // Scan characters 217 | NSUInteger tokenStart = 0; 218 | for (NSUInteger i = 0; i < range.length; ++i) { 219 | SourceToken oldState = state; 220 | switch (buffer[i]) { 221 | 222 | case '#': { 223 | if (state != kSourceToken_Code) { 224 | break; 225 | } 226 | if ((language == kSourceTextViewLanguage_C) || (language == kSourceTextViewLanguage_CPP)) { 227 | state = kSourceToken_Preprocessor; 228 | } else if (language == kSourceTextViewLanguage_ShellScript) { 229 | state = kSourceToken_LineComment; 230 | } 231 | break; 232 | } 233 | 234 | case '-': { 235 | if (language == kSourceTextViewLanguage_Lua) { 236 | if (state != kSourceToken_Code) { 237 | break; 238 | } 239 | if (i + 1 == range.length) { 240 | break; 241 | } 242 | if (buffer[i + 1] == '-') { 243 | if ((i + 3 < range.length) && (buffer[i + 2] == '[') && (buffer[i + 3] == '[')) { 244 | state = kSourceToken_BlockComment; 245 | i += 3; 246 | } else { 247 | state = kSourceToken_LineComment; 248 | i += 1; 249 | } 250 | } 251 | } 252 | break; 253 | } 254 | 255 | case ']': { 256 | if (language == kSourceTextViewLanguage_Lua) { 257 | if (state != kSourceToken_BlockComment) { 258 | break; 259 | } 260 | if (i + 1 == range.length) { 261 | break; 262 | } 263 | if (buffer[i + 1] == ']') { 264 | state = kSourceToken_Code; 265 | i += 1; 266 | } 267 | } 268 | break; 269 | } 270 | 271 | case '/': { 272 | if ((language == kSourceTextViewLanguage_C) || (language == kSourceTextViewLanguage_CPP) || (language == kSourceTextViewLanguage_JavaScript)) { 273 | if ((state != kSourceToken_Code) && (state != kSourceToken_Preprocessor)) { 274 | break; 275 | } 276 | if (i + 1 == range.length) { 277 | break; 278 | } 279 | if (buffer[i + 1] == '/') { 280 | state = kSourceToken_LineComment; 281 | i += 1; 282 | } else if (buffer[i + 1] == '*') { 283 | state = kSourceToken_BlockComment; 284 | i += 1; 285 | } 286 | } 287 | break; 288 | } 289 | 290 | case '\n': { 291 | if ((state == kSourceToken_LineComment) || (state == kSourceToken_Preprocessor)) { 292 | state = kSourceToken_Code; 293 | } 294 | break; 295 | } 296 | 297 | case '*': { 298 | if ((language == kSourceTextViewLanguage_C) || (language == kSourceTextViewLanguage_CPP) || (language == kSourceTextViewLanguage_JavaScript)) { 299 | if (state != kSourceToken_BlockComment) { 300 | break; 301 | } 302 | if (i + 1 == range.length) { 303 | break; 304 | } 305 | if (buffer[i + 1] == '/') { 306 | state = kSourceToken_Code; 307 | i += 1; 308 | } 309 | } 310 | break; 311 | } 312 | 313 | case '\'': { 314 | if ((state != kSourceToken_Code) && (state != kSourceToken_SingleQuoteString)) { 315 | break; 316 | } 317 | if (i > 0) { 318 | if(buffer[i - 1] == '\\') 319 | break; 320 | } 321 | if (state == kSourceToken_SingleQuoteString) { 322 | state = kSourceToken_Code; 323 | } else { 324 | state = kSourceToken_SingleQuoteString; 325 | } 326 | break; 327 | } 328 | 329 | case '"': { 330 | if ((state != kSourceToken_Code) && (state != kSourceToken_DoubleQuoteString)) { 331 | break; 332 | } 333 | if (i > 0) { 334 | if(buffer[i - 1] == '\\') 335 | break; 336 | } 337 | if(state == kSourceToken_DoubleQuoteString) { 338 | state = kSourceToken_Code; 339 | } else { 340 | state = kSourceToken_DoubleQuoteString; 341 | } 342 | break; 343 | } 344 | 345 | } 346 | 347 | if ((state != oldState) && (i > 0)) { 348 | subRange.location = tokenStart; 349 | if (state == kSourceToken_BlockComment) { 350 | tokenStart = i - (language == kSourceTextViewLanguage_Lua ? 3 : 1); 351 | } else if (state == kSourceToken_LineComment) { 352 | tokenStart = i - 1; 353 | } else if ((state == kSourceToken_Code) && (oldState != kSourceToken_LineComment)) { 354 | tokenStart = i + 1; 355 | } else { 356 | tokenStart = i; 357 | } 358 | subRange.length = tokenStart - subRange.location; 359 | 360 | (*callback)(source, oldState, NSMakeRange(range.location + subRange.location, subRange.length), info); 361 | } 362 | } 363 | if (tokenStart < range.length) { 364 | subRange.location = tokenStart; 365 | subRange.length = range.length - tokenStart; 366 | 367 | (*callback)(source, state, NSMakeRange(range.location + subRange.location, subRange.length), info); 368 | } 369 | 370 | //Release buffer 371 | free(buffer); 372 | } 373 | 374 | - (void)_finishInitialization { 375 | [self setMaxSize:NSMakeSize(10000000, 10000000)]; 376 | [self setAutoresizingMask:NSViewNotSizable]; 377 | 378 | [self setDelegate:self]; 379 | _language = kSourceTextViewLanguage_Undefined; 380 | _showLineNumbers = YES; 381 | _stringColor = [NSColor colorWithDeviceRed:0.6 green:0.3 blue:0.0 alpha:1.0]; 382 | _commentColor = [NSColor darkGrayColor]; 383 | _preprocessorColor = [NSColor blueColor]; 384 | _errorHighlightColor = [NSColor colorWithDeviceRed:1.0 green:0.4 blue:0.5 alpha:1.0]; 385 | [self setFont:[NSFont fontWithName:kFontName size:kFontSize]]; 386 | [self setSmartInsertDeleteEnabled:NO]; 387 | [self setAutomaticQuoteSubstitutionEnabled:NO]; 388 | [self setAutomaticDashSubstitutionEnabled:NO]; 389 | [self setAutomaticTextReplacementEnabled:NO]; 390 | [self setAutomaticSpellingCorrectionEnabled:NO]; 391 | [self setAllowsUndo:YES]; 392 | 393 | #if 0 394 | NSMutableParagraphStyle* style = [NSMutableParagraphStyle new]; 395 | [style setTabStops:[NSArray array]]; 396 | for (NSUInteger i = 0; i < 128; ++i) { 397 | NSTextTab* tabStop = [[NSTextTab alloc] initWithType:NSLeftTabStopType location:(i * 4 * kCharacterWidth)]; 398 | [style addTabStop:tabStop]; 399 | [tabStop release]; 400 | } 401 | [[self textStorage] addAttributes:[NSDictionary dictionaryWithObject:style forKey:NSParagraphStyleAttributeName] range:NSMakeRange(0, [[[self textStorage] string] length])]; 402 | [style release]; 403 | #endif 404 | } 405 | 406 | - (id)initWithFrame:(NSRect)frame { 407 | if ((self = [super initWithFrame:frame])) { 408 | [self _finishInitialization]; 409 | } 410 | return self; 411 | } 412 | 413 | - (id)initWithCoder:(NSCoder*)coder { 414 | if ((self = [super initWithCoder:coder])) { 415 | [self _finishInitialization]; 416 | } 417 | return self; 418 | } 419 | 420 | - (BOOL)validateUserInterfaceItem:(id)anItem { 421 | if ([anItem action] == @selector(paste:)) { 422 | return ([self isEditable] && [self preferredPasteboardTypeFromArray:[[NSPasteboard generalPasteboard] types] restrictedToTypesFromArray:[NSArray arrayWithObject:NSStringPboardType]]); 423 | } 424 | if (([anItem action] == @selector(shiftLeft:)) || ([anItem action] == @selector(shiftRight:))) { 425 | return ([[self window] firstResponder] == self); 426 | } 427 | return [super validateUserInterfaceItem:anItem]; 428 | } 429 | 430 | - (void)paste:(id)sender { 431 | [self pasteAsPlainText:sender]; 432 | } 433 | 434 | // FIXME: This does not work correctly if the text view contains text 435 | - (void)_showLineNumbers:(BOOL)flag { 436 | NSScrollView* scrollView = (NSScrollView*)[[self superview] superview]; 437 | NSTextContainer* container = [[[self layoutManager] textContainers] objectAtIndex:0]; 438 | 439 | if ([scrollView isKindOfClass:[NSScrollView class]]) { 440 | if (flag) { 441 | Class rulerClass = [NSScrollView rulerViewClass]; 442 | [NSScrollView setRulerViewClass:[SourceRulerView class]]; 443 | [scrollView setHasVerticalRuler:YES]; 444 | [scrollView setRulersVisible:YES]; 445 | [NSScrollView setRulerViewClass:rulerClass]; 446 | SourceRulerView* rulerView = (SourceRulerView*)[scrollView verticalRulerView]; 447 | [rulerView setSourceView:self]; 448 | [rulerView setRuleThickness:kRulerThickness]; 449 | 450 | [scrollView setHasHorizontalScroller:YES]; 451 | [container setWidthTracksTextView:NO]; 452 | [container setHeightTracksTextView:NO]; 453 | [container setContainerSize:NSMakeSize(10000000, 10000000)]; // This forces a refresh 454 | [self setHorizontallyResizable:YES]; 455 | } else { 456 | [scrollView setHasVerticalRuler:NO]; 457 | 458 | [scrollView setHasHorizontalScroller:NO]; 459 | [container setWidthTracksTextView:YES]; 460 | [container setHeightTracksTextView:NO]; 461 | [container setContainerSize:NSMakeSize(10, 10000000)]; // This forces a refresh 462 | [self setHorizontallyResizable:NO]; 463 | } 464 | } 465 | } 466 | 467 | - (void)setShowLineNumbers:(BOOL)flag { 468 | if (flag != _showLineNumbers) { 469 | [self _showLineNumbers:flag]; 470 | _showLineNumbers = flag; 471 | } 472 | } 473 | 474 | - (void)viewDidMoveToSuperview { 475 | NSScrollView* scrollView = (NSScrollView*)[[self superview] superview]; 476 | 477 | [self _showLineNumbers:_showLineNumbers]; 478 | 479 | if ([scrollView isKindOfClass:[NSScrollView class]]) { 480 | [scrollView setLineScroll:kLineHeight]; 481 | } 482 | } 483 | 484 | - (void)insertNewline:(id)sender { 485 | NSString* string = [[self textStorage] mutableString]; 486 | NSRange range = [self selectedRange]; 487 | 488 | [self insertText:@"\n"]; 489 | 490 | if ((range.location != NSNotFound) && (range.location > 0)) { 491 | NSRange subRange = [string rangeOfString:@"\n" options:NSBackwardsSearch range:NSMakeRange(0, range.location)]; 492 | if (subRange.location == NSNotFound) { 493 | subRange.location = 0; 494 | } else { 495 | subRange.location += 1; 496 | } 497 | NSRange subRange2 = [string rangeOfCharacterFromSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet] options:0 range:NSMakeRange(subRange.location, range.location - subRange.location)]; 498 | if (subRange2.location == NSNotFound) { 499 | subRange2.location = range.location; 500 | } 501 | [self insertText:[string substringWithRange:NSMakeRange(subRange.location, subRange2.location - subRange.location)]]; 502 | } 503 | } 504 | 505 | - (void)_highlightLine:(NSUInteger)line withColor:(NSColor*)color { 506 | NSString* string = [self string]; 507 | NSUInteger length = [string length]; 508 | NSUInteger count = 0; 509 | NSUInteger location = 0; 510 | 511 | while (location < length) { 512 | NSRange range = [string rangeOfString:@"\n" options:0 range:NSMakeRange(location, length - location)]; 513 | if (range.location == NSNotFound) { 514 | range.location = length; 515 | } 516 | if (line == count) { 517 | range = NSMakeRange(location, range.location - location); 518 | if(color) { 519 | [[self textStorage] addAttribute:NSBackgroundColorAttributeName value:color range:range]; 520 | } else { 521 | [[self textStorage] removeAttribute:NSBackgroundColorAttributeName range:range]; 522 | } 523 | break; 524 | } 525 | location = range.location + 1; 526 | ++count; 527 | } 528 | } 529 | 530 | - (void)_highlightAllLinesWithColor:(NSColor*)color { 531 | NSRange range = NSMakeRange(0, [[self string] length]); 532 | if(color) { 533 | [[self textStorage] addAttribute:NSBackgroundColorAttributeName value:color range:range]; 534 | } else { 535 | [[self textStorage] removeAttribute:NSBackgroundColorAttributeName range:range]; 536 | } 537 | } 538 | 539 | - (void)_textDidChange { 540 | NSString* string = [self string]; 541 | NSRange range = NSMakeRange(0, [string length]); 542 | 543 | [self setTextColor:nil range:range]; 544 | [SourceTextView _parseSource:string range:range language:_language callback:_SourceColorizeCallback userInfo:(__bridge void*)self]; 545 | 546 | [self _highlightAllLinesWithColor:nil]; 547 | } 548 | 549 | - (void)setErrorLine:(NSUInteger)line { 550 | if (line > 0) { 551 | [self _highlightLine:(line - 1) withColor:_errorHighlightColor]; 552 | } else { 553 | [self _highlightAllLinesWithColor:nil]; 554 | } 555 | } 556 | 557 | - (void)setLanguage:(SourceTextViewLanguage)language { 558 | _language = language; 559 | [self _textDidChange]; 560 | } 561 | 562 | - (void)setSource:(NSString*)source { 563 | if (![source isEqualToString:[self string]]) { 564 | [self setString:([source length] ? source : @"")]; 565 | [self _textDidChange]; 566 | } 567 | } 568 | 569 | - (NSString*)source { 570 | return [self string]; 571 | } 572 | 573 | - (void)setKeywordColors:(NSDictionary*)keywords { 574 | _keywordColors = [keywords copy]; 575 | 576 | [self _textDidChange]; 577 | } 578 | 579 | - (void)setStringColor:(NSColor*)color { 580 | _stringColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace]; 581 | 582 | [self _textDidChange]; 583 | } 584 | 585 | - (void)setCommentColor:(NSColor*)color { 586 | _commentColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace]; 587 | 588 | [self _textDidChange]; 589 | } 590 | 591 | - (void)setPreprocessorColor:(NSColor*)color { 592 | _preprocessorColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace]; 593 | 594 | [self _textDidChange]; 595 | } 596 | 597 | - (void) setErrorHighlightColor:(NSColor*)color { 598 | _errorHighlightColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace]; 599 | 600 | [self _textDidChange]; 601 | } 602 | 603 | #pragma mark - NSTextViewDelegate 604 | 605 | - (void)textDidChange:(NSNotification*)notification { 606 | [self _textDidChange]; 607 | } 608 | 609 | @end 610 | 611 | @implementation SourceTextView (Actions) 612 | 613 | - (NSRange)__shiftLeft:(NSRange)range { 614 | NSString* string = [[self textStorage] mutableString]; 615 | NSRange newRange = range; 616 | 617 | if (![string length]) { 618 | return newRange; 619 | } 620 | 621 | NSRange subRange = [string rangeOfString:@"\n" options:NSBackwardsSearch range:NSMakeRange(0, range.location)]; 622 | if (subRange.location == NSNotFound) { 623 | range.length += range.location; 624 | range.location = 0; 625 | } else { 626 | range.length += range.location - subRange.location - 1; 627 | range.location = subRange.location + 1; 628 | } 629 | if ([string characterAtIndex:range.location] == '\t') { 630 | if (range.location < newRange.location) { 631 | newRange.location -= 1; 632 | newRange.length += 1; 633 | } 634 | } else if (range.length == 0) { 635 | return newRange; 636 | } 637 | 638 | while (1) { 639 | if ([string characterAtIndex:range.location] == '\t') { 640 | [self replaceCharactersInRange:NSMakeRange(range.location, 1) withString:@""]; 641 | if (newRange.length > 0) { 642 | newRange.length -= 1; 643 | } 644 | if (range.length > 0) { 645 | range.length -= 1; 646 | } 647 | } 648 | 649 | subRange = [string rangeOfString:@"\n" options:0 range:range]; 650 | if ((subRange.location == NSNotFound) || (subRange.location + 1 == range.location + range.length)) { 651 | break; 652 | } 653 | range.length -= subRange.location - range.location + 1; 654 | range.location = subRange.location + 1; 655 | } 656 | 657 | [self didChangeText]; 658 | 659 | return newRange; 660 | } 661 | 662 | - (NSRange)__shiftRight:(NSRange)range { 663 | NSString* string = [[self textStorage] mutableString]; 664 | NSRange newRange = range; 665 | 666 | NSRange subRange = [string rangeOfString:@"\n" options:NSBackwardsSearch range:NSMakeRange(0, range.location)]; 667 | if (subRange.location == NSNotFound) { 668 | range.length += range.location; 669 | range.location = 0; 670 | } else { 671 | range.length += range.location - subRange.location - 1; 672 | range.location = subRange.location + 1; 673 | } 674 | newRange.location += 1; 675 | newRange.length -= 1; 676 | 677 | while (1) { 678 | [self replaceCharactersInRange:NSMakeRange(range.location, 0) withString:@"\t"]; 679 | newRange.length += 1; 680 | range.length += 1; 681 | 682 | subRange = [string rangeOfString:@"\n" options:0 range:range]; 683 | if ((subRange.location == NSNotFound) || (subRange.location + 1 == range.location + range.length)) { 684 | break; 685 | } 686 | range.length -= subRange.location - range.location + 1; 687 | range.location = subRange.location + 1; 688 | } 689 | 690 | [self didChangeText]; 691 | 692 | return newRange; 693 | } 694 | 695 | - (void)_shiftLeft:(NSValue*)valueRange { 696 | NSRange range = [valueRange rangeValue]; 697 | NSRange newRange = [self __shiftLeft:range]; 698 | if(!NSEqualRanges(newRange, range)) { 699 | [[self undoManager] registerUndoWithTarget:self selector:@selector(_shiftRight:) object:[NSValue valueWithRange:newRange]]; 700 | [self setSelectedRange:newRange]; 701 | } 702 | } 703 | 704 | - (void)shiftLeft:(id)sender { 705 | [self _shiftLeft:[NSValue valueWithRange:[self selectedRange]]]; 706 | } 707 | 708 | - (void)_shiftRight:(NSValue*)valueRange { 709 | NSRange range = [valueRange rangeValue]; 710 | NSRange newRange = [self __shiftRight:range]; 711 | if(!NSEqualRanges(newRange, range)) { 712 | [[self undoManager] registerUndoWithTarget:self selector:@selector(_shiftLeft:) object:[NSValue valueWithRange:newRange]]; 713 | [self setSelectedRange:newRange]; 714 | } 715 | } 716 | 717 | - (void)shiftRight:(id)sender { 718 | [self _shiftRight:[NSValue valueWithRange:[self selectedRange]]]; 719 | } 720 | 721 | @end 722 | --------------------------------------------------------------------------------