Browse code
first commit
SamRothCA authored on 29/10/2014 03:43:47
Showing 38 changed files
Showing 38 changed files
- README.md
- Today Scripts.xcodeproj/project.pbxproj
- Today Scripts/AppDelegate.h
- Today Scripts/AppDelegate.m
- Today Scripts/AppIcon.icns
- Today Scripts/Icon Credit.txt
- Today Scripts/Info.plist
- Today Scripts/MainMenu.xib
- Today Scripts/main.m
- TodayScripts.h
- TodayScripts.m
- Widget/AMR_ANSIEscapeHelper.h
- Widget/AMR_ANSIEscapeHelper.m
- Widget/AMR_ANSIEscapeHelper/AMR_ANSIEscapeHelper.h
- Widget/AMR_ANSIEscapeHelper/AMR_ANSIEscapeHelper.m
- Widget/AMR_ANSIEscapeHelper/readme.txt
- Widget/EditViewController.h
- Widget/EditViewController.m
- Widget/EditViewController.xib
- Widget/Images.xcassets/AppIcon.appiconset/Contents.json
- Widget/Info.plist
- Widget/ListRowViewController.h
- Widget/ListRowViewController.m
- Widget/ListRowViewController.xib
- Widget/SearchViewController.h
- Widget/SearchViewController.m
- Widget/SearchViewController.xib
- Widget/TodayScript.h
- Widget/TodayScript.m
- Widget/TodayViewController.h
- Widget/TodayViewController.m
- Widget/TodayViewController.xib
- Widget/Widget.entitlements
- Widget/en.lproj/InfoPlist.strings
- XPC/Info.plist
- XPC/XPCHelper.m
- XPC/XPCMain.h
- XPC/XPCMain.m
2 | 2 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,667 @@ |
1 |
+// !$*UTF8*$! |
|
2 |
+{ |
|
3 |
+ archiveVersion = 1; |
|
4 |
+ classes = { |
|
5 |
+ }; |
|
6 |
+ objectVersion = 46; |
|
7 |
+ objects = { |
|
8 |
+ |
|
9 |
+/* Begin PBXBuildFile section */ |
|
10 |
+ 223F9A0A19F894AF000802FB /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A0919F894AF000802FB /* AppDelegate.m */; }; |
|
11 |
+ 223F9A0C19F894AF000802FB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A0B19F894AF000802FB /* main.m */; }; |
|
12 |
+ 223F9A2D19F894DB000802FB /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 223F9A2C19F894DB000802FB /* NotificationCenter.framework */; }; |
|
13 |
+ 223F9A3419F894DB000802FB /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 223F9A3219F894DB000802FB /* InfoPlist.strings */; }; |
|
14 |
+ 223F9A4119F894DB000802FB /* Widget.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 223F9A2A19F894DB000802FB /* Widget.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; |
|
15 |
+ 223F9A5D19F895AF000802FB /* TodayScripts.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A5C19F895AF000802FB /* TodayScripts.m */; }; |
|
16 |
+ 223F9A5E19F895AF000802FB /* TodayScripts.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A5C19F895AF000802FB /* TodayScripts.m */; }; |
|
17 |
+ 223F9A6C19F8963C000802FB /* AMR_ANSIEscapeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A6019F8963C000802FB /* AMR_ANSIEscapeHelper.m */; }; |
|
18 |
+ 223F9A6D19F8963C000802FB /* ListRowViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A6219F8963C000802FB /* ListRowViewController.m */; }; |
|
19 |
+ 223F9A6E19F8963C000802FB /* ListRowViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 223F9A6319F8963C000802FB /* ListRowViewController.xib */; }; |
|
20 |
+ 223F9A6F19F8963C000802FB /* EditViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A6519F8963C000802FB /* EditViewController.m */; }; |
|
21 |
+ 223F9A7019F8963C000802FB /* EditViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 223F9A6619F8963C000802FB /* EditViewController.xib */; }; |
|
22 |
+ 223F9A7119F8963C000802FB /* TodayScript.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A6819F8963C000802FB /* TodayScript.m */; }; |
|
23 |
+ 223F9A7219F8963C000802FB /* TodayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A6A19F8963C000802FB /* TodayViewController.m */; }; |
|
24 |
+ 223F9A7319F8963C000802FB /* TodayViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 223F9A6B19F8963C000802FB /* TodayViewController.xib */; }; |
|
25 |
+ 223F9A7719F896BF000802FB /* XPCHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A7419F896BF000802FB /* XPCHelper.m */; }; |
|
26 |
+ 223F9A7819F896BF000802FB /* XPCMain.m in Sources */ = {isa = PBXBuildFile; fileRef = 223F9A7619F896BF000802FB /* XPCMain.m */; }; |
|
27 |
+ 2265B37619FC519C000802FB /* AppIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 2265B37519FC519C000802FB /* AppIcon.icns */; }; |
|
28 |
+ 2265B37819FC51DF000802FB /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2265B37719FC51DF000802FB /* Images.xcassets */; }; |
|
29 |
+ 2265CC7319F89D18000802FB /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2265CC7219F89D18000802FB /* MainMenu.xib */; }; |
|
30 |
+ 2265CC7619F89D86000802FB /* XPC.xpc in CopyFiles */ = {isa = PBXBuildFile; fileRef = 223F9A4A19F89532000802FB /* XPC.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; |
|
31 |
+ 22A1BFCD1A004D8C000802FB /* Icon Credit.txt in Resources */ = {isa = PBXBuildFile; fileRef = 22A1BFCC1A004D8C000802FB /* Icon Credit.txt */; }; |
|
32 |
+/* End PBXBuildFile section */ |
|
33 |
+ |
|
34 |
+/* Begin PBXContainerItemProxy section */ |
|
35 |
+ 223F9A3F19F894DB000802FB /* PBXContainerItemProxy */ = { |
|
36 |
+ isa = PBXContainerItemProxy; |
|
37 |
+ containerPortal = 223F99FB19F894AE000802FB /* Project object */; |
|
38 |
+ proxyType = 1; |
|
39 |
+ remoteGlobalIDString = 223F9A2919F894DB000802FB; |
|
40 |
+ remoteInfo = Widget; |
|
41 |
+ }; |
|
42 |
+ 223F9A5419F89532000802FB /* PBXContainerItemProxy */ = { |
|
43 |
+ isa = PBXContainerItemProxy; |
|
44 |
+ containerPortal = 223F99FB19F894AE000802FB /* Project object */; |
|
45 |
+ proxyType = 1; |
|
46 |
+ remoteGlobalIDString = 223F9A4919F89532000802FB; |
|
47 |
+ remoteInfo = XPC; |
|
48 |
+ }; |
|
49 |
+ 223F9A7919F89745000802FB /* PBXContainerItemProxy */ = { |
|
50 |
+ isa = PBXContainerItemProxy; |
|
51 |
+ containerPortal = 223F99FB19F894AE000802FB /* Project object */; |
|
52 |
+ proxyType = 1; |
|
53 |
+ remoteGlobalIDString = 223F9A4919F89532000802FB; |
|
54 |
+ remoteInfo = XPC; |
|
55 |
+ }; |
|
56 |
+/* End PBXContainerItemProxy section */ |
|
57 |
+ |
|
58 |
+/* Begin PBXCopyFilesBuildPhase section */ |
|
59 |
+ 223F9A4519F894DB000802FB /* Embed App Extensions */ = { |
|
60 |
+ isa = PBXCopyFilesBuildPhase; |
|
61 |
+ buildActionMask = 2147483647; |
|
62 |
+ dstPath = ""; |
|
63 |
+ dstSubfolderSpec = 13; |
|
64 |
+ files = ( |
|
65 |
+ 223F9A4119F894DB000802FB /* Widget.appex in Embed App Extensions */, |
|
66 |
+ ); |
|
67 |
+ name = "Embed App Extensions"; |
|
68 |
+ runOnlyForDeploymentPostprocessing = 0; |
|
69 |
+ }; |
|
70 |
+ 223F9A5A19F89532000802FB /* Embed XPC Services */ = { |
|
71 |
+ isa = PBXCopyFilesBuildPhase; |
|
72 |
+ buildActionMask = 2147483647; |
|
73 |
+ dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; |
|
74 |
+ dstSubfolderSpec = 16; |
|
75 |
+ files = ( |
|
76 |
+ ); |
|
77 |
+ name = "Embed XPC Services"; |
|
78 |
+ runOnlyForDeploymentPostprocessing = 0; |
|
79 |
+ }; |
|
80 |
+ 2265CC7519F89D7F000802FB /* CopyFiles */ = { |
|
81 |
+ isa = PBXCopyFilesBuildPhase; |
|
82 |
+ buildActionMask = 2147483647; |
|
83 |
+ dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; |
|
84 |
+ dstSubfolderSpec = 16; |
|
85 |
+ files = ( |
|
86 |
+ 2265CC7619F89D86000802FB /* XPC.xpc in CopyFiles */, |
|
87 |
+ ); |
|
88 |
+ runOnlyForDeploymentPostprocessing = 0; |
|
89 |
+ }; |
|
90 |
+/* End PBXCopyFilesBuildPhase section */ |
|
91 |
+ |
|
92 |
+/* Begin PBXFileReference section */ |
|
93 |
+ 223F9A0319F894AE000802FB /* Today Scripts.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Today Scripts.app"; sourceTree = BUILT_PRODUCTS_DIR; }; |
|
94 |
+ 223F9A0719F894AF000802FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; |
|
95 |
+ 223F9A0819F894AF000802FB /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; }; |
|
96 |
+ 223F9A0919F894AF000802FB /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; }; |
|
97 |
+ 223F9A0B19F894AF000802FB /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; |
|
98 |
+ 223F9A2A19F894DB000802FB /* Widget.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Widget.appex; sourceTree = BUILT_PRODUCTS_DIR; }; |
|
99 |
+ 223F9A2C19F894DB000802FB /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; |
|
100 |
+ 223F9A3019F894DB000802FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; |
|
101 |
+ 223F9A3119F894DB000802FB /* Widget.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Widget.entitlements; sourceTree = "<group>"; }; |
|
102 |
+ 223F9A3319F894DB000802FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; }; |
|
103 |
+ 223F9A4A19F89532000802FB /* XPC.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = XPC.xpc; sourceTree = BUILT_PRODUCTS_DIR; }; |
|
104 |
+ 223F9A4D19F89532000802FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; |
|
105 |
+ 223F9A5B19F895AF000802FB /* TodayScripts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TodayScripts.h; sourceTree = "<group>"; }; |
|
106 |
+ 223F9A5C19F895AF000802FB /* TodayScripts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TodayScripts.m; sourceTree = "<group>"; }; |
|
107 |
+ 223F9A5F19F8963C000802FB /* AMR_ANSIEscapeHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMR_ANSIEscapeHelper.h; sourceTree = "<group>"; }; |
|
108 |
+ 223F9A6019F8963C000802FB /* AMR_ANSIEscapeHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMR_ANSIEscapeHelper.m; sourceTree = "<group>"; }; |
|
109 |
+ 223F9A6119F8963C000802FB /* ListRowViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ListRowViewController.h; sourceTree = "<group>"; }; |
|
110 |
+ 223F9A6219F8963C000802FB /* ListRowViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ListRowViewController.m; sourceTree = "<group>"; }; |
|
111 |
+ 223F9A6319F8963C000802FB /* ListRowViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ListRowViewController.xib; sourceTree = "<group>"; }; |
|
112 |
+ 223F9A6419F8963C000802FB /* EditViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EditViewController.h; sourceTree = "<group>"; }; |
|
113 |
+ 223F9A6519F8963C000802FB /* EditViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EditViewController.m; sourceTree = "<group>"; }; |
|
114 |
+ 223F9A6619F8963C000802FB /* EditViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EditViewController.xib; sourceTree = "<group>"; }; |
|
115 |
+ 223F9A6719F8963C000802FB /* TodayScript.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TodayScript.h; sourceTree = "<group>"; }; |
|
116 |
+ 223F9A6819F8963C000802FB /* TodayScript.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TodayScript.m; sourceTree = "<group>"; }; |
|
117 |
+ 223F9A6919F8963C000802FB /* TodayViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TodayViewController.h; sourceTree = "<group>"; }; |
|
118 |
+ 223F9A6A19F8963C000802FB /* TodayViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TodayViewController.m; sourceTree = "<group>"; }; |
|
119 |
+ 223F9A6B19F8963C000802FB /* TodayViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TodayViewController.xib; sourceTree = "<group>"; }; |
|
120 |
+ 223F9A7419F896BF000802FB /* XPCHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XPCHelper.m; sourceTree = "<group>"; }; |
|
121 |
+ 223F9A7519F896BF000802FB /* XPCMain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XPCMain.h; sourceTree = "<group>"; }; |
|
122 |
+ 223F9A7619F896BF000802FB /* XPCMain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XPCMain.m; sourceTree = "<group>"; }; |
|
123 |
+ 2265B37519FC519C000802FB /* AppIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = AppIcon.icns; sourceTree = "<group>"; }; |
|
124 |
+ 2265B37719FC51DF000802FB /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; }; |
|
125 |
+ 2265B37A19FC53CA000802FB /* readme.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = readme.txt; sourceTree = "<group>"; }; |
|
126 |
+ 2265CC7219F89D18000802FB /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; }; |
|
127 |
+ 22A1BFCC1A004D8C000802FB /* Icon Credit.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Icon Credit.txt"; sourceTree = "<group>"; }; |
|
128 |
+/* End PBXFileReference section */ |
|
129 |
+ |
|
130 |
+/* Begin PBXFrameworksBuildPhase section */ |
|
131 |
+ 223F9A0019F894AE000802FB /* Frameworks */ = { |
|
132 |
+ isa = PBXFrameworksBuildPhase; |
|
133 |
+ buildActionMask = 2147483647; |
|
134 |
+ files = ( |
|
135 |
+ ); |
|
136 |
+ runOnlyForDeploymentPostprocessing = 0; |
|
137 |
+ }; |
|
138 |
+ 223F9A2719F894DB000802FB /* Frameworks */ = { |
|
139 |
+ isa = PBXFrameworksBuildPhase; |
|
140 |
+ buildActionMask = 2147483647; |
|
141 |
+ files = ( |
|
142 |
+ 223F9A2D19F894DB000802FB /* NotificationCenter.framework in Frameworks */, |
|
143 |
+ ); |
|
144 |
+ runOnlyForDeploymentPostprocessing = 0; |
|
145 |
+ }; |
|
146 |
+ 223F9A4719F89532000802FB /* Frameworks */ = { |
|
147 |
+ isa = PBXFrameworksBuildPhase; |
|
148 |
+ buildActionMask = 2147483647; |
|
149 |
+ files = ( |
|
150 |
+ ); |
|
151 |
+ runOnlyForDeploymentPostprocessing = 0; |
|
152 |
+ }; |
|
153 |
+/* End PBXFrameworksBuildPhase section */ |
|
154 |
+ |
|
155 |
+/* Begin PBXGroup section */ |
|
156 |
+ 223F99FA19F894AE000802FB = { |
|
157 |
+ isa = PBXGroup; |
|
158 |
+ children = ( |
|
159 |
+ 223F9A5B19F895AF000802FB /* TodayScripts.h */, |
|
160 |
+ 223F9A5C19F895AF000802FB /* TodayScripts.m */, |
|
161 |
+ 223F9A0519F894AE000802FB /* Today Scripts */, |
|
162 |
+ 223F9A2E19F894DB000802FB /* Widget */, |
|
163 |
+ 223F9A4B19F89532000802FB /* XPC */, |
|
164 |
+ 223F9A2B19F894DB000802FB /* Frameworks */, |
|
165 |
+ 223F9A0419F894AE000802FB /* Products */, |
|
166 |
+ ); |
|
167 |
+ sourceTree = "<group>"; |
|
168 |
+ }; |
|
169 |
+ 223F9A0419F894AE000802FB /* Products */ = { |
|
170 |
+ isa = PBXGroup; |
|
171 |
+ children = ( |
|
172 |
+ 223F9A0319F894AE000802FB /* Today Scripts.app */, |
|
173 |
+ 223F9A2A19F894DB000802FB /* Widget.appex */, |
|
174 |
+ 223F9A4A19F89532000802FB /* XPC.xpc */, |
|
175 |
+ ); |
|
176 |
+ name = Products; |
|
177 |
+ sourceTree = "<group>"; |
|
178 |
+ }; |
|
179 |
+ 223F9A0519F894AE000802FB /* Today Scripts */ = { |
|
180 |
+ isa = PBXGroup; |
|
181 |
+ children = ( |
|
182 |
+ 223F9A0819F894AF000802FB /* AppDelegate.h */, |
|
183 |
+ 223F9A0919F894AF000802FB /* AppDelegate.m */, |
|
184 |
+ 2265CC7219F89D18000802FB /* MainMenu.xib */, |
|
185 |
+ 223F9A0619F894AF000802FB /* Supporting Files */, |
|
186 |
+ ); |
|
187 |
+ path = "Today Scripts"; |
|
188 |
+ sourceTree = "<group>"; |
|
189 |
+ }; |
|
190 |
+ 223F9A0619F894AF000802FB /* Supporting Files */ = { |
|
191 |
+ isa = PBXGroup; |
|
192 |
+ children = ( |
|
193 |
+ 22A1BFCC1A004D8C000802FB /* Icon Credit.txt */, |
|
194 |
+ 2265B37519FC519C000802FB /* AppIcon.icns */, |
|
195 |
+ 223F9A0719F894AF000802FB /* Info.plist */, |
|
196 |
+ 223F9A0B19F894AF000802FB /* main.m */, |
|
197 |
+ ); |
|
198 |
+ name = "Supporting Files"; |
|
199 |
+ sourceTree = "<group>"; |
|
200 |
+ }; |
|
201 |
+ 223F9A2B19F894DB000802FB /* Frameworks */ = { |
|
202 |
+ isa = PBXGroup; |
|
203 |
+ children = ( |
|
204 |
+ 223F9A2C19F894DB000802FB /* NotificationCenter.framework */, |
|
205 |
+ ); |
|
206 |
+ name = Frameworks; |
|
207 |
+ sourceTree = "<group>"; |
|
208 |
+ }; |
|
209 |
+ 223F9A2E19F894DB000802FB /* Widget */ = { |
|
210 |
+ isa = PBXGroup; |
|
211 |
+ children = ( |
|
212 |
+ 223F9A6719F8963C000802FB /* TodayScript.h */, |
|
213 |
+ 223F9A6819F8963C000802FB /* TodayScript.m */, |
|
214 |
+ 223F9A6919F8963C000802FB /* TodayViewController.h */, |
|
215 |
+ 223F9A6A19F8963C000802FB /* TodayViewController.m */, |
|
216 |
+ 223F9A6B19F8963C000802FB /* TodayViewController.xib */, |
|
217 |
+ 223F9A6119F8963C000802FB /* ListRowViewController.h */, |
|
218 |
+ 223F9A6219F8963C000802FB /* ListRowViewController.m */, |
|
219 |
+ 223F9A6319F8963C000802FB /* ListRowViewController.xib */, |
|
220 |
+ 223F9A6419F8963C000802FB /* EditViewController.h */, |
|
221 |
+ 223F9A6519F8963C000802FB /* EditViewController.m */, |
|
222 |
+ 223F9A6619F8963C000802FB /* EditViewController.xib */, |
|
223 |
+ 2265B37919FC539E000802FB /* AMR_ANSIEscapeHelper */, |
|
224 |
+ 223F9A2F19F894DB000802FB /* Supporting Files */, |
|
225 |
+ ); |
|
226 |
+ path = Widget; |
|
227 |
+ sourceTree = "<group>"; |
|
228 |
+ }; |
|
229 |
+ 223F9A2F19F894DB000802FB /* Supporting Files */ = { |
|
230 |
+ isa = PBXGroup; |
|
231 |
+ children = ( |
|
232 |
+ 2265B37719FC51DF000802FB /* Images.xcassets */, |
|
233 |
+ 223F9A3019F894DB000802FB /* Info.plist */, |
|
234 |
+ 223F9A3119F894DB000802FB /* Widget.entitlements */, |
|
235 |
+ 223F9A3219F894DB000802FB /* InfoPlist.strings */, |
|
236 |
+ ); |
|
237 |
+ name = "Supporting Files"; |
|
238 |
+ sourceTree = "<group>"; |
|
239 |
+ }; |
|
240 |
+ 223F9A4B19F89532000802FB /* XPC */ = { |
|
241 |
+ isa = PBXGroup; |
|
242 |
+ children = ( |
|
243 |
+ 223F9A7519F896BF000802FB /* XPCMain.h */, |
|
244 |
+ 223F9A7619F896BF000802FB /* XPCMain.m */, |
|
245 |
+ 223F9A7419F896BF000802FB /* XPCHelper.m */, |
|
246 |
+ 223F9A4C19F89532000802FB /* Supporting Files */, |
|
247 |
+ ); |
|
248 |
+ path = XPC; |
|
249 |
+ sourceTree = "<group>"; |
|
250 |
+ }; |
|
251 |
+ 223F9A4C19F89532000802FB /* Supporting Files */ = { |
|
252 |
+ isa = PBXGroup; |
|
253 |
+ children = ( |
|
254 |
+ 223F9A4D19F89532000802FB /* Info.plist */, |
|
255 |
+ ); |
|
256 |
+ name = "Supporting Files"; |
|
257 |
+ sourceTree = "<group>"; |
|
258 |
+ }; |
|
259 |
+ 2265B37919FC539E000802FB /* AMR_ANSIEscapeHelper */ = { |
|
260 |
+ isa = PBXGroup; |
|
261 |
+ children = ( |
|
262 |
+ 2265B37A19FC53CA000802FB /* readme.txt */, |
|
263 |
+ 223F9A5F19F8963C000802FB /* AMR_ANSIEscapeHelper.h */, |
|
264 |
+ 223F9A6019F8963C000802FB /* AMR_ANSIEscapeHelper.m */, |
|
265 |
+ ); |
|
266 |
+ path = AMR_ANSIEscapeHelper; |
|
267 |
+ sourceTree = "<group>"; |
|
268 |
+ }; |
|
269 |
+/* End PBXGroup section */ |
|
270 |
+ |
|
271 |
+/* Begin PBXNativeTarget section */ |
|
272 |
+ 223F9A0219F894AE000802FB /* Today Scripts */ = { |
|
273 |
+ isa = PBXNativeTarget; |
|
274 |
+ buildConfigurationList = 223F9A2019F894AF000802FB /* Build configuration list for PBXNativeTarget "Today Scripts" */; |
|
275 |
+ buildPhases = ( |
|
276 |
+ 223F99FF19F894AE000802FB /* Sources */, |
|
277 |
+ 223F9A0019F894AE000802FB /* Frameworks */, |
|
278 |
+ 223F9A0119F894AE000802FB /* Resources */, |
|
279 |
+ 223F9A4519F894DB000802FB /* Embed App Extensions */, |
|
280 |
+ 223F9A5A19F89532000802FB /* Embed XPC Services */, |
|
281 |
+ ); |
|
282 |
+ buildRules = ( |
|
283 |
+ ); |
|
284 |
+ dependencies = ( |
|
285 |
+ 223F9A4019F894DB000802FB /* PBXTargetDependency */, |
|
286 |
+ 223F9A5519F89532000802FB /* PBXTargetDependency */, |
|
287 |
+ ); |
|
288 |
+ name = "Today Scripts"; |
|
289 |
+ productName = "Today Scripts"; |
|
290 |
+ productReference = 223F9A0319F894AE000802FB /* Today Scripts.app */; |
|
291 |
+ productType = "com.apple.product-type.application"; |
|
292 |
+ }; |
|
293 |
+ 223F9A2919F894DB000802FB /* Widget */ = { |
|
294 |
+ isa = PBXNativeTarget; |
|
295 |
+ buildConfigurationList = 223F9A4219F894DB000802FB /* Build configuration list for PBXNativeTarget "Widget" */; |
|
296 |
+ buildPhases = ( |
|
297 |
+ 223F9A2619F894DB000802FB /* Sources */, |
|
298 |
+ 223F9A2719F894DB000802FB /* Frameworks */, |
|
299 |
+ 223F9A2819F894DB000802FB /* Resources */, |
|
300 |
+ 2265CC7519F89D7F000802FB /* CopyFiles */, |
|
301 |
+ ); |
|
302 |
+ buildRules = ( |
|
303 |
+ ); |
|
304 |
+ dependencies = ( |
|
305 |
+ 223F9A7A19F89745000802FB /* PBXTargetDependency */, |
|
306 |
+ ); |
|
307 |
+ name = Widget; |
|
308 |
+ productName = Widget; |
|
309 |
+ productReference = 223F9A2A19F894DB000802FB /* Widget.appex */; |
|
310 |
+ productType = "com.apple.product-type.app-extension"; |
|
311 |
+ }; |
|
312 |
+ 223F9A4919F89532000802FB /* XPC */ = { |
|
313 |
+ isa = PBXNativeTarget; |
|
314 |
+ buildConfigurationList = 223F9A5719F89532000802FB /* Build configuration list for PBXNativeTarget "XPC" */; |
|
315 |
+ buildPhases = ( |
|
316 |
+ 223F9A4619F89532000802FB /* Sources */, |
|
317 |
+ 223F9A4719F89532000802FB /* Frameworks */, |
|
318 |
+ 223F9A4819F89532000802FB /* Resources */, |
|
319 |
+ ); |
|
320 |
+ buildRules = ( |
|
321 |
+ ); |
|
322 |
+ dependencies = ( |
|
323 |
+ ); |
|
324 |
+ name = XPC; |
|
325 |
+ productName = XPC; |
|
326 |
+ productReference = 223F9A4A19F89532000802FB /* XPC.xpc */; |
|
327 |
+ productType = "com.apple.product-type.xpc-service"; |
|
328 |
+ }; |
|
329 |
+/* End PBXNativeTarget section */ |
|
330 |
+ |
|
331 |
+/* Begin PBXProject section */ |
|
332 |
+ 223F99FB19F894AE000802FB /* Project object */ = { |
|
333 |
+ isa = PBXProject; |
|
334 |
+ attributes = { |
|
335 |
+ LastUpgradeCheck = 0610; |
|
336 |
+ ORGANIZATIONNAME = "Sam Rothenberg"; |
|
337 |
+ TargetAttributes = { |
|
338 |
+ 223F9A0219F894AE000802FB = { |
|
339 |
+ CreatedOnToolsVersion = 6.1; |
|
340 |
+ }; |
|
341 |
+ 223F9A2919F894DB000802FB = { |
|
342 |
+ CreatedOnToolsVersion = 6.1; |
|
343 |
+ }; |
|
344 |
+ 223F9A4919F89532000802FB = { |
|
345 |
+ CreatedOnToolsVersion = 6.1; |
|
346 |
+ SystemCapabilities = { |
|
347 |
+ com.apple.Sandbox = { |
|
348 |
+ enabled = 0; |
|
349 |
+ }; |
|
350 |
+ }; |
|
351 |
+ }; |
|
352 |
+ }; |
|
353 |
+ }; |
|
354 |
+ buildConfigurationList = 223F99FE19F894AE000802FB /* Build configuration list for PBXProject "Today Scripts" */; |
|
355 |
+ compatibilityVersion = "Xcode 3.2"; |
|
356 |
+ developmentRegion = English; |
|
357 |
+ hasScannedForEncodings = 0; |
|
358 |
+ knownRegions = ( |
|
359 |
+ en, |
|
360 |
+ Base, |
|
361 |
+ ); |
|
362 |
+ mainGroup = 223F99FA19F894AE000802FB; |
|
363 |
+ productRefGroup = 223F9A0419F894AE000802FB /* Products */; |
|
364 |
+ projectDirPath = ""; |
|
365 |
+ projectRoot = ""; |
|
366 |
+ targets = ( |
|
367 |
+ 223F9A0219F894AE000802FB /* Today Scripts */, |
|
368 |
+ 223F9A2919F894DB000802FB /* Widget */, |
|
369 |
+ 223F9A4919F89532000802FB /* XPC */, |
|
370 |
+ ); |
|
371 |
+ }; |
|
372 |
+/* End PBXProject section */ |
|
373 |
+ |
|
374 |
+/* Begin PBXResourcesBuildPhase section */ |
|
375 |
+ 223F9A0119F894AE000802FB /* Resources */ = { |
|
376 |
+ isa = PBXResourcesBuildPhase; |
|
377 |
+ buildActionMask = 2147483647; |
|
378 |
+ files = ( |
|
379 |
+ 2265CC7319F89D18000802FB /* MainMenu.xib in Resources */, |
|
380 |
+ 2265B37619FC519C000802FB /* AppIcon.icns in Resources */, |
|
381 |
+ 22A1BFCD1A004D8C000802FB /* Icon Credit.txt in Resources */, |
|
382 |
+ ); |
|
383 |
+ runOnlyForDeploymentPostprocessing = 0; |
|
384 |
+ }; |
|
385 |
+ 223F9A2819F894DB000802FB /* Resources */ = { |
|
386 |
+ isa = PBXResourcesBuildPhase; |
|
387 |
+ buildActionMask = 2147483647; |
|
388 |
+ files = ( |
|
389 |
+ 223F9A6E19F8963C000802FB /* ListRowViewController.xib in Resources */, |
|
390 |
+ 2265B37819FC51DF000802FB /* Images.xcassets in Resources */, |
|
391 |
+ 223F9A7319F8963C000802FB /* TodayViewController.xib in Resources */, |
|
392 |
+ 223F9A7019F8963C000802FB /* EditViewController.xib in Resources */, |
|
393 |
+ 223F9A3419F894DB000802FB /* InfoPlist.strings in Resources */, |
|
394 |
+ ); |
|
395 |
+ runOnlyForDeploymentPostprocessing = 0; |
|
396 |
+ }; |
|
397 |
+ 223F9A4819F89532000802FB /* Resources */ = { |
|
398 |
+ isa = PBXResourcesBuildPhase; |
|
399 |
+ buildActionMask = 2147483647; |
|
400 |
+ files = ( |
|
401 |
+ ); |
|
402 |
+ runOnlyForDeploymentPostprocessing = 0; |
|
403 |
+ }; |
|
404 |
+/* End PBXResourcesBuildPhase section */ |
|
405 |
+ |
|
406 |
+/* Begin PBXSourcesBuildPhase section */ |
|
407 |
+ 223F99FF19F894AE000802FB /* Sources */ = { |
|
408 |
+ isa = PBXSourcesBuildPhase; |
|
409 |
+ buildActionMask = 2147483647; |
|
410 |
+ files = ( |
|
411 |
+ 223F9A0C19F894AF000802FB /* main.m in Sources */, |
|
412 |
+ 223F9A0A19F894AF000802FB /* AppDelegate.m in Sources */, |
|
413 |
+ ); |
|
414 |
+ runOnlyForDeploymentPostprocessing = 0; |
|
415 |
+ }; |
|
416 |
+ 223F9A2619F894DB000802FB /* Sources */ = { |
|
417 |
+ isa = PBXSourcesBuildPhase; |
|
418 |
+ buildActionMask = 2147483647; |
|
419 |
+ files = ( |
|
420 |
+ 223F9A7219F8963C000802FB /* TodayViewController.m in Sources */, |
|
421 |
+ 223F9A6C19F8963C000802FB /* AMR_ANSIEscapeHelper.m in Sources */, |
|
422 |
+ 223F9A5D19F895AF000802FB /* TodayScripts.m in Sources */, |
|
423 |
+ 223F9A6D19F8963C000802FB /* ListRowViewController.m in Sources */, |
|
424 |
+ 223F9A6F19F8963C000802FB /* EditViewController.m in Sources */, |
|
425 |
+ 223F9A7119F8963C000802FB /* TodayScript.m in Sources */, |
|
426 |
+ ); |
|
427 |
+ runOnlyForDeploymentPostprocessing = 0; |
|
428 |
+ }; |
|
429 |
+ 223F9A4619F89532000802FB /* Sources */ = { |
|
430 |
+ isa = PBXSourcesBuildPhase; |
|
431 |
+ buildActionMask = 2147483647; |
|
432 |
+ files = ( |
|
433 |
+ 223F9A5E19F895AF000802FB /* TodayScripts.m in Sources */, |
|
434 |
+ 223F9A7819F896BF000802FB /* XPCMain.m in Sources */, |
|
435 |
+ 223F9A7719F896BF000802FB /* XPCHelper.m in Sources */, |
|
436 |
+ ); |
|
437 |
+ runOnlyForDeploymentPostprocessing = 0; |
|
438 |
+ }; |
|
439 |
+/* End PBXSourcesBuildPhase section */ |
|
440 |
+ |
|
441 |
+/* Begin PBXTargetDependency section */ |
|
442 |
+ 223F9A4019F894DB000802FB /* PBXTargetDependency */ = { |
|
443 |
+ isa = PBXTargetDependency; |
|
444 |
+ target = 223F9A2919F894DB000802FB /* Widget */; |
|
445 |
+ targetProxy = 223F9A3F19F894DB000802FB /* PBXContainerItemProxy */; |
|
446 |
+ }; |
|
447 |
+ 223F9A5519F89532000802FB /* PBXTargetDependency */ = { |
|
448 |
+ isa = PBXTargetDependency; |
|
449 |
+ target = 223F9A4919F89532000802FB /* XPC */; |
|
450 |
+ targetProxy = 223F9A5419F89532000802FB /* PBXContainerItemProxy */; |
|
451 |
+ }; |
|
452 |
+ 223F9A7A19F89745000802FB /* PBXTargetDependency */ = { |
|
453 |
+ isa = PBXTargetDependency; |
|
454 |
+ target = 223F9A4919F89532000802FB /* XPC */; |
|
455 |
+ targetProxy = 223F9A7919F89745000802FB /* PBXContainerItemProxy */; |
|
456 |
+ }; |
|
457 |
+/* End PBXTargetDependency section */ |
|
458 |
+ |
|
459 |
+/* Begin PBXVariantGroup section */ |
|
460 |
+ 223F9A3219F894DB000802FB /* InfoPlist.strings */ = { |
|
461 |
+ isa = PBXVariantGroup; |
|
462 |
+ children = ( |
|
463 |
+ 223F9A3319F894DB000802FB /* en */, |
|
464 |
+ ); |
|
465 |
+ name = InfoPlist.strings; |
|
466 |
+ sourceTree = "<group>"; |
|
467 |
+ }; |
|
468 |
+/* End PBXVariantGroup section */ |
|
469 |
+ |
|
470 |
+/* Begin XCBuildConfiguration section */ |
|
471 |
+ 223F9A1E19F894AF000802FB /* Debug */ = { |
|
472 |
+ isa = XCBuildConfiguration; |
|
473 |
+ buildSettings = { |
|
474 |
+ ALWAYS_SEARCH_USER_PATHS = NO; |
|
475 |
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; |
|
476 |
+ CLANG_CXX_LIBRARY = "libc++"; |
|
477 |
+ CLANG_ENABLE_MODULES = YES; |
|
478 |
+ CLANG_ENABLE_OBJC_ARC = YES; |
|
479 |
+ CLANG_WARN_BOOL_CONVERSION = YES; |
|
480 |
+ CLANG_WARN_CONSTANT_CONVERSION = YES; |
|
481 |
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; |
|
482 |
+ CLANG_WARN_EMPTY_BODY = YES; |
|
483 |
+ CLANG_WARN_ENUM_CONVERSION = YES; |
|
484 |
+ CLANG_WARN_INT_CONVERSION = YES; |
|
485 |
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; |
|
486 |
+ CLANG_WARN_UNREACHABLE_CODE = YES; |
|
487 |
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; |
|
488 |
+ CODE_SIGN_IDENTITY = "-"; |
|
489 |
+ COPY_PHASE_STRIP = NO; |
|
490 |
+ ENABLE_STRICT_OBJC_MSGSEND = YES; |
|
491 |
+ GCC_C_LANGUAGE_STANDARD = gnu99; |
|
492 |
+ GCC_DYNAMIC_NO_PIC = NO; |
|
493 |
+ GCC_OPTIMIZATION_LEVEL = 0; |
|
494 |
+ GCC_PREPROCESSOR_DEFINITIONS = ( |
|
495 |
+ "DEBUG=1", |
|
496 |
+ "$(inherited)", |
|
497 |
+ ); |
|
498 |
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO; |
|
499 |
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; |
|
500 |
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; |
|
501 |
+ GCC_WARN_UNDECLARED_SELECTOR = YES; |
|
502 |
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; |
|
503 |
+ GCC_WARN_UNUSED_FUNCTION = YES; |
|
504 |
+ GCC_WARN_UNUSED_VARIABLE = YES; |
|
505 |
+ MACOSX_DEPLOYMENT_TARGET = 10.10; |
|
506 |
+ MTL_ENABLE_DEBUG_INFO = YES; |
|
507 |
+ ONLY_ACTIVE_ARCH = YES; |
|
508 |
+ SDKROOT = macosx; |
|
509 |
+ }; |
|
510 |
+ name = Debug; |
|
511 |
+ }; |
|
512 |
+ 223F9A1F19F894AF000802FB /* Release */ = { |
|
513 |
+ isa = XCBuildConfiguration; |
|
514 |
+ buildSettings = { |
|
515 |
+ ALWAYS_SEARCH_USER_PATHS = NO; |
|
516 |
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; |
|
517 |
+ CLANG_CXX_LIBRARY = "libc++"; |
|
518 |
+ CLANG_ENABLE_MODULES = YES; |
|
519 |
+ CLANG_ENABLE_OBJC_ARC = YES; |
|
520 |
+ CLANG_WARN_BOOL_CONVERSION = YES; |
|
521 |
+ CLANG_WARN_CONSTANT_CONVERSION = YES; |
|
522 |
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; |
|
523 |
+ CLANG_WARN_EMPTY_BODY = YES; |
|
524 |
+ CLANG_WARN_ENUM_CONVERSION = YES; |
|
525 |
+ CLANG_WARN_INT_CONVERSION = YES; |
|
526 |
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; |
|
527 |
+ CLANG_WARN_UNREACHABLE_CODE = YES; |
|
528 |
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; |
|
529 |
+ CODE_SIGN_IDENTITY = "-"; |
|
530 |
+ COPY_PHASE_STRIP = YES; |
|
531 |
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; |
|
532 |
+ ENABLE_NS_ASSERTIONS = NO; |
|
533 |
+ ENABLE_STRICT_OBJC_MSGSEND = YES; |
|
534 |
+ GCC_C_LANGUAGE_STANDARD = gnu99; |
|
535 |
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES; |
|
536 |
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; |
|
537 |
+ GCC_WARN_UNDECLARED_SELECTOR = YES; |
|
538 |
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; |
|
539 |
+ GCC_WARN_UNUSED_FUNCTION = YES; |
|
540 |
+ GCC_WARN_UNUSED_VARIABLE = YES; |
|
541 |
+ MACOSX_DEPLOYMENT_TARGET = 10.10; |
|
542 |
+ MTL_ENABLE_DEBUG_INFO = NO; |
|
543 |
+ SDKROOT = macosx; |
|
544 |
+ }; |
|
545 |
+ name = Release; |
|
546 |
+ }; |
|
547 |
+ 223F9A2119F894AF000802FB /* Debug */ = { |
|
548 |
+ isa = XCBuildConfiguration; |
|
549 |
+ buildSettings = { |
|
550 |
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
|
551 |
+ COMBINE_HIDPI_IMAGES = YES; |
|
552 |
+ INFOPLIST_FILE = "Today Scripts/Info.plist"; |
|
553 |
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; |
|
554 |
+ PRODUCT_NAME = "$(TARGET_NAME)"; |
|
555 |
+ }; |
|
556 |
+ name = Debug; |
|
557 |
+ }; |
|
558 |
+ 223F9A2219F894AF000802FB /* Release */ = { |
|
559 |
+ isa = XCBuildConfiguration; |
|
560 |
+ buildSettings = { |
|
561 |
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
|
562 |
+ COMBINE_HIDPI_IMAGES = YES; |
|
563 |
+ INFOPLIST_FILE = "Today Scripts/Info.plist"; |
|
564 |
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; |
|
565 |
+ PRODUCT_NAME = "$(TARGET_NAME)"; |
|
566 |
+ }; |
|
567 |
+ name = Release; |
|
568 |
+ }; |
|
569 |
+ 223F9A4319F894DB000802FB /* Debug */ = { |
|
570 |
+ isa = XCBuildConfiguration; |
|
571 |
+ buildSettings = { |
|
572 |
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
|
573 |
+ CODE_SIGN_ENTITLEMENTS = Widget/Widget.entitlements; |
|
574 |
+ CODE_SIGN_IDENTITY = "-"; |
|
575 |
+ COMBINE_HIDPI_IMAGES = YES; |
|
576 |
+ GCC_PREPROCESSOR_DEFINITIONS = ( |
|
577 |
+ "DEBUG=1", |
|
578 |
+ "$(inherited)", |
|
579 |
+ ); |
|
580 |
+ INFOPLIST_FILE = Widget/Info.plist; |
|
581 |
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; |
|
582 |
+ PRODUCT_NAME = "$(TARGET_NAME)"; |
|
583 |
+ SKIP_INSTALL = YES; |
|
584 |
+ }; |
|
585 |
+ name = Debug; |
|
586 |
+ }; |
|
587 |
+ 223F9A4419F894DB000802FB /* Release */ = { |
|
588 |
+ isa = XCBuildConfiguration; |
|
589 |
+ buildSettings = { |
|
590 |
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; |
|
591 |
+ CODE_SIGN_ENTITLEMENTS = Widget/Widget.entitlements; |
|
592 |
+ CODE_SIGN_IDENTITY = "-"; |
|
593 |
+ COMBINE_HIDPI_IMAGES = YES; |
|
594 |
+ INFOPLIST_FILE = Widget/Info.plist; |
|
595 |
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; |
|
596 |
+ PRODUCT_NAME = "$(TARGET_NAME)"; |
|
597 |
+ SKIP_INSTALL = YES; |
|
598 |
+ }; |
|
599 |
+ name = Release; |
|
600 |
+ }; |
|
601 |
+ 223F9A5819F89532000802FB /* Debug */ = { |
|
602 |
+ isa = XCBuildConfiguration; |
|
603 |
+ buildSettings = { |
|
604 |
+ COMBINE_HIDPI_IMAGES = YES; |
|
605 |
+ GCC_PREPROCESSOR_DEFINITIONS = ( |
|
606 |
+ "DEBUG=1", |
|
607 |
+ "$(inherited)", |
|
608 |
+ ); |
|
609 |
+ INFOPLIST_FILE = XPC/Info.plist; |
|
610 |
+ PRODUCT_NAME = "$(TARGET_NAME)"; |
|
611 |
+ SKIP_INSTALL = YES; |
|
612 |
+ }; |
|
613 |
+ name = Debug; |
|
614 |
+ }; |
|
615 |
+ 223F9A5919F89532000802FB /* Release */ = { |
|
616 |
+ isa = XCBuildConfiguration; |
|
617 |
+ buildSettings = { |
|
618 |
+ COMBINE_HIDPI_IMAGES = YES; |
|
619 |
+ INFOPLIST_FILE = XPC/Info.plist; |
|
620 |
+ PRODUCT_NAME = "$(TARGET_NAME)"; |
|
621 |
+ SKIP_INSTALL = YES; |
|
622 |
+ }; |
|
623 |
+ name = Release; |
|
624 |
+ }; |
|
625 |
+/* End XCBuildConfiguration section */ |
|
626 |
+ |
|
627 |
+/* Begin XCConfigurationList section */ |
|
628 |
+ 223F99FE19F894AE000802FB /* Build configuration list for PBXProject "Today Scripts" */ = { |
|
629 |
+ isa = XCConfigurationList; |
|
630 |
+ buildConfigurations = ( |
|
631 |
+ 223F9A1E19F894AF000802FB /* Debug */, |
|
632 |
+ 223F9A1F19F894AF000802FB /* Release */, |
|
633 |
+ ); |
|
634 |
+ defaultConfigurationIsVisible = 0; |
|
635 |
+ defaultConfigurationName = Release; |
|
636 |
+ }; |
|
637 |
+ 223F9A2019F894AF000802FB /* Build configuration list for PBXNativeTarget "Today Scripts" */ = { |
|
638 |
+ isa = XCConfigurationList; |
|
639 |
+ buildConfigurations = ( |
|
640 |
+ 223F9A2119F894AF000802FB /* Debug */, |
|
641 |
+ 223F9A2219F894AF000802FB /* Release */, |
|
642 |
+ ); |
|
643 |
+ defaultConfigurationIsVisible = 0; |
|
644 |
+ defaultConfigurationName = Release; |
|
645 |
+ }; |
|
646 |
+ 223F9A4219F894DB000802FB /* Build configuration list for PBXNativeTarget "Widget" */ = { |
|
647 |
+ isa = XCConfigurationList; |
|
648 |
+ buildConfigurations = ( |
|
649 |
+ 223F9A4319F894DB000802FB /* Debug */, |
|
650 |
+ 223F9A4419F894DB000802FB /* Release */, |
|
651 |
+ ); |
|
652 |
+ defaultConfigurationIsVisible = 0; |
|
653 |
+ defaultConfigurationName = Release; |
|
654 |
+ }; |
|
655 |
+ 223F9A5719F89532000802FB /* Build configuration list for PBXNativeTarget "XPC" */ = { |
|
656 |
+ isa = XCConfigurationList; |
|
657 |
+ buildConfigurations = ( |
|
658 |
+ 223F9A5819F89532000802FB /* Debug */, |
|
659 |
+ 223F9A5919F89532000802FB /* Release */, |
|
660 |
+ ); |
|
661 |
+ defaultConfigurationIsVisible = 0; |
|
662 |
+ defaultConfigurationName = Release; |
|
663 |
+ }; |
|
664 |
+/* End XCConfigurationList section */ |
|
665 |
+ }; |
|
666 |
+ rootObject = 223F99FB19F894AE000802FB /* Project object */; |
|
667 |
+} |
0 | 668 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,15 @@ |
1 |
+// |
|
2 |
+// AppDelegate.h |
|
3 |
+// Today Scripts |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 10/22/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import <Cocoa/Cocoa.h> |
|
10 |
+ |
|
11 |
+@interface AppDelegate : NSObject <NSApplicationDelegate> |
|
12 |
+ |
|
13 |
+ |
|
14 |
+@end |
|
15 |
+ |
0 | 16 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,21 @@ |
1 |
+// |
|
2 |
+// AppDelegate.m |
|
3 |
+// Today Scripts |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 10/22/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import "AppDelegate.h" |
|
10 |
+ |
|
11 |
+@interface AppDelegate () |
|
12 |
+ |
|
13 |
+@end |
|
14 |
+ |
|
15 |
+@implementation AppDelegate |
|
16 |
+ |
|
17 |
+- (void)applicationWillFinishLaunching:(NSNotification *)aNotification { |
|
18 |
+// [NSApplication.sharedApplication terminate:self]; |
|
19 |
+} |
|
20 |
+ |
|
21 |
+@end |
1 | 4 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,36 @@ |
1 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
2 |
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|
3 |
+<plist version="1.0"> |
|
4 |
+<dict> |
|
5 |
+ <key>CFBundleDevelopmentRegion</key> |
|
6 |
+ <string>en</string> |
|
7 |
+ <key>CFBundleExecutable</key> |
|
8 |
+ <string>$(EXECUTABLE_NAME)</string> |
|
9 |
+ <key>CFBundleIconFile</key> |
|
10 |
+ <string>AppIcon</string> |
|
11 |
+ <key>CFBundleIdentifier</key> |
|
12 |
+ <string>org.samroth.$(PRODUCT_NAME:rfc1034identifier)</string> |
|
13 |
+ <key>CFBundleInfoDictionaryVersion</key> |
|
14 |
+ <string>6.0</string> |
|
15 |
+ <key>CFBundleName</key> |
|
16 |
+ <string>$(PRODUCT_NAME)</string> |
|
17 |
+ <key>CFBundlePackageType</key> |
|
18 |
+ <string>APPL</string> |
|
19 |
+ <key>CFBundleShortVersionString</key> |
|
20 |
+ <string>1.0</string> |
|
21 |
+ <key>CFBundleSignature</key> |
|
22 |
+ <string>????</string> |
|
23 |
+ <key>CFBundleVersion</key> |
|
24 |
+ <string>1</string> |
|
25 |
+ <key>LSMinimumSystemVersion</key> |
|
26 |
+ <string>$(MACOSX_DEPLOYMENT_TARGET)</string> |
|
27 |
+ <key>LSUIElement</key> |
|
28 |
+ <true/> |
|
29 |
+ <key>NSHumanReadableCopyright</key> |
|
30 |
+ <string>Copyright © 2014 Sam Rothenberg. All rights reserved.</string> |
|
31 |
+ <key>NSMainNibFile</key> |
|
32 |
+ <string>MainMenu</string> |
|
33 |
+ <key>NSPrincipalClass</key> |
|
34 |
+ <string>NSApplication</string> |
|
35 |
+</dict> |
|
36 |
+</plist> |
0 | 37 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,16 @@ |
1 |
+<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
|
2 |
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A389" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> |
|
3 |
+ <dependencies> |
|
4 |
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/> |
|
5 |
+ </dependencies> |
|
6 |
+ <objects> |
|
7 |
+ <customObject id="-2" userLabel="File's Owner" customClass="NSApplication"> |
|
8 |
+ <connections> |
|
9 |
+ <outlet property="delegate" destination="yPq-Rt-e4H" id="dZ1-Qd-NPf"/> |
|
10 |
+ </connections> |
|
11 |
+ </customObject> |
|
12 |
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> |
|
13 |
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/> |
|
14 |
+ <customObject id="yPq-Rt-e4H" customClass="AppDelegate"/> |
|
15 |
+ </objects> |
|
16 |
+</document> |
0 | 17 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,13 @@ |
1 |
+// |
|
2 |
+// main.m |
|
3 |
+// Today Scripts |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 10/22/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import <Cocoa/Cocoa.h> |
|
10 |
+ |
|
11 |
+int main(int argc, const char * argv[]) { |
|
12 |
+ return NSApplicationMain(argc, argv); |
|
13 |
+} |
0 | 14 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,29 @@ |
1 |
+// |
|
2 |
+// XPCHelper.h |
|
3 |
+// xpc |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 8/10/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import <Foundation/Foundation.h> |
|
10 |
+ |
|
11 |
+extern const NSString *TodayScriptLabelKey; |
|
12 |
+extern const NSString *TodayScriptProgramKey; |
|
13 |
+extern const NSString *TodayScriptScriptKey; |
|
14 |
+extern const NSString *TodayScriptAutoRunKey; |
|
15 |
+extern const NSString *TodayScriptShowStatusKey; |
|
16 |
+ |
|
17 |
+// The protocol that this service will vend as its API. This header file will |
|
18 |
+// also need to be visible to the process hosting the service. Replace the API |
|
19 |
+// of this protocol with an API appropriate to the service you are vending. |
|
20 |
+ |
|
21 |
+typedef void (^ XPCHandler )(int status, NSString *output); |
|
22 |
+ |
|
23 |
+@protocol XPCHelping |
|
24 |
+ |
|
25 |
+- (void)launchScript:(NSDictionary *)script forUUID:(NSString *)UUID handler:(XPCHandler)handler; |
|
26 |
+ |
|
27 |
+- (void)terminateScriptForUUID:(NSString *)UUID; |
|
28 |
+ |
|
29 |
+@end |
0 | 30 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,15 @@ |
1 |
+// |
|
2 |
+// ScriptsArray.m |
|
3 |
+// Today Scripts |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 8/14/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import "TodayScripts.h" |
|
10 |
+ |
|
11 |
+const NSString *TodayScriptLabelKey = @"Label"; |
|
12 |
+const NSString *TodayScriptProgramKey = @"Program"; |
|
13 |
+const NSString *TodayScriptScriptKey = @"Script"; |
|
14 |
+const NSString *TodayScriptAutoRunKey = @"AutoRun"; |
|
15 |
+const NSString *TodayScriptShowStatusKey = @"ShowStatus"; |
0 | 16 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,326 @@ |
1 |
+// |
|
2 |
+// ANSIEscapeHelper.h |
|
3 |
+// AnsiColorsTest |
|
4 |
+// |
|
5 |
+// Created by Ali Rantakari on 18.3.09. |
|
6 |
+// |
|
7 |
+// Version 0.9.6 |
|
8 |
+// |
|
9 |
+/* |
|
10 |
+The MIT License |
|
11 |
+ |
|
12 |
+Copyright (c) 2008-2009,2013 Ali Rantakari |
|
13 |
+ |
|
14 |
+Permission is hereby granted, free of charge, to any person obtaining a copy |
|
15 |
+of this software and associated documentation files (the "Software"), to deal |
|
16 |
+in the Software without restriction, including without limitation the rights |
|
17 |
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
18 |
+copies of the Software, and to permit persons to whom the Software is |
|
19 |
+furnished to do so, subject to the following conditions: |
|
20 |
+ |
|
21 |
+The above copyright notice and this permission notice shall be included in |
|
22 |
+all copies or substantial portions of the Software. |
|
23 |
+ |
|
24 |
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
25 |
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
26 |
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
27 |
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
28 |
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
29 |
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
30 |
+THE SOFTWARE. |
|
31 |
+*/ |
|
32 |
+ |
|
33 |
+#import <Cocoa/Cocoa.h> |
|
34 |
+ |
|
35 |
+ |
|
36 |
+#if !__has_feature(objc_arc) |
|
37 |
+#warning "This code requires ARC to be enabled." |
|
38 |
+#endif |
|
39 |
+ |
|
40 |
+ |
|
41 |
+// dictionary keys for the SGR code dictionaries that the array |
|
42 |
+// escapeCodesForString:cleanString: returns contains |
|
43 |
+#define kAMRCodeDictKey_code @"code" |
|
44 |
+#define kAMRCodeDictKey_location @"location" |
|
45 |
+ |
|
46 |
+// dictionary keys for the string formatting attribute |
|
47 |
+// dictionaries that the array attributesForString:cleanString: |
|
48 |
+// returns contains |
|
49 |
+#define kAMRAttrDictKey_range @"range" |
|
50 |
+#define kAMRAttrDictKey_attrName @"attributeName" |
|
51 |
+#define kAMRAttrDictKey_attrValue @"attributeValue" |
|
52 |
+ |
|
53 |
+ |
|
54 |
+/*! |
|
55 |
+ @enum AMR_SGRCode |
|
56 |
+ |
|
57 |
+ @abstract SGR (Select Graphic Rendition) ANSI control codes. |
|
58 |
+ */ |
|
59 |
+typedef enum |
|
60 |
+{ |
|
61 |
+ AMR_SGRCodeNoneOrInvalid = -1, |
|
62 |
+ |
|
63 |
+ AMR_SGRCodeAllReset = 0, |
|
64 |
+ |
|
65 |
+ AMR_SGRCodeIntensityBold = 1, |
|
66 |
+ AMR_SGRCodeIntensityFaint = 2, |
|
67 |
+ AMR_SGRCodeIntensityNormal = 22, |
|
68 |
+ |
|
69 |
+ AMR_SGRCodeItalicOn = 3, |
|
70 |
+ |
|
71 |
+ AMR_SGRCodeUnderlineSingle = 4, |
|
72 |
+ AMR_SGRCodeUnderlineDouble = 21, |
|
73 |
+ AMR_SGRCodeUnderlineNone = 24, |
|
74 |
+ |
|
75 |
+ AMR_SGRCodeFgBlack = 30, |
|
76 |
+ AMR_SGRCodeFgRed = 31, |
|
77 |
+ AMR_SGRCodeFgGreen = 32, |
|
78 |
+ AMR_SGRCodeFgYellow = 33, |
|
79 |
+ AMR_SGRCodeFgBlue = 34, |
|
80 |
+ AMR_SGRCodeFgMagenta = 35, |
|
81 |
+ AMR_SGRCodeFgCyan = 36, |
|
82 |
+ AMR_SGRCodeFgWhite = 37, |
|
83 |
+ AMR_SGRCodeFgReset = 39, |
|
84 |
+ |
|
85 |
+ AMR_SGRCodeBgBlack = 40, |
|
86 |
+ AMR_SGRCodeBgRed = 41, |
|
87 |
+ AMR_SGRCodeBgGreen = 42, |
|
88 |
+ AMR_SGRCodeBgYellow = 43, |
|
89 |
+ AMR_SGRCodeBgBlue = 44, |
|
90 |
+ AMR_SGRCodeBgMagenta = 45, |
|
91 |
+ AMR_SGRCodeBgCyan = 46, |
|
92 |
+ AMR_SGRCodeBgWhite = 47, |
|
93 |
+ AMR_SGRCodeBgReset = 49, |
|
94 |
+ |
|
95 |
+ AMR_SGRCodeFgBrightBlack = 90, |
|
96 |
+ AMR_SGRCodeFgBrightRed = 91, |
|
97 |
+ AMR_SGRCodeFgBrightGreen = 92, |
|
98 |
+ AMR_SGRCodeFgBrightYellow = 93, |
|
99 |
+ AMR_SGRCodeFgBrightBlue = 94, |
|
100 |
+ AMR_SGRCodeFgBrightMagenta = 95, |
|
101 |
+ AMR_SGRCodeFgBrightCyan = 96, |
|
102 |
+ AMR_SGRCodeFgBrightWhite = 97, |
|
103 |
+ |
|
104 |
+ AMR_SGRCodeBgBrightBlack = 100, |
|
105 |
+ AMR_SGRCodeBgBrightRed = 101, |
|
106 |
+ AMR_SGRCodeBgBrightGreen = 102, |
|
107 |
+ AMR_SGRCodeBgBrightYellow = 103, |
|
108 |
+ AMR_SGRCodeBgBrightBlue = 104, |
|
109 |
+ AMR_SGRCodeBgBrightMagenta = 105, |
|
110 |
+ AMR_SGRCodeBgBrightCyan = 106, |
|
111 |
+ AMR_SGRCodeBgBrightWhite = 107 |
|
112 |
+} AMR_SGRCode; |
|
113 |
+ |
|
114 |
+ |
|
115 |
+ |
|
116 |
+ |
|
117 |
+ |
|
118 |
+ |
|
119 |
+/*! |
|
120 |
+ @class AMR_ANSIEscapeHelper |
|
121 |
+ |
|
122 |
+ @abstract Contains helper methods for dealing with strings |
|
123 |
+ that contain ANSI escape sequences for formatting (colors, |
|
124 |
+ underlining, bold etc.) |
|
125 |
+ */ |
|
126 |
+@interface AMR_ANSIEscapeHelper : NSObject |
|
127 |
+ |
|
128 |
+/*! |
|
129 |
+ @property defaultStringColor |
|
130 |
+ |
|
131 |
+ @abstract The default color used when creating an attributed string (default is black). |
|
132 |
+ */ |
|
133 |
+@property(copy) NSColor *defaultStringColor; |
|
134 |
+ |
|
135 |
+ |
|
136 |
+/*! |
|
137 |
+ @property font |
|
138 |
+ |
|
139 |
+ @abstract The font to use when creating string formatting attribute values. |
|
140 |
+ */ |
|
141 |
+@property(copy) NSFont *font; |
|
142 |
+ |
|
143 |
+/*! |
|
144 |
+ @property ansiColors |
|
145 |
+ |
|
146 |
+ @abstract The colors to use for displaying ANSI colors. |
|
147 |
+ |
|
148 |
+ @discussion Keys in this dictionary should be NSNumber objects containing SGR code |
|
149 |
+ values from the AMR_SGRCode enum. The corresponding values for these keys |
|
150 |
+ should be NSColor objects. If this property is nil or if it doesn't |
|
151 |
+ contain a key for a specific SGR code, the default color will be used |
|
152 |
+ instead. |
|
153 |
+ */ |
|
154 |
+@property(retain) NSMutableDictionary *ansiColors; |
|
155 |
+ |
|
156 |
+ |
|
157 |
+/*! |
|
158 |
+ @method attributedStringWithANSIEscapedString: |
|
159 |
+ |
|
160 |
+ @abstract Returns an attributed string that corresponds both in contents |
|
161 |
+ and formatting to a given string that contains ANSI escape |
|
162 |
+ sequences. |
|
163 |
+ |
|
164 |
+ @param aString A String containing ANSI escape sequences |
|
165 |
+ |
|
166 |
+ @result An attributed string that mimics as closely as possible |
|
167 |
+ the formatting of the given ANSI-escaped string. |
|
168 |
+ */ |
|
169 |
+- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString; |
|
170 |
+ |
|
171 |
+ |
|
172 |
+/*! |
|
173 |
+ @method ansiEscapedStringWithAttributedString: |
|
174 |
+ |
|
175 |
+ @abstract Returns a string containing ANSI escape sequences that corresponds |
|
176 |
+ both in contents and formatting to a given attributed string. |
|
177 |
+ |
|
178 |
+ @param aAttributedString An attributed string |
|
179 |
+ |
|
180 |
+ @result A string that mimics as closely as possible |
|
181 |
+ the formatting of the given attributed string with |
|
182 |
+ ANSI escape sequences. |
|
183 |
+ */ |
|
184 |
+- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString; |
|
185 |
+ |
|
186 |
+ |
|
187 |
+/*! |
|
188 |
+ @method escapeCodesForString:cleanString: |
|
189 |
+ |
|
190 |
+ @abstract Returns an array of SGR codes and their locations from a |
|
191 |
+ string containing ANSI escape sequences as well as a "clean" |
|
192 |
+ version of the string (i.e. one without the ANSI escape |
|
193 |
+ sequences.) |
|
194 |
+ |
|
195 |
+ @param aString A String containing ANSI escape sequences |
|
196 |
+ @param aCleanString Upon return, contains a "clean" version of aString (i.e. aString |
|
197 |
+ without the ANSI escape sequences) |
|
198 |
+ |
|
199 |
+ @result An array of NSDictionary objects, each of which has |
|
200 |
+ an NSNumber value for the key "code" (specifying an SGR code) and |
|
201 |
+ another NSNumber value for the key "location" (specifying the |
|
202 |
+ location of the code within aCleanString.) |
|
203 |
+ */ |
|
204 |
+- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString; |
|
205 |
+ |
|
206 |
+ |
|
207 |
+/*! |
|
208 |
+ @method ansiEscapedStringWithCodesAndLocations:cleanString: |
|
209 |
+ |
|
210 |
+ @abstract Returns a string containing ANSI escape codes for formatting based |
|
211 |
+ on a string and an array of SGR codes and their locations within |
|
212 |
+ the given string. |
|
213 |
+ |
|
214 |
+ @param aCodesArray An array of NSDictionary objects, each of which should have |
|
215 |
+ an NSNumber value for the key "code" (specifying an SGR |
|
216 |
+ code) and another NSNumber value for the key "location" |
|
217 |
+ (specifying the location of this SGR code in aCleanString.) |
|
218 |
+ @param aCleanString The string to which to insert the ANSI escape codes |
|
219 |
+ described in aCodesArray. |
|
220 |
+ |
|
221 |
+ @result A string containing ANSI escape sequences. |
|
222 |
+ */ |
|
223 |
+- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString; |
|
224 |
+ |
|
225 |
+ |
|
226 |
+/*! |
|
227 |
+ @method attributesForString:cleanString: |
|
228 |
+ |
|
229 |
+ @abstract Convert ANSI escape sequences in a string to string formatting attributes. |
|
230 |
+ |
|
231 |
+ @discussion Given a string with some ANSI escape sequences in it, this method returns |
|
232 |
+ attributes for formatting the specified string according to those ANSI |
|
233 |
+ escape sequences as well as a "clean" (i.e. free of the escape sequences) |
|
234 |
+ version of this string. |
|
235 |
+ |
|
236 |
+ @param aString A String containing ANSI escape sequences |
|
237 |
+ @param aCleanString Upon return, contains a "clean" version of aString (i.e. aString |
|
238 |
+ without the ANSI escape sequences.) Pass in NULL if you're not |
|
239 |
+ interested in this. |
|
240 |
+ |
|
241 |
+ @result An array containing NSDictionary objects, each of which has keys "range" |
|
242 |
+ (an NSValue containing an NSRange, specifying the range for the |
|
243 |
+ attribute within the "clean" version of aString), "attributeName" (an |
|
244 |
+ NSString) and "attributeValue" (an NSObject). You may use these as |
|
245 |
+ arguments for NSMutableAttributedString's methods for setting the |
|
246 |
+ visual formatting. |
|
247 |
+ */ |
|
248 |
+- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString; |
|
249 |
+ |
|
250 |
+ |
|
251 |
+/*! |
|
252 |
+ @method AMR_SGRCode:endsFormattingIntroducedByCode: |
|
253 |
+ |
|
254 |
+ @abstract Whether the occurrence of a given SGR code would end the formatting run |
|
255 |
+ introduced by another SGR code. |
|
256 |
+ |
|
257 |
+ @discussion For example, AMR_SGRCodeFgReset, AMR_SGRCodeAllReset or any SGR code |
|
258 |
+ specifying a foreground color would end the formatting run |
|
259 |
+ introduced by a foreground color -specifying SGR code. |
|
260 |
+ |
|
261 |
+ @param endCode The SGR code to test as a candidate for ending the formatting run |
|
262 |
+ introduced by startCode |
|
263 |
+ @param startCode The SGR code that has introduced a formatting run |
|
264 |
+ |
|
265 |
+ @result YES if the occurrence of endCode would end the formatting run |
|
266 |
+ introduced by startCode, NO otherwise. |
|
267 |
+ */ |
|
268 |
+- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode; |
|
269 |
+ |
|
270 |
+ |
|
271 |
+/*! |
|
272 |
+ @method colorForSGRCode: |
|
273 |
+ |
|
274 |
+ @abstract Returns the color to use for displaying a specific ANSI color. |
|
275 |
+ |
|
276 |
+ @discussion This method first considers the values set in the ansiColors |
|
277 |
+ property and only then the standard basic colors (NSColor's |
|
278 |
+ redColor, blueColor etc.) |
|
279 |
+ |
|
280 |
+ @param code An SGR code that specifies an ANSI color. |
|
281 |
+ |
|
282 |
+ @result The color to use for displaying the ANSI color specified by code. |
|
283 |
+ */ |
|
284 |
+- (NSColor*) colorForSGRCode:(AMR_SGRCode)code; |
|
285 |
+ |
|
286 |
+ |
|
287 |
+/*! |
|
288 |
+ @method AMR_SGRCodeForColor:isForegroundColor: |
|
289 |
+ |
|
290 |
+ @abstract Returns a color SGR code that corresponds to a given color. |
|
291 |
+ |
|
292 |
+ @discussion This method matches colors to their equivalent SGR codes |
|
293 |
+ by going through the colors specified in the ansiColors |
|
294 |
+ dictionary, and if ansiColors is null or if a match is |
|
295 |
+ not found there, by comparing the given color to the |
|
296 |
+ standard basic colors (NSColor's redColor, blueColor |
|
297 |
+ etc.) The comparison is done simply by checking for |
|
298 |
+ equality. |
|
299 |
+ |
|
300 |
+ @param aColor The color to get a corresponding SGR code for |
|
301 |
+ @param aForeground Whether you want a foreground or background color code |
|
302 |
+ |
|
303 |
+ @result SGR code that corresponds with aColor. |
|
304 |
+ */ |
|
305 |
+- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground; |
|
306 |
+ |
|
307 |
+ |
|
308 |
+/*! |
|
309 |
+ @method closestSGRCodeForColor:isForegroundColor: |
|
310 |
+ |
|
311 |
+ @abstract Returns a color SGR code that represents the closest ANSI |
|
312 |
+ color to a given color. |
|
313 |
+ |
|
314 |
+ @discussion This method attempts to find the closest ANSI color to |
|
315 |
+ aColor and return its SGR code. |
|
316 |
+ |
|
317 |
+ @param aColor The color to get a closest color SGR code match for |
|
318 |
+ @param aForeground Whether you want a foreground or background color code |
|
319 |
+ |
|
320 |
+ @result SGR code for the ANSI color that is closest to aColor. |
|
321 |
+ */ |
|
322 |
+- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground; |
|
323 |
+ |
|
324 |
+ |
|
325 |
+ |
|
326 |
+@end |
0 | 327 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,996 @@ |
1 |
+// |
|
2 |
+// ANSIEscapeHelper.m |
|
3 |
+// |
|
4 |
+// Created by Ali Rantakari on 18.3.09. |
|
5 |
+ |
|
6 |
+/* |
|
7 |
+The MIT License |
|
8 |
+ |
|
9 |
+Copyright (c) 2008-2009,2013 Ali Rantakari |
|
10 |
+ |
|
11 |
+Permission is hereby granted, free of charge, to any person obtaining a copy |
|
12 |
+of this software and associated documentation files (the "Software"), to deal |
|
13 |
+in the Software without restriction, including without limitation the rights |
|
14 |
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
15 |
+copies of the Software, and to permit persons to whom the Software is |
|
16 |
+furnished to do so, subject to the following conditions: |
|
17 |
+ |
|
18 |
+The above copyright notice and this permission notice shall be included in |
|
19 |
+all copies or substantial portions of the Software. |
|
20 |
+ |
|
21 |
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
22 |
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
23 |
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
24 |
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
25 |
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
26 |
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
27 |
+THE SOFTWARE. |
|
28 |
+*/ |
|
29 |
+ |
|
30 |
+/* |
|
31 |
+ todo: |
|
32 |
+ |
|
33 |
+ - don't add useless "reset" escape codes to the string in |
|
34 |
+ -ansiEscapedStringWithAttributedString: |
|
35 |
+ |
|
36 |
+ */ |
|
37 |
+ |
|
38 |
+ |
|
39 |
+ |
|
40 |
+#import "AMR_ANSIEscapeHelper.h" |
|
41 |
+ |
|
42 |
+ |
|
43 |
+// the CSI (Control Sequence Initiator) -- i.e. "escape sequence prefix". |
|
44 |
+// (add your own CSI:Miami joke here) |
|
45 |
+#define kANSIEscapeCSI @"\033[" |
|
46 |
+ |
|
47 |
+// the end byte of an SGR (Select Graphic Rendition) |
|
48 |
+// ANSI Escape Sequence |
|
49 |
+#define kANSIEscapeSGREnd @"m" |
|
50 |
+ |
|
51 |
+ |
|
52 |
+// color definition helper macros |
|
53 |
+#define kBrightColorBrightness 1.0 |
|
54 |
+#define kBrightColorSaturation 0.4 |
|
55 |
+#define kBrightColorAlpha 1.0 |
|
56 |
+#define kBrightColorWithHue(h) [NSColor colorWithCalibratedHue:(h) saturation:kBrightColorSaturation brightness:kBrightColorBrightness alpha:kBrightColorAlpha] |
|
57 |
+ |
|
58 |
+// default colors |
|
59 |
+#define kDefaultANSIColorFgBlack NSColor.blackColor |
|
60 |
+#define kDefaultANSIColorFgRed NSColor.redColor |
|
61 |
+#define kDefaultANSIColorFgGreen NSColor.greenColor |
|
62 |
+#define kDefaultANSIColorFgYellow NSColor.yellowColor |
|
63 |
+#define kDefaultANSIColorFgBlue NSColor.blueColor |
|
64 |
+#define kDefaultANSIColorFgMagenta NSColor.magentaColor |
|
65 |
+#define kDefaultANSIColorFgCyan NSColor.cyanColor |
|
66 |
+#define kDefaultANSIColorFgWhite NSColor.whiteColor |
|
67 |
+ |
|
68 |
+#define kDefaultANSIColorFgBrightBlack [NSColor colorWithCalibratedWhite:0.337 alpha:1.0] |
|
69 |
+#define kDefaultANSIColorFgBrightRed kBrightColorWithHue(1.0) |
|
70 |
+#define kDefaultANSIColorFgBrightGreen kBrightColorWithHue(1.0/3.0) |
|
71 |
+#define kDefaultANSIColorFgBrightYellow kBrightColorWithHue(1.0/6.0) |
|
72 |
+#define kDefaultANSIColorFgBrightBlue kBrightColorWithHue(2.0/3.0) |
|
73 |
+#define kDefaultANSIColorFgBrightMagenta kBrightColorWithHue(5.0/6.0) |
|
74 |
+#define kDefaultANSIColorFgBrightCyan kBrightColorWithHue(0.5) |
|
75 |
+#define kDefaultANSIColorFgBrightWhite NSColor.whiteColor |
|
76 |
+ |
|
77 |
+#define kDefaultANSIColorBgBlack NSColor.blackColor |
|
78 |
+#define kDefaultANSIColorBgRed NSColor.redColor |
|
79 |
+#define kDefaultANSIColorBgGreen NSColor.greenColor |
|
80 |
+#define kDefaultANSIColorBgYellow NSColor.yellowColor |
|
81 |
+#define kDefaultANSIColorBgBlue NSColor.blueColor |
|
82 |
+#define kDefaultANSIColorBgMagenta NSColor.magentaColor |
|
83 |
+#define kDefaultANSIColorBgCyan NSColor.cyanColor |
|
84 |
+#define kDefaultANSIColorBgWhite NSColor.whiteColor |
|
85 |
+ |
|
86 |
+#define kDefaultANSIColorBgBrightBlack kDefaultANSIColorFgBrightBlack |
|
87 |
+#define kDefaultANSIColorBgBrightRed kDefaultANSIColorFgBrightRed |
|
88 |
+#define kDefaultANSIColorBgBrightGreen kDefaultANSIColorFgBrightGreen |
|
89 |
+#define kDefaultANSIColorBgBrightYellow kDefaultANSIColorFgBrightYellow |
|
90 |
+#define kDefaultANSIColorBgBrightBlue kDefaultANSIColorFgBrightBlue |
|
91 |
+#define kDefaultANSIColorBgBrightMagenta kDefaultANSIColorFgBrightMagenta |
|
92 |
+#define kDefaultANSIColorBgBrightCyan kDefaultANSIColorFgBrightCyan |
|
93 |
+#define kDefaultANSIColorBgBrightWhite kDefaultANSIColorFgBrightWhite |
|
94 |
+ |
|
95 |
+#define kDefaultFontSize [NSFont systemFontOfSize:NSFont.systemFontSize] |
|
96 |
+#define kDefaultForegroundColor NSColor.blackColor |
|
97 |
+ |
|
98 |
+// minimum weight for an NSFont for it to be considered bold |
|
99 |
+#define kBoldFontMinWeight 9 |
|
100 |
+ |
|
101 |
+ |
|
102 |
+@implementation AMR_ANSIEscapeHelper |
|
103 |
+ |
|
104 |
+- (id) init |
|
105 |
+{ |
|
106 |
+ if (!(self = [super init])) |
|
107 |
+ return nil; |
|
108 |
+ |
|
109 |
+ self.ansiColors = [NSMutableDictionary dictionary]; |
|
110 |
+ |
|
111 |
+ return self; |
|
112 |
+} |
|
113 |
+ |
|
114 |
+ |
|
115 |
+ |
|
116 |
+- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString |
|
117 |
+{ |
|
118 |
+ if (aString == nil) |
|
119 |
+ return nil; |
|
120 |
+ |
|
121 |
+ NSString *cleanString; |
|
122 |
+ NSArray *attributesAndRanges = [self attributesForString:aString cleanString:&cleanString]; |
|
123 |
+ NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] |
|
124 |
+ initWithString:cleanString |
|
125 |
+ attributes:@{ |
|
126 |
+ NSFontAttributeName: self.font ?: kDefaultFontSize, |
|
127 |
+ NSForegroundColorAttributeName: self.defaultStringColor ?: kDefaultForegroundColor |
|
128 |
+ }]; |
|
129 |
+ |
|
130 |
+ for (NSDictionary *thisAttributeDict in attributesAndRanges) |
|
131 |
+ { |
|
132 |
+ [attributedString |
|
133 |
+ addAttribute:thisAttributeDict[kAMRAttrDictKey_attrName] |
|
134 |
+ value:thisAttributeDict[kAMRAttrDictKey_attrValue] |
|
135 |
+ range:[thisAttributeDict[kAMRAttrDictKey_range] rangeValue] |
|
136 |
+ ]; |
|
137 |
+ } |
|
138 |
+ |
|
139 |
+ return attributedString; |
|
140 |
+} |
|
141 |
+ |
|
142 |
+ |
|
143 |
+ |
|
144 |
+- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString |
|
145 |
+{ |
|
146 |
+ NSMutableArray *codesAndLocations = [NSMutableArray array]; |
|
147 |
+ |
|
148 |
+ NSArray *attrNames = @[ |
|
149 |
+ NSFontAttributeName, NSForegroundColorAttributeName, |
|
150 |
+ NSBackgroundColorAttributeName, NSUnderlineStyleAttributeName, |
|
151 |
+ ]; |
|
152 |
+ |
|
153 |
+ for (NSString *thisAttrName in attrNames) |
|
154 |
+ { |
|
155 |
+ NSRange limitRange = NSMakeRange(0, aAttributedString.length); |
|
156 |
+ id attributeValue; |
|
157 |
+ NSRange effectiveRange; |
|
158 |
+ |
|
159 |
+ while (limitRange.length > 0) |
|
160 |
+ { |
|
161 |
+ attributeValue = [aAttributedString |
|
162 |
+ attribute:thisAttrName |
|
163 |
+ atIndex:limitRange.location |
|
164 |
+ longestEffectiveRange:&effectiveRange |
|
165 |
+ inRange:limitRange |
|
166 |
+ ]; |
|
167 |
+ |
|
168 |
+ AMR_SGRCode thisSGRCode = AMR_SGRCodeNoneOrInvalid; |
|
169 |
+ |
|
170 |
+ if ([thisAttrName isEqualToString:NSForegroundColorAttributeName]) |
|
171 |
+ { |
|
172 |
+ if (attributeValue != nil) |
|
173 |
+ thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:YES]; |
|
174 |
+ else |
|
175 |
+ thisSGRCode = AMR_SGRCodeFgReset; |
|
176 |
+ } |
|
177 |
+ else if ([thisAttrName isEqualToString:NSBackgroundColorAttributeName]) |
|
178 |
+ { |
|
179 |
+ if (attributeValue != nil) |
|
180 |
+ thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:NO]; |
|
181 |
+ else |
|
182 |
+ thisSGRCode = AMR_SGRCodeBgReset; |
|
183 |
+ } |
|
184 |
+ else if ([thisAttrName isEqualToString:NSFontAttributeName]) |
|
185 |
+ { |
|
186 |
+ // we currently only use NSFontAttributeName for bolding so |
|
187 |
+ // here we assume that the formatting "type" in ANSI SGR |
|
188 |
+ // terms is indeed intensity |
|
189 |
+ if (attributeValue != nil) |
|
190 |
+ thisSGRCode = ([NSFontManager.sharedFontManager weightOfFont:attributeValue] >= kBoldFontMinWeight) |
|
191 |
+ ? AMR_SGRCodeIntensityBold : AMR_SGRCodeIntensityNormal; |
|
192 |
+ else |
|
193 |
+ thisSGRCode = AMR_SGRCodeIntensityNormal; |
|
194 |
+ } |
|
195 |
+ else if ([thisAttrName isEqualToString:NSUnderlineStyleAttributeName]) |
|
196 |
+ { |
|
197 |
+ if (attributeValue != nil) |
|
198 |
+ { |
|
199 |
+ if ([attributeValue intValue] == NSUnderlineStyleSingle) |
|
200 |
+ thisSGRCode = AMR_SGRCodeUnderlineSingle; |
|
201 |
+ else if ([attributeValue intValue] == NSUnderlineStyleDouble) |
|
202 |
+ thisSGRCode = AMR_SGRCodeUnderlineDouble; |
|
203 |
+ else |
|
204 |
+ thisSGRCode = AMR_SGRCodeUnderlineNone; |
|
205 |
+ } |
|
206 |
+ else |
|
207 |
+ thisSGRCode = AMR_SGRCodeUnderlineNone; |
|
208 |
+ } |
|
209 |
+ |
|
210 |
+ if (thisSGRCode != AMR_SGRCodeNoneOrInvalid) |
|
211 |
+ { |
|
212 |
+ [codesAndLocations addObject: @{ |
|
213 |
+ kAMRCodeDictKey_code: @(thisSGRCode), |
|
214 |
+ kAMRCodeDictKey_location: @(effectiveRange.location), |
|
215 |
+ }]; |
|
216 |
+ } |
|
217 |
+ |
|
218 |
+ limitRange = NSMakeRange(NSMaxRange(effectiveRange), |
|
219 |
+ NSMaxRange(limitRange) - NSMaxRange(effectiveRange)); |
|
220 |
+ } |
|
221 |
+ } |
|
222 |
+ |
|
223 |
+ return [self ansiEscapedStringWithCodesAndLocations:codesAndLocations cleanString:aAttributedString.string]; |
|
224 |
+} |
|
225 |
+ |
|
226 |
+ |
|
227 |
+- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString |
|
228 |
+{ |
|
229 |
+ if (aString == nil) |
|
230 |
+ return nil; |
|
231 |
+ if (aString.length <= kANSIEscapeCSI.length) |
|
232 |
+ { |
|
233 |
+ if (aCleanString) |
|
234 |
+ *aCleanString = aString.copy; |
|
235 |
+ return @[]; |
|
236 |
+ } |
|
237 |
+ |
|
238 |
+ NSString *cleanString = @""; |
|
239 |
+ |
|
240 |
+ // find all escape sequence codes from aString and put them in this array |
|
241 |
+ // along with their start locations within the "clean" version of aString |
|
242 |
+ NSMutableArray *formatCodes = [NSMutableArray array]; |
|
243 |
+ |
|
244 |
+ NSUInteger aStringLength = aString.length; |
|
245 |
+ NSUInteger coveredLength = 0; |
|
246 |
+ NSRange searchRange = NSMakeRange(0,aStringLength); |
|
247 |
+ NSRange thisEscapeSequenceRange; |
|
248 |
+ do |
|
249 |
+ { |
|
250 |
+ thisEscapeSequenceRange = [aString rangeOfString:kANSIEscapeCSI options:NSLiteralSearch range:searchRange]; |
|
251 |
+ if (thisEscapeSequenceRange.location != NSNotFound) |
|
252 |
+ { |
|
253 |
+ // adjust range's length so that it encompasses the whole ANSI escape sequence |
|
254 |
+ // and not just the Control Sequence Initiator (the "prefix") by finding the |
|
255 |
+ // final byte of the control sequence (one that has an ASCII decimal value |
|
256 |
+ // between 64 and 126.) at the same time, read all formatting codes from inside |
|
257 |
+ // this escape sequence (there may be several, separated by semicolons.) |
|
258 |
+ NSMutableArray *codes = [NSMutableArray array]; |
|
259 |
+ unsigned int code = 0; |
|
260 |
+ unsigned int lengthAddition = 1; |
|
261 |
+ NSUInteger thisIndex; |
|
262 |
+ for (;;) |
|
263 |
+ { |
|
264 |
+ thisIndex = (NSMaxRange(thisEscapeSequenceRange)+lengthAddition-1); |
|
265 |
+ if (thisIndex >= aStringLength) |
|
266 |
+ break; |
|
267 |
+ |
|
268 |
+ unichar c = [aString characterAtIndex:thisIndex]; |
|
269 |
+ |
|
270 |
+ if (('0' <= c) && (c <= '9')) |
|
271 |
+ { |
|
272 |
+ int digit = c - '0'; |
|
273 |
+ code = (code == 0) ? digit : code*10+digit; |
|
274 |
+ } |
|
275 |
+ |
|
276 |
+ // ASCII decimal 109 is the SGR (Select Graphic Rendition) final byte |
|
277 |
+ // ("m"). this means that the code value we've just read specifies formatting |
|
278 |
+ // for the output; exactly what we're interested in. |
|
279 |
+ if (c == 'm') |
|
280 |
+ { |
|
281 |
+ [codes addObject:@(code)]; |
|
282 |
+ break; |
|
283 |
+ } |
|
284 |
+ else if ((64 <= c) && (c <= 126)) // any other valid final byte |
|
285 |
+ { |
|
286 |
+ [codes removeAllObjects]; |
|
287 |
+ break; |
|
288 |
+ } |
|
289 |
+ else if (c == ';') // separates codes within the same sequence |
|
290 |
+ { |
|
291 |
+ [codes addObject:@(code)]; |
|
292 |
+ code = 0; |
|
293 |
+ } |
|
294 |
+ |
|
295 |
+ lengthAddition++; |
|
296 |
+ } |
|
297 |
+ thisEscapeSequenceRange.length += lengthAddition; |
|
298 |
+ |
|
299 |
+ NSUInteger locationInCleanString = coveredLength+thisEscapeSequenceRange.location-searchRange.location; |
|
300 |
+ |
|
301 |
+ for (NSNumber *codeToAdd in codes) |
|
302 |
+ { |
|
303 |
+ [formatCodes addObject: @{ |
|
304 |
+ kAMRCodeDictKey_code: codeToAdd, |
|
305 |
+ kAMRCodeDictKey_location: @(locationInCleanString) |
|
306 |
+ }]; |
|
307 |
+ } |
|
308 |
+ |
|
309 |
+ NSUInteger thisCoveredLength = thisEscapeSequenceRange.location-searchRange.location; |
|
310 |
+ if (thisCoveredLength > 0) |
|
311 |
+ cleanString = [cleanString stringByAppendingString:[aString substringWithRange:NSMakeRange(searchRange.location, thisCoveredLength)]]; |
|
312 |
+ |
|
313 |
+ coveredLength += thisCoveredLength; |
|
314 |
+ searchRange.location = NSMaxRange(thisEscapeSequenceRange); |
|
315 |
+ searchRange.length = aStringLength-searchRange.location; |
|
316 |
+ } |
|
317 |
+ } |
|
318 |
+ while(thisEscapeSequenceRange.location != NSNotFound); |
|
319 |
+ |
|
320 |
+ if (searchRange.length > 0) |
|
321 |
+ cleanString = [cleanString stringByAppendingString:[aString substringWithRange:searchRange]]; |
|
322 |
+ |
|
323 |
+ if (aCleanString) |
|
324 |
+ *aCleanString = cleanString; |
|
325 |
+ return formatCodes; |
|
326 |
+} |
|
327 |
+ |
|
328 |
+ |
|
329 |
+ |
|
330 |
+ |
|
331 |
+- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString |
|
332 |
+{ |
|
333 |
+ NSMutableString* retStr = [NSMutableString stringWithCapacity:aCleanString.length]; |
|
334 |
+ |
|
335 |
+ NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:kAMRCodeDictKey_location ascending:YES]; |
|
336 |
+ NSArray *codesArray = [aCodesArray sortedArrayUsingDescriptors:@[sortDescriptor]]; |
|
337 |
+ |
|
338 |
+ NSUInteger aCleanStringIndex = 0; |
|
339 |
+ NSUInteger aCleanStringLength = aCleanString.length; |
|
340 |
+ for (NSDictionary *thisCodeDict in codesArray) |
|
341 |
+ { |
|
342 |
+ if (!( thisCodeDict[kAMRCodeDictKey_code] && |
|
343 |
+ thisCodeDict[kAMRCodeDictKey_location] |
|
344 |
+ )) |
|
345 |
+ continue; |
|
346 |
+ |
|
347 |
+ AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue]; |
|
348 |
+ NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue]; |
|
349 |
+ |
|
350 |
+ if (formattingRunStartLocation > aCleanStringLength) |
|
351 |
+ continue; |
|
352 |
+ |
|
353 |
+ if (aCleanStringIndex < formattingRunStartLocation) |
|
354 |
+ [retStr appendString:[aCleanString substringWithRange:NSMakeRange(aCleanStringIndex, formattingRunStartLocation-aCleanStringIndex)]]; |
|
355 |
+ [retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, thisCode, kANSIEscapeSGREnd]; |
|
356 |
+ |
|
357 |
+ aCleanStringIndex = formattingRunStartLocation; |
|
358 |
+ } |
|
359 |
+ |
|
360 |
+ if (aCleanStringIndex < aCleanStringLength) |
|
361 |
+ [retStr appendString:[aCleanString substringFromIndex:aCleanStringIndex]]; |
|
362 |
+ |
|
363 |
+ [retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, AMR_SGRCodeAllReset, kANSIEscapeSGREnd]; |
|
364 |
+ |
|
365 |
+ return retStr; |
|
366 |
+} |
|
367 |
+ |
|
368 |
+ |
|
369 |
+ |
|
370 |
+ |
|
371 |
+ |
|
372 |
+- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString |
|
373 |
+{ |
|
374 |
+ if (aString == nil) |
|
375 |
+ return nil; |
|
376 |
+ if (aString.length <= kANSIEscapeCSI.length) |
|
377 |
+ { |
|
378 |
+ if (aCleanString) |
|
379 |
+ *aCleanString = aString.copy; |
|
380 |
+ return @[]; |
|
381 |
+ } |
|
382 |
+ |
|
383 |
+ NSMutableArray *attrsAndRanges = [NSMutableArray array]; |
|
384 |
+ |
|
385 |
+ NSString *cleanString; |
|
386 |
+ NSArray *formatCodes = [self escapeCodesForString:aString cleanString:&cleanString]; |
|
387 |
+ |
|
388 |
+ // go through all the found escape sequence codes and for each one, create |
|
389 |
+ // the string formatting attribute name and value, find the next escape |
|
390 |
+ // sequence that specifies the end of the formatting run started by |
|
391 |
+ // the currently handled code, and generate a range from the difference |
|
392 |
+ // in those codes' locations within the clean aString. |
|
393 |
+ for (NSUInteger iCode = 0; iCode < formatCodes.count; iCode++) |
|
394 |
+ { |
|
395 |
+ NSDictionary *thisCodeDict = formatCodes[iCode]; |
|
396 |
+ AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue]; |
|
397 |
+ NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue]; |
|
398 |
+ |
|
399 |
+ // the attributed string attribute name for the formatting run introduced |
|
400 |
+ // by this code |
|
401 |
+ NSString *thisAttributeName = nil; |
|
402 |
+ |
|
403 |
+ // the attributed string attribute value for this formatting run introduced |
|
404 |
+ // by this code |
|
405 |
+ NSObject *thisAttributeValue = nil; |
|
406 |
+ |
|
407 |
+ // set attribute name |
|
408 |
+ switch(thisCode) |
|
409 |
+ { |
|
410 |
+ case AMR_SGRCodeFgBlack: |
|
411 |
+ case AMR_SGRCodeFgRed: |
|
412 |
+ case AMR_SGRCodeFgGreen: |
|
413 |
+ case AMR_SGRCodeFgYellow: |
|
414 |
+ case AMR_SGRCodeFgBlue: |
|
415 |
+ case AMR_SGRCodeFgMagenta: |
|
416 |
+ case AMR_SGRCodeFgCyan: |
|
417 |
+ case AMR_SGRCodeFgWhite: |
|
418 |
+ case AMR_SGRCodeFgBrightBlack: |
|
419 |
+ case AMR_SGRCodeFgBrightRed: |
|
420 |
+ case AMR_SGRCodeFgBrightGreen: |
|
421 |
+ case AMR_SGRCodeFgBrightYellow: |
|
422 |
+ case AMR_SGRCodeFgBrightBlue: |
|
423 |
+ case AMR_SGRCodeFgBrightMagenta: |
|
424 |
+ case AMR_SGRCodeFgBrightCyan: |
|
425 |
+ case AMR_SGRCodeFgBrightWhite: |
|
426 |
+ thisAttributeName = NSForegroundColorAttributeName; |
|
427 |
+ break; |
|
428 |
+ case AMR_SGRCodeBgBlack: |
|
429 |
+ case AMR_SGRCodeBgRed: |
|
430 |
+ case AMR_SGRCodeBgGreen: |
|
431 |
+ case AMR_SGRCodeBgYellow: |
|
432 |
+ case AMR_SGRCodeBgBlue: |
|
433 |
+ case AMR_SGRCodeBgMagenta: |
|
434 |
+ case AMR_SGRCodeBgCyan: |
|
435 |
+ case AMR_SGRCodeBgWhite: |
|
436 |
+ case AMR_SGRCodeBgBrightBlack: |
|
437 |
+ case AMR_SGRCodeBgBrightRed: |
|
438 |
+ case AMR_SGRCodeBgBrightGreen: |
|
439 |
+ case AMR_SGRCodeBgBrightYellow: |
|
440 |
+ case AMR_SGRCodeBgBrightBlue: |
|
441 |
+ case AMR_SGRCodeBgBrightMagenta: |
|
442 |
+ case AMR_SGRCodeBgBrightCyan: |
|
443 |
+ case AMR_SGRCodeBgBrightWhite: |
|
444 |
+ thisAttributeName = NSBackgroundColorAttributeName; |
|
445 |
+ break; |
|
446 |
+ case AMR_SGRCodeIntensityBold: |
|
447 |
+ case AMR_SGRCodeIntensityNormal: |
|
448 |
+ case AMR_SGRCodeIntensityFaint: |
|
449 |
+ thisAttributeName = NSFontAttributeName; |
|
450 |
+ break; |
|
451 |
+ case AMR_SGRCodeUnderlineSingle: |
|
452 |
+ case AMR_SGRCodeUnderlineDouble: |
|
453 |
+ case AMR_SGRCodeUnderlineNone: |
|
454 |
+ thisAttributeName = NSUnderlineStyleAttributeName; |
|
455 |
+ break; |
|
456 |
+ case AMR_SGRCodeAllReset: |
|
457 |
+ case AMR_SGRCodeFgReset: |
|
458 |
+ case AMR_SGRCodeBgReset: |
|
459 |
+ case AMR_SGRCodeNoneOrInvalid: |
|
460 |
+ case AMR_SGRCodeItalicOn: |
|
461 |
+ continue; |
|
462 |
+ } |
|
463 |
+ |
|
464 |
+ // set attribute value |
|
465 |
+ switch(thisCode) |
|
466 |
+ { |
|
467 |
+ case AMR_SGRCodeBgBlack: |
|
468 |
+ case AMR_SGRCodeFgBlack: |
|
469 |
+ case AMR_SGRCodeBgRed: |
|
470 |
+ case AMR_SGRCodeFgRed: |
|
471 |
+ case AMR_SGRCodeBgGreen: |
|
472 |
+ case AMR_SGRCodeFgGreen: |
|
473 |
+ case AMR_SGRCodeBgYellow: |
|
474 |
+ case AMR_SGRCodeFgYellow: |
|
475 |
+ case AMR_SGRCodeBgBlue: |
|
476 |
+ case AMR_SGRCodeFgBlue: |
|
477 |
+ case AMR_SGRCodeBgMagenta: |
|
478 |
+ case AMR_SGRCodeFgMagenta: |
|
479 |
+ case AMR_SGRCodeBgCyan: |
|
480 |
+ case AMR_SGRCodeFgCyan: |
|
481 |
+ case AMR_SGRCodeBgWhite: |
|
482 |
+ case AMR_SGRCodeFgWhite: |
|
483 |
+ case AMR_SGRCodeBgBrightBlack: |
|
484 |
+ case AMR_SGRCodeFgBrightBlack: |
|
485 |
+ case AMR_SGRCodeBgBrightRed: |
|
486 |
+ case AMR_SGRCodeFgBrightRed: |
|
487 |
+ case AMR_SGRCodeBgBrightGreen: |
|
488 |
+ case AMR_SGRCodeFgBrightGreen: |
|
489 |
+ case AMR_SGRCodeBgBrightYellow: |
|
490 |
+ case AMR_SGRCodeFgBrightYellow: |
|
491 |
+ case AMR_SGRCodeBgBrightBlue: |
|
492 |
+ case AMR_SGRCodeFgBrightBlue: |
|
493 |
+ case AMR_SGRCodeBgBrightMagenta: |
|
494 |
+ case AMR_SGRCodeFgBrightMagenta: |
|
495 |
+ case AMR_SGRCodeBgBrightCyan: |
|
496 |
+ case AMR_SGRCodeFgBrightCyan: |
|
497 |
+ case AMR_SGRCodeBgBrightWhite: |
|
498 |
+ case AMR_SGRCodeFgBrightWhite: |
|
499 |
+ thisAttributeValue = [self colorForSGRCode:thisCode]; |
|
500 |
+ break; |
|
501 |
+ case AMR_SGRCodeIntensityBold: |
|
502 |
+ { |
|
503 |
+ NSFont *boldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSBoldFontMask]; |
|
504 |
+ thisAttributeValue = boldFont; |
|
505 |
+ } |
|
506 |
+ break; |
|
507 |
+ case AMR_SGRCodeIntensityNormal: |
|
508 |
+ case AMR_SGRCodeIntensityFaint: |
|
509 |
+ { |
|
510 |
+ NSFont *unboldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSUnboldFontMask]; |
|
511 |
+ thisAttributeValue = unboldFont; |
|
512 |
+ } |
|
513 |
+ break; |
|
514 |
+ case AMR_SGRCodeUnderlineSingle: |
|
515 |
+ thisAttributeValue = @(NSUnderlineStyleSingle); |
|
516 |
+ break; |
|
517 |
+ case AMR_SGRCodeUnderlineDouble: |
|
518 |
+ thisAttributeValue = @(NSUnderlineStyleDouble); |
|
519 |
+ break; |
|
520 |
+ case AMR_SGRCodeUnderlineNone: |
|
521 |
+ thisAttributeValue = @(NSUnderlineStyleNone); |
|
522 |
+ break; |
|
523 |
+ case AMR_SGRCodeAllReset: |
|
524 |
+ case AMR_SGRCodeFgReset: |
|
525 |
+ case AMR_SGRCodeBgReset: |
|
526 |
+ case AMR_SGRCodeNoneOrInvalid: |
|
527 |
+ case AMR_SGRCodeItalicOn: |
|
528 |
+ break; |
|
529 |
+ } |
|
530 |
+ |
|
531 |
+ |
|
532 |
+ // find the next sequence that specifies the end of this formatting run |
|
533 |
+ NSInteger formattingRunEndLocation = -1; |
|
534 |
+ if (iCode < (formatCodes.count - 1)) |
|
535 |
+ { |
|
536 |
+ NSDictionary *thisEndCodeCandidateDict; |
|
537 |
+ unichar thisEndCodeCandidate; |
|
538 |
+ for (NSUInteger iEndCode = iCode+1; iEndCode < formatCodes.count; iEndCode++) |
|
539 |
+ { |
|
540 |
+ thisEndCodeCandidateDict = formatCodes[iEndCode]; |
|
541 |
+ thisEndCodeCandidate = [thisEndCodeCandidateDict[kAMRCodeDictKey_code] unsignedIntValue]; |
|
542 |
+ |
|
543 |
+ if ([self AMR_SGRCode:thisEndCodeCandidate endsFormattingIntroducedByCode:thisCode]) |
|
544 |
+ { |
|
545 |
+ formattingRunEndLocation = [thisEndCodeCandidateDict[kAMRCodeDictKey_location] unsignedIntegerValue]; |
|
546 |
+ break; |
|
547 |
+ } |
|
548 |
+ } |
|
549 |
+ } |
|
550 |
+ if (formattingRunEndLocation == -1) |
|
551 |
+ formattingRunEndLocation = cleanString.length; |
|
552 |
+ |
|
553 |
+ if (thisAttributeName && thisAttributeValue) |
|
554 |
+ { |
|
555 |
+ [attrsAndRanges addObject:@{ |
|
556 |
+ kAMRAttrDictKey_range: [NSValue valueWithRange:NSMakeRange(formattingRunStartLocation, (formattingRunEndLocation-formattingRunStartLocation))], |
|
557 |
+ kAMRAttrDictKey_attrName: thisAttributeName, |
|
558 |
+ kAMRAttrDictKey_attrValue: thisAttributeValue, |
|
559 |
+ }]; |
|
560 |
+ } |
|
561 |
+ } |
|
562 |
+ |
|
563 |
+ if (aCleanString) |
|
564 |
+ *aCleanString = cleanString; |
|
565 |
+ return attrsAndRanges; |
|
566 |
+} |
|
567 |
+ |
|
568 |
+ |
|
569 |
+ |
|
570 |
+ |
|
571 |
+ |
|
572 |
+- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode |
|
573 |
+{ |
|
574 |
+ switch(startCode) |
|
575 |
+ { |
|
576 |
+ case AMR_SGRCodeFgBlack: |
|
577 |
+ case AMR_SGRCodeFgRed: |
|
578 |
+ case AMR_SGRCodeFgGreen: |
|
579 |
+ case AMR_SGRCodeFgYellow: |
|
580 |
+ case AMR_SGRCodeFgBlue: |
|
581 |
+ case AMR_SGRCodeFgMagenta: |
|
582 |
+ case AMR_SGRCodeFgCyan: |
|
583 |
+ case AMR_SGRCodeFgWhite: |
|
584 |
+ case AMR_SGRCodeFgBrightBlack: |
|
585 |
+ case AMR_SGRCodeFgBrightRed: |
|
586 |
+ case AMR_SGRCodeFgBrightGreen: |
|
587 |
+ case AMR_SGRCodeFgBrightYellow: |
|
588 |
+ case AMR_SGRCodeFgBrightBlue: |
|
589 |
+ case AMR_SGRCodeFgBrightMagenta: |
|
590 |
+ case AMR_SGRCodeFgBrightCyan: |
|
591 |
+ case AMR_SGRCodeFgBrightWhite: |
|
592 |
+ return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeFgReset || |
|
593 |
+ endCode == AMR_SGRCodeFgBlack || endCode == AMR_SGRCodeFgRed || |
|
594 |
+ endCode == AMR_SGRCodeFgGreen || endCode == AMR_SGRCodeFgYellow || |
|
595 |
+ endCode == AMR_SGRCodeFgBlue || endCode == AMR_SGRCodeFgMagenta || |
|
596 |
+ endCode == AMR_SGRCodeFgCyan || endCode == AMR_SGRCodeFgWhite || |
|
597 |
+ endCode == AMR_SGRCodeFgBrightBlack || endCode == AMR_SGRCodeFgBrightRed || |
|
598 |
+ endCode == AMR_SGRCodeFgBrightGreen || endCode == AMR_SGRCodeFgBrightYellow || |
|
599 |
+ endCode == AMR_SGRCodeFgBrightBlue || endCode == AMR_SGRCodeFgBrightMagenta || |
|
600 |
+ endCode == AMR_SGRCodeFgBrightCyan || endCode == AMR_SGRCodeFgBrightWhite); |
|
601 |
+ case AMR_SGRCodeBgBlack: |
|
602 |
+ case AMR_SGRCodeBgRed: |
|
603 |
+ case AMR_SGRCodeBgGreen: |
|
604 |
+ case AMR_SGRCodeBgYellow: |
|
605 |
+ case AMR_SGRCodeBgBlue: |
|
606 |
+ case AMR_SGRCodeBgMagenta: |
|
607 |
+ case AMR_SGRCodeBgCyan: |
|
608 |
+ case AMR_SGRCodeBgWhite: |
|
609 |
+ case AMR_SGRCodeBgBrightBlack: |
|
610 |
+ case AMR_SGRCodeBgBrightRed: |
|
611 |
+ case AMR_SGRCodeBgBrightGreen: |
|
612 |
+ case AMR_SGRCodeBgBrightYellow: |
|
613 |
+ case AMR_SGRCodeBgBrightBlue: |
|
614 |
+ case AMR_SGRCodeBgBrightMagenta: |
|
615 |
+ case AMR_SGRCodeBgBrightCyan: |
|
616 |
+ case AMR_SGRCodeBgBrightWhite: |
|
617 |
+ return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeBgReset || |
|
618 |
+ endCode == AMR_SGRCodeBgBlack || endCode == AMR_SGRCodeBgRed || |
|
619 |
+ endCode == AMR_SGRCodeBgGreen || endCode == AMR_SGRCodeBgYellow || |
|
620 |
+ endCode == AMR_SGRCodeBgBlue || endCode == AMR_SGRCodeBgMagenta || |
|
621 |
+ endCode == AMR_SGRCodeBgCyan || endCode == AMR_SGRCodeBgWhite || |
|
622 |
+ endCode == AMR_SGRCodeBgBrightBlack || endCode == AMR_SGRCodeBgBrightRed || |
|
623 |
+ endCode == AMR_SGRCodeBgBrightGreen || endCode == AMR_SGRCodeBgBrightYellow || |
|
624 |
+ endCode == AMR_SGRCodeBgBrightBlue || endCode == AMR_SGRCodeBgBrightMagenta || |
|
625 |
+ endCode == AMR_SGRCodeBgBrightCyan || endCode == AMR_SGRCodeBgBrightWhite); |
|
626 |
+ case AMR_SGRCodeIntensityBold: |
|
627 |
+ case AMR_SGRCodeIntensityNormal: |
|
628 |
+ return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeIntensityNormal || |
|
629 |
+ endCode == AMR_SGRCodeIntensityBold || endCode == AMR_SGRCodeIntensityFaint); |
|
630 |
+ case AMR_SGRCodeUnderlineSingle: |
|
631 |
+ case AMR_SGRCodeUnderlineDouble: |
|
632 |
+ return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeUnderlineNone || |
|
633 |
+ endCode == AMR_SGRCodeUnderlineSingle || endCode == AMR_SGRCodeUnderlineDouble); |
|
634 |
+ case AMR_SGRCodeNoneOrInvalid: |
|
635 |
+ case AMR_SGRCodeItalicOn: |
|
636 |
+ case AMR_SGRCodeUnderlineNone: |
|
637 |
+ case AMR_SGRCodeIntensityFaint: |
|
638 |
+ case AMR_SGRCodeAllReset: |
|
639 |
+ case AMR_SGRCodeBgReset: |
|
640 |
+ case AMR_SGRCodeFgReset: |
|
641 |
+ return NO; |
|
642 |
+ } |
|
643 |
+ |
|
644 |
+ return NO; |
|
645 |
+} |
|
646 |
+ |
|
647 |
+ |
|
648 |
+ |
|
649 |
+ |
|
650 |
+- (NSColor*) colorForSGRCode:(AMR_SGRCode)code |
|
651 |
+{ |
|
652 |
+ if (self.ansiColors) |
|
653 |
+ { |
|
654 |
+ NSColor *preferredColor = self.ansiColors[@(code)]; |
|
655 |
+ if (preferredColor) |
|
656 |
+ return preferredColor; |
|
657 |
+ } |
|
658 |
+ |
|
659 |
+ switch(code) |
|
660 |
+ { |
|
661 |
+ case AMR_SGRCodeFgBlack: |
|
662 |
+ return kDefaultANSIColorFgBlack; |
|
663 |
+ case AMR_SGRCodeFgRed: |
|
664 |
+ return kDefaultANSIColorFgRed; |
|
665 |
+ case AMR_SGRCodeFgGreen: |
|
666 |
+ return kDefaultANSIColorFgGreen; |
|
667 |
+ case AMR_SGRCodeFgYellow: |
|
668 |
+ return kDefaultANSIColorFgYellow; |
|
669 |
+ case AMR_SGRCodeFgBlue: |
|
670 |
+ return kDefaultANSIColorFgBlue; |
|
671 |
+ case AMR_SGRCodeFgMagenta: |
|
672 |
+ return kDefaultANSIColorFgMagenta; |
|
673 |
+ case AMR_SGRCodeFgCyan: |
|
674 |
+ return kDefaultANSIColorFgCyan; |
|
675 |
+ case AMR_SGRCodeFgWhite: |
|
676 |
+ return kDefaultANSIColorFgWhite; |
|
677 |
+ case AMR_SGRCodeFgBrightBlack: |
|
678 |
+ return kDefaultANSIColorFgBrightBlack; |
|
679 |
+ case AMR_SGRCodeFgBrightRed: |
|
680 |
+ return kDefaultANSIColorFgBrightRed; |
|
681 |
+ case AMR_SGRCodeFgBrightGreen: |
|
682 |
+ return kDefaultANSIColorFgBrightGreen; |
|
683 |
+ case AMR_SGRCodeFgBrightYellow: |
|
684 |
+ return kDefaultANSIColorFgBrightYellow; |
|
685 |
+ case AMR_SGRCodeFgBrightBlue: |
|
686 |
+ return kDefaultANSIColorFgBrightBlue; |
|
687 |
+ case AMR_SGRCodeFgBrightMagenta: |
|
688 |
+ return kDefaultANSIColorFgBrightMagenta; |
|
689 |
+ case AMR_SGRCodeFgBrightCyan: |
|
690 |
+ return kDefaultANSIColorFgBrightCyan; |
|
691 |
+ case AMR_SGRCodeFgBrightWhite: |
|
692 |
+ return kDefaultANSIColorFgBrightWhite; |
|
693 |
+ case AMR_SGRCodeBgBlack: |
|
694 |
+ return kDefaultANSIColorBgBlack; |
|
695 |
+ case AMR_SGRCodeBgRed: |
|
696 |
+ return kDefaultANSIColorBgRed; |
|
697 |
+ case AMR_SGRCodeBgGreen: |
|
698 |
+ return kDefaultANSIColorBgGreen; |
|
699 |
+ case AMR_SGRCodeBgYellow: |
|
700 |
+ return kDefaultANSIColorBgYellow; |
|
701 |
+ case AMR_SGRCodeBgBlue: |
|
702 |
+ return kDefaultANSIColorBgBlue; |
|
703 |
+ case AMR_SGRCodeBgMagenta: |
|
704 |
+ return kDefaultANSIColorBgMagenta; |
|
705 |
+ case AMR_SGRCodeBgCyan: |
|
706 |
+ return kDefaultANSIColorBgCyan; |
|
707 |
+ case AMR_SGRCodeBgWhite: |
|
708 |
+ return kDefaultANSIColorBgWhite; |
|
709 |
+ case AMR_SGRCodeBgBrightBlack: |
|
710 |
+ return kDefaultANSIColorBgBrightBlack; |
|
711 |
+ case AMR_SGRCodeBgBrightRed: |
|
712 |
+ return kDefaultANSIColorBgBrightRed; |
|
713 |
+ case AMR_SGRCodeBgBrightGreen: |
|
714 |
+ return kDefaultANSIColorBgBrightGreen; |
|
715 |
+ case AMR_SGRCodeBgBrightYellow: |
|
716 |
+ return kDefaultANSIColorBgBrightYellow; |
|
717 |
+ case AMR_SGRCodeBgBrightBlue: |
|
718 |
+ return kDefaultANSIColorBgBrightBlue; |
|
719 |
+ case AMR_SGRCodeBgBrightMagenta: |
|
720 |
+ return kDefaultANSIColorBgBrightMagenta; |
|
721 |
+ case AMR_SGRCodeBgBrightCyan: |
|
722 |
+ return kDefaultANSIColorBgBrightCyan; |
|
723 |
+ case AMR_SGRCodeBgBrightWhite: |
|
724 |
+ return kDefaultANSIColorBgBrightWhite; |
|
725 |
+ case AMR_SGRCodeNoneOrInvalid: |
|
726 |
+ case AMR_SGRCodeItalicOn: |
|
727 |
+ case AMR_SGRCodeUnderlineNone: |
|
728 |
+ case AMR_SGRCodeIntensityFaint: |
|
729 |
+ case AMR_SGRCodeAllReset: |
|
730 |
+ case AMR_SGRCodeBgReset: |
|
731 |
+ case AMR_SGRCodeFgReset: |
|
732 |
+ case AMR_SGRCodeIntensityBold: |
|
733 |
+ case AMR_SGRCodeIntensityNormal: |
|
734 |
+ case AMR_SGRCodeUnderlineSingle: |
|
735 |
+ case AMR_SGRCodeUnderlineDouble: |
|
736 |
+ break; |
|
737 |
+ } |
|
738 |
+ |
|
739 |
+ return kDefaultANSIColorFgBlack; |
|
740 |
+} |
|
741 |
+ |
|
742 |
+ |
|
743 |
+- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground |
|
744 |
+{ |
|
745 |
+ if (self.ansiColors) |
|
746 |
+ { |
|
747 |
+ NSArray *codesForGivenColor = [self.ansiColors allKeysForObject:aColor]; |
|
748 |
+ |
|
749 |
+ if (codesForGivenColor != nil && 0 < codesForGivenColor.count) |
|
750 |
+ { |
|
751 |
+ for (NSNumber *thisCode in codesForGivenColor) |
|
752 |
+ { |
|
753 |
+ BOOL thisIsForegroundColor = (thisCode.intValue < 40); |
|
754 |
+ if (aForeground == thisIsForegroundColor) |
|
755 |
+ return thisCode.intValue; |
|
756 |
+ } |
|
757 |
+ } |
|
758 |
+ } |
|
759 |
+ |
|
760 |
+ if (aForeground) |
|
761 |
+ { |
|
762 |
+ if ([aColor isEqual:kDefaultANSIColorFgBlack]) |
|
763 |
+ return AMR_SGRCodeFgBlack; |
|
764 |
+ else if ([aColor isEqual:kDefaultANSIColorFgRed]) |
|
765 |
+ return AMR_SGRCodeFgRed; |
|
766 |
+ else if ([aColor isEqual:kDefaultANSIColorFgGreen]) |
|
767 |
+ return AMR_SGRCodeFgGreen; |
|
768 |
+ else if ([aColor isEqual:kDefaultANSIColorFgYellow]) |
|
769 |
+ return AMR_SGRCodeFgYellow; |
|
770 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBlue]) |
|
771 |
+ return AMR_SGRCodeFgBlue; |
|
772 |
+ else if ([aColor isEqual:kDefaultANSIColorFgMagenta]) |
|
773 |
+ return AMR_SGRCodeFgMagenta; |
|
774 |
+ else if ([aColor isEqual:kDefaultANSIColorFgCyan]) |
|
775 |
+ return AMR_SGRCodeFgCyan; |
|
776 |
+ else if ([aColor isEqual:kDefaultANSIColorFgWhite]) |
|
777 |
+ return AMR_SGRCodeFgWhite; |
|
778 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightBlack]) |
|
779 |
+ return AMR_SGRCodeFgBrightBlack; |
|
780 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightRed]) |
|
781 |
+ return AMR_SGRCodeFgBrightRed; |
|
782 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightGreen]) |
|
783 |
+ return AMR_SGRCodeFgBrightGreen; |
|
784 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightYellow]) |
|
785 |
+ return AMR_SGRCodeFgBrightYellow; |
|
786 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightBlue]) |
|
787 |
+ return AMR_SGRCodeFgBrightBlue; |
|
788 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightMagenta]) |
|
789 |
+ return AMR_SGRCodeFgBrightMagenta; |
|
790 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightCyan]) |
|
791 |
+ return AMR_SGRCodeFgBrightCyan; |
|
792 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightWhite]) |
|
793 |
+ return AMR_SGRCodeFgBrightWhite; |
|
794 |
+ } |
|
795 |
+ else |
|
796 |
+ { |
|
797 |
+ if ([aColor isEqual:kDefaultANSIColorBgBlack]) |
|
798 |
+ return AMR_SGRCodeBgBlack; |
|
799 |
+ else if ([aColor isEqual:kDefaultANSIColorBgRed]) |
|
800 |
+ return AMR_SGRCodeBgRed; |
|
801 |
+ else if ([aColor isEqual:kDefaultANSIColorBgGreen]) |
|
802 |
+ return AMR_SGRCodeBgGreen; |
|
803 |
+ else if ([aColor isEqual:kDefaultANSIColorBgYellow]) |
|
804 |
+ return AMR_SGRCodeBgYellow; |
|
805 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBlue]) |
|
806 |
+ return AMR_SGRCodeBgBlue; |
|
807 |
+ else if ([aColor isEqual:kDefaultANSIColorBgMagenta]) |
|
808 |
+ return AMR_SGRCodeBgMagenta; |
|
809 |
+ else if ([aColor isEqual:kDefaultANSIColorBgCyan]) |
|
810 |
+ return AMR_SGRCodeBgCyan; |
|
811 |
+ else if ([aColor isEqual:kDefaultANSIColorBgWhite]) |
|
812 |
+ return AMR_SGRCodeBgWhite; |
|
813 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightBlack]) |
|
814 |
+ return AMR_SGRCodeBgBrightBlack; |
|
815 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightRed]) |
|
816 |
+ return AMR_SGRCodeBgBrightRed; |
|
817 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightGreen]) |
|
818 |
+ return AMR_SGRCodeBgBrightGreen; |
|
819 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightYellow]) |
|
820 |
+ return AMR_SGRCodeBgBrightYellow; |
|
821 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightBlue]) |
|
822 |
+ return AMR_SGRCodeBgBrightBlue; |
|
823 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightMagenta]) |
|
824 |
+ return AMR_SGRCodeBgBrightMagenta; |
|
825 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightCyan]) |
|
826 |
+ return AMR_SGRCodeBgBrightCyan; |
|
827 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightWhite]) |
|
828 |
+ return AMR_SGRCodeBgBrightWhite; |
|
829 |
+ } |
|
830 |
+ |
|
831 |
+ return AMR_SGRCodeNoneOrInvalid; |
|
832 |
+} |
|
833 |
+ |
|
834 |
+ |
|
835 |
+ |
|
836 |
+// helper struct typedef and a few functions for |
|
837 |
+// -closestSGRCodeForColor:isForegroundColor: |
|
838 |
+ |
|
839 |
+typedef struct { |
|
840 |
+ CGFloat hue; |
|
841 |
+ CGFloat saturation; |
|
842 |
+ CGFloat brightness; |
|
843 |
+} AMR_HSB; |
|
844 |
+ |
|
845 |
+AMR_HSB makeHSB(CGFloat hue, CGFloat saturation, CGFloat brightness) |
|
846 |
+{ |
|
847 |
+ AMR_HSB outHSB; |
|
848 |
+ outHSB.hue = hue; |
|
849 |
+ outHSB.saturation = saturation; |
|
850 |
+ outHSB.brightness = brightness; |
|
851 |
+ return outHSB; |
|
852 |
+} |
|
853 |
+ |
|
854 |
+AMR_HSB getHSBFromColor(NSColor *color) |
|
855 |
+{ |
|
856 |
+ CGFloat hue = 0.0; |
|
857 |
+ CGFloat saturation = 0.0; |
|
858 |
+ CGFloat brightness = 0.0; |
|
859 |
+ [[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace] |
|
860 |
+ getHue:&hue |
|
861 |
+ saturation:&saturation |
|
862 |
+ brightness:&brightness |
|
863 |
+ alpha:NULL |
|
864 |
+ ]; |
|
865 |
+ return makeHSB(hue, saturation, brightness); |
|
866 |
+} |
|
867 |
+ |
|
868 |
+BOOL floatsEqual(CGFloat first, CGFloat second, CGFloat maxAbsError) |
|
869 |
+{ |
|
870 |
+ return (fabs(first-second)) < maxAbsError; |
|
871 |
+} |
|
872 |
+ |
|
873 |
+#define MAX_HUE_FLOAT_EQUALITY_ABS_ERROR 0.000001 |
|
874 |
+ |
|
875 |
+- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground |
|
876 |
+{ |
|
877 |
+ if (color == nil) |
|
878 |
+ return AMR_SGRCodeNoneOrInvalid; |
|
879 |
+ |
|
880 |
+ AMR_SGRCode closestColorSGRCode = [self AMR_SGRCodeForColor:color isForegroundColor:foreground]; |
|
881 |
+ if (closestColorSGRCode != AMR_SGRCodeNoneOrInvalid) |
|
882 |
+ return closestColorSGRCode; |
|
883 |
+ |
|
884 |
+ AMR_HSB givenColorHSB = getHSBFromColor(color); |
|
885 |
+ |
|
886 |
+ CGFloat closestColorHueDiff = FLT_MAX; |
|
887 |
+ CGFloat closestColorSaturationDiff = FLT_MAX; |
|
888 |
+ CGFloat closestColorBrightnessDiff = FLT_MAX; |
|
889 |
+ |
|
890 |
+ // (background SGR codes are +10 from foreground ones:) |
|
891 |
+ NSUInteger AMR_SGRCodeShift = (foreground)?0:10; |
|
892 |
+ NSArray *ansiFgColorCodes = @[ |
|
893 |
+ @(AMR_SGRCodeFgBlack+AMR_SGRCodeShift), |
|
894 |
+ @(AMR_SGRCodeFgRed+AMR_SGRCodeShift), |
|
895 |
+ @(AMR_SGRCodeFgGreen+AMR_SGRCodeShift), |
|
896 |
+ @(AMR_SGRCodeFgYellow+AMR_SGRCodeShift), |
|
897 |
+ @(AMR_SGRCodeFgBlue+AMR_SGRCodeShift), |
|
898 |
+ @(AMR_SGRCodeFgMagenta+AMR_SGRCodeShift), |
|
899 |
+ @(AMR_SGRCodeFgCyan+AMR_SGRCodeShift), |
|
900 |
+ @(AMR_SGRCodeFgWhite+AMR_SGRCodeShift), |
|
901 |
+ @(AMR_SGRCodeFgBrightBlack+AMR_SGRCodeShift), |
|
902 |
+ @(AMR_SGRCodeFgBrightRed+AMR_SGRCodeShift), |
|
903 |
+ @(AMR_SGRCodeFgBrightGreen+AMR_SGRCodeShift), |
|
904 |
+ @(AMR_SGRCodeFgBrightYellow+AMR_SGRCodeShift), |
|
905 |
+ @(AMR_SGRCodeFgBrightBlue+AMR_SGRCodeShift), |
|
906 |
+ @(AMR_SGRCodeFgBrightMagenta+AMR_SGRCodeShift), |
|
907 |
+ @(AMR_SGRCodeFgBrightCyan+AMR_SGRCodeShift), |
|
908 |
+ @(AMR_SGRCodeFgBrightWhite+AMR_SGRCodeShift), |
|
909 |
+ ]; |
|
910 |
+ for (NSNumber *thisSGRCodeNumber in ansiFgColorCodes) |
|
911 |
+ { |
|
912 |
+ AMR_SGRCode thisSGRCode = thisSGRCodeNumber.intValue; |
|
913 |
+ NSColor *thisColor = [self colorForSGRCode:thisSGRCode]; |
|
914 |
+ |
|
915 |
+ AMR_HSB thisColorHSB = getHSBFromColor(thisColor); |
|
916 |
+ |
|
917 |
+ CGFloat hueDiff = fabs(givenColorHSB.hue - thisColorHSB.hue); |
|
918 |
+ CGFloat saturationDiff = fabs(givenColorHSB.saturation - thisColorHSB.saturation); |
|
919 |
+ CGFloat brightnessDiff = fabs(givenColorHSB.brightness - thisColorHSB.brightness); |
|
920 |
+ |
|
921 |
+ // comparison depends on hue, saturation and brightness |
|
922 |
+ // (strictly in that order): |
|
923 |
+ |
|
924 |
+ if (!floatsEqual(hueDiff, closestColorHueDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR)) |
|
925 |
+ { |
|
926 |
+ if (hueDiff > closestColorHueDiff) |
|
927 |
+ continue; |
|
928 |
+ closestColorSGRCode = thisSGRCode; |
|
929 |
+ closestColorHueDiff = hueDiff; |
|
930 |
+ closestColorSaturationDiff = saturationDiff; |
|
931 |
+ closestColorBrightnessDiff = brightnessDiff; |
|
932 |
+ continue; |
|
933 |
+ } |
|
934 |
+ |
|
935 |
+ if (!floatsEqual(saturationDiff, closestColorSaturationDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR)) |
|
936 |
+ { |
|
937 |
+ if (saturationDiff > closestColorSaturationDiff) |
|
938 |
+ continue; |
|
939 |
+ closestColorSGRCode = thisSGRCode; |
|
940 |
+ closestColorHueDiff = hueDiff; |
|
941 |
+ closestColorSaturationDiff = saturationDiff; |
|
942 |
+ closestColorBrightnessDiff = brightnessDiff; |
|
943 |
+ continue; |
|
944 |
+ } |
|
945 |
+ |
|
946 |
+ if (!floatsEqual(brightnessDiff, closestColorBrightnessDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR)) |
|
947 |
+ { |
|
948 |
+ if (brightnessDiff > closestColorBrightnessDiff) |
|
949 |
+ continue; |
|
950 |
+ closestColorSGRCode = thisSGRCode; |
|
951 |
+ closestColorHueDiff = hueDiff; |
|
952 |
+ closestColorSaturationDiff = saturationDiff; |
|
953 |
+ closestColorBrightnessDiff = brightnessDiff; |
|
954 |
+ continue; |
|
955 |
+ } |
|
956 |
+ |
|
957 |
+ // If hue (especially hue!), saturation and brightness diffs all |
|
958 |
+ // are equal to some other color, we need to prefer one or the |
|
959 |
+ // other so we'll select the more 'distinctive' color of the |
|
960 |
+ // two (this is *very* subjective, obviously). I basically just |
|
961 |
+ // looked at the hue chart, went through all the points between |
|
962 |
+ // our main ANSI colors and decided which side the middle point |
|
963 |
+ // would lean on. (e.g. the purple color that is exactly between |
|
964 |
+ // the blue and magenta ANSI colors looks more magenta than |
|
965 |
+ // blue to me so I put magenta higher than blue in the list |
|
966 |
+ // below.) |
|
967 |
+ // |
|
968 |
+ // subjective ordering of colors from most to least 'distinctive': |
|
969 |
+ int colorDistinctivenessOrder[6] = { |
|
970 |
+ AMR_SGRCodeFgRed+(int)AMR_SGRCodeShift, |
|
971 |
+ AMR_SGRCodeFgMagenta+(int)AMR_SGRCodeShift, |
|
972 |
+ AMR_SGRCodeFgBlue+(int)AMR_SGRCodeShift, |
|
973 |
+ AMR_SGRCodeFgGreen+(int)AMR_SGRCodeShift, |
|
974 |
+ AMR_SGRCodeFgCyan+(int)AMR_SGRCodeShift, |
|
975 |
+ AMR_SGRCodeFgYellow+(int)AMR_SGRCodeShift |
|
976 |
+ }; |
|
977 |
+ for (int i = 0; i < 6; i++) |
|
978 |
+ { |
|
979 |
+ if (colorDistinctivenessOrder[i] == closestColorSGRCode) |
|
980 |
+ break; |
|
981 |
+ else if (colorDistinctivenessOrder[i] == thisSGRCode) |
|
982 |
+ { |
|
983 |
+ closestColorSGRCode = thisSGRCode; |
|
984 |
+ closestColorHueDiff = hueDiff; |
|
985 |
+ closestColorSaturationDiff = saturationDiff; |
|
986 |
+ closestColorBrightnessDiff = brightnessDiff; |
|
987 |
+ } |
|
988 |
+ } |
|
989 |
+ } |
|
990 |
+ |
|
991 |
+ return closestColorSGRCode; |
|
992 |
+} |
|
993 |
+ |
|
994 |
+ |
|
995 |
+ |
|
996 |
+@end |
0 | 997 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,326 @@ |
1 |
+// |
|
2 |
+// ANSIEscapeHelper.h |
|
3 |
+// AnsiColorsTest |
|
4 |
+// |
|
5 |
+// Created by Ali Rantakari on 18.3.09. |
|
6 |
+// |
|
7 |
+// Version 0.9.6 |
|
8 |
+// |
|
9 |
+/* |
|
10 |
+The MIT License |
|
11 |
+ |
|
12 |
+Copyright (c) 2008-2009,2013 Ali Rantakari |
|
13 |
+ |
|
14 |
+Permission is hereby granted, free of charge, to any person obtaining a copy |
|
15 |
+of this software and associated documentation files (the "Software"), to deal |
|
16 |
+in the Software without restriction, including without limitation the rights |
|
17 |
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
18 |
+copies of the Software, and to permit persons to whom the Software is |
|
19 |
+furnished to do so, subject to the following conditions: |
|
20 |
+ |
|
21 |
+The above copyright notice and this permission notice shall be included in |
|
22 |
+all copies or substantial portions of the Software. |
|
23 |
+ |
|
24 |
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
25 |
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
26 |
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
27 |
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
28 |
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
29 |
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
30 |
+THE SOFTWARE. |
|
31 |
+*/ |
|
32 |
+ |
|
33 |
+#import <Cocoa/Cocoa.h> |
|
34 |
+ |
|
35 |
+ |
|
36 |
+#if !__has_feature(objc_arc) |
|
37 |
+#warning "This code requires ARC to be enabled." |
|
38 |
+#endif |
|
39 |
+ |
|
40 |
+ |
|
41 |
+// dictionary keys for the SGR code dictionaries that the array |
|
42 |
+// escapeCodesForString:cleanString: returns contains |
|
43 |
+#define kAMRCodeDictKey_code @"code" |
|
44 |
+#define kAMRCodeDictKey_location @"location" |
|
45 |
+ |
|
46 |
+// dictionary keys for the string formatting attribute |
|
47 |
+// dictionaries that the array attributesForString:cleanString: |
|
48 |
+// returns contains |
|
49 |
+#define kAMRAttrDictKey_range @"range" |
|
50 |
+#define kAMRAttrDictKey_attrName @"attributeName" |
|
51 |
+#define kAMRAttrDictKey_attrValue @"attributeValue" |
|
52 |
+ |
|
53 |
+ |
|
54 |
+/*! |
|
55 |
+ @enum AMR_SGRCode |
|
56 |
+ |
|
57 |
+ @abstract SGR (Select Graphic Rendition) ANSI control codes. |
|
58 |
+ */ |
|
59 |
+typedef enum |
|
60 |
+{ |
|
61 |
+ AMR_SGRCodeNoneOrInvalid = -1, |
|
62 |
+ |
|
63 |
+ AMR_SGRCodeAllReset = 0, |
|
64 |
+ |
|
65 |
+ AMR_SGRCodeIntensityBold = 1, |
|
66 |
+ AMR_SGRCodeIntensityFaint = 2, |
|
67 |
+ AMR_SGRCodeIntensityNormal = 22, |
|
68 |
+ |
|
69 |
+ AMR_SGRCodeItalicOn = 3, |
|
70 |
+ |
|
71 |
+ AMR_SGRCodeUnderlineSingle = 4, |
|
72 |
+ AMR_SGRCodeUnderlineDouble = 21, |
|
73 |
+ AMR_SGRCodeUnderlineNone = 24, |
|
74 |
+ |
|
75 |
+ AMR_SGRCodeFgBlack = 30, |
|
76 |
+ AMR_SGRCodeFgRed = 31, |
|
77 |
+ AMR_SGRCodeFgGreen = 32, |
|
78 |
+ AMR_SGRCodeFgYellow = 33, |
|
79 |
+ AMR_SGRCodeFgBlue = 34, |
|
80 |
+ AMR_SGRCodeFgMagenta = 35, |
|
81 |
+ AMR_SGRCodeFgCyan = 36, |
|
82 |
+ AMR_SGRCodeFgWhite = 37, |
|
83 |
+ AMR_SGRCodeFgReset = 39, |
|
84 |
+ |
|
85 |
+ AMR_SGRCodeBgBlack = 40, |
|
86 |
+ AMR_SGRCodeBgRed = 41, |
|
87 |
+ AMR_SGRCodeBgGreen = 42, |
|
88 |
+ AMR_SGRCodeBgYellow = 43, |
|
89 |
+ AMR_SGRCodeBgBlue = 44, |
|
90 |
+ AMR_SGRCodeBgMagenta = 45, |
|
91 |
+ AMR_SGRCodeBgCyan = 46, |
|
92 |
+ AMR_SGRCodeBgWhite = 47, |
|
93 |
+ AMR_SGRCodeBgReset = 49, |
|
94 |
+ |
|
95 |
+ AMR_SGRCodeFgBrightBlack = 90, |
|
96 |
+ AMR_SGRCodeFgBrightRed = 91, |
|
97 |
+ AMR_SGRCodeFgBrightGreen = 92, |
|
98 |
+ AMR_SGRCodeFgBrightYellow = 93, |
|
99 |
+ AMR_SGRCodeFgBrightBlue = 94, |
|
100 |
+ AMR_SGRCodeFgBrightMagenta = 95, |
|
101 |
+ AMR_SGRCodeFgBrightCyan = 96, |
|
102 |
+ AMR_SGRCodeFgBrightWhite = 97, |
|
103 |
+ |
|
104 |
+ AMR_SGRCodeBgBrightBlack = 100, |
|
105 |
+ AMR_SGRCodeBgBrightRed = 101, |
|
106 |
+ AMR_SGRCodeBgBrightGreen = 102, |
|
107 |
+ AMR_SGRCodeBgBrightYellow = 103, |
|
108 |
+ AMR_SGRCodeBgBrightBlue = 104, |
|
109 |
+ AMR_SGRCodeBgBrightMagenta = 105, |
|
110 |
+ AMR_SGRCodeBgBrightCyan = 106, |
|
111 |
+ AMR_SGRCodeBgBrightWhite = 107 |
|
112 |
+} AMR_SGRCode; |
|
113 |
+ |
|
114 |
+ |
|
115 |
+ |
|
116 |
+ |
|
117 |
+ |
|
118 |
+ |
|
119 |
+/*! |
|
120 |
+ @class AMR_ANSIEscapeHelper |
|
121 |
+ |
|
122 |
+ @abstract Contains helper methods for dealing with strings |
|
123 |
+ that contain ANSI escape sequences for formatting (colors, |
|
124 |
+ underlining, bold etc.) |
|
125 |
+ */ |
|
126 |
+@interface AMR_ANSIEscapeHelper : NSObject |
|
127 |
+ |
|
128 |
+/*! |
|
129 |
+ @property defaultStringColor |
|
130 |
+ |
|
131 |
+ @abstract The default color used when creating an attributed string (default is black). |
|
132 |
+ */ |
|
133 |
+@property(copy) NSColor *defaultStringColor; |
|
134 |
+ |
|
135 |
+ |
|
136 |
+/*! |
|
137 |
+ @property font |
|
138 |
+ |
|
139 |
+ @abstract The font to use when creating string formatting attribute values. |
|
140 |
+ */ |
|
141 |
+@property(copy) NSFont *font; |
|
142 |
+ |
|
143 |
+/*! |
|
144 |
+ @property ansiColors |
|
145 |
+ |
|
146 |
+ @abstract The colors to use for displaying ANSI colors. |
|
147 |
+ |
|
148 |
+ @discussion Keys in this dictionary should be NSNumber objects containing SGR code |
|
149 |
+ values from the AMR_SGRCode enum. The corresponding values for these keys |
|
150 |
+ should be NSColor objects. If this property is nil or if it doesn't |
|
151 |
+ contain a key for a specific SGR code, the default color will be used |
|
152 |
+ instead. |
|
153 |
+ */ |
|
154 |
+@property(retain) NSMutableDictionary *ansiColors; |
|
155 |
+ |
|
156 |
+ |
|
157 |
+/*! |
|
158 |
+ @method attributedStringWithANSIEscapedString: |
|
159 |
+ |
|
160 |
+ @abstract Returns an attributed string that corresponds both in contents |
|
161 |
+ and formatting to a given string that contains ANSI escape |
|
162 |
+ sequences. |
|
163 |
+ |
|
164 |
+ @param aString A String containing ANSI escape sequences |
|
165 |
+ |
|
166 |
+ @result An attributed string that mimics as closely as possible |
|
167 |
+ the formatting of the given ANSI-escaped string. |
|
168 |
+ */ |
|
169 |
+- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString; |
|
170 |
+ |
|
171 |
+ |
|
172 |
+/*! |
|
173 |
+ @method ansiEscapedStringWithAttributedString: |
|
174 |
+ |
|
175 |
+ @abstract Returns a string containing ANSI escape sequences that corresponds |
|
176 |
+ both in contents and formatting to a given attributed string. |
|
177 |
+ |
|
178 |
+ @param aAttributedString An attributed string |
|
179 |
+ |
|
180 |
+ @result A string that mimics as closely as possible |
|
181 |
+ the formatting of the given attributed string with |
|
182 |
+ ANSI escape sequences. |
|
183 |
+ */ |
|
184 |
+- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString; |
|
185 |
+ |
|
186 |
+ |
|
187 |
+/*! |
|
188 |
+ @method escapeCodesForString:cleanString: |
|
189 |
+ |
|
190 |
+ @abstract Returns an array of SGR codes and their locations from a |
|
191 |
+ string containing ANSI escape sequences as well as a "clean" |
|
192 |
+ version of the string (i.e. one without the ANSI escape |
|
193 |
+ sequences.) |
|
194 |
+ |
|
195 |
+ @param aString A String containing ANSI escape sequences |
|
196 |
+ @param aCleanString Upon return, contains a "clean" version of aString (i.e. aString |
|
197 |
+ without the ANSI escape sequences) |
|
198 |
+ |
|
199 |
+ @result An array of NSDictionary objects, each of which has |
|
200 |
+ an NSNumber value for the key "code" (specifying an SGR code) and |
|
201 |
+ another NSNumber value for the key "location" (specifying the |
|
202 |
+ location of the code within aCleanString.) |
|
203 |
+ */ |
|
204 |
+- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString; |
|
205 |
+ |
|
206 |
+ |
|
207 |
+/*! |
|
208 |
+ @method ansiEscapedStringWithCodesAndLocations:cleanString: |
|
209 |
+ |
|
210 |
+ @abstract Returns a string containing ANSI escape codes for formatting based |
|
211 |
+ on a string and an array of SGR codes and their locations within |
|
212 |
+ the given string. |
|
213 |
+ |
|
214 |
+ @param aCodesArray An array of NSDictionary objects, each of which should have |
|
215 |
+ an NSNumber value for the key "code" (specifying an SGR |
|
216 |
+ code) and another NSNumber value for the key "location" |
|
217 |
+ (specifying the location of this SGR code in aCleanString.) |
|
218 |
+ @param aCleanString The string to which to insert the ANSI escape codes |
|
219 |
+ described in aCodesArray. |
|
220 |
+ |
|
221 |
+ @result A string containing ANSI escape sequences. |
|
222 |
+ */ |
|
223 |
+- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString; |
|
224 |
+ |
|
225 |
+ |
|
226 |
+/*! |
|
227 |
+ @method attributesForString:cleanString: |
|
228 |
+ |
|
229 |
+ @abstract Convert ANSI escape sequences in a string to string formatting attributes. |
|
230 |
+ |
|
231 |
+ @discussion Given a string with some ANSI escape sequences in it, this method returns |
|
232 |
+ attributes for formatting the specified string according to those ANSI |
|
233 |
+ escape sequences as well as a "clean" (i.e. free of the escape sequences) |
|
234 |
+ version of this string. |
|
235 |
+ |
|
236 |
+ @param aString A String containing ANSI escape sequences |
|
237 |
+ @param aCleanString Upon return, contains a "clean" version of aString (i.e. aString |
|
238 |
+ without the ANSI escape sequences.) Pass in NULL if you're not |
|
239 |
+ interested in this. |
|
240 |
+ |
|
241 |
+ @result An array containing NSDictionary objects, each of which has keys "range" |
|
242 |
+ (an NSValue containing an NSRange, specifying the range for the |
|
243 |
+ attribute within the "clean" version of aString), "attributeName" (an |
|
244 |
+ NSString) and "attributeValue" (an NSObject). You may use these as |
|
245 |
+ arguments for NSMutableAttributedString's methods for setting the |
|
246 |
+ visual formatting. |
|
247 |
+ */ |
|
248 |
+- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString; |
|
249 |
+ |
|
250 |
+ |
|
251 |
+/*! |
|
252 |
+ @method AMR_SGRCode:endsFormattingIntroducedByCode: |
|
253 |
+ |
|
254 |
+ @abstract Whether the occurrence of a given SGR code would end the formatting run |
|
255 |
+ introduced by another SGR code. |
|
256 |
+ |
|
257 |
+ @discussion For example, AMR_SGRCodeFgReset, AMR_SGRCodeAllReset or any SGR code |
|
258 |
+ specifying a foreground color would end the formatting run |
|
259 |
+ introduced by a foreground color -specifying SGR code. |
|
260 |
+ |
|
261 |
+ @param endCode The SGR code to test as a candidate for ending the formatting run |
|
262 |
+ introduced by startCode |
|
263 |
+ @param startCode The SGR code that has introduced a formatting run |
|
264 |
+ |
|
265 |
+ @result YES if the occurrence of endCode would end the formatting run |
|
266 |
+ introduced by startCode, NO otherwise. |
|
267 |
+ */ |
|
268 |
+- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode; |
|
269 |
+ |
|
270 |
+ |
|
271 |
+/*! |
|
272 |
+ @method colorForSGRCode: |
|
273 |
+ |
|
274 |
+ @abstract Returns the color to use for displaying a specific ANSI color. |
|
275 |
+ |
|
276 |
+ @discussion This method first considers the values set in the ansiColors |
|
277 |
+ property and only then the standard basic colors (NSColor's |
|
278 |
+ redColor, blueColor etc.) |
|
279 |
+ |
|
280 |
+ @param code An SGR code that specifies an ANSI color. |
|
281 |
+ |
|
282 |
+ @result The color to use for displaying the ANSI color specified by code. |
|
283 |
+ */ |
|
284 |
+- (NSColor*) colorForSGRCode:(AMR_SGRCode)code; |
|
285 |
+ |
|
286 |
+ |
|
287 |
+/*! |
|
288 |
+ @method AMR_SGRCodeForColor:isForegroundColor: |
|
289 |
+ |
|
290 |
+ @abstract Returns a color SGR code that corresponds to a given color. |
|
291 |
+ |
|
292 |
+ @discussion This method matches colors to their equivalent SGR codes |
|
293 |
+ by going through the colors specified in the ansiColors |
|
294 |
+ dictionary, and if ansiColors is null or if a match is |
|
295 |
+ not found there, by comparing the given color to the |
|
296 |
+ standard basic colors (NSColor's redColor, blueColor |
|
297 |
+ etc.) The comparison is done simply by checking for |
|
298 |
+ equality. |
|
299 |
+ |
|
300 |
+ @param aColor The color to get a corresponding SGR code for |
|
301 |
+ @param aForeground Whether you want a foreground or background color code |
|
302 |
+ |
|
303 |
+ @result SGR code that corresponds with aColor. |
|
304 |
+ */ |
|
305 |
+- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground; |
|
306 |
+ |
|
307 |
+ |
|
308 |
+/*! |
|
309 |
+ @method closestSGRCodeForColor:isForegroundColor: |
|
310 |
+ |
|
311 |
+ @abstract Returns a color SGR code that represents the closest ANSI |
|
312 |
+ color to a given color. |
|
313 |
+ |
|
314 |
+ @discussion This method attempts to find the closest ANSI color to |
|
315 |
+ aColor and return its SGR code. |
|
316 |
+ |
|
317 |
+ @param aColor The color to get a closest color SGR code match for |
|
318 |
+ @param aForeground Whether you want a foreground or background color code |
|
319 |
+ |
|
320 |
+ @result SGR code for the ANSI color that is closest to aColor. |
|
321 |
+ */ |
|
322 |
+- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground; |
|
323 |
+ |
|
324 |
+ |
|
325 |
+ |
|
326 |
+@end |
0 | 327 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,996 @@ |
1 |
+// |
|
2 |
+// ANSIEscapeHelper.m |
|
3 |
+// |
|
4 |
+// Created by Ali Rantakari on 18.3.09. |
|
5 |
+ |
|
6 |
+/* |
|
7 |
+The MIT License |
|
8 |
+ |
|
9 |
+Copyright (c) 2008-2009,2013 Ali Rantakari |
|
10 |
+ |
|
11 |
+Permission is hereby granted, free of charge, to any person obtaining a copy |
|
12 |
+of this software and associated documentation files (the "Software"), to deal |
|
13 |
+in the Software without restriction, including without limitation the rights |
|
14 |
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
15 |
+copies of the Software, and to permit persons to whom the Software is |
|
16 |
+furnished to do so, subject to the following conditions: |
|
17 |
+ |
|
18 |
+The above copyright notice and this permission notice shall be included in |
|
19 |
+all copies or substantial portions of the Software. |
|
20 |
+ |
|
21 |
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
22 |
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
23 |
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
24 |
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
25 |
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
26 |
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
27 |
+THE SOFTWARE. |
|
28 |
+*/ |
|
29 |
+ |
|
30 |
+/* |
|
31 |
+ todo: |
|
32 |
+ |
|
33 |
+ - don't add useless "reset" escape codes to the string in |
|
34 |
+ -ansiEscapedStringWithAttributedString: |
|
35 |
+ |
|
36 |
+ */ |
|
37 |
+ |
|
38 |
+ |
|
39 |
+ |
|
40 |
+#import "AMR_ANSIEscapeHelper.h" |
|
41 |
+ |
|
42 |
+ |
|
43 |
+// the CSI (Control Sequence Initiator) -- i.e. "escape sequence prefix". |
|
44 |
+// (add your own CSI:Miami joke here) |
|
45 |
+#define kANSIEscapeCSI @"\033[" |
|
46 |
+ |
|
47 |
+// the end byte of an SGR (Select Graphic Rendition) |
|
48 |
+// ANSI Escape Sequence |
|
49 |
+#define kANSIEscapeSGREnd @"m" |
|
50 |
+ |
|
51 |
+ |
|
52 |
+// color definition helper macros |
|
53 |
+#define kBrightColorBrightness 1.0 |
|
54 |
+#define kBrightColorSaturation 0.4 |
|
55 |
+#define kBrightColorAlpha 1.0 |
|
56 |
+#define kBrightColorWithHue(h) [NSColor colorWithCalibratedHue:(h) saturation:kBrightColorSaturation brightness:kBrightColorBrightness alpha:kBrightColorAlpha] |
|
57 |
+ |
|
58 |
+// default colors |
|
59 |
+#define kDefaultANSIColorFgBlack NSColor.blackColor |
|
60 |
+#define kDefaultANSIColorFgRed NSColor.redColor |
|
61 |
+#define kDefaultANSIColorFgGreen NSColor.greenColor |
|
62 |
+#define kDefaultANSIColorFgYellow NSColor.yellowColor |
|
63 |
+#define kDefaultANSIColorFgBlue NSColor.blueColor |
|
64 |
+#define kDefaultANSIColorFgMagenta NSColor.magentaColor |
|
65 |
+#define kDefaultANSIColorFgCyan NSColor.cyanColor |
|
66 |
+#define kDefaultANSIColorFgWhite NSColor.whiteColor |
|
67 |
+ |
|
68 |
+#define kDefaultANSIColorFgBrightBlack [NSColor colorWithCalibratedWhite:0.337 alpha:1.0] |
|
69 |
+#define kDefaultANSIColorFgBrightRed kBrightColorWithHue(1.0) |
|
70 |
+#define kDefaultANSIColorFgBrightGreen kBrightColorWithHue(1.0/3.0) |
|
71 |
+#define kDefaultANSIColorFgBrightYellow kBrightColorWithHue(1.0/6.0) |
|
72 |
+#define kDefaultANSIColorFgBrightBlue kBrightColorWithHue(2.0/3.0) |
|
73 |
+#define kDefaultANSIColorFgBrightMagenta kBrightColorWithHue(5.0/6.0) |
|
74 |
+#define kDefaultANSIColorFgBrightCyan kBrightColorWithHue(0.5) |
|
75 |
+#define kDefaultANSIColorFgBrightWhite NSColor.whiteColor |
|
76 |
+ |
|
77 |
+#define kDefaultANSIColorBgBlack NSColor.blackColor |
|
78 |
+#define kDefaultANSIColorBgRed NSColor.redColor |
|
79 |
+#define kDefaultANSIColorBgGreen NSColor.greenColor |
|
80 |
+#define kDefaultANSIColorBgYellow NSColor.yellowColor |
|
81 |
+#define kDefaultANSIColorBgBlue NSColor.blueColor |
|
82 |
+#define kDefaultANSIColorBgMagenta NSColor.magentaColor |
|
83 |
+#define kDefaultANSIColorBgCyan NSColor.cyanColor |
|
84 |
+#define kDefaultANSIColorBgWhite NSColor.whiteColor |
|
85 |
+ |
|
86 |
+#define kDefaultANSIColorBgBrightBlack kDefaultANSIColorFgBrightBlack |
|
87 |
+#define kDefaultANSIColorBgBrightRed kDefaultANSIColorFgBrightRed |
|
88 |
+#define kDefaultANSIColorBgBrightGreen kDefaultANSIColorFgBrightGreen |
|
89 |
+#define kDefaultANSIColorBgBrightYellow kDefaultANSIColorFgBrightYellow |
|
90 |
+#define kDefaultANSIColorBgBrightBlue kDefaultANSIColorFgBrightBlue |
|
91 |
+#define kDefaultANSIColorBgBrightMagenta kDefaultANSIColorFgBrightMagenta |
|
92 |
+#define kDefaultANSIColorBgBrightCyan kDefaultANSIColorFgBrightCyan |
|
93 |
+#define kDefaultANSIColorBgBrightWhite kDefaultANSIColorFgBrightWhite |
|
94 |
+ |
|
95 |
+#define kDefaultFontSize [NSFont systemFontOfSize:NSFont.systemFontSize] |
|
96 |
+#define kDefaultForegroundColor NSColor.blackColor |
|
97 |
+ |
|
98 |
+// minimum weight for an NSFont for it to be considered bold |
|
99 |
+#define kBoldFontMinWeight 9 |
|
100 |
+ |
|
101 |
+ |
|
102 |
+@implementation AMR_ANSIEscapeHelper |
|
103 |
+ |
|
104 |
+- (id) init |
|
105 |
+{ |
|
106 |
+ if (!(self = [super init])) |
|
107 |
+ return nil; |
|
108 |
+ |
|
109 |
+ self.ansiColors = [NSMutableDictionary dictionary]; |
|
110 |
+ |
|
111 |
+ return self; |
|
112 |
+} |
|
113 |
+ |
|
114 |
+ |
|
115 |
+ |
|
116 |
+- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString |
|
117 |
+{ |
|
118 |
+ if (aString == nil) |
|
119 |
+ return nil; |
|
120 |
+ |
|
121 |
+ NSString *cleanString; |
|
122 |
+ NSArray *attributesAndRanges = [self attributesForString:aString cleanString:&cleanString]; |
|
123 |
+ NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] |
|
124 |
+ initWithString:cleanString |
|
125 |
+ attributes:@{ |
|
126 |
+ NSFontAttributeName: self.font ?: kDefaultFontSize, |
|
127 |
+ NSForegroundColorAttributeName: self.defaultStringColor ?: kDefaultForegroundColor |
|
128 |
+ }]; |
|
129 |
+ |
|
130 |
+ for (NSDictionary *thisAttributeDict in attributesAndRanges) |
|
131 |
+ { |
|
132 |
+ [attributedString |
|
133 |
+ addAttribute:thisAttributeDict[kAMRAttrDictKey_attrName] |
|
134 |
+ value:thisAttributeDict[kAMRAttrDictKey_attrValue] |
|
135 |
+ range:[thisAttributeDict[kAMRAttrDictKey_range] rangeValue] |
|
136 |
+ ]; |
|
137 |
+ } |
|
138 |
+ |
|
139 |
+ return attributedString; |
|
140 |
+} |
|
141 |
+ |
|
142 |
+ |
|
143 |
+ |
|
144 |
+- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString |
|
145 |
+{ |
|
146 |
+ NSMutableArray *codesAndLocations = [NSMutableArray array]; |
|
147 |
+ |
|
148 |
+ NSArray *attrNames = @[ |
|
149 |
+ NSFontAttributeName, NSForegroundColorAttributeName, |
|
150 |
+ NSBackgroundColorAttributeName, NSUnderlineStyleAttributeName, |
|
151 |
+ ]; |
|
152 |
+ |
|
153 |
+ for (NSString *thisAttrName in attrNames) |
|
154 |
+ { |
|
155 |
+ NSRange limitRange = NSMakeRange(0, aAttributedString.length); |
|
156 |
+ id attributeValue; |
|
157 |
+ NSRange effectiveRange; |
|
158 |
+ |
|
159 |
+ while (limitRange.length > 0) |
|
160 |
+ { |
|
161 |
+ attributeValue = [aAttributedString |
|
162 |
+ attribute:thisAttrName |
|
163 |
+ atIndex:limitRange.location |
|
164 |
+ longestEffectiveRange:&effectiveRange |
|
165 |
+ inRange:limitRange |
|
166 |
+ ]; |
|
167 |
+ |
|
168 |
+ AMR_SGRCode thisSGRCode = AMR_SGRCodeNoneOrInvalid; |
|
169 |
+ |
|
170 |
+ if ([thisAttrName isEqualToString:NSForegroundColorAttributeName]) |
|
171 |
+ { |
|
172 |
+ if (attributeValue != nil) |
|
173 |
+ thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:YES]; |
|
174 |
+ else |
|
175 |
+ thisSGRCode = AMR_SGRCodeFgReset; |
|
176 |
+ } |
|
177 |
+ else if ([thisAttrName isEqualToString:NSBackgroundColorAttributeName]) |
|
178 |
+ { |
|
179 |
+ if (attributeValue != nil) |
|
180 |
+ thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:NO]; |
|
181 |
+ else |
|
182 |
+ thisSGRCode = AMR_SGRCodeBgReset; |
|
183 |
+ } |
|
184 |
+ else if ([thisAttrName isEqualToString:NSFontAttributeName]) |
|
185 |
+ { |
|
186 |
+ // we currently only use NSFontAttributeName for bolding so |
|
187 |
+ // here we assume that the formatting "type" in ANSI SGR |
|
188 |
+ // terms is indeed intensity |
|
189 |
+ if (attributeValue != nil) |
|
190 |
+ thisSGRCode = ([NSFontManager.sharedFontManager weightOfFont:attributeValue] >= kBoldFontMinWeight) |
|
191 |
+ ? AMR_SGRCodeIntensityBold : AMR_SGRCodeIntensityNormal; |
|
192 |
+ else |
|
193 |
+ thisSGRCode = AMR_SGRCodeIntensityNormal; |
|
194 |
+ } |
|
195 |
+ else if ([thisAttrName isEqualToString:NSUnderlineStyleAttributeName]) |
|
196 |
+ { |
|
197 |
+ if (attributeValue != nil) |
|
198 |
+ { |
|
199 |
+ if ([attributeValue intValue] == NSUnderlineStyleSingle) |
|
200 |
+ thisSGRCode = AMR_SGRCodeUnderlineSingle; |
|
201 |
+ else if ([attributeValue intValue] == NSUnderlineStyleDouble) |
|
202 |
+ thisSGRCode = AMR_SGRCodeUnderlineDouble; |
|
203 |
+ else |
|
204 |
+ thisSGRCode = AMR_SGRCodeUnderlineNone; |
|
205 |
+ } |
|
206 |
+ else |
|
207 |
+ thisSGRCode = AMR_SGRCodeUnderlineNone; |
|
208 |
+ } |
|
209 |
+ |
|
210 |
+ if (thisSGRCode != AMR_SGRCodeNoneOrInvalid) |
|
211 |
+ { |
|
212 |
+ [codesAndLocations addObject: @{ |
|
213 |
+ kAMRCodeDictKey_code: @(thisSGRCode), |
|
214 |
+ kAMRCodeDictKey_location: @(effectiveRange.location), |
|
215 |
+ }]; |
|
216 |
+ } |
|
217 |
+ |
|
218 |
+ limitRange = NSMakeRange(NSMaxRange(effectiveRange), |
|
219 |
+ NSMaxRange(limitRange) - NSMaxRange(effectiveRange)); |
|
220 |
+ } |
|
221 |
+ } |
|
222 |
+ |
|
223 |
+ return [self ansiEscapedStringWithCodesAndLocations:codesAndLocations cleanString:aAttributedString.string]; |
|
224 |
+} |
|
225 |
+ |
|
226 |
+ |
|
227 |
+- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString |
|
228 |
+{ |
|
229 |
+ if (aString == nil) |
|
230 |
+ return nil; |
|
231 |
+ if (aString.length <= kANSIEscapeCSI.length) |
|
232 |
+ { |
|
233 |
+ if (aCleanString) |
|
234 |
+ *aCleanString = aString.copy; |
|
235 |
+ return @[]; |
|
236 |
+ } |
|
237 |
+ |
|
238 |
+ NSString *cleanString = @""; |
|
239 |
+ |
|
240 |
+ // find all escape sequence codes from aString and put them in this array |
|
241 |
+ // along with their start locations within the "clean" version of aString |
|
242 |
+ NSMutableArray *formatCodes = [NSMutableArray array]; |
|
243 |
+ |
|
244 |
+ NSUInteger aStringLength = aString.length; |
|
245 |
+ NSUInteger coveredLength = 0; |
|
246 |
+ NSRange searchRange = NSMakeRange(0,aStringLength); |
|
247 |
+ NSRange thisEscapeSequenceRange; |
|
248 |
+ do |
|
249 |
+ { |
|
250 |
+ thisEscapeSequenceRange = [aString rangeOfString:kANSIEscapeCSI options:NSLiteralSearch range:searchRange]; |
|
251 |
+ if (thisEscapeSequenceRange.location != NSNotFound) |
|
252 |
+ { |
|
253 |
+ // adjust range's length so that it encompasses the whole ANSI escape sequence |
|
254 |
+ // and not just the Control Sequence Initiator (the "prefix") by finding the |
|
255 |
+ // final byte of the control sequence (one that has an ASCII decimal value |
|
256 |
+ // between 64 and 126.) at the same time, read all formatting codes from inside |
|
257 |
+ // this escape sequence (there may be several, separated by semicolons.) |
|
258 |
+ NSMutableArray *codes = [NSMutableArray array]; |
|
259 |
+ unsigned int code = 0; |
|
260 |
+ unsigned int lengthAddition = 1; |
|
261 |
+ NSUInteger thisIndex; |
|
262 |
+ for (;;) |
|
263 |
+ { |
|
264 |
+ thisIndex = (NSMaxRange(thisEscapeSequenceRange)+lengthAddition-1); |
|
265 |
+ if (thisIndex >= aStringLength) |
|
266 |
+ break; |
|
267 |
+ |
|
268 |
+ unichar c = [aString characterAtIndex:thisIndex]; |
|
269 |
+ |
|
270 |
+ if (('0' <= c) && (c <= '9')) |
|
271 |
+ { |
|
272 |
+ int digit = c - '0'; |
|
273 |
+ code = (code == 0) ? digit : code*10+digit; |
|
274 |
+ } |
|
275 |
+ |
|
276 |
+ // ASCII decimal 109 is the SGR (Select Graphic Rendition) final byte |
|
277 |
+ // ("m"). this means that the code value we've just read specifies formatting |
|
278 |
+ // for the output; exactly what we're interested in. |
|
279 |
+ if (c == 'm') |
|
280 |
+ { |
|
281 |
+ [codes addObject:@(code)]; |
|
282 |
+ break; |
|
283 |
+ } |
|
284 |
+ else if ((64 <= c) && (c <= 126)) // any other valid final byte |
|
285 |
+ { |
|
286 |
+ [codes removeAllObjects]; |
|
287 |
+ break; |
|
288 |
+ } |
|
289 |
+ else if (c == ';') // separates codes within the same sequence |
|
290 |
+ { |
|
291 |
+ [codes addObject:@(code)]; |
|
292 |
+ code = 0; |
|
293 |
+ } |
|
294 |
+ |
|
295 |
+ lengthAddition++; |
|
296 |
+ } |
|
297 |
+ thisEscapeSequenceRange.length += lengthAddition; |
|
298 |
+ |
|
299 |
+ NSUInteger locationInCleanString = coveredLength+thisEscapeSequenceRange.location-searchRange.location; |
|
300 |
+ |
|
301 |
+ for (NSNumber *codeToAdd in codes) |
|
302 |
+ { |
|
303 |
+ [formatCodes addObject: @{ |
|
304 |
+ kAMRCodeDictKey_code: codeToAdd, |
|
305 |
+ kAMRCodeDictKey_location: @(locationInCleanString) |
|
306 |
+ }]; |
|
307 |
+ } |
|
308 |
+ |
|
309 |
+ NSUInteger thisCoveredLength = thisEscapeSequenceRange.location-searchRange.location; |
|
310 |
+ if (thisCoveredLength > 0) |
|
311 |
+ cleanString = [cleanString stringByAppendingString:[aString substringWithRange:NSMakeRange(searchRange.location, thisCoveredLength)]]; |
|
312 |
+ |
|
313 |
+ coveredLength += thisCoveredLength; |
|
314 |
+ searchRange.location = NSMaxRange(thisEscapeSequenceRange); |
|
315 |
+ searchRange.length = aStringLength-searchRange.location; |
|
316 |
+ } |
|
317 |
+ } |
|
318 |
+ while(thisEscapeSequenceRange.location != NSNotFound); |
|
319 |
+ |
|
320 |
+ if (searchRange.length > 0) |
|
321 |
+ cleanString = [cleanString stringByAppendingString:[aString substringWithRange:searchRange]]; |
|
322 |
+ |
|
323 |
+ if (aCleanString) |
|
324 |
+ *aCleanString = cleanString; |
|
325 |
+ return formatCodes; |
|
326 |
+} |
|
327 |
+ |
|
328 |
+ |
|
329 |
+ |
|
330 |
+ |
|
331 |
+- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString |
|
332 |
+{ |
|
333 |
+ NSMutableString* retStr = [NSMutableString stringWithCapacity:aCleanString.length]; |
|
334 |
+ |
|
335 |
+ NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:kAMRCodeDictKey_location ascending:YES]; |
|
336 |
+ NSArray *codesArray = [aCodesArray sortedArrayUsingDescriptors:@[sortDescriptor]]; |
|
337 |
+ |
|
338 |
+ NSUInteger aCleanStringIndex = 0; |
|
339 |
+ NSUInteger aCleanStringLength = aCleanString.length; |
|
340 |
+ for (NSDictionary *thisCodeDict in codesArray) |
|
341 |
+ { |
|
342 |
+ if (!( thisCodeDict[kAMRCodeDictKey_code] && |
|
343 |
+ thisCodeDict[kAMRCodeDictKey_location] |
|
344 |
+ )) |
|
345 |
+ continue; |
|
346 |
+ |
|
347 |
+ AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue]; |
|
348 |
+ NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue]; |
|
349 |
+ |
|
350 |
+ if (formattingRunStartLocation > aCleanStringLength) |
|
351 |
+ continue; |
|
352 |
+ |
|
353 |
+ if (aCleanStringIndex < formattingRunStartLocation) |
|
354 |
+ [retStr appendString:[aCleanString substringWithRange:NSMakeRange(aCleanStringIndex, formattingRunStartLocation-aCleanStringIndex)]]; |
|
355 |
+ [retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, thisCode, kANSIEscapeSGREnd]; |
|
356 |
+ |
|
357 |
+ aCleanStringIndex = formattingRunStartLocation; |
|
358 |
+ } |
|
359 |
+ |
|
360 |
+ if (aCleanStringIndex < aCleanStringLength) |
|
361 |
+ [retStr appendString:[aCleanString substringFromIndex:aCleanStringIndex]]; |
|
362 |
+ |
|
363 |
+ [retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, AMR_SGRCodeAllReset, kANSIEscapeSGREnd]; |
|
364 |
+ |
|
365 |
+ return retStr; |
|
366 |
+} |
|
367 |
+ |
|
368 |
+ |
|
369 |
+ |
|
370 |
+ |
|
371 |
+ |
|
372 |
+- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString |
|
373 |
+{ |
|
374 |
+ if (aString == nil) |
|
375 |
+ return nil; |
|
376 |
+ if (aString.length <= kANSIEscapeCSI.length) |
|
377 |
+ { |
|
378 |
+ if (aCleanString) |
|
379 |
+ *aCleanString = aString.copy; |
|
380 |
+ return @[]; |
|
381 |
+ } |
|
382 |
+ |
|
383 |
+ NSMutableArray *attrsAndRanges = [NSMutableArray array]; |
|
384 |
+ |
|
385 |
+ NSString *cleanString; |
|
386 |
+ NSArray *formatCodes = [self escapeCodesForString:aString cleanString:&cleanString]; |
|
387 |
+ |
|
388 |
+ // go through all the found escape sequence codes and for each one, create |
|
389 |
+ // the string formatting attribute name and value, find the next escape |
|
390 |
+ // sequence that specifies the end of the formatting run started by |
|
391 |
+ // the currently handled code, and generate a range from the difference |
|
392 |
+ // in those codes' locations within the clean aString. |
|
393 |
+ for (NSUInteger iCode = 0; iCode < formatCodes.count; iCode++) |
|
394 |
+ { |
|
395 |
+ NSDictionary *thisCodeDict = formatCodes[iCode]; |
|
396 |
+ AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue]; |
|
397 |
+ NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue]; |
|
398 |
+ |
|
399 |
+ // the attributed string attribute name for the formatting run introduced |
|
400 |
+ // by this code |
|
401 |
+ NSString *thisAttributeName = nil; |
|
402 |
+ |
|
403 |
+ // the attributed string attribute value for this formatting run introduced |
|
404 |
+ // by this code |
|
405 |
+ NSObject *thisAttributeValue = nil; |
|
406 |
+ |
|
407 |
+ // set attribute name |
|
408 |
+ switch(thisCode) |
|
409 |
+ { |
|
410 |
+ case AMR_SGRCodeFgBlack: |
|
411 |
+ case AMR_SGRCodeFgRed: |
|
412 |
+ case AMR_SGRCodeFgGreen: |
|
413 |
+ case AMR_SGRCodeFgYellow: |
|
414 |
+ case AMR_SGRCodeFgBlue: |
|
415 |
+ case AMR_SGRCodeFgMagenta: |
|
416 |
+ case AMR_SGRCodeFgCyan: |
|
417 |
+ case AMR_SGRCodeFgWhite: |
|
418 |
+ case AMR_SGRCodeFgBrightBlack: |
|
419 |
+ case AMR_SGRCodeFgBrightRed: |
|
420 |
+ case AMR_SGRCodeFgBrightGreen: |
|
421 |
+ case AMR_SGRCodeFgBrightYellow: |
|
422 |
+ case AMR_SGRCodeFgBrightBlue: |
|
423 |
+ case AMR_SGRCodeFgBrightMagenta: |
|
424 |
+ case AMR_SGRCodeFgBrightCyan: |
|
425 |
+ case AMR_SGRCodeFgBrightWhite: |
|
426 |
+ thisAttributeName = NSForegroundColorAttributeName; |
|
427 |
+ break; |
|
428 |
+ case AMR_SGRCodeBgBlack: |
|
429 |
+ case AMR_SGRCodeBgRed: |
|
430 |
+ case AMR_SGRCodeBgGreen: |
|
431 |
+ case AMR_SGRCodeBgYellow: |
|
432 |
+ case AMR_SGRCodeBgBlue: |
|
433 |
+ case AMR_SGRCodeBgMagenta: |
|
434 |
+ case AMR_SGRCodeBgCyan: |
|
435 |
+ case AMR_SGRCodeBgWhite: |
|
436 |
+ case AMR_SGRCodeBgBrightBlack: |
|
437 |
+ case AMR_SGRCodeBgBrightRed: |
|
438 |
+ case AMR_SGRCodeBgBrightGreen: |
|
439 |
+ case AMR_SGRCodeBgBrightYellow: |
|
440 |
+ case AMR_SGRCodeBgBrightBlue: |
|
441 |
+ case AMR_SGRCodeBgBrightMagenta: |
|
442 |
+ case AMR_SGRCodeBgBrightCyan: |
|
443 |
+ case AMR_SGRCodeBgBrightWhite: |
|
444 |
+ thisAttributeName = NSBackgroundColorAttributeName; |
|
445 |
+ break; |
|
446 |
+ case AMR_SGRCodeIntensityBold: |
|
447 |
+ case AMR_SGRCodeIntensityNormal: |
|
448 |
+ case AMR_SGRCodeIntensityFaint: |
|
449 |
+ thisAttributeName = NSFontAttributeName; |
|
450 |
+ break; |
|
451 |
+ case AMR_SGRCodeUnderlineSingle: |
|
452 |
+ case AMR_SGRCodeUnderlineDouble: |
|
453 |
+ case AMR_SGRCodeUnderlineNone: |
|
454 |
+ thisAttributeName = NSUnderlineStyleAttributeName; |
|
455 |
+ break; |
|
456 |
+ case AMR_SGRCodeAllReset: |
|
457 |
+ case AMR_SGRCodeFgReset: |
|
458 |
+ case AMR_SGRCodeBgReset: |
|
459 |
+ case AMR_SGRCodeNoneOrInvalid: |
|
460 |
+ case AMR_SGRCodeItalicOn: |
|
461 |
+ continue; |
|
462 |
+ } |
|
463 |
+ |
|
464 |
+ // set attribute value |
|
465 |
+ switch(thisCode) |
|
466 |
+ { |
|
467 |
+ case AMR_SGRCodeBgBlack: |
|
468 |
+ case AMR_SGRCodeFgBlack: |
|
469 |
+ case AMR_SGRCodeBgRed: |
|
470 |
+ case AMR_SGRCodeFgRed: |
|
471 |
+ case AMR_SGRCodeBgGreen: |
|
472 |
+ case AMR_SGRCodeFgGreen: |
|
473 |
+ case AMR_SGRCodeBgYellow: |
|
474 |
+ case AMR_SGRCodeFgYellow: |
|
475 |
+ case AMR_SGRCodeBgBlue: |
|
476 |
+ case AMR_SGRCodeFgBlue: |
|
477 |
+ case AMR_SGRCodeBgMagenta: |
|
478 |
+ case AMR_SGRCodeFgMagenta: |
|
479 |
+ case AMR_SGRCodeBgCyan: |
|
480 |
+ case AMR_SGRCodeFgCyan: |
|
481 |
+ case AMR_SGRCodeBgWhite: |
|
482 |
+ case AMR_SGRCodeFgWhite: |
|
483 |
+ case AMR_SGRCodeBgBrightBlack: |
|
484 |
+ case AMR_SGRCodeFgBrightBlack: |
|
485 |
+ case AMR_SGRCodeBgBrightRed: |
|
486 |
+ case AMR_SGRCodeFgBrightRed: |
|
487 |
+ case AMR_SGRCodeBgBrightGreen: |
|
488 |
+ case AMR_SGRCodeFgBrightGreen: |
|
489 |
+ case AMR_SGRCodeBgBrightYellow: |
|
490 |
+ case AMR_SGRCodeFgBrightYellow: |
|
491 |
+ case AMR_SGRCodeBgBrightBlue: |
|
492 |
+ case AMR_SGRCodeFgBrightBlue: |
|
493 |
+ case AMR_SGRCodeBgBrightMagenta: |
|
494 |
+ case AMR_SGRCodeFgBrightMagenta: |
|
495 |
+ case AMR_SGRCodeBgBrightCyan: |
|
496 |
+ case AMR_SGRCodeFgBrightCyan: |
|
497 |
+ case AMR_SGRCodeBgBrightWhite: |
|
498 |
+ case AMR_SGRCodeFgBrightWhite: |
|
499 |
+ thisAttributeValue = [self colorForSGRCode:thisCode]; |
|
500 |
+ break; |
|
501 |
+ case AMR_SGRCodeIntensityBold: |
|
502 |
+ { |
|
503 |
+ NSFont *boldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSBoldFontMask]; |
|
504 |
+ thisAttributeValue = boldFont; |
|
505 |
+ } |
|
506 |
+ break; |
|
507 |
+ case AMR_SGRCodeIntensityNormal: |
|
508 |
+ case AMR_SGRCodeIntensityFaint: |
|
509 |
+ { |
|
510 |
+ NSFont *unboldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSUnboldFontMask]; |
|
511 |
+ thisAttributeValue = unboldFont; |
|
512 |
+ } |
|
513 |
+ break; |
|
514 |
+ case AMR_SGRCodeUnderlineSingle: |
|
515 |
+ thisAttributeValue = @(NSUnderlineStyleSingle); |
|
516 |
+ break; |
|
517 |
+ case AMR_SGRCodeUnderlineDouble: |
|
518 |
+ thisAttributeValue = @(NSUnderlineStyleDouble); |
|
519 |
+ break; |
|
520 |
+ case AMR_SGRCodeUnderlineNone: |
|
521 |
+ thisAttributeValue = @(NSUnderlineStyleNone); |
|
522 |
+ break; |
|
523 |
+ case AMR_SGRCodeAllReset: |
|
524 |
+ case AMR_SGRCodeFgReset: |
|
525 |
+ case AMR_SGRCodeBgReset: |
|
526 |
+ case AMR_SGRCodeNoneOrInvalid: |
|
527 |
+ case AMR_SGRCodeItalicOn: |
|
528 |
+ break; |
|
529 |
+ } |
|
530 |
+ |
|
531 |
+ |
|
532 |
+ // find the next sequence that specifies the end of this formatting run |
|
533 |
+ NSInteger formattingRunEndLocation = -1; |
|
534 |
+ if (iCode < (formatCodes.count - 1)) |
|
535 |
+ { |
|
536 |
+ NSDictionary *thisEndCodeCandidateDict; |
|
537 |
+ unichar thisEndCodeCandidate; |
|
538 |
+ for (NSUInteger iEndCode = iCode+1; iEndCode < formatCodes.count; iEndCode++) |
|
539 |
+ { |
|
540 |
+ thisEndCodeCandidateDict = formatCodes[iEndCode]; |
|
541 |
+ thisEndCodeCandidate = [thisEndCodeCandidateDict[kAMRCodeDictKey_code] unsignedIntValue]; |
|
542 |
+ |
|
543 |
+ if ([self AMR_SGRCode:thisEndCodeCandidate endsFormattingIntroducedByCode:thisCode]) |
|
544 |
+ { |
|
545 |
+ formattingRunEndLocation = [thisEndCodeCandidateDict[kAMRCodeDictKey_location] unsignedIntegerValue]; |
|
546 |
+ break; |
|
547 |
+ } |
|
548 |
+ } |
|
549 |
+ } |
|
550 |
+ if (formattingRunEndLocation == -1) |
|
551 |
+ formattingRunEndLocation = cleanString.length; |
|
552 |
+ |
|
553 |
+ if (thisAttributeName && thisAttributeValue) |
|
554 |
+ { |
|
555 |
+ [attrsAndRanges addObject:@{ |
|
556 |
+ kAMRAttrDictKey_range: [NSValue valueWithRange:NSMakeRange(formattingRunStartLocation, (formattingRunEndLocation-formattingRunStartLocation))], |
|
557 |
+ kAMRAttrDictKey_attrName: thisAttributeName, |
|
558 |
+ kAMRAttrDictKey_attrValue: thisAttributeValue, |
|
559 |
+ }]; |
|
560 |
+ } |
|
561 |
+ } |
|
562 |
+ |
|
563 |
+ if (aCleanString) |
|
564 |
+ *aCleanString = cleanString; |
|
565 |
+ return attrsAndRanges; |
|
566 |
+} |
|
567 |
+ |
|
568 |
+ |
|
569 |
+ |
|
570 |
+ |
|
571 |
+ |
|
572 |
+- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode |
|
573 |
+{ |
|
574 |
+ switch(startCode) |
|
575 |
+ { |
|
576 |
+ case AMR_SGRCodeFgBlack: |
|
577 |
+ case AMR_SGRCodeFgRed: |
|
578 |
+ case AMR_SGRCodeFgGreen: |
|
579 |
+ case AMR_SGRCodeFgYellow: |
|
580 |
+ case AMR_SGRCodeFgBlue: |
|
581 |
+ case AMR_SGRCodeFgMagenta: |
|
582 |
+ case AMR_SGRCodeFgCyan: |
|
583 |
+ case AMR_SGRCodeFgWhite: |
|
584 |
+ case AMR_SGRCodeFgBrightBlack: |
|
585 |
+ case AMR_SGRCodeFgBrightRed: |
|
586 |
+ case AMR_SGRCodeFgBrightGreen: |
|
587 |
+ case AMR_SGRCodeFgBrightYellow: |
|
588 |
+ case AMR_SGRCodeFgBrightBlue: |
|
589 |
+ case AMR_SGRCodeFgBrightMagenta: |
|
590 |
+ case AMR_SGRCodeFgBrightCyan: |
|
591 |
+ case AMR_SGRCodeFgBrightWhite: |
|
592 |
+ return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeFgReset || |
|
593 |
+ endCode == AMR_SGRCodeFgBlack || endCode == AMR_SGRCodeFgRed || |
|
594 |
+ endCode == AMR_SGRCodeFgGreen || endCode == AMR_SGRCodeFgYellow || |
|
595 |
+ endCode == AMR_SGRCodeFgBlue || endCode == AMR_SGRCodeFgMagenta || |
|
596 |
+ endCode == AMR_SGRCodeFgCyan || endCode == AMR_SGRCodeFgWhite || |
|
597 |
+ endCode == AMR_SGRCodeFgBrightBlack || endCode == AMR_SGRCodeFgBrightRed || |
|
598 |
+ endCode == AMR_SGRCodeFgBrightGreen || endCode == AMR_SGRCodeFgBrightYellow || |
|
599 |
+ endCode == AMR_SGRCodeFgBrightBlue || endCode == AMR_SGRCodeFgBrightMagenta || |
|
600 |
+ endCode == AMR_SGRCodeFgBrightCyan || endCode == AMR_SGRCodeFgBrightWhite); |
|
601 |
+ case AMR_SGRCodeBgBlack: |
|
602 |
+ case AMR_SGRCodeBgRed: |
|
603 |
+ case AMR_SGRCodeBgGreen: |
|
604 |
+ case AMR_SGRCodeBgYellow: |
|
605 |
+ case AMR_SGRCodeBgBlue: |
|
606 |
+ case AMR_SGRCodeBgMagenta: |
|
607 |
+ case AMR_SGRCodeBgCyan: |
|
608 |
+ case AMR_SGRCodeBgWhite: |
|
609 |
+ case AMR_SGRCodeBgBrightBlack: |
|
610 |
+ case AMR_SGRCodeBgBrightRed: |
|
611 |
+ case AMR_SGRCodeBgBrightGreen: |
|
612 |
+ case AMR_SGRCodeBgBrightYellow: |
|
613 |
+ case AMR_SGRCodeBgBrightBlue: |
|
614 |
+ case AMR_SGRCodeBgBrightMagenta: |
|
615 |
+ case AMR_SGRCodeBgBrightCyan: |
|
616 |
+ case AMR_SGRCodeBgBrightWhite: |
|
617 |
+ return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeBgReset || |
|
618 |
+ endCode == AMR_SGRCodeBgBlack || endCode == AMR_SGRCodeBgRed || |
|
619 |
+ endCode == AMR_SGRCodeBgGreen || endCode == AMR_SGRCodeBgYellow || |
|
620 |
+ endCode == AMR_SGRCodeBgBlue || endCode == AMR_SGRCodeBgMagenta || |
|
621 |
+ endCode == AMR_SGRCodeBgCyan || endCode == AMR_SGRCodeBgWhite || |
|
622 |
+ endCode == AMR_SGRCodeBgBrightBlack || endCode == AMR_SGRCodeBgBrightRed || |
|
623 |
+ endCode == AMR_SGRCodeBgBrightGreen || endCode == AMR_SGRCodeBgBrightYellow || |
|
624 |
+ endCode == AMR_SGRCodeBgBrightBlue || endCode == AMR_SGRCodeBgBrightMagenta || |
|
625 |
+ endCode == AMR_SGRCodeBgBrightCyan || endCode == AMR_SGRCodeBgBrightWhite); |
|
626 |
+ case AMR_SGRCodeIntensityBold: |
|
627 |
+ case AMR_SGRCodeIntensityNormal: |
|
628 |
+ return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeIntensityNormal || |
|
629 |
+ endCode == AMR_SGRCodeIntensityBold || endCode == AMR_SGRCodeIntensityFaint); |
|
630 |
+ case AMR_SGRCodeUnderlineSingle: |
|
631 |
+ case AMR_SGRCodeUnderlineDouble: |
|
632 |
+ return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeUnderlineNone || |
|
633 |
+ endCode == AMR_SGRCodeUnderlineSingle || endCode == AMR_SGRCodeUnderlineDouble); |
|
634 |
+ case AMR_SGRCodeNoneOrInvalid: |
|
635 |
+ case AMR_SGRCodeItalicOn: |
|
636 |
+ case AMR_SGRCodeUnderlineNone: |
|
637 |
+ case AMR_SGRCodeIntensityFaint: |
|
638 |
+ case AMR_SGRCodeAllReset: |
|
639 |
+ case AMR_SGRCodeBgReset: |
|
640 |
+ case AMR_SGRCodeFgReset: |
|
641 |
+ return NO; |
|
642 |
+ } |
|
643 |
+ |
|
644 |
+ return NO; |
|
645 |
+} |
|
646 |
+ |
|
647 |
+ |
|
648 |
+ |
|
649 |
+ |
|
650 |
+- (NSColor*) colorForSGRCode:(AMR_SGRCode)code |
|
651 |
+{ |
|
652 |
+ if (self.ansiColors) |
|
653 |
+ { |
|
654 |
+ NSColor *preferredColor = self.ansiColors[@(code)]; |
|
655 |
+ if (preferredColor) |
|
656 |
+ return preferredColor; |
|
657 |
+ } |
|
658 |
+ |
|
659 |
+ switch(code) |
|
660 |
+ { |
|
661 |
+ case AMR_SGRCodeFgBlack: |
|
662 |
+ return kDefaultANSIColorFgBlack; |
|
663 |
+ case AMR_SGRCodeFgRed: |
|
664 |
+ return kDefaultANSIColorFgRed; |
|
665 |
+ case AMR_SGRCodeFgGreen: |
|
666 |
+ return kDefaultANSIColorFgGreen; |
|
667 |
+ case AMR_SGRCodeFgYellow: |
|
668 |
+ return kDefaultANSIColorFgYellow; |
|
669 |
+ case AMR_SGRCodeFgBlue: |
|
670 |
+ return kDefaultANSIColorFgBlue; |
|
671 |
+ case AMR_SGRCodeFgMagenta: |
|
672 |
+ return kDefaultANSIColorFgMagenta; |
|
673 |
+ case AMR_SGRCodeFgCyan: |
|
674 |
+ return kDefaultANSIColorFgCyan; |
|
675 |
+ case AMR_SGRCodeFgWhite: |
|
676 |
+ return kDefaultANSIColorFgWhite; |
|
677 |
+ case AMR_SGRCodeFgBrightBlack: |
|
678 |
+ return kDefaultANSIColorFgBrightBlack; |
|
679 |
+ case AMR_SGRCodeFgBrightRed: |
|
680 |
+ return kDefaultANSIColorFgBrightRed; |
|
681 |
+ case AMR_SGRCodeFgBrightGreen: |
|
682 |
+ return kDefaultANSIColorFgBrightGreen; |
|
683 |
+ case AMR_SGRCodeFgBrightYellow: |
|
684 |
+ return kDefaultANSIColorFgBrightYellow; |
|
685 |
+ case AMR_SGRCodeFgBrightBlue: |
|
686 |
+ return kDefaultANSIColorFgBrightBlue; |
|
687 |
+ case AMR_SGRCodeFgBrightMagenta: |
|
688 |
+ return kDefaultANSIColorFgBrightMagenta; |
|
689 |
+ case AMR_SGRCodeFgBrightCyan: |
|
690 |
+ return kDefaultANSIColorFgBrightCyan; |
|
691 |
+ case AMR_SGRCodeFgBrightWhite: |
|
692 |
+ return kDefaultANSIColorFgBrightWhite; |
|
693 |
+ case AMR_SGRCodeBgBlack: |
|
694 |
+ return kDefaultANSIColorBgBlack; |
|
695 |
+ case AMR_SGRCodeBgRed: |
|
696 |
+ return kDefaultANSIColorBgRed; |
|
697 |
+ case AMR_SGRCodeBgGreen: |
|
698 |
+ return kDefaultANSIColorBgGreen; |
|
699 |
+ case AMR_SGRCodeBgYellow: |
|
700 |
+ return kDefaultANSIColorBgYellow; |
|
701 |
+ case AMR_SGRCodeBgBlue: |
|
702 |
+ return kDefaultANSIColorBgBlue; |
|
703 |
+ case AMR_SGRCodeBgMagenta: |
|
704 |
+ return kDefaultANSIColorBgMagenta; |
|
705 |
+ case AMR_SGRCodeBgCyan: |
|
706 |
+ return kDefaultANSIColorBgCyan; |
|
707 |
+ case AMR_SGRCodeBgWhite: |
|
708 |
+ return kDefaultANSIColorBgWhite; |
|
709 |
+ case AMR_SGRCodeBgBrightBlack: |
|
710 |
+ return kDefaultANSIColorBgBrightBlack; |
|
711 |
+ case AMR_SGRCodeBgBrightRed: |
|
712 |
+ return kDefaultANSIColorBgBrightRed; |
|
713 |
+ case AMR_SGRCodeBgBrightGreen: |
|
714 |
+ return kDefaultANSIColorBgBrightGreen; |
|
715 |
+ case AMR_SGRCodeBgBrightYellow: |
|
716 |
+ return kDefaultANSIColorBgBrightYellow; |
|
717 |
+ case AMR_SGRCodeBgBrightBlue: |
|
718 |
+ return kDefaultANSIColorBgBrightBlue; |
|
719 |
+ case AMR_SGRCodeBgBrightMagenta: |
|
720 |
+ return kDefaultANSIColorBgBrightMagenta; |
|
721 |
+ case AMR_SGRCodeBgBrightCyan: |
|
722 |
+ return kDefaultANSIColorBgBrightCyan; |
|
723 |
+ case AMR_SGRCodeBgBrightWhite: |
|
724 |
+ return kDefaultANSIColorBgBrightWhite; |
|
725 |
+ case AMR_SGRCodeNoneOrInvalid: |
|
726 |
+ case AMR_SGRCodeItalicOn: |
|
727 |
+ case AMR_SGRCodeUnderlineNone: |
|
728 |
+ case AMR_SGRCodeIntensityFaint: |
|
729 |
+ case AMR_SGRCodeAllReset: |
|
730 |
+ case AMR_SGRCodeBgReset: |
|
731 |
+ case AMR_SGRCodeFgReset: |
|
732 |
+ case AMR_SGRCodeIntensityBold: |
|
733 |
+ case AMR_SGRCodeIntensityNormal: |
|
734 |
+ case AMR_SGRCodeUnderlineSingle: |
|
735 |
+ case AMR_SGRCodeUnderlineDouble: |
|
736 |
+ break; |
|
737 |
+ } |
|
738 |
+ |
|
739 |
+ return kDefaultANSIColorFgBlack; |
|
740 |
+} |
|
741 |
+ |
|
742 |
+ |
|
743 |
+- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground |
|
744 |
+{ |
|
745 |
+ if (self.ansiColors) |
|
746 |
+ { |
|
747 |
+ NSArray *codesForGivenColor = [self.ansiColors allKeysForObject:aColor]; |
|
748 |
+ |
|
749 |
+ if (codesForGivenColor != nil && 0 < codesForGivenColor.count) |
|
750 |
+ { |
|
751 |
+ for (NSNumber *thisCode in codesForGivenColor) |
|
752 |
+ { |
|
753 |
+ BOOL thisIsForegroundColor = (thisCode.intValue < 40); |
|
754 |
+ if (aForeground == thisIsForegroundColor) |
|
755 |
+ return thisCode.intValue; |
|
756 |
+ } |
|
757 |
+ } |
|
758 |
+ } |
|
759 |
+ |
|
760 |
+ if (aForeground) |
|
761 |
+ { |
|
762 |
+ if ([aColor isEqual:kDefaultANSIColorFgBlack]) |
|
763 |
+ return AMR_SGRCodeFgBlack; |
|
764 |
+ else if ([aColor isEqual:kDefaultANSIColorFgRed]) |
|
765 |
+ return AMR_SGRCodeFgRed; |
|
766 |
+ else if ([aColor isEqual:kDefaultANSIColorFgGreen]) |
|
767 |
+ return AMR_SGRCodeFgGreen; |
|
768 |
+ else if ([aColor isEqual:kDefaultANSIColorFgYellow]) |
|
769 |
+ return AMR_SGRCodeFgYellow; |
|
770 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBlue]) |
|
771 |
+ return AMR_SGRCodeFgBlue; |
|
772 |
+ else if ([aColor isEqual:kDefaultANSIColorFgMagenta]) |
|
773 |
+ return AMR_SGRCodeFgMagenta; |
|
774 |
+ else if ([aColor isEqual:kDefaultANSIColorFgCyan]) |
|
775 |
+ return AMR_SGRCodeFgCyan; |
|
776 |
+ else if ([aColor isEqual:kDefaultANSIColorFgWhite]) |
|
777 |
+ return AMR_SGRCodeFgWhite; |
|
778 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightBlack]) |
|
779 |
+ return AMR_SGRCodeFgBrightBlack; |
|
780 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightRed]) |
|
781 |
+ return AMR_SGRCodeFgBrightRed; |
|
782 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightGreen]) |
|
783 |
+ return AMR_SGRCodeFgBrightGreen; |
|
784 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightYellow]) |
|
785 |
+ return AMR_SGRCodeFgBrightYellow; |
|
786 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightBlue]) |
|
787 |
+ return AMR_SGRCodeFgBrightBlue; |
|
788 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightMagenta]) |
|
789 |
+ return AMR_SGRCodeFgBrightMagenta; |
|
790 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightCyan]) |
|
791 |
+ return AMR_SGRCodeFgBrightCyan; |
|
792 |
+ else if ([aColor isEqual:kDefaultANSIColorFgBrightWhite]) |
|
793 |
+ return AMR_SGRCodeFgBrightWhite; |
|
794 |
+ } |
|
795 |
+ else |
|
796 |
+ { |
|
797 |
+ if ([aColor isEqual:kDefaultANSIColorBgBlack]) |
|
798 |
+ return AMR_SGRCodeBgBlack; |
|
799 |
+ else if ([aColor isEqual:kDefaultANSIColorBgRed]) |
|
800 |
+ return AMR_SGRCodeBgRed; |
|
801 |
+ else if ([aColor isEqual:kDefaultANSIColorBgGreen]) |
|
802 |
+ return AMR_SGRCodeBgGreen; |
|
803 |
+ else if ([aColor isEqual:kDefaultANSIColorBgYellow]) |
|
804 |
+ return AMR_SGRCodeBgYellow; |
|
805 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBlue]) |
|
806 |
+ return AMR_SGRCodeBgBlue; |
|
807 |
+ else if ([aColor isEqual:kDefaultANSIColorBgMagenta]) |
|
808 |
+ return AMR_SGRCodeBgMagenta; |
|
809 |
+ else if ([aColor isEqual:kDefaultANSIColorBgCyan]) |
|
810 |
+ return AMR_SGRCodeBgCyan; |
|
811 |
+ else if ([aColor isEqual:kDefaultANSIColorBgWhite]) |
|
812 |
+ return AMR_SGRCodeBgWhite; |
|
813 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightBlack]) |
|
814 |
+ return AMR_SGRCodeBgBrightBlack; |
|
815 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightRed]) |
|
816 |
+ return AMR_SGRCodeBgBrightRed; |
|
817 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightGreen]) |
|
818 |
+ return AMR_SGRCodeBgBrightGreen; |
|
819 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightYellow]) |
|
820 |
+ return AMR_SGRCodeBgBrightYellow; |
|
821 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightBlue]) |
|
822 |
+ return AMR_SGRCodeBgBrightBlue; |
|
823 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightMagenta]) |
|
824 |
+ return AMR_SGRCodeBgBrightMagenta; |
|
825 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightCyan]) |
|
826 |
+ return AMR_SGRCodeBgBrightCyan; |
|
827 |
+ else if ([aColor isEqual:kDefaultANSIColorBgBrightWhite]) |
|
828 |
+ return AMR_SGRCodeBgBrightWhite; |
|
829 |
+ } |
|
830 |
+ |
|
831 |
+ return AMR_SGRCodeNoneOrInvalid; |
|
832 |
+} |
|
833 |
+ |
|
834 |
+ |
|
835 |
+ |
|
836 |
+// helper struct typedef and a few functions for |
|
837 |
+// -closestSGRCodeForColor:isForegroundColor: |
|
838 |
+ |
|
839 |
+typedef struct { |
|
840 |
+ CGFloat hue; |
|
841 |
+ CGFloat saturation; |
|
842 |
+ CGFloat brightness; |
|
843 |
+} AMR_HSB; |
|
844 |
+ |
|
845 |
+AMR_HSB makeHSB(CGFloat hue, CGFloat saturation, CGFloat brightness) |
|
846 |
+{ |
|
847 |
+ AMR_HSB outHSB; |
|
848 |
+ outHSB.hue = hue; |
|
849 |
+ outHSB.saturation = saturation; |
|
850 |
+ outHSB.brightness = brightness; |
|
851 |
+ return outHSB; |
|
852 |
+} |
|
853 |
+ |
|
854 |
+AMR_HSB getHSBFromColor(NSColor *color) |
|
855 |
+{ |
|
856 |
+ CGFloat hue = 0.0; |
|
857 |
+ CGFloat saturation = 0.0; |
|
858 |
+ CGFloat brightness = 0.0; |
|
859 |
+ [[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace] |
|
860 |
+ getHue:&hue |
|
861 |
+ saturation:&saturation |
|
862 |
+ brightness:&brightness |
|
863 |
+ alpha:NULL |
|
864 |
+ ]; |
|
865 |
+ return makeHSB(hue, saturation, brightness); |
|
866 |
+} |
|
867 |
+ |
|
868 |
+BOOL floatsEqual(CGFloat first, CGFloat second, CGFloat maxAbsError) |
|
869 |
+{ |
|
870 |
+ return (fabs(first-second)) < maxAbsError; |
|
871 |
+} |
|
872 |
+ |
|
873 |
+#define MAX_HUE_FLOAT_EQUALITY_ABS_ERROR 0.000001 |
|
874 |
+ |
|
875 |
+- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground |
|
876 |
+{ |
|
877 |
+ if (color == nil) |
|
878 |
+ return AMR_SGRCodeNoneOrInvalid; |
|
879 |
+ |
|
880 |
+ AMR_SGRCode closestColorSGRCode = [self AMR_SGRCodeForColor:color isForegroundColor:foreground]; |
|
881 |
+ if (closestColorSGRCode != AMR_SGRCodeNoneOrInvalid) |
|
882 |
+ return closestColorSGRCode; |
|
883 |
+ |
|
884 |
+ AMR_HSB givenColorHSB = getHSBFromColor(color); |
|
885 |
+ |
|
886 |
+ CGFloat closestColorHueDiff = FLT_MAX; |
|
887 |
+ CGFloat closestColorSaturationDiff = FLT_MAX; |
|
888 |
+ CGFloat closestColorBrightnessDiff = FLT_MAX; |
|
889 |
+ |
|
890 |
+ // (background SGR codes are +10 from foreground ones:) |
|
891 |
+ NSUInteger AMR_SGRCodeShift = (foreground)?0:10; |
|
892 |
+ NSArray *ansiFgColorCodes = @[ |
|
893 |
+ @(AMR_SGRCodeFgBlack+AMR_SGRCodeShift), |
|
894 |
+ @(AMR_SGRCodeFgRed+AMR_SGRCodeShift), |
|
895 |
+ @(AMR_SGRCodeFgGreen+AMR_SGRCodeShift), |
|
896 |
+ @(AMR_SGRCodeFgYellow+AMR_SGRCodeShift), |
|
897 |
+ @(AMR_SGRCodeFgBlue+AMR_SGRCodeShift), |
|
898 |
+ @(AMR_SGRCodeFgMagenta+AMR_SGRCodeShift), |
|
899 |
+ @(AMR_SGRCodeFgCyan+AMR_SGRCodeShift), |
|
900 |
+ @(AMR_SGRCodeFgWhite+AMR_SGRCodeShift), |
|
901 |
+ @(AMR_SGRCodeFgBrightBlack+AMR_SGRCodeShift), |
|
902 |
+ @(AMR_SGRCodeFgBrightRed+AMR_SGRCodeShift), |
|
903 |
+ @(AMR_SGRCodeFgBrightGreen+AMR_SGRCodeShift), |
|
904 |
+ @(AMR_SGRCodeFgBrightYellow+AMR_SGRCodeShift), |
|
905 |
+ @(AMR_SGRCodeFgBrightBlue+AMR_SGRCodeShift), |
|
906 |
+ @(AMR_SGRCodeFgBrightMagenta+AMR_SGRCodeShift), |
|
907 |
+ @(AMR_SGRCodeFgBrightCyan+AMR_SGRCodeShift), |
|
908 |
+ @(AMR_SGRCodeFgBrightWhite+AMR_SGRCodeShift), |
|
909 |
+ ]; |
|
910 |
+ for (NSNumber *thisSGRCodeNumber in ansiFgColorCodes) |
|
911 |
+ { |
|
912 |
+ AMR_SGRCode thisSGRCode = thisSGRCodeNumber.intValue; |
|
913 |
+ NSColor *thisColor = [self colorForSGRCode:thisSGRCode]; |
|
914 |
+ |
|
915 |
+ AMR_HSB thisColorHSB = getHSBFromColor(thisColor); |
|
916 |
+ |
|
917 |
+ CGFloat hueDiff = fabs(givenColorHSB.hue - thisColorHSB.hue); |
|
918 |
+ CGFloat saturationDiff = fabs(givenColorHSB.saturation - thisColorHSB.saturation); |
|
919 |
+ CGFloat brightnessDiff = fabs(givenColorHSB.brightness - thisColorHSB.brightness); |
|
920 |
+ |
|
921 |
+ // comparison depends on hue, saturation and brightness |
|
922 |
+ // (strictly in that order): |
|
923 |
+ |
|
924 |
+ if (!floatsEqual(hueDiff, closestColorHueDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR)) |
|
925 |
+ { |
|
926 |
+ if (hueDiff > closestColorHueDiff) |
|
927 |
+ continue; |
|
928 |
+ closestColorSGRCode = thisSGRCode; |
|
929 |
+ closestColorHueDiff = hueDiff; |
|
930 |
+ closestColorSaturationDiff = saturationDiff; |
|
931 |
+ closestColorBrightnessDiff = brightnessDiff; |
|
932 |
+ continue; |
|
933 |
+ } |
|
934 |
+ |
|
935 |
+ if (!floatsEqual(saturationDiff, closestColorSaturationDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR)) |
|
936 |
+ { |
|
937 |
+ if (saturationDiff > closestColorSaturationDiff) |
|
938 |
+ continue; |
|
939 |
+ closestColorSGRCode = thisSGRCode; |
|
940 |
+ closestColorHueDiff = hueDiff; |
|
941 |
+ closestColorSaturationDiff = saturationDiff; |
|
942 |
+ closestColorBrightnessDiff = brightnessDiff; |
|
943 |
+ continue; |
|
944 |
+ } |
|
945 |
+ |
|
946 |
+ if (!floatsEqual(brightnessDiff, closestColorBrightnessDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR)) |
|
947 |
+ { |
|
948 |
+ if (brightnessDiff > closestColorBrightnessDiff) |
|
949 |
+ continue; |
|
950 |
+ closestColorSGRCode = thisSGRCode; |
|
951 |
+ closestColorHueDiff = hueDiff; |
|
952 |
+ closestColorSaturationDiff = saturationDiff; |
|
953 |
+ closestColorBrightnessDiff = brightnessDiff; |
|
954 |
+ continue; |
|
955 |
+ } |
|
956 |
+ |
|
957 |
+ // If hue (especially hue!), saturation and brightness diffs all |
|
958 |
+ // are equal to some other color, we need to prefer one or the |
|
959 |
+ // other so we'll select the more 'distinctive' color of the |
|
960 |
+ // two (this is *very* subjective, obviously). I basically just |
|
961 |
+ // looked at the hue chart, went through all the points between |
|
962 |
+ // our main ANSI colors and decided which side the middle point |
|
963 |
+ // would lean on. (e.g. the purple color that is exactly between |
|
964 |
+ // the blue and magenta ANSI colors looks more magenta than |
|
965 |
+ // blue to me so I put magenta higher than blue in the list |
|
966 |
+ // below.) |
|
967 |
+ // |
|
968 |
+ // subjective ordering of colors from most to least 'distinctive': |
|
969 |
+ int colorDistinctivenessOrder[6] = { |
|
970 |
+ AMR_SGRCodeFgRed+(int)AMR_SGRCodeShift, |
|
971 |
+ AMR_SGRCodeFgMagenta+(int)AMR_SGRCodeShift, |
|
972 |
+ AMR_SGRCodeFgBlue+(int)AMR_SGRCodeShift, |
|
973 |
+ AMR_SGRCodeFgGreen+(int)AMR_SGRCodeShift, |
|
974 |
+ AMR_SGRCodeFgCyan+(int)AMR_SGRCodeShift, |
|
975 |
+ AMR_SGRCodeFgYellow+(int)AMR_SGRCodeShift |
|
976 |
+ }; |
|
977 |
+ for (int i = 0; i < 6; i++) |
|
978 |
+ { |
|
979 |
+ if (colorDistinctivenessOrder[i] == closestColorSGRCode) |
|
980 |
+ break; |
|
981 |
+ else if (colorDistinctivenessOrder[i] == thisSGRCode) |
|
982 |
+ { |
|
983 |
+ closestColorSGRCode = thisSGRCode; |
|
984 |
+ closestColorHueDiff = hueDiff; |
|
985 |
+ closestColorSaturationDiff = saturationDiff; |
|
986 |
+ closestColorBrightnessDiff = brightnessDiff; |
|
987 |
+ } |
|
988 |
+ } |
|
989 |
+ } |
|
990 |
+ |
|
991 |
+ return closestColorSGRCode; |
|
992 |
+} |
|
993 |
+ |
|
994 |
+ |
|
995 |
+ |
|
996 |
+@end |
0 | 997 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,50 @@ |
1 |
+ |
|
2 |
+DESCRIPTION: |
|
3 |
+-------------------------------- |
|
4 |
+ |
|
5 |
+ANSIEscapeHelper is an Objective-C class for dealing with ANSI escape |
|
6 |
+sequences. Its main purpose is to translate between NSStrings |
|
7 |
+that contain ANSI escape sequences and similarly formatted |
|
8 |
+NSAttributedStrings. |
|
9 |
+ |
|
10 |
+The headerdoc directory contains the API documentation in HTML format. |
|
11 |
+ |
|
12 |
+If you fix something in this code or use it in your software, please |
|
13 |
+let me know -- I'm curious ;). You can contact me at http://hasseg.org. |
|
14 |
+Thanks. |
|
15 |
+ |
|
16 |
+ |
|
17 |
+Copyright 2009 Ali Rantakari |
|
18 |
+http://hasseg.org/ansiEscapeHelper |
|
19 |
+ |
|
20 |
+ |
|
21 |
+ |
|
22 |
+ |
|
23 |
+ |
|
24 |
+LICENSE: |
|
25 |
+-------------------------------- |
|
26 |
+ |
|
27 |
+The MIT License |
|
28 |
+ |
|
29 |
+Copyright (c) 2009 Ali Rantakari |
|
30 |
+ |
|
31 |
+Permission is hereby granted, free of charge, to any person obtaining a copy |
|
32 |
+of this software and associated documentation files (the "Software"), to deal |
|
33 |
+in the Software without restriction, including without limitation the rights |
|
34 |
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
35 |
+copies of the Software, and to permit persons to whom the Software is |
|
36 |
+furnished to do so, subject to the following conditions: |
|
37 |
+ |
|
38 |
+The above copyright notice and this permission notice shall be included in |
|
39 |
+all copies or substantial portions of the Software. |
|
40 |
+ |
|
41 |
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
42 |
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
43 |
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
44 |
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
45 |
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
46 |
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
47 |
+THE SOFTWARE. |
|
48 |
+ |
|
49 |
+ |
|
50 |
+ |
0 | 51 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,19 @@ |
1 |
+// |
|
2 |
+// SearchViewController.h |
|
3 |
+// Today Scripts |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 10/21/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import "TodayScript.h" |
|
10 |
+ |
|
11 |
+@interface EditViewController : NCWidgetSearchViewController |
|
12 |
+ |
|
13 |
+- (void)editScript:(TodayScript *)script; |
|
14 |
+- (void)createScript; |
|
15 |
+- (void)cancelScript; |
|
16 |
+ |
|
17 |
+@end |
|
18 |
+ |
|
19 |
+@interface EditViewProgramField : NSTextField @end |
0 | 20 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,162 @@ |
1 |
+// |
|
2 |
+// SearchViewController.m |
|
3 |
+// Today Scripts |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 10/21/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import <Cocoa/Cocoa.h> |
|
10 |
+ |
|
11 |
+#import "EditViewController.h" |
|
12 |
+#import "TodayViewController.h" |
|
13 |
+ |
|
14 |
+@implementation EditViewController |
|
15 |
+{ |
|
16 |
+ IBOutlet NSTextField *labelField; |
|
17 |
+ IBOutlet EditViewProgramField *programField; |
|
18 |
+ IBOutlet NSTextView *scriptField; |
|
19 |
+ IBOutlet NSButton *autoRunButton; |
|
20 |
+ IBOutlet NSButton *showStatusButton; |
|
21 |
+ IBOutlet NSButton *saveButton; |
|
22 |
+ |
|
23 |
+ NSString *defaultProgram; |
|
24 |
+ |
|
25 |
+ TodayScript *script; |
|
26 |
+} |
|
27 |
+ |
|
28 |
+- (void)viewDidLoad |
|
29 |
+{ |
|
30 |
+ [super viewDidLoad]; |
|
31 |
+ |
|
32 |
+ // Set up the appearence of the script field. |
|
33 |
+ scriptField.font = [NSFont fontWithName:@"Menlo" size:10]; |
|
34 |
+ scriptField.textColor = [NSColor colorWithWhite:1 alpha:1]; |
|
35 |
+ scriptField.backgroundColor = NSColor.clearColor; |
|
36 |
+ // Disable all substitutions in the script field. |
|
37 |
+ scriptField.automaticDashSubstitutionEnabled = NO; |
|
38 |
+ scriptField.automaticQuoteSubstitutionEnabled = NO; |
|
39 |
+ scriptField.automaticTextReplacementEnabled = NO; |
|
40 |
+ scriptField.automaticLinkDetectionEnabled = NO; |
|
41 |
+ scriptField.automaticSpellingCorrectionEnabled = NO; |
|
42 |
+ scriptField.automaticDataDetectionEnabled = NO; |
|
43 |
+} |
|
44 |
+ |
|
45 |
+- (void)editScript:(TodayScript *)existingScript |
|
46 |
+{ |
|
47 |
+ // Show ourselves in the widget. |
|
48 |
+ [todayViewController presentViewControllerInWidget:self]; |
|
49 |
+ // Set the button's title to designate that we are editing a script. |
|
50 |
+ saveButton.title = @"Save Script"; |
|
51 |
+ |
|
52 |
+ // Set our script variable to the script passed to us. |
|
53 |
+ script = existingScript; |
|
54 |
+ |
|
55 |
+ // Set the values in our form to those of the script. |
|
56 |
+ labelField.stringValue = script.label; |
|
57 |
+ programField.stringValue = script.program; |
|
58 |
+ scriptField.string = script.script; |
|
59 |
+ autoRunButton.state = script.autoRun ? NSOnState : NSOffState; |
|
60 |
+ showStatusButton.state = script.showStatus ? NSOnState : NSOffState; |
|
61 |
+} |
|
62 |
+ |
|
63 |
+- (void)createScript |
|
64 |
+{ |
|
65 |
+ // We will not be working with an existing script. |
|
66 |
+ script = nil; |
|
67 |
+ |
|
68 |
+ // Show ourselves in the widget. |
|
69 |
+ [todayViewController presentViewControllerInWidget:self]; |
|
70 |
+ // Set the button's title to designate that we are creating a script. |
|
71 |
+ saveButton.title = @"Add Script"; |
|
72 |
+ |
|
73 |
+ // Set up our fields with the default values. |
|
74 |
+ labelField.stringValue = @""; |
|
75 |
+ programField.stringValue = NSProcessInfo.processInfo.environment[@"SHELL"]; |
|
76 |
+ scriptField.string = @""; |
|
77 |
+ autoRunButton.state = NSOnState; |
|
78 |
+ showStatusButton.state = NSOnState; |
|
79 |
+} |
|
80 |
+ |
|
81 |
+// Method invoked when user presses the "Add Script" button. |
|
82 |
+- (IBAction)saveScript:(id)sender |
|
83 |
+{ |
|
84 |
+ // If the interpreter is not a valid executable file, style the text to |
|
85 |
+ // indicate the error to the user, then abort. |
|
86 |
+ NSString *programString = programField.stringValue; |
|
87 |
+ if (! [NSFileManager.defaultManager isExecutableFileAtPath:programString]) { |
|
88 |
+ programField.textColor = [NSColor colorWithRed:1.0 green:0.2 blue:0.2 alpha:1.0]; |
|
89 |
+ return; |
|
90 |
+ } |
|
91 |
+ |
|
92 |
+ // If we were not given an existing dictionary to modify, set that up to |
|
93 |
+ // work with. Otherwise, create a new one. |
|
94 |
+ TodayScript *newScript = script ?: [[TodayScript alloc] init]; |
|
95 |
+ |
|
96 |
+ newScript.program = programString.copy; |
|
97 |
+ |
|
98 |
+ // Set the script to the dictionary if the user provided one. Otherwise, |
|
99 |
+ // remove any which may have previously existed. |
|
100 |
+ newScript.script = scriptField.string.copy; |
|
101 |
+ |
|
102 |
+ // Set the script's title to the user provided one. |
|
103 |
+ newScript.label = labelField.stringValue.copy; |
|
104 |
+ // If a title was not provided, use the text of the script, or the name of |
|
105 |
+ // the program itself if there is no script. |
|
106 |
+ if (! newScript.label.length) |
|
107 |
+ newScript.label = newScript.script.length ? newScript.script : newScript.program; |
|
108 |
+ |
|
109 |
+ // If the checkbox wasn't unchecked, this script is to be run automatically. |
|
110 |
+ newScript.autoRun = (autoRunButton.state != NSOffState); |
|
111 |
+ |
|
112 |
+ // If the checkbox wasn't unchecked, this script is to be run automatically. |
|
113 |
+ newScript.showStatus = (showStatusButton.state != NSOffState); |
|
114 |
+ |
|
115 |
+ // If we were given a script to work with, remove it from our form, make |
|
116 |
+ // sure it it's stopped running, then update our defaults. |
|
117 |
+ if (script) |
|
118 |
+ { |
|
119 |
+ script = nil; |
|
120 |
+ [newScript terminate]; |
|
121 |
+ [TodayScriptArray.sharedScripts saveDefaults]; |
|
122 |
+ } |
|
123 |
+ // Otherwise, add the new script to the list array and update our list view. |
|
124 |
+ else { |
|
125 |
+ [todayViewController.arrayController addObject:newScript]; |
|
126 |
+ todayViewController.listViewController.contents = |
|
127 |
+ todayViewController.arrayController.arrangedObjects; |
|
128 |
+ } |
|
129 |
+ // Hide ourselves. |
|
130 |
+ [todayViewController dismissViewController:self]; |
|
131 |
+ |
|
132 |
+ // If the newly saved script is set to run automatically, do so now. |
|
133 |
+ if (newScript.autoRun) |
|
134 |
+ [newScript run]; |
|
135 |
+} |
|
136 |
+ |
|
137 |
+- (void)cancelScript |
|
138 |
+{ |
|
139 |
+ script = nil; |
|
140 |
+ [todayViewController dismissViewController:self]; |
|
141 |
+} |
|
142 |
+ |
|
143 |
+@end |
|
144 |
+ |
|
145 |
+ |
|
146 |
+@implementation EditViewProgramField |
|
147 |
+ |
|
148 |
+// When the user starts editing the program field, make sure the text color goes |
|
149 |
+// back to normal in case it was previously changed to indicate an error. |
|
150 |
+- (void)textDidBeginEditing:(NSNotification *)notification { |
|
151 |
+ self.textColor = [NSColor colorWithWhite:1.0 alpha:1.0]; |
|
152 |
+} |
|
153 |
+ |
|
154 |
+// If user enters an invalid program in the program field, set its text as red |
|
155 |
+// to indicate this. |
|
156 |
+- (void)textDidEndEditing:(NSNotification *)notification |
|
157 |
+{ |
|
158 |
+ if (! [NSFileManager.defaultManager isExecutableFileAtPath:self.stringValue]) |
|
159 |
+ self.textColor = [NSColor colorWithRed:1.0 green:0.3 blue:0.3 alpha:1.0]; |
|
160 |
+} |
|
161 |
+ |
|
162 |
+@end |
0 | 163 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,144 @@ |
1 |
+<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
|
2 |
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A389" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> |
|
3 |
+ <dependencies> |
|
4 |
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/> |
|
5 |
+ </dependencies> |
|
6 |
+ <objects> |
|
7 |
+ <customObject id="-2" userLabel="File's Owner" customClass="EditViewController"> |
|
8 |
+ <connections> |
|
9 |
+ <outlet property="autoRunButton" destination="xfS-In-uVf" id="kpc-do-dBD"/> |
|
10 |
+ <outlet property="labelField" destination="Cw9-1N-gpc" id="XQR-8Y-lAx"/> |
|
11 |
+ <outlet property="programField" destination="d8p-jf-eHv" id="x58-P6-Szz"/> |
|
12 |
+ <outlet property="saveButton" destination="u1c-zf-cvc" id="Bgs-g5-JPM"/> |
|
13 |
+ <outlet property="scriptField" destination="g23-5c-rM3" id="Bx8-Xp-IHD"/> |
|
14 |
+ <outlet property="showStatusButton" destination="vcF-tM-LhU" id="1Ru-v6-0Kk"/> |
|
15 |
+ <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/> |
|
16 |
+ </connections> |
|
17 |
+ </customObject> |
|
18 |
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> |
|
19 |
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/> |
|
20 |
+ <customView id="Hz6-mo-xeY" userLabel="Edit View"> |
|
21 |
+ <rect key="frame" x="0.0" y="0.0" width="300" height="213"/> |
|
22 |
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> |
|
23 |
+ <subviews> |
|
24 |
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WKU-4c-tEB" userLabel="Label Label"> |
|
25 |
+ <rect key="frame" x="20" y="176" width="49" height="14"/> |
|
26 |
+ <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Label" id="zYG-jl-Xmw"> |
|
27 |
+ <font key="font" metaFont="smallSystem"/> |
|
28 |
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |
|
29 |
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> |
|
30 |
+ </textFieldCell> |
|
31 |
+ </textField> |
|
32 |
+ <textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Cw9-1N-gpc" userLabel="Label Field"> |
|
33 |
+ <rect key="frame" x="75" y="174" width="205" height="19"/> |
|
34 |
+ <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" focusRingType="none" usesSingleLineMode="YES" id="Axj-1g-Ksw"> |
|
35 |
+ <font key="font" metaFont="smallSystem"/> |
|
36 |
+ <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> |
|
37 |
+ <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> |
|
38 |
+ </textFieldCell> |
|
39 |
+ <connections> |
|
40 |
+ <action selector="performClick:" target="WKU-4c-tEB" id="sER-gA-QiZ"/> |
|
41 |
+ </connections> |
|
42 |
+ </textField> |
|
43 |
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ppa-Tb-oBt" userLabel="Program Label"> |
|
44 |
+ <rect key="frame" x="20" y="149" width="49" height="14"/> |
|
45 |
+ <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Program" id="YMK-m2-DXR"> |
|
46 |
+ <font key="font" metaFont="smallSystem"/> |
|
47 |
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |
|
48 |
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> |
|
49 |
+ </textFieldCell> |
|
50 |
+ </textField> |
|
51 |
+ <textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d8p-jf-eHv" userLabel="Program Field" customClass="EditViewProgramField"> |
|
52 |
+ <rect key="frame" x="75" y="147" width="205" height="19"/> |
|
53 |
+ <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" focusRingType="none" usesSingleLineMode="YES" id="4gu-U4-CFG"> |
|
54 |
+ <font key="font" metaFont="smallSystem"/> |
|
55 |
+ <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> |
|
56 |
+ <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> |
|
57 |
+ </textFieldCell> |
|
58 |
+ <connections> |
|
59 |
+ <action selector="performClick:" target="WKU-4c-tEB" id="wBo-d2-vss"/> |
|
60 |
+ </connections> |
|
61 |
+ </textField> |
|
62 |
+ <scrollView horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OVb-f0-amM" userLabel="Script Scroll"> |
|
63 |
+ <rect key="frame" x="20" y="63" width="260" height="76"/> |
|
64 |
+ <clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="Fmm-9L-RgW" userLabel="Script Clip"> |
|
65 |
+ <rect key="frame" x="1" y="1" width="223" height="133"/> |
|
66 |
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |
|
67 |
+ <subviews> |
|
68 |
+ <textView importsGraphics="NO" richText="NO" allowsUndo="YES" verticallyResizable="YES" id="g23-5c-rM3" userLabel="Script View"> |
|
69 |
+ <rect key="frame" x="0.0" y="0.0" width="223" height="133"/> |
|
70 |
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |
|
71 |
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> |
|
72 |
+ <size key="minSize" width="258" height="74"/> |
|
73 |
+ <size key="maxSize" width="463" height="10000000"/> |
|
74 |
+ <color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> |
|
75 |
+ <size key="minSize" width="258" height="74"/> |
|
76 |
+ <size key="maxSize" width="463" height="10000000"/> |
|
77 |
+ </textView> |
|
78 |
+ </subviews> |
|
79 |
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> |
|
80 |
+ </clipView> |
|
81 |
+ <constraints> |
|
82 |
+ <constraint firstAttribute="height" constant="76" id="qzD-ga-hai"/> |
|
83 |
+ </constraints> |
|
84 |
+ <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="RxR-jh-mRq"> |
|
85 |
+ <rect key="frame" x="-100" y="-100" width="87" height="18"/> |
|
86 |
+ <autoresizingMask key="autoresizingMask"/> |
|
87 |
+ </scroller> |
|
88 |
+ <scroller key="verticalScroller" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="zBW-qB-PIF"> |
|
89 |
+ <rect key="frame" x="224" y="1" width="15" height="133"/> |
|
90 |
+ <autoresizingMask key="autoresizingMask"/> |
|
91 |
+ </scroller> |
|
92 |
+ </scrollView> |
|
93 |
+ <button translatesAutoresizingMaskIntoConstraints="NO" id="xfS-In-uVf" userLabel="Auto Run Button"> |
|
94 |
+ <rect key="frame" x="17" y="35" width="117" height="18"/> |
|
95 |
+ <buttonCell key="cell" type="check" title="Run automatically" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="b0u-sz-0IN"> |
|
96 |
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> |
|
97 |
+ <font key="font" metaFont="smallSystem"/> |
|
98 |
+ </buttonCell> |
|
99 |
+ </button> |
|
100 |
+ <button translatesAutoresizingMaskIntoConstraints="NO" id="vcF-tM-LhU" userLabel="Show Status Button"> |
|
101 |
+ <rect key="frame" x="17" y="17" width="137" height="18"/> |
|
102 |
+ <buttonCell key="cell" type="check" title="Show status indicator" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="SpY-3Z-Q6J"> |
|
103 |
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> |
|
104 |
+ <font key="font" metaFont="smallSystem"/> |
|
105 |
+ </buttonCell> |
|
106 |
+ </button> |
|
107 |
+ <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="u1c-zf-cvc" userLabel="Save Button"> |
|
108 |
+ <rect key="frame" x="202" y="14" width="83" height="28"/> |
|
109 |
+ <buttonCell key="cell" type="push" title="Add Script" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="1gZ-Fp-eku"> |
|
110 |
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> |
|
111 |
+ <font key="font" metaFont="smallSystem"/> |
|
112 |
+ </buttonCell> |
|
113 |
+ <connections> |
|
114 |
+ <action selector="saveScript:" target="-2" id="nKv-Oy-P6z"/> |
|
115 |
+ </connections> |
|
116 |
+ </button> |
|
117 |
+ </subviews> |
|
118 |
+ <constraints> |
|
119 |
+ <constraint firstItem="ppa-Tb-oBt" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="22" id="49q-dw-byP"/> |
|
120 |
+ <constraint firstAttribute="bottom" secondItem="vcF-tM-LhU" secondAttribute="bottom" constant="20" id="4X7-Ah-lqE"/> |
|
121 |
+ <constraint firstAttribute="bottom" secondItem="u1c-zf-cvc" secondAttribute="bottom" constant="20" id="8QG-ta-FAH"/> |
|
122 |
+ <constraint firstAttribute="trailing" secondItem="OVb-f0-amM" secondAttribute="trailing" constant="20" id="AEu-89-C2c"/> |
|
123 |
+ <constraint firstItem="Cw9-1N-gpc" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="20" id="AxF-bN-coP"/> |
|
124 |
+ <constraint firstItem="d8p-jf-eHv" firstAttribute="top" secondItem="Cw9-1N-gpc" secondAttribute="bottom" constant="8" id="Bve-CI-2ld"/> |
|
125 |
+ <constraint firstItem="WKU-4c-tEB" firstAttribute="centerY" secondItem="Cw9-1N-gpc" secondAttribute="centerY" constant="0.5" id="Eto-Z2-p49"/> |
|
126 |
+ <constraint firstItem="WKU-4c-tEB" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="22" id="GGe-nN-fTe"/> |
|
127 |
+ <constraint firstItem="vcF-tM-LhU" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="20" id="Mgw-h4-kDB"/> |
|
128 |
+ <constraint firstItem="OVb-f0-amM" firstAttribute="top" secondItem="d8p-jf-eHv" secondAttribute="bottom" constant="8" id="UGD-ts-dKz"/> |
|
129 |
+ <constraint firstItem="Cw9-1N-gpc" firstAttribute="leading" secondItem="WKU-4c-tEB" secondAttribute="trailing" constant="8" id="YEm-DM-Tu9"/> |
|
130 |
+ <constraint firstItem="xfS-In-uVf" firstAttribute="top" secondItem="OVb-f0-amM" secondAttribute="bottom" constant="13" id="aT1-mv-f76"/> |
|
131 |
+ <constraint firstItem="ppa-Tb-oBt" firstAttribute="width" secondItem="WKU-4c-tEB" secondAttribute="width" id="asr-5j-TmC"/> |
|
132 |
+ <constraint firstItem="OVb-f0-amM" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="20" id="fdc-EO-NSJ"/> |
|
133 |
+ <constraint firstAttribute="trailing" secondItem="Cw9-1N-gpc" secondAttribute="trailing" constant="20" id="gnM-Fw-GZp"/> |
|
134 |
+ <constraint firstItem="vcF-tM-LhU" firstAttribute="top" secondItem="xfS-In-uVf" secondAttribute="bottom" constant="6" id="lxH-us-63N"/> |
|
135 |
+ <constraint firstAttribute="trailing" secondItem="d8p-jf-eHv" secondAttribute="trailing" constant="20" id="pHf-ne-zuS"/> |
|
136 |
+ <constraint firstItem="xfS-In-uVf" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="20" id="q2u-73-JgO"/> |
|
137 |
+ <constraint firstItem="d8p-jf-eHv" firstAttribute="centerY" secondItem="ppa-Tb-oBt" secondAttribute="centerY" constant="-0.5" id="rwG-Fu-XX1"/> |
|
138 |
+ <constraint firstAttribute="trailing" secondItem="u1c-zf-cvc" secondAttribute="trailing" constant="20" id="vOR-em-HdA"/> |
|
139 |
+ <constraint firstItem="d8p-jf-eHv" firstAttribute="leading" secondItem="ppa-Tb-oBt" secondAttribute="trailing" constant="8" id="wWK-WK-3gm"/> |
|
140 |
+ </constraints> |
|
141 |
+ <point key="canvasLocation" x="169" y="329.5"/> |
|
142 |
+ </customView> |
|
143 |
+ </objects> |
|
144 |
+</document> |
0 | 145 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,58 @@ |
1 |
+{ |
|
2 |
+ "images" : [ |
|
3 |
+ { |
|
4 |
+ "idiom" : "mac", |
|
5 |
+ "scale" : "1x", |
|
6 |
+ "size" : "16x16" |
|
7 |
+ }, |
|
8 |
+ { |
|
9 |
+ "idiom" : "mac", |
|
10 |
+ "scale" : "2x", |
|
11 |
+ "size" : "16x16" |
|
12 |
+ }, |
|
13 |
+ { |
|
14 |
+ "idiom" : "mac", |
|
15 |
+ "scale" : "1x", |
|
16 |
+ "size" : "32x32" |
|
17 |
+ }, |
|
18 |
+ { |
|
19 |
+ "idiom" : "mac", |
|
20 |
+ "scale" : "2x", |
|
21 |
+ "size" : "32x32" |
|
22 |
+ }, |
|
23 |
+ { |
|
24 |
+ "idiom" : "mac", |
|
25 |
+ "scale" : "1x", |
|
26 |
+ "size" : "128x128" |
|
27 |
+ }, |
|
28 |
+ { |
|
29 |
+ "idiom" : "mac", |
|
30 |
+ "scale" : "2x", |
|
31 |
+ "size" : "128x128" |
|
32 |
+ }, |
|
33 |
+ { |
|
34 |
+ "idiom" : "mac", |
|
35 |
+ "scale" : "1x", |
|
36 |
+ "size" : "256x256" |
|
37 |
+ }, |
|
38 |
+ { |
|
39 |
+ "idiom" : "mac", |
|
40 |
+ "scale" : "2x", |
|
41 |
+ "size" : "256x256" |
|
42 |
+ }, |
|
43 |
+ { |
|
44 |
+ "idiom" : "mac", |
|
45 |
+ "scale" : "1x", |
|
46 |
+ "size" : "512x512" |
|
47 |
+ }, |
|
48 |
+ { |
|
49 |
+ "idiom" : "mac", |
|
50 |
+ "scale" : "2x", |
|
51 |
+ "size" : "512x512" |
|
52 |
+ } |
|
53 |
+ ], |
|
54 |
+ "info" : { |
|
55 |
+ "version" : 1, |
|
56 |
+ "author" : "xcode" |
|
57 |
+ } |
|
58 |
+} |
|
0 | 59 |
\ No newline at end of file |
1 | 60 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,39 @@ |
1 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
2 |
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|
3 |
+<plist version="1.0"> |
|
4 |
+<dict> |
|
5 |
+ <key>CFBundleDevelopmentRegion</key> |
|
6 |
+ <string>en</string> |
|
7 |
+ <key>CFBundleDisplayName</key> |
|
8 |
+ <string>Widget</string> |
|
9 |
+ <key>CFBundleExecutable</key> |
|
10 |
+ <string>$(EXECUTABLE_NAME)</string> |
|
11 |
+ <key>CFBundleIdentifier</key> |
|
12 |
+ <string>org.samroth.Today-Scripts.$(PRODUCT_NAME:rfc1034identifier)</string> |
|
13 |
+ <key>CFBundleInfoDictionaryVersion</key> |
|
14 |
+ <string>6.0</string> |
|
15 |
+ <key>CFBundleName</key> |
|
16 |
+ <string>$(PRODUCT_NAME)</string> |
|
17 |
+ <key>CFBundlePackageType</key> |
|
18 |
+ <string>XPC!</string> |
|
19 |
+ <key>CFBundleShortVersionString</key> |
|
20 |
+ <string>1.0</string> |
|
21 |
+ <key>CFBundleSignature</key> |
|
22 |
+ <string>????</string> |
|
23 |
+ <key>CFBundleVersion</key> |
|
24 |
+ <string>1</string> |
|
25 |
+ <key>LSMinimumSystemVersion</key> |
|
26 |
+ <string>$(MACOSX_DEPLOYMENT_TARGET)</string> |
|
27 |
+ <key>NSExtension</key> |
|
28 |
+ <dict> |
|
29 |
+ <key>NSExtensionPointIdentifier</key> |
|
30 |
+ <string>com.apple.widget-extension</string> |
|
31 |
+ <key>NSExtensionPrincipalClass</key> |
|
32 |
+ <string>TodayViewController</string> |
|
33 |
+ <key>com.apple.notificationcenter.widget.description</key> |
|
34 |
+ <string>Widget</string> |
|
35 |
+ </dict> |
|
36 |
+ <key>NSHumanReadableCopyright</key> |
|
37 |
+ <string>Copyright © 2014 Sam Rothenberg. All rights reserved.</string> |
|
38 |
+</dict> |
|
39 |
+</plist> |
0 | 40 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,14 @@ |
1 |
+// |
|
2 |
+// ListRowViewController.h |
|
3 |
+// Scripts |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 8/14/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import <Cocoa/Cocoa.h> |
|
10 |
+ |
|
11 |
+@interface ListRowViewController : NSViewController <NSTextViewDelegate> |
|
12 |
+ |
|
13 |
+@end |
|
14 |
+ |
0 | 15 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,59 @@ |
1 |
+// |
|
2 |
+// ListRowViewController.m |
|
3 |
+// Scripts |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 8/14/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import "ListRowViewController.h" |
|
10 |
+#import "TodayScript.h" |
|
11 |
+#import "TodayViewController.h" |
|
12 |
+#import "EditViewController.h" |
|
13 |
+ |
|
14 |
+@implementation ListRowViewController |
|
15 |
+{ |
|
16 |
+ // Our UI objects. |
|
17 |
+ IBOutlet NSTextView *outputView; |
|
18 |
+ IBOutlet NSButton *editButton; |
|
19 |
+} |
|
20 |
+ |
|
21 |
+- (NSString *)nibName { |
|
22 |
+ return @"ListRowViewController"; |
|
23 |
+} |
|
24 |
+ |
|
25 |
+- (void)loadView |
|
26 |
+{ |
|
27 |
+ [super loadView]; |
|
28 |
+ |
|
29 |
+ // Make the text cursor for the output view (mostly) invisible. |
|
30 |
+ outputView.insertionPointColor = NSColor.clearColor; |
|
31 |
+ |
|
32 |
+ // Make the edit button dim. |
|
33 |
+ editButton.alphaValue = 0.1; |
|
34 |
+} |
|
35 |
+ |
|
36 |
+- (BOOL)textShouldBeginEditing:(NSText *)textObject { |
|
37 |
+ // Prevent editing of the output field. |
|
38 |
+ return NO; |
|
39 |
+} |
|
40 |
+ |
|
41 |
+ |
|
42 |
+- (IBAction)startOrStop:(id)sender |
|
43 |
+{ |
|
44 |
+ // Get the current script we represent. |
|
45 |
+ TodayScript *script = self.representedObject; |
|
46 |
+ // Otherwise if the script is not running, we are being asked to run it. |
|
47 |
+ if (script.running) |
|
48 |
+ [script terminate]; |
|
49 |
+ // If it is running however, then we are being asked to terminate it. |
|
50 |
+ else |
|
51 |
+ [script run]; |
|
52 |
+} |
|
53 |
+ |
|
54 |
+- (IBAction)edit:(id)sender { |
|
55 |
+ // Get the current script we represent and tell the editor to open it. |
|
56 |
+ [todayViewController.editViewController editScript:(TodayScript *)self.representedObject]; |
|
57 |
+} |
|
58 |
+ |
|
59 |
+@end |
0 | 60 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,238 @@ |
1 |
+<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
|
2 |
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A389" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> |
|
3 |
+ <dependencies> |
|
4 |
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/> |
|
5 |
+ </dependencies> |
|
6 |
+ <objects> |
|
7 |
+ <customObject id="-2" userLabel="File's Owner" customClass="ListRowViewController"> |
|
8 |
+ <connections> |
|
9 |
+ <outlet property="editButton" destination="vmd-LL-kN7" id="XqT-yI-stO"/> |
|
10 |
+ <outlet property="outputView" destination="uU1-7d-p6h" id="uCv-j7-UIA"/> |
|
11 |
+ <outlet property="view" destination="Hz6-mo-xeY" id="yog-AM-u9s"/> |
|
12 |
+ </connections> |
|
13 |
+ </customObject> |
|
14 |
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> |
|
15 |
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/> |
|
16 |
+ <customView translatesAutoresizingMaskIntoConstraints="NO" id="Hz6-mo-xeY" userLabel="List Item View"> |
|
17 |
+ <rect key="frame" x="0.0" y="0.0" width="266" height="31"/> |
|
18 |
+ <subviews> |
|
19 |
+ <textField hidden="YES" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DqU-bT-PYy" userLabel="Output Field"> |
|
20 |
+ <rect key="frame" x="-2" y="0.0" width="177" height="16"/> |
|
21 |
+ <constraints> |
|
22 |
+ <constraint firstAttribute="width" constant="173" id="4oG-yU-3d5"/> |
|
23 |
+ <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="16" id="AYa-dn-812"/> |
|
24 |
+ </constraints> |
|
25 |
+ <textFieldCell key="cell" sendsActionOnEndEditing="YES" placeholderString="" id="J4j-pX-hd4" userLabel="Output Cell"> |
|
26 |
+ <font key="font" size="7" name="Menlo-Regular"/> |
|
27 |
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |
|
28 |
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> |
|
29 |
+ </textFieldCell> |
|
30 |
+ <connections> |
|
31 |
+ <binding destination="-2" name="value" keyPath="self.representedObject.unescapedOutput" id="zmr-fB-CLu"/> |
|
32 |
+ </connections> |
|
33 |
+ </textField> |
|
34 |
+ <progressIndicator horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="q9k-hz-yg9" userLabel="Progress View"> |
|
35 |
+ <rect key="frame" x="0.0" y="8" width="16" height="16"/> |
|
36 |
+ <connections> |
|
37 |
+ <binding destination="-2" name="animate" keyPath="representedObject.running" id="Alt-1H-Jjg"> |
|
38 |
+ <dictionary key="options"> |
|
39 |
+ <bool key="NSRaisesForNotApplicableKeys" value="NO"/> |
|
40 |
+ </dictionary> |
|
41 |
+ </binding> |
|
42 |
+ </connections> |
|
43 |
+ </progressIndicator> |
|
44 |
+ <imageView translatesAutoresizingMaskIntoConstraints="NO" id="8gs-d1-WuT" userLabel="Stopped View"> |
|
45 |
+ <rect key="frame" x="2" y="10" width="12" height="12"/> |
|
46 |
+ <constraints> |
|
47 |
+ <constraint firstAttribute="height" constant="12" id="Rvr-8y-uDX"/> |
|
48 |
+ <constraint firstAttribute="width" constant="12" id="huW-m8-zEP"/> |
|
49 |
+ </constraints> |
|
50 |
+ <imageCell key="cell" refusesFirstResponder="YES" alignment="left" animates="YES" imageScaling="proportionallyDown" image="NSStatusPartiallyAvailable" id="Evm-gX-bb1"/> |
|
51 |
+ <connections> |
|
52 |
+ <binding destination="-2" name="hidden3" keyPath="representedObject.running" previousBinding="VYp-6y-9dH" id="SL8-Cp-EUc"> |
|
53 |
+ <dictionary key="options"> |
|
54 |
+ <integer key="NSMultipleValuesPlaceholder" value="-1"/> |
|
55 |
+ <integer key="NSNoSelectionPlaceholder" value="-1"/> |
|
56 |
+ <integer key="NSNotApplicablePlaceholder" value="-1"/> |
|
57 |
+ <integer key="NSNullPlaceholder" value="-1"/> |
|
58 |
+ </dictionary> |
|
59 |
+ </binding> |
|
60 |
+ <binding destination="-2" name="hidden2" keyPath="representedObject.showStatus" previousBinding="5a5-GO-fXA" id="VYp-6y-9dH"> |
|
61 |
+ <dictionary key="options"> |
|
62 |
+ <integer key="NSMultipleValuesPlaceholder" value="-1"/> |
|
63 |
+ <integer key="NSNoSelectionPlaceholder" value="-1"/> |
|
64 |
+ <integer key="NSNotApplicablePlaceholder" value="-1"/> |
|
65 |
+ <integer key="NSNullPlaceholder" value="-1"/> |
|
66 |
+ <string key="NSValueTransformerName">NSNegateBoolean</string> |
|
67 |
+ </dictionary> |
|
68 |
+ </binding> |
|
69 |
+ <binding destination="-2" name="hidden" keyPath="representedObject.status" id="5a5-GO-fXA"> |
|
70 |
+ <dictionary key="options"> |
|
71 |
+ <integer key="NSNullPlaceholder" value="0"/> |
|
72 |
+ <bool key="NSRaisesForNotApplicableKeys" value="NO"/> |
|
73 |
+ </dictionary> |
|
74 |
+ </binding> |
|
75 |
+ </connections> |
|
76 |
+ </imageView> |
|
77 |
+ <imageView translatesAutoresizingMaskIntoConstraints="NO" id="DcK-US-Y7c" userLabel="Failure View"> |
|
78 |
+ <rect key="frame" x="2" y="10" width="12" height="12"/> |
|
79 |
+ <constraints> |
|
80 |
+ <constraint firstAttribute="width" constant="12" id="KiL-Mi-I2A"/> |
|
81 |
+ <constraint firstAttribute="height" constant="12" id="ozW-gP-bgw"/> |
|
82 |
+ </constraints> |
|
83 |
+ <imageCell key="cell" refusesFirstResponder="YES" alignment="left" animates="YES" imageScaling="proportionallyDown" image="NSStatusUnavailable" id="KRf-js-5mY" userLabel="Failure Cell"/> |
|
84 |
+ <connections> |
|
85 |
+ <binding destination="-2" name="hidden3" keyPath="representedObject.running" previousBinding="lma-N8-GIA" id="G48-5y-dm9"> |
|
86 |
+ <dictionary key="options"> |
|
87 |
+ <integer key="NSMultipleValuesPlaceholder" value="-1"/> |
|
88 |
+ <integer key="NSNoSelectionPlaceholder" value="-1"/> |
|
89 |
+ <integer key="NSNotApplicablePlaceholder" value="-1"/> |
|
90 |
+ <integer key="NSNullPlaceholder" value="-1"/> |
|
91 |
+ </dictionary> |
|
92 |
+ </binding> |
|
93 |
+ <binding destination="-2" name="hidden2" keyPath="representedObject.showStatus" previousBinding="tLZ-fA-ZJP" id="lma-N8-GIA"> |
|
94 |
+ <dictionary key="options"> |
|
95 |
+ <integer key="NSMultipleValuesPlaceholder" value="-1"/> |
|
96 |
+ <integer key="NSNoSelectionPlaceholder" value="-1"/> |
|
97 |
+ <integer key="NSNotApplicablePlaceholder" value="-1"/> |
|
98 |
+ <integer key="NSNullPlaceholder" value="-1"/> |
|
99 |
+ <string key="NSValueTransformerName">NSNegateBoolean</string> |
|
100 |
+ </dictionary> |
|
101 |
+ </binding> |
|
102 |
+ <binding destination="-2" name="hidden" keyPath="representedObject.status" id="tLZ-fA-ZJP"> |
|
103 |
+ <dictionary key="options"> |
|
104 |
+ <integer key="NSNullPlaceholder" value="1"/> |
|
105 |
+ <bool key="NSRaisesForNotApplicableKeys" value="NO"/> |
|
106 |
+ <string key="NSValueTransformerName">NSNegateBoolean</string> |
|
107 |
+ </dictionary> |
|
108 |
+ </binding> |
|
109 |
+ </connections> |
|
110 |
+ </imageView> |
|
111 |
+ <imageView translatesAutoresizingMaskIntoConstraints="NO" id="ujW-FR-oxR" userLabel="Success View"> |
|
112 |
+ <rect key="frame" x="2" y="10" width="12" height="12"/> |
|
113 |
+ <constraints> |
|
114 |
+ <constraint firstAttribute="width" constant="12" id="lEn-yZ-4tX"/> |
|
115 |
+ <constraint firstAttribute="height" constant="12" id="sGr-6n-qLy"/> |
|
116 |
+ </constraints> |
|
117 |
+ <imageCell key="cell" refusesFirstResponder="YES" alignment="left" animates="YES" imageScaling="proportionallyDown" image="NSStatusAvailable" id="Ubc-Vb-LAb" userLabel="Success Cell"/> |
|
118 |
+ <connections> |
|
119 |
+ <binding destination="-2" name="hidden3" keyPath="representedObject.running" previousBinding="nCa-Pt-Buo" id="HkU-Oa-dOI"> |
|
120 |
+ <dictionary key="options"> |
|
121 |
+ <integer key="NSMultipleValuesPlaceholder" value="-1"/> |
|
122 |
+ <integer key="NSNoSelectionPlaceholder" value="-1"/> |
|
123 |
+ <integer key="NSNotApplicablePlaceholder" value="-1"/> |
|
124 |
+ <integer key="NSNullPlaceholder" value="-1"/> |
|
125 |
+ </dictionary> |
|
126 |
+ </binding> |
|
127 |
+ <binding destination="-2" name="hidden2" keyPath="representedObject.showStatus" previousBinding="7Fs-Mc-Q1G" id="nCa-Pt-Buo"> |
|
128 |
+ <dictionary key="options"> |
|
129 |
+ <integer key="NSMultipleValuesPlaceholder" value="-1"/> |
|
130 |
+ <integer key="NSNoSelectionPlaceholder" value="-1"/> |
|
131 |
+ <integer key="NSNotApplicablePlaceholder" value="-1"/> |
|
132 |
+ <integer key="NSNullPlaceholder" value="-1"/> |
|
133 |
+ <string key="NSValueTransformerName">NSNegateBoolean</string> |
|
134 |
+ </dictionary> |
|
135 |
+ </binding> |
|
136 |
+ <binding destination="-2" name="hidden" keyPath="representedObject.status" id="7Fs-Mc-Q1G"> |
|
137 |
+ <dictionary key="options"> |
|
138 |
+ <integer key="NSNullPlaceholder" value="1"/> |
|
139 |
+ <bool key="NSRaisesForNotApplicableKeys" value="NO"/> |
|
140 |
+ </dictionary> |
|
141 |
+ </binding> |
|
142 |
+ </connections> |
|
143 |
+ </imageView> |
|
144 |
+ <button translatesAutoresizingMaskIntoConstraints="NO" id="U7F-Hl-Qzm" userLabel="Label Button"> |
|
145 |
+ <rect key="frame" x="17" y="9" width="43" height="14"/> |
|
146 |
+ <constraints> |
|
147 |
+ <constraint firstAttribute="height" constant="14" id="QyF-Wz-Bm6"/> |
|
148 |
+ </constraints> |
|
149 |
+ <buttonCell key="cell" type="inline" title="Label" bezelStyle="inline" alignment="center" inset="2" id="u1I-BO-Xx7"> |
|
150 |
+ <behavior key="behavior" lightByContents="YES"/> |
|
151 |
+ <font key="font" metaFont="smallSystemBold"/> |
|
152 |
+ </buttonCell> |
|
153 |
+ <connections> |
|
154 |
+ <action selector="startOrStop:" target="-2" id="Sbd-pV-Vb3"/> |
|
155 |
+ <binding destination="-2" name="title" keyPath="representedObject.label" id="Eeq-2t-eRs"/> |
|
156 |
+ </connections> |
|
157 |
+ </button> |
|
158 |
+ <scrollView borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mQA-xG-ni4" userLabel="Output Scroll"> |
|
159 |
+ <rect key="frame" x="17" y="-10" width="256" height="16"/> |
|
160 |
+ <clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="war-YV-W7W" userLabel="Output Clip"> |
|
161 |
+ <rect key="frame" x="1" y="1" width="223" height="133"/> |
|
162 |
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |
|
163 |
+ <subviews> |
|
164 |
+ <textView focusRingType="none" verticalCompressionResistancePriority="749" editable="NO" selectable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" id="uU1-7d-p6h" userLabel="Output View"> |
|
165 |
+ <rect key="frame" x="0.0" y="0.0" width="223" height="133"/> |
|
166 |
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |
|
167 |
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> |
|
168 |
+ <size key="minSize" width="256" height="16"/> |
|
169 |
+ <size key="maxSize" width="463" height="10000000"/> |
|
170 |
+ <color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> |
|
171 |
+ <size key="minSize" width="256" height="16"/> |
|
172 |
+ <size key="maxSize" width="463" height="10000000"/> |
|
173 |
+ <connections> |
|
174 |
+ <binding destination="-2" name="attributedString" keyPath="representedObject.attributedOutput" id="qar-ix-3dl"/> |
|
175 |
+ <outlet property="delegate" destination="-2" id="Oop-el-HJe"/> |
|
176 |
+ </connections> |
|
177 |
+ </textView> |
|
178 |
+ </subviews> |
|
179 |
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> |
|
180 |
+ </clipView> |
|
181 |
+ <constraints> |
|
182 |
+ <constraint firstAttribute="width" constant="256" id="ZEd-2N-132"/> |
|
183 |
+ </constraints> |
|
184 |
+ <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="ydT-VY-1LJ"> |
|
185 |
+ <rect key="frame" x="-100" y="-100" width="87" height="18"/> |
|
186 |
+ <autoresizingMask key="autoresizingMask"/> |
|
187 |
+ </scroller> |
|
188 |
+ <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="h4h-V6-n4r"> |
|
189 |
+ <rect key="frame" x="224" y="1" width="15" height="133"/> |
|
190 |
+ <autoresizingMask key="autoresizingMask"/> |
|
191 |
+ </scroller> |
|
192 |
+ </scrollView> |
|
193 |
+ <button translatesAutoresizingMaskIntoConstraints="NO" id="vmd-LL-kN7" userLabel="Edit Button"> |
|
194 |
+ <rect key="frame" x="257" y="7" width="9" height="18"/> |
|
195 |
+ <constraints> |
|
196 |
+ <constraint firstAttribute="width" constant="9" id="8JI-Ko-wYQ"/> |
|
197 |
+ </constraints> |
|
198 |
+ <buttonCell key="cell" type="inline" title="Edit" bezelStyle="inline" image="NSSmartBadgeTemplate" imagePosition="only" alignment="center" state="on" imageScaling="proportionallyDown" inset="2" id="HXH-EV-ooF"> |
|
199 |
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> |
|
200 |
+ <font key="font" metaFont="smallSystemBold"/> |
|
201 |
+ </buttonCell> |
|
202 |
+ <connections> |
|
203 |
+ <action selector="edit:" target="-2" id="eIS-34-cOA"/> |
|
204 |
+ <binding destination="-2" name="hidden" keyPath="self.parentViewController.editing" id="9B0-9N-3kd"/> |
|
205 |
+ </connections> |
|
206 |
+ </button> |
|
207 |
+ </subviews> |
|
208 |
+ <constraints> |
|
209 |
+ <constraint firstItem="DqU-bT-PYy" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="1pA-WW-UCS"/> |
|
210 |
+ <constraint firstItem="vmd-LL-kN7" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="257" id="4dv-Gw-VLT"/> |
|
211 |
+ <constraint firstItem="q9k-hz-yg9" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="7" id="8su-ML-sis"/> |
|
212 |
+ <constraint firstItem="mQA-xG-ni4" firstAttribute="top" secondItem="U7F-Hl-Qzm" secondAttribute="bottom" constant="3" id="AcR-pj-FOo"/> |
|
213 |
+ <constraint firstItem="mQA-xG-ni4" firstAttribute="height" secondItem="DqU-bT-PYy" secondAttribute="height" id="ByQ-Pd-qdq"/> |
|
214 |
+ <constraint firstItem="q9k-hz-yg9" firstAttribute="centerX" secondItem="ujW-FR-oxR" secondAttribute="centerX" id="CA3-Ub-PaB"/> |
|
215 |
+ <constraint firstAttribute="bottom" secondItem="DqU-bT-PYy" secondAttribute="bottom" id="Ku8-75-uez"/> |
|
216 |
+ <constraint firstItem="q9k-hz-yg9" firstAttribute="centerY" secondItem="8gs-d1-WuT" secondAttribute="centerY" id="U3m-5r-1pM"/> |
|
217 |
+ <constraint firstItem="mQA-xG-ni4" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="17" id="VhH-bc-g81"/> |
|
218 |
+ <constraint firstItem="U7F-Hl-Qzm" firstAttribute="centerY" secondItem="vmd-LL-kN7" secondAttribute="centerY" id="VlI-hL-vmw"/> |
|
219 |
+ <constraint firstItem="q9k-hz-yg9" firstAttribute="centerX" secondItem="8gs-d1-WuT" secondAttribute="centerX" id="WBg-at-0L3"/> |
|
220 |
+ <constraint firstItem="U7F-Hl-Qzm" firstAttribute="leading" secondItem="q9k-hz-yg9" secondAttribute="trailing" constant="1" id="XJc-oO-yUv"/> |
|
221 |
+ <constraint firstItem="DqU-bT-PYy" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="15" id="eCF-Dt-Wu5"/> |
|
222 |
+ <constraint firstItem="q9k-hz-yg9" firstAttribute="centerY" secondItem="ujW-FR-oxR" secondAttribute="centerY" id="gLc-Ep-k2m"/> |
|
223 |
+ <constraint firstItem="q9k-hz-yg9" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="q8I-Ev-NjI"/> |
|
224 |
+ <constraint firstItem="U7F-Hl-Qzm" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="8" id="r2O-d7-pVA"/> |
|
225 |
+ <constraint firstItem="q9k-hz-yg9" firstAttribute="centerY" secondItem="DcK-US-Y7c" secondAttribute="centerY" id="vmE-Va-egP"/> |
|
226 |
+ <constraint firstItem="q9k-hz-yg9" firstAttribute="centerX" secondItem="DcK-US-Y7c" secondAttribute="centerX" id="yzP-za-8ju"/> |
|
227 |
+ </constraints> |
|
228 |
+ <point key="canvasLocation" x="191" y="345.5"/> |
|
229 |
+ </customView> |
|
230 |
+ <userDefaultsController representsSharedInstance="YES" id="62Q-bz-bUL"/> |
|
231 |
+ </objects> |
|
232 |
+ <resources> |
|
233 |
+ <image name="NSSmartBadgeTemplate" width="14" height="14"/> |
|
234 |
+ <image name="NSStatusAvailable" width="16" height="16"/> |
|
235 |
+ <image name="NSStatusPartiallyAvailable" width="16" height="16"/> |
|
236 |
+ <image name="NSStatusUnavailable" width="16" height="16"/> |
|
237 |
+ </resources> |
|
238 |
+</document> |
0 | 239 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,20 @@ |
1 |
+// |
|
2 |
+// SearchViewController.h |
|
3 |
+// Today Scripts |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 10/21/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import "TodayScript.h" |
|
10 |
+ |
|
11 |
+@interface SearchViewController : NCWidgetSearchViewController |
|
12 |
+ |
|
13 |
+@property TodayScript *script; |
|
14 |
+ |
|
15 |
+@end |
|
16 |
+ |
|
17 |
+ |
|
18 |
+@interface SearchProgramTextField : NSTextField |
|
19 |
+ |
|
20 |
+@end |
0 | 21 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,120 @@ |
1 |
+// |
|
2 |
+// SearchViewController.m |
|
3 |
+// Today Scripts |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 10/21/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import "SearchViewController.h" |
|
10 |
+#import <Cocoa/Cocoa.h> |
|
11 |
+ |
|
12 |
+@implementation SearchViewController |
|
13 |
+{ |
|
14 |
+ IBOutlet NSTextField *labelField; |
|
15 |
+ IBOutlet SearchProgramTextField *programField; |
|
16 |
+ IBOutlet NSTextView *scriptField; |
|
17 |
+ IBOutlet NSButton *autorunButton; |
|
18 |
+ IBOutlet NSButton *saveButton; |
|
19 |
+ |
|
20 |
+ TodayScript *_script; |
|
21 |
+} |
|
22 |
+ |
|
23 |
+- (TodayScript *)script { |
|
24 |
+ return _script; |
|
25 |
+} |
|
26 |
+- (void)setScript:(TodayScript *)script |
|
27 |
+{ |
|
28 |
+ // If we are given a script to edit, set up our fields to its values. |
|
29 |
+ if (script) { |
|
30 |
+ labelField.stringValue = script.label; |
|
31 |
+ programField.stringValue = script.program; |
|
32 |
+ scriptField.string = script.script; |
|
33 |
+ autorunButton.state = script.autoRun ? NSOnState : NSOffState; |
|
34 |
+ saveButton.stringValue = @"Save Script"; |
|
35 |
+ } |
|
36 |
+ // Otherwise, set up our fields with the default values. |
|
37 |
+ else { |
|
38 |
+ labelField.stringValue = @""; |
|
39 |
+ programField.stringValue = NSProcessInfo.processInfo.environment[@"SHELL"]; |
|
40 |
+ scriptField.string = @""; |
|
41 |
+ autorunButton.state = NSOnState; |
|
42 |
+ saveButton.stringValue = @"Add Script"; |
|
43 |
+ } |
|
44 |
+ |
|
45 |
+ [self willChangeValueForKey:@"script"]; |
|
46 |
+ _script = script; |
|
47 |
+ [self didChangeValueForKey:@"script"]; |
|
48 |
+} |
|
49 |
+ |
|
50 |
+- (void)viewDidLoad |
|
51 |
+{ |
|
52 |
+ [super viewDidLoad]; |
|
53 |
+ |
|
54 |
+ // Set up the appearence of the script field. |
|
55 |
+ scriptField.font = [NSFont fontWithName:@"Menlo" size:10]; |
|
56 |
+ scriptField.textColor = [NSColor colorWithWhite:1 alpha:1]; |
|
57 |
+ scriptField.backgroundColor = NSColor.clearColor; |
|
58 |
+ // Disable all substitutions in the script field. |
|
59 |
+ scriptField.automaticDashSubstitutionEnabled = NO; |
|
60 |
+ scriptField.automaticQuoteSubstitutionEnabled = NO; |
|
61 |
+ scriptField.automaticTextReplacementEnabled = NO; |
|
62 |
+ scriptField.automaticLinkDetectionEnabled = NO; |
|
63 |
+ scriptField.automaticSpellingCorrectionEnabled = NO; |
|
64 |
+ scriptField.automaticDataDetectionEnabled = NO; |
|
65 |
+} |
|
66 |
+ |
|
67 |
+// Method invoked when user presses the "Add Script" button. |
|
68 |
+- (IBAction)resultSelected:(id)sender |
|
69 |
+{ |
|
70 |
+ // If the interpreter is not a valid executable file, style the text to |
|
71 |
+ // indicate the error to the user, then abort. |
|
72 |
+ NSString *programString = programField.stringValue; |
|
73 |
+ if (! [NSFileManager.defaultManager isExecutableFileAtPath:programString]) { |
|
74 |
+ programField.textColor = [NSColor colorWithRed:1.0 green:0.25 blue:0.25 alpha:1]; |
|
75 |
+ return; |
|
76 |
+ } |
|
77 |
+ |
|
78 |
+ // If we were not given an existing dictionary to modify, set that up to |
|
79 |
+ // work with. Otherwise, create a new one. |
|
80 |
+ TodayScript *script = self.script ?: [[TodayScript alloc] init]; |
|
81 |
+ |
|
82 |
+ script.program = programString; |
|
83 |
+ |
|
84 |
+ // Set the script to the dictionary if the user provided one. Otherwise, |
|
85 |
+ // remove any which may have previously existed. |
|
86 |
+ script.script = scriptField.string; |
|
87 |
+ |
|
88 |
+ // Set the script's title to the user provided one. |
|
89 |
+ script.label = labelField.stringValue; |
|
90 |
+ if (! script.label.length) |
|
91 |
+ // If they didn't provide a title, use the text of the script, or the |
|
92 |
+ // name of the program itself if there is no script. |
|
93 |
+ script.label = script.script.length |
|
94 |
+ ? script.script |
|
95 |
+ : script.program; |
|
96 |
+ |
|
97 |
+ // Set whether the script should run automatically according to the user's |
|
98 |
+ // specification. |
|
99 |
+ script.autoRun = (autorunButton.state != NSOffState); |
|
100 |
+ |
|
101 |
+ // Initialize the script's output and status to empty objects. |
|
102 |
+ script.output = @""; |
|
103 |
+ script.status = (id)NSNull.null; |
|
104 |
+ |
|
105 |
+ // Send the completed dictionary to the ListViewController. |
|
106 |
+ [self.delegate widgetSearch:self resultSelected:self.script]; |
|
107 |
+} |
|
108 |
+ |
|
109 |
+@end |
|
110 |
+ |
|
111 |
+ |
|
112 |
+@implementation SearchProgramTextField |
|
113 |
+ |
|
114 |
+// When the user starts editing the program field, make sure the text color goes |
|
115 |
+// back to white if it was changed to indicate an error with the previous input. |
|
116 |
+- (void)textDidBeginEditing:(NSNotification *)notification { |
|
117 |
+ self.textColor = [NSColor colorWithWhite:1 alpha:1]; |
|
118 |
+} |
|
119 |
+ |
|
120 |
+@end |
|
0 | 121 |
\ No newline at end of file |
1 | 122 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,142 @@ |
1 |
+<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
|
2 |
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A389" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> |
|
3 |
+ <dependencies> |
|
4 |
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/> |
|
5 |
+ </dependencies> |
|
6 |
+ <objects> |
|
7 |
+ <customObject id="-2" userLabel="File's Owner" customClass="SearchViewController"> |
|
8 |
+ <connections> |
|
9 |
+ <outlet property="autorunButton" destination="xfS-In-uVf" id="SvW-Fg-1ri"/> |
|
10 |
+ <outlet property="labelField" destination="Cw9-1N-gpc" id="XQR-8Y-lAx"/> |
|
11 |
+ <outlet property="programField" destination="seC-H6-j6W" id="RgL-3Q-Ijf"/> |
|
12 |
+ <outlet property="saveButton" destination="u1c-zf-cvc" id="Bgs-g5-JPM"/> |
|
13 |
+ <outlet property="scriptField" destination="g23-5c-rM3" id="Bx8-Xp-IHD"/> |
|
14 |
+ <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/> |
|
15 |
+ </connections> |
|
16 |
+ </customObject> |
|
17 |
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> |
|
18 |
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/> |
|
19 |
+ <customView id="Hz6-mo-xeY"> |
|
20 |
+ <rect key="frame" x="0.0" y="0.0" width="300" height="196"/> |
|
21 |
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> |
|
22 |
+ <subviews> |
|
23 |
+ <textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Cw9-1N-gpc" userLabel="Label Field"> |
|
24 |
+ <rect key="frame" x="75" y="157" width="205" height="19"/> |
|
25 |
+ <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" usesSingleLineMode="YES" id="Axj-1g-Ksw"> |
|
26 |
+ <font key="font" metaFont="smallSystem"/> |
|
27 |
+ <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> |
|
28 |
+ <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> |
|
29 |
+ </textFieldCell> |
|
30 |
+ <connections> |
|
31 |
+ <action selector="performClick:" target="WKU-4c-tEB" id="sER-gA-QiZ"/> |
|
32 |
+ <outlet property="nextKeyView" destination="seC-H6-j6W" id="ict-Xe-smw"/> |
|
33 |
+ </connections> |
|
34 |
+ </textField> |
|
35 |
+ <textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="seC-H6-j6W" userLabel="Program Field" customClass="SearchProgramTextField"> |
|
36 |
+ <rect key="frame" x="75" y="130" width="205" height="19"/> |
|
37 |
+ <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" usesSingleLineMode="YES" id="tfX-Np-Dve"> |
|
38 |
+ <font key="font" metaFont="smallSystem"/> |
|
39 |
+ <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> |
|
40 |
+ <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> |
|
41 |
+ </textFieldCell> |
|
42 |
+ <connections> |
|
43 |
+ <action selector="performClick:" target="ppa-Tb-oBt" id="e0B-YE-pZT"/> |
|
44 |
+ <outlet property="nextKeyView" destination="g23-5c-rM3" id="GxE-Uf-WWO"/> |
|
45 |
+ </connections> |
|
46 |
+ </textField> |
|
47 |
+ <scrollView horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OVb-f0-amM" userLabel="Script Scroll"> |
|
48 |
+ <rect key="frame" x="20" y="46" width="260" height="76"/> |
|
49 |
+ <clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="Fmm-9L-RgW" userLabel="Script Clip"> |
|
50 |
+ <rect key="frame" x="1" y="1" width="223" height="133"/> |
|
51 |
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |
|
52 |
+ <subviews> |
|
53 |
+ <textView importsGraphics="NO" richText="NO" allowsUndo="YES" verticallyResizable="YES" id="g23-5c-rM3" userLabel="Script View"> |
|
54 |
+ <rect key="frame" x="0.0" y="0.0" width="223" height="133"/> |
|
55 |
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> |
|
56 |
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> |
|
57 |
+ <size key="minSize" width="258" height="74"/> |
|
58 |
+ <size key="maxSize" width="463" height="10000000"/> |
|
59 |
+ <color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> |
|
60 |
+ <size key="minSize" width="258" height="74"/> |
|
61 |
+ <size key="maxSize" width="463" height="10000000"/> |
|
62 |
+ </textView> |
|
63 |
+ </subviews> |
|
64 |
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> |
|
65 |
+ </clipView> |
|
66 |
+ <constraints> |
|
67 |
+ <constraint firstAttribute="height" constant="76" id="qzD-ga-hai"/> |
|
68 |
+ </constraints> |
|
69 |
+ <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="RxR-jh-mRq"> |
|
70 |
+ <rect key="frame" x="-100" y="-100" width="87" height="18"/> |
|
71 |
+ <autoresizingMask key="autoresizingMask"/> |
|
72 |
+ </scroller> |
|
73 |
+ <scroller key="verticalScroller" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="zBW-qB-PIF"> |
|
74 |
+ <rect key="frame" x="224" y="1" width="15" height="133"/> |
|
75 |
+ <autoresizingMask key="autoresizingMask"/> |
|
76 |
+ </scroller> |
|
77 |
+ </scrollView> |
|
78 |
+ <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="u1c-zf-cvc"> |
|
79 |
+ <rect key="frame" x="181" y="14" width="104" height="28"/> |
|
80 |
+ <constraints> |
|
81 |
+ <constraint firstAttribute="width" constant="94" id="7LB-TW-HRD"/> |
|
82 |
+ </constraints> |
|
83 |
+ <buttonCell key="cell" type="push" title="Add Script" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="1gZ-Fp-eku"> |
|
84 |
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> |
|
85 |
+ <font key="font" metaFont="smallSystem"/> |
|
86 |
+ </buttonCell> |
|
87 |
+ <connections> |
|
88 |
+ <action selector="resultSelected:" target="-2" id="5W6-KS-fVF"/> |
|
89 |
+ </connections> |
|
90 |
+ </button> |
|
91 |
+ <button translatesAutoresizingMaskIntoConstraints="NO" id="xfS-In-uVf"> |
|
92 |
+ <rect key="frame" x="17" y="20" width="134" height="18"/> |
|
93 |
+ <constraints> |
|
94 |
+ <constraint firstAttribute="width" constant="129" id="Nwk-ve-D1j"/> |
|
95 |
+ </constraints> |
|
96 |
+ <buttonCell key="cell" type="check" title="Run automatically" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="b0u-sz-0IN"> |
|
97 |
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> |
|
98 |
+ <font key="font" metaFont="smallSystem"/> |
|
99 |
+ </buttonCell> |
|
100 |
+ </button> |
|
101 |
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WKU-4c-tEB"> |
|
102 |
+ <rect key="frame" x="20" y="159" width="49" height="14"/> |
|
103 |
+ <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Label" id="zYG-jl-Xmw"> |
|
104 |
+ <font key="font" metaFont="smallSystem"/> |
|
105 |
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |
|
106 |
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> |
|
107 |
+ </textFieldCell> |
|
108 |
+ </textField> |
|
109 |
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ppa-Tb-oBt"> |
|
110 |
+ <rect key="frame" x="20" y="132" width="49" height="14"/> |
|
111 |
+ <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Program" id="YMK-m2-DXR"> |
|
112 |
+ <font key="font" metaFont="smallSystem"/> |
|
113 |
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> |
|
114 |
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> |
|
115 |
+ </textFieldCell> |
|
116 |
+ </textField> |
|
117 |
+ </subviews> |
|
118 |
+ <constraints> |
|
119 |
+ <constraint firstItem="ppa-Tb-oBt" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="22" id="49q-dw-byP"/> |
|
120 |
+ <constraint firstAttribute="bottom" secondItem="OVb-f0-amM" secondAttribute="bottom" constant="46" id="4Lk-yk-821"/> |
|
121 |
+ <constraint firstItem="seC-H6-j6W" firstAttribute="leading" secondItem="ppa-Tb-oBt" secondAttribute="trailing" constant="8" id="85S-xF-OVg"/> |
|
122 |
+ <constraint firstAttribute="trailing" secondItem="OVb-f0-amM" secondAttribute="trailing" constant="20" id="AEu-89-C2c"/> |
|
123 |
+ <constraint firstItem="Cw9-1N-gpc" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="20" id="AxF-bN-coP"/> |
|
124 |
+ <constraint firstItem="xfS-In-uVf" firstAttribute="centerY" secondItem="u1c-zf-cvc" secondAttribute="centerY" id="D34-tT-86A"/> |
|
125 |
+ <constraint firstItem="seC-H6-j6W" firstAttribute="width" secondItem="Cw9-1N-gpc" secondAttribute="width" id="EbR-13-GjG"/> |
|
126 |
+ <constraint firstItem="WKU-4c-tEB" firstAttribute="centerY" secondItem="Cw9-1N-gpc" secondAttribute="centerY" constant="0.5" id="Eto-Z2-p49"/> |
|
127 |
+ <constraint firstItem="WKU-4c-tEB" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="22" id="GGe-nN-fTe"/> |
|
128 |
+ <constraint firstItem="u1c-zf-cvc" firstAttribute="top" secondItem="OVb-f0-amM" secondAttribute="bottom" constant="8" id="IJP-PU-1Av"/> |
|
129 |
+ <constraint firstItem="seC-H6-j6W" firstAttribute="centerY" secondItem="ppa-Tb-oBt" secondAttribute="centerY" constant="-0.5" id="TjU-0K-rUZ"/> |
|
130 |
+ <constraint firstItem="Cw9-1N-gpc" firstAttribute="leading" secondItem="WKU-4c-tEB" secondAttribute="trailing" constant="8" id="YEm-DM-Tu9"/> |
|
131 |
+ <constraint firstItem="OVb-f0-amM" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="20" id="fdc-EO-NSJ"/> |
|
132 |
+ <constraint firstAttribute="trailing" secondItem="Cw9-1N-gpc" secondAttribute="trailing" constant="20" id="gnM-Fw-GZp"/> |
|
133 |
+ <constraint firstItem="OVb-f0-amM" firstAttribute="top" secondItem="seC-H6-j6W" secondAttribute="bottom" constant="8" id="mkp-EW-Utf"/> |
|
134 |
+ <constraint firstAttribute="trailing" secondItem="seC-H6-j6W" secondAttribute="trailing" constant="20" id="my8-8I-UHI"/> |
|
135 |
+ <constraint firstItem="xfS-In-uVf" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="20" id="q2u-73-JgO"/> |
|
136 |
+ <constraint firstItem="seC-H6-j6W" firstAttribute="top" secondItem="Cw9-1N-gpc" secondAttribute="bottom" constant="8" id="sju-Wi-Nhx"/> |
|
137 |
+ <constraint firstAttribute="trailing" secondItem="u1c-zf-cvc" secondAttribute="trailing" constant="20" id="vOR-em-HdA"/> |
|
138 |
+ </constraints> |
|
139 |
+ <point key="canvasLocation" x="169" y="321"/> |
|
140 |
+ </customView> |
|
141 |
+ </objects> |
|
142 |
+</document> |
0 | 143 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,54 @@ |
1 |
+// |
|
2 |
+// ScriptsArray.h |
|
3 |
+// Today Scripts |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 8/14/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import <Foundation/Foundation.h> |
|
10 |
+#import <NotificationCenter/NotificationCenter.h> |
|
11 |
+ |
|
12 |
+#import "TodayScripts.h" |
|
13 |
+ |
|
14 |
+ |
|
15 |
+@interface TodayScriptArray : NSMutableArray |
|
16 |
+ |
|
17 |
++ (TodayScriptArray *)sharedScripts; |
|
18 |
+ |
|
19 |
+- (void)saveDefaults; |
|
20 |
+ |
|
21 |
+- (NCUpdateResult)autoRun; |
|
22 |
+ |
|
23 |
+@end |
|
24 |
+ |
|
25 |
+ |
|
26 |
+@interface TodayScript : NSObject |
|
27 |
+ |
|
28 |
+// The user-defined settings for the script. |
|
29 |
+@property NSString *label; |
|
30 |
+@property NSString *program; |
|
31 |
+@property NSString *script; |
|
32 |
+@property BOOL autoRun; |
|
33 |
+@property BOOL showStatus; |
|
34 |
+ |
|
35 |
+// The above properties, represented by a dictionary. The dictionary's keys are |
|
36 |
+// TodayScriptLabelKey, TodayScriptProgramKey, etcetera. Setting this dictionary |
|
37 |
+// also sets the corresponding properties. |
|
38 |
+@property NSDictionary *dictionary; |
|
39 |
+ |
|
40 |
+// Keep track of whether we are running or not. |
|
41 |
+@property (readonly) BOOL running; |
|
42 |
+ |
|
43 |
+// The exit status and TTY output of the script. Setting the value output string |
|
44 |
+// also causes the attributed output string to update accordingly. |
|
45 |
+@property (readonly) NSNumber *status; |
|
46 |
+ |
|
47 |
+@property (readonly) NSString *output; |
|
48 |
+@property (readonly) NSString *unescapedOutput; |
|
49 |
+@property (readonly) NSAttributedString *attributedOutput; |
|
50 |
+ |
|
51 |
+- (void)run; |
|
52 |
+- (void)terminate; |
|
53 |
+ |
|
54 |
+@end |
0 | 55 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,477 @@ |
1 |
+ // |
|
2 |
+// ScriptsArray.m |
|
3 |
+// Today Scripts |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 8/14/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import "TodayScript.h" |
|
10 |
+#import "AMR_ANSIEscapeHelper.h" |
|
11 |
+ |
|
12 |
+// The queue we will use for asynchronous tasks. |
|
13 |
+dispatch_queue_t queue; |
|
14 |
+ |
|
15 |
+ |
|
16 |
+// The XPC connection. |
|
17 |
+NSXPCConnection *XPC; |
|
18 |
+// The XPC proxy object for the helper. |
|
19 |
+id<XPCHelping> XPCHelper; |
|
20 |
+// A lock for working with the XPC helper. |
|
21 |
+NSConditionLock *XPCLock; |
|
22 |
+ |
|
23 |
+ |
|
24 |
+// Translater from ANSI-escaped NSStrings to NSAttributedStrings. |
|
25 |
+AMR_ANSIEscapeHelper *ANSIHelper; |
|
26 |
+ |
|
27 |
+#define PADDING_LEFT 0 |
|
28 |
+#define COLUMN_WIDTH 6.02 |
|
29 |
+#define COLUMN_COUNT 40.0 |
|
30 |
+#define TAB_COLUMNS 8 |
|
31 |
+ |
|
32 |
+// Paragraph style describing our tab stops. |
|
33 |
+NSMutableParagraphStyle *paragraphStyle; |
|
34 |
+// Characters to be trimmed from the beginning and end of displayed output. |
|
35 |
+NSCharacterSet *lineBreaks; |
|
36 |
+ |
|
37 |
+ |
|
38 |
+@interface TodayScript () |
|
39 |
+ |
|
40 |
+@property (readwrite) BOOL running; |
|
41 |
+ |
|
42 |
+@property (readwrite) NSNumber *status; |
|
43 |
+ |
|
44 |
+@property (readwrite) NSString *output; |
|
45 |
+@property (readwrite) NSString *unescapedOutput; |
|
46 |
+@property (readwrite) NSAttributedString *attributedOutput; |
|
47 |
+ |
|
48 |
+@end |
|
49 |
+@implementation TodayScript |
|
50 |
+{ |
|
51 |
+ NSString *UUID; |
|
52 |
+} |
|
53 |
+ |
|
54 |
+- (id)init |
|
55 |
+{ |
|
56 |
+ if (! (self = super.init)) return nil; |
|
57 |
+ |
|
58 |
+ // Generate a UUID for consistently identifying this script internally. |
|
59 |
+ UUID = NSUUID.UUID.UUIDString; |
|
60 |
+ |
|
61 |
+ // Initialize the result-related properties. |
|
62 |
+ self.status = (id)NSNull.null; |
|
63 |
+ self.output = @""; |
|
64 |
+ |
|
65 |
+ return self; |
|
66 |
+} |
|
67 |
+ |
|
68 |
+// When our dictionary version is requested, create one with the relevant items. |
|
69 |
+- (NSDictionary *)dictionary |
|
70 |
+{ |
|
71 |
+ return @{ |
|
72 |
+ TodayScriptLabelKey : self.label, |
|
73 |
+ TodayScriptProgramKey : self.program, |
|
74 |
+ TodayScriptScriptKey : self.script, |
|
75 |
+ TodayScriptAutoRunKey : self.autoRun ? @YES : @NO, |
|
76 |
+ TodayScriptShowStatusKey : self.showStatus ? @YES : @NO |
|
77 |
+ }; |
|
78 |
+} |
|
79 |
+// When we are modeling our values from a dictionary, set the relevant items. |
|
80 |
+- (void)setDictionary:(NSDictionary *)dictionary |
|
81 |
+{ |
|
82 |
+ self.label = dictionary[ TodayScriptLabelKey ]; |
|
83 |
+ self.program = dictionary[ TodayScriptProgramKey ]; |
|
84 |
+ self.script = dictionary[ TodayScriptScriptKey ]; |
|
85 |
+ self.autoRun = [dictionary[ TodayScriptAutoRunKey ] boolValue]; |
|
86 |
+ self.showStatus = [dictionary[ TodayScriptShowStatusKey ] boolValue]; |
|
87 |
+ // Inititalize our state-related properies as well. |
|
88 |
+ self.running = NO; |
|
89 |
+ self.status = (id)NSNull.null; |
|
90 |
+ self.output = @""; |
|
91 |
+} |
|
92 |
+ |
|
93 |
+- (void)runWithLock:(NSConditionLock *)lock resultPointer:(NSValue *)resultPointer |
|
94 |
+{ |
|
95 |
+ // If we were passed a pointer for tracking update results, upwrap it. |
|
96 |
+ NCUpdateResult *updateResult = resultPointer ? resultPointer.pointerValue : NULL; |
|
97 |
+ |
|
98 |
+ |
|
99 |
+ // Wait for our turn to influence the XPC connectivity. |
|
100 |
+ [XPCLock lock]; |
|
101 |
+ |
|
102 |
+ // Set to property to indicate that we are now running. |
|
103 |
+ self.running = YES; |
|
104 |
+ |
|
105 |
+ // Check the value of the lock. If it is 0, then the connection will not |
|
106 |
+ // currently be active, so we should awaken it. |
|
107 |
+ if (XPCLock.condition == 0) |
|
108 |
+ [XPC resume]; |
|
109 |
+ // Regardless of whether it was already active or whether we activated it, |
|
110 |
+ // increment the value of the lock to indicate our needing the connection. |
|
111 |
+ [XPCLock unlockWithCondition:XPCLock.condition + 1]; |
|
112 |
+ |
|
113 |
+ // Pass ourselves in dictionary form to the XPC helper, with handler block |
|
114 |
+ // for receiving the script's PID and initiating our observation of it. |
|
115 |
+ [XPCHelper launchScript:self.dictionary forUUID:UUID handler: ^(int status, NSString *output) |
|
116 |
+ { |
|
117 |
+ // Once the process has terminated, we are no longer in need of the |
|
118 |
+ // XPC connection. Wait for our turn again to influence it. |
|
119 |
+ [XPCLock lock]; |
|
120 |
+ |
|
121 |
+ // Check the value of the lock. If it is 1, then we were the last |
|
122 |
+ // ones to be using it, so it may now be suspended. |
|
123 |
+ if (XPCLock.condition == 1) |
|
124 |
+ [XPC suspend]; |
|
125 |
+ // Either way, decrement the lock's value to indicate our own lack of |
|
126 |
+ // need for it. |
|
127 |
+ [XPCLock unlockWithCondition:XPCLock.condition - 1]; |
|
128 |
+ |
|
129 |
+ // Unset our running status. Use the main thread so as to emit the |
|
130 |
+ // proper KVO and UI updates. |
|
131 |
+ [self performSelectorOnMainThread: |
|
132 |
+ @selector(setRunning:) withObject:nil waitUntilDone:NO]; |
|
133 |
+ |
|
134 |
+ // Create a variable representing whether we have updates to display. |
|
135 |
+ BOOL newData = NO; |
|
136 |
+ |
|
137 |
+ // If the script was terminated prematurely, we should set our status to |
|
138 |
+ // to a null object, and no updates will need to be reported. |
|
139 |
+ if (status < 0) |
|
140 |
+ [self performSelectorOnMainThread: |
|
141 |
+ @selector(setStatus:) withObject:NSNull.null waitUntilDone:NO]; |
|
142 |
+ |
|
143 |
+ // If the script ran to completion, process its results to see whether |
|
144 |
+ // they will need to be updated. |
|
145 |
+ else { |
|
146 |
+ // If we had not run to completion previously, or if the exit status |
|
147 |
+ // of the previous run was different than this one, our status is to |
|
148 |
+ // be updated with the new one. |
|
149 |
+ if (self.status == (id)NSNull.null || self.status.intValue != status) |
|
150 |
+ { |
|
151 |
+ [self performSelectorOnMainThread: |
|
152 |
+ @selector(setStatus:) withObject:@(status) waitUntilDone:NO]; |
|
153 |
+ |
|
154 |
+ newData = YES; |
|
155 |
+ } |
|
156 |
+ |
|
157 |
+ // Check whether the raw character output is identical to that of |
|
158 |
+ // the last completed run. |
|
159 |
+ if (! [self.output isEqualToString:output]) |
|
160 |
+ { |
|
161 |
+ // If the raw output has changed, update the property. |
|
162 |
+ self.output = output; |
|
163 |
+ |
|
164 |
+ // Parsed the ANSI escaped string to an attributed string. |
|
165 |
+ NSMutableAttributedString *attributedOutput = [ANSIHelper |
|
166 |
+ attributedStringWithANSIEscapedString:output].mutableCopy; |
|
167 |
+ |
|
168 |
+ // Get the bare characters from the parsed string. |
|
169 |
+ NSString *unescapedOutput = attributedOutput.string; |
|
170 |
+ |
|
171 |
+ // Get the unescaped output with excess line breaks trimmed off. |
|
172 |
+ NSString *trimmedOutput = [unescapedOutput stringByTrimmingCharactersInSet:lineBreaks]; |
|
173 |
+ |
|
174 |
+ // If the string is empty after being trimmed of line breaks, we |
|
175 |
+ // will not be displaying an output field, so keep it simple. |
|
176 |
+ if ([trimmedOutput isEqualToString:@""]) { |
|
177 |
+ attributedOutput = [[NSMutableAttributedString alloc] initWithString:@""]; |
|
178 |
+ unescapedOutput = @""; |
|
179 |
+ } |
|
180 |
+ |
|
181 |
+ // If the trimmed output is not empty, format the attributed and |
|
182 |
+ // unescaped versions of our output. The unescaped one is |
|
183 |
+ // inserted into a text field which is hidden, but expands with |
|
184 |
+ // its contents, causing the script's row view to expand with it. |
|
185 |
+ else { |
|
186 |
+ // Determine the range of the untrimmed output, and use it |
|
187 |
+ // to trim the line breaks from the attributed version. |
|
188 |
+ NSRange trimmedOutputRange = [unescapedOutput rangeOfString:trimmedOutput]; |
|
189 |
+ attributedOutput = [attributedOutput |
|
190 |
+ attributedSubstringFromRange:trimmedOutputRange].mutableCopy; |
|
191 |
+ // Apply our tab stops to the formatted string. |
|
192 |
+ |
|
193 |
+ [attributedOutput addAttribute:NSParagraphStyleAttributeName |
|
194 |
+ value:paragraphStyle range:NSMakeRange(0, attributedOutput.length)]; |
|
195 |
+ |
|
196 |
+ // This text field needs two extra line breaks to expand |
|
197 |
+ // the row properly. |
|
198 |
+ unescapedOutput = [@"\n\n" stringByAppendingString:trimmedOutput]; |
|
199 |
+ } |
|
200 |
+ |
|
201 |
+ // If the final attributed output does not match the one from |
|
202 |
+ // the previous run, we will need to update our output. |
|
203 |
+ if (! [attributedOutput isEqualToAttributedString:self.attributedOutput]) |
|
204 |
+ { |
|
205 |
+ [self performSelectorOnMainThread:@selector(setAttributedOutput:) |
|
206 |
+ withObject:attributedOutput waitUntilDone:NO]; |
|
207 |
+ |
|
208 |
+ [self performSelectorOnMainThread:@selector(setUnescapedOutput:) |
|
209 |
+ withObject:unescapedOutput waitUntilDone:NO]; |
|
210 |
+ |
|
211 |
+ newData = YES; |
|
212 |
+ } |
|
213 |
+ } |
|
214 |
+ } |
|
215 |
+ |
|
216 |
+ if (newData) { |
|
217 |
+ // If we were given a pointer to an update result variable, set it |
|
218 |
+ // to indicate that there are indeed changes. |
|
219 |
+ if (updateResult) |
|
220 |
+ *updateResult = NCUpdateResultNewData; |
|
221 |
+ |
|
222 |
+ // If we were passed a lock for tracking progress, we can now set it |
|
223 |
+ // to zero since we've already determined there are updates to show. |
|
224 |
+ if (lock) { |
|
225 |
+ [lock lock]; |
|
226 |
+ [lock unlockWithCondition:0]; |
|
227 |
+ } |
|
228 |
+ } |
|
229 |
+ // If we were passed a lock for tracking progress, we can now decrement |
|
230 |
+ // it to indicate we no longer need to be waited on. |
|
231 |
+ else if (lock) { |
|
232 |
+ [lock lock]; |
|
233 |
+ [lock unlockWithCondition:lock.condition - 1]; |
|
234 |
+ } |
|
235 |
+ }]; |
|
236 |
+} |
|
237 |
+ |
|
238 |
+- (void)run |
|
239 |
+{ |
|
240 |
+ // Safely determine whether we are already running. If we are not, go |
|
241 |
+ // through our run routine, no lock or result pointer required. |
|
242 |
+ [XPCLock lock]; |
|
243 |
+ BOOL running = self.running; |
|
244 |
+ [XPCLock unlock]; |
|
245 |
+ |
|
246 |
+ if (! running) |
|
247 |
+ [self runWithLock:nil resultPointer:nil]; |
|
248 |
+} |
|
249 |
+ |
|
250 |
+- (void)terminate |
|
251 |
+{ |
|
252 |
+ // Safely determine whether we are currently running. If in fact we are not, |
|
253 |
+ // ask the helper to terminate the process. |
|
254 |
+ [XPCLock lock]; |
|
255 |
+ BOOL running = self.running; |
|
256 |
+ [XPCLock unlock]; |
|
257 |
+ |
|
258 |
+ if (running) |
|
259 |
+ [XPCHelper terminateScriptForUUID:UUID]; |
|
260 |
+} |
|
261 |
+ |
|
262 |
+- (oneway void)dealloc { |
|
263 |
+ [self terminate]; |
|
264 |
+} |
|
265 |
+ |
|
266 |
+@end |
|
267 |
+ |
|
268 |
+ |
|
269 |
+ |
|
270 |
+@implementation TodayScriptArray |
|
271 |
+{ |
|
272 |
+ // Backing store for our array. |
|
273 |
+ NSMutableArray *array; |
|
274 |
+} |
|
275 |
+ |
|
276 |
++ (TodayScriptArray *)sharedScripts |
|
277 |
+{ |
|
278 |
+ static TodayScriptArray *instance = nil; |
|
279 |
+ if (! instance) instance = [[TodayScriptArray alloc] init]; |
|
280 |
+ return instance; |
|
281 |
+} |
|
282 |
+ |
|
283 |
+- (id)init |
|
284 |
+{ |
|
285 |
+ if (! (self = super.init)) return nil; |
|
286 |
+ |
|
287 |
+ // We will use the default priority queue for asynchronous tasks. |
|
288 |
+ queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); |
|
289 |
+ |
|
290 |
+ // Set up the XPC connection to the helper object. |
|
291 |
+ XPC = [NSXPCConnection.alloc initWithServiceName:@"org.samroth.Today-Scripts.Widget.XPC"]; |
|
292 |
+ XPC.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCHelping)]; |
|
293 |
+ XPCHelper = XPC.remoteObjectProxy; |
|
294 |
+ XPCLock = [[NSConditionLock alloc] initWithCondition:0]; |
|
295 |
+ |
|
296 |
+ // Create the ANSI translator. |
|
297 |
+ ANSIHelper = [[AMR_ANSIEscapeHelper alloc] init]; |
|
298 |
+ |
|
299 |
+ // Define the font to use. |
|
300 |
+ ANSIHelper.font = [NSFont fontWithName:@"Menlo" size:10]; |
|
301 |
+ |
|
302 |
+#define SET_AMR_SGRCode(CODE, RED, GREEN, BLUE, ALPHA) \ |
|
303 |
+ ANSIHelper.ansiColors[ @(AMR_SGRCode##CODE) ] = \ |
|
304 |
+ [NSColor colorWithRed:RED green:GREEN blue:BLUE alpha:ALPHA] |
|
305 |
+ |
|
306 |
+ // Define the standard text colors to use. |
|
307 |
+ SET_AMR_SGRCode( FgBlack, 0.31764706, 0.31764706, 0.31764706, 1.0 ); |
|
308 |
+ SET_AMR_SGRCode( FgRed, 0.94901961, 0.46666667, 0.47843137, 1.0 ); |
|
309 |
+ SET_AMR_SGRCode( FgGreen, 0.60000000, 0.80000000, 0.60000000, 1.0 ); |
|
310 |
+ SET_AMR_SGRCode( FgYellow, 1.00000000, 0.80000000, 0.40000000, 1.0 ); |
|
311 |
+ SET_AMR_SGRCode( FgBlue, 0.40000000, 0.60000000, 0.80000000, 1.0 ); |
|
312 |
+ SET_AMR_SGRCode( FgMagenta, 0.80000000, 0.60000000, 0.80000000, 1.0 ); |
|
313 |
+ SET_AMR_SGRCode( FgCyan, 0.40000000, 0.80000000, 0.80000000, 1.0 ); |
|
314 |
+ SET_AMR_SGRCode( FgWhite, 0.80000000, 0.80000000, 0.80000000, 1.0 ); |
|
315 |
+ // Background colors are the same as the text colors, but with half opacity. |
|
316 |
+ SET_AMR_SGRCode( BgBlack, 0.31764706, 0.31764706, 0.31764706, 0.5 ); |
|
317 |
+ SET_AMR_SGRCode( BgRed, 0.94901961, 0.46666667, 0.47843137, 0.5 ); |
|
318 |
+ SET_AMR_SGRCode( BgGreen, 0.60000000, 0.80000000, 0.60000000, 0.5 ); |
|
319 |
+ SET_AMR_SGRCode( BgYellow, 1.00000000, 0.80000000, 0.40000000, 0.5 ); |
|
320 |
+ SET_AMR_SGRCode( BgBlue, 0.40000000, 0.60000000, 0.80000000, 0.5 ); |
|
321 |
+ SET_AMR_SGRCode( BgMagenta, 0.80000000, 0.60000000, 0.80000000, 0.5 ); |
|
322 |
+ SET_AMR_SGRCode( BgCyan, 0.40000000, 0.80000000, 0.80000000, 0.5 ); |
|
323 |
+ SET_AMR_SGRCode( BgWhite, 0.80000000, 0.80000000, 0.80000000, 0.5 ); |
|
324 |
+ // Bright text colors are more vivid than normal text colors. |
|
325 |
+ SET_AMR_SGRCode( FgBrightBlack, 0.00000000, 0.00000000, 0.00000000, 1.0 ); |
|
326 |
+ SET_AMR_SGRCode( FgBrightRed, 0.86666667, 0.40000000, 0.40000000, 1.0 ); |
|
327 |
+ SET_AMR_SGRCode( FgBrightGreen, 0.72941176, 0.77254902, 0.36470588, 1.0 ); |
|
328 |
+ SET_AMR_SGRCode( FgBrightYellow, 0.92156863, 0.80392157, 0.38039216, 1.0 ); |
|
329 |
+ SET_AMR_SGRCode( FgBrightBlue, 0.55686275, 0.71764706, 0.87450980, 1.0 ); |
|
330 |
+ SET_AMR_SGRCode( FgBrightMagenta, 0.80784314, 0.67450980, 0.87058824, 1.0 ); |
|
331 |
+ SET_AMR_SGRCode( FgBrightCyan, 0.51372549, 0.79215686, 0.74901961, 1.0 ); |
|
332 |
+ SET_AMR_SGRCode( FgBrightWhite, 1.00000000, 1.00000000, 1.00000000, 1.0 ); |
|
333 |
+ // Bright backgrounds are the same as bright text, but with half opacity. |
|
334 |
+ SET_AMR_SGRCode( BgBrightBlack, 0.00000000, 0.00000000, 0.00000000, 0.5 ); |
|
335 |
+ SET_AMR_SGRCode( BgBrightRed, 0.86666667, 0.40000000, 0.40000000, 0.5 ); |
|
336 |
+ SET_AMR_SGRCode( BgBrightGreen, 0.72941176, 0.77254902, 0.36470588, 0.5 ); |
|
337 |
+ SET_AMR_SGRCode( BgBrightYellow, 0.92156863, 0.80392157, 0.38039216, 0.5 ); |
|
338 |
+ SET_AMR_SGRCode( BgBrightBlue, 0.55686275, 0.71764706, 0.87450980, 0.5 ); |
|
339 |
+ SET_AMR_SGRCode( BgBrightMagenta, 0.80784314, 0.67450980, 0.87058824, 0.5 ); |
|
340 |
+ SET_AMR_SGRCode( BgBrightCyan, 0.51372549, 0.79215686, 0.74901961, 0.5 ); |
|
341 |
+ SET_AMR_SGRCode( BgBrightWhite, 1.00000000, 1.00000000, 1.00000000, 0.5 ); |
|
342 |
+ |
|
343 |
+ // Default text color is our non-bright white. |
|
344 |
+ ANSIHelper.ansiColors[ @(AMR_SGRCodeFgReset) ] = ANSIHelper.defaultStringColor = |
|
345 |
+ ANSIHelper.ansiColors[ @(AMR_SGRCodeFgWhite) ]; |
|
346 |
+ |
|
347 |
+ // Default background is transparent. |
|
348 |
+ ANSIHelper.ansiColors[@( AMR_SGRCodeBgReset )] = NSColor.clearColor; |
|
349 |
+ |
|
350 |
+ paragraphStyle = [[NSMutableParagraphStyle alloc] init]; |
|
351 |
+ paragraphStyle.defaultTabInterval = COLUMN_WIDTH * TAB_COLUMNS; |
|
352 |
+ |
|
353 |
+ NSMutableArray *tabStops = [[NSMutableArray alloc] init]; |
|
354 |
+ for (NSUInteger tabIndex = 1; tabIndex < COLUMN_COUNT / TAB_COLUMNS; tabIndex++) |
|
355 |
+ { |
|
356 |
+ CGFloat tabLocation = COLUMN_WIDTH * TAB_COLUMNS * tabIndex + PADDING_LEFT; |
|
357 |
+ [tabStops addObject:[NSTextTab.alloc initWithType:NSLeftTabStopType location:tabLocation]]; |
|
358 |
+ } |
|
359 |
+ paragraphStyle.tabStops = tabStops; |
|
360 |
+ |
|
361 |
+ // Set up our list of line breaks. |
|
362 |
+ lineBreaks = [NSCharacterSet characterSetWithCharactersInString:@"\n\r"]; |
|
363 |
+ |
|
364 |
+ |
|
365 |
+ |
|
366 |
+ // Set up the "true" backing array for our live scripts list. |
|
367 |
+ array = [[NSMutableArray alloc] init]; |
|
368 |
+ |
|
369 |
+// [NSUserDefaults.standardUserDefaults removeObjectForKey:@"Scripts"]; |
|
370 |
+ |
|
371 |
+ // Get the list of scripts as stored in our defaults. |
|
372 |
+ NSArray *defaultsScripts = [NSUserDefaults.standardUserDefaults arrayForKey:@"Scripts"]; |
|
373 |
+ |
|
374 |
+ // Iterate through the scripts to translate them to our live representation. |
|
375 |
+ for (id defaultsScript in defaultsScripts) |
|
376 |
+ { |
|
377 |
+ // If the script is not dictionary form, ignore it. |
|
378 |
+ if (! [defaultsScript isKindOfClass:NSDictionary.class]) |
|
379 |
+ continue; |
|
380 |
+ |
|
381 |
+ TodayScript *script = [[TodayScript alloc] init]; |
|
382 |
+ script.dictionary = defaultsScript; |
|
383 |
+ [array addObject:script]; |
|
384 |
+ } |
|
385 |
+ |
|
386 |
+ return self; |
|
387 |
+} |
|
388 |
+ |
|
389 |
+- (NSUInteger)count { |
|
390 |
+ return array.count; |
|
391 |
+} |
|
392 |
+- (id)objectAtIndex:(NSUInteger)index { |
|
393 |
+ return [array objectAtIndex:index]; |
|
394 |
+} |
|
395 |
+ |
|
396 |
+// All changes to our array should be saved to the array in our user defaults. |
|
397 |
+// This method does this for each overridden NSMutableArray method. |
|
398 |
+- (void)saveDefaults |
|
399 |
+{ |
|
400 |
+ // Create a new array to be copied to our defaults. |
|
401 |
+ NSMutableArray *defaultsScripts = [[NSMutableArray alloc] init]; |
|
402 |
+ // Iterate through our own script dictionaries in order to add them to it. |
|
403 |
+ for (TodayScript *script in array) |
|
404 |
+ [defaultsScripts addObject:script.dictionary]; |
|
405 |
+ |
|
406 |
+ // Finally, write the array to our defaults. |
|
407 |
+ [NSUserDefaults.standardUserDefaults setObject:defaultsScripts forKey:@"Scripts"]; |
|
408 |
+} |
|
409 |
+- (void)insertObject:(id)anObject atIndex:(NSUInteger)index { |
|
410 |
+ [array insertObject:anObject atIndex:index]; |
|
411 |
+ [self saveDefaults]; |
|
412 |
+} |
|
413 |
+- (void)removeObjectAtIndex:(NSUInteger)index { |
|
414 |
+ [array removeObjectAtIndex:index]; |
|
415 |
+ [self saveDefaults]; |
|
416 |
+} |
|
417 |
+- (void)addObject:(id)anObject { |
|
418 |
+ [array addObject:anObject]; |
|
419 |
+ [self saveDefaults]; |
|
420 |
+} |
|
421 |
+- (void)removeLastObject { |
|
422 |
+ [array removeLastObject]; |
|
423 |
+ [self saveDefaults]; |
|
424 |
+} |
|
425 |
+- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject { |
|
426 |
+ [array replaceObjectAtIndex:index withObject:anObject]; |
|
427 |
+ [self saveDefaults]; |
|
428 |
+} |
|
429 |
+ |
|
430 |
+- (NCUpdateResult)autoRun |
|
431 |
+{ |
|
432 |
+ // Count how many scripts we will need to run. |
|
433 |
+ NSUInteger count = 0; |
|
434 |
+ for (TodayScript *script in array) |
|
435 |
+ if (script.autoRun) |
|
436 |
+ count++; |
|
437 |
+ |
|
438 |
+ // If there are no scripts to run, stop here, and return an indication that |
|
439 |
+ // there are no changes to display. |
|
440 |
+ if (! count) |
|
441 |
+ return NCUpdateResultNoData; |
|
442 |
+ |
|
443 |
+ // Initialize a lock to use for waiting until each script has decremented it |
|
444 |
+ // after their completion. |
|
445 |
+ NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:count]; |
|
446 |
+ |
|
447 |
+ // Create a variable that's modifiable by blocks, to use for tracking |
|
448 |
+ // whether we will have updates to display in the end. |
|
449 |
+ __block NCUpdateResult updateResult = NCUpdateResultNoData; |
|
450 |
+ NSValue *resultPointer = [NSValue valueWithPointer:&updateResult]; |
|
451 |
+ |
|
452 |
+ // Run each script that is marked to automatically run. |
|
453 |
+ for (TodayScript *script in array) |
|
454 |
+ if (script.autoRun) |
|
455 |
+ [script runWithLock:lock resultPointer:resultPointer]; |
|
456 |
+ |
|
457 |
+ // Set up a timeout routine to dispatch after three seconds. |
|
458 |
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), queue, ^ |
|
459 |
+ { |
|
460 |
+ // If we time out, we will tell Notification Center to update the view |
|
461 |
+ // in order to display the ongoing progress to the user. |
|
462 |
+ updateResult = NCUpdateResultNewData; |
|
463 |
+ // Set the value of lock to zero in order to allow us to proceed. |
|
464 |
+ [lock lock]; |
|
465 |
+ [lock unlockWithCondition:0]; |
|
466 |
+ }); |
|
467 |
+ |
|
468 |
+ // Wait on our lock until all of the scripts have decremented it, or until |
|
469 |
+ // one has updates to display and sets it to zero, or until we've timed out. |
|
470 |
+ [lock lockWhenCondition:0]; |
|
471 |
+ [lock unlock]; |
|
472 |
+ |
|
473 |
+ // Return the final results to Notification Center. |
|
474 |
+ return updateResult; |
|
475 |
+} |
|
476 |
+ |
|
477 |
+@end |
0 | 478 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,21 @@ |
1 |
+// |
|
2 |
+// TodayViewController.h |
|
3 |
+// Scripts |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 8/14/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import "TodayScript.h" |
|
10 |
+#import "EditViewController.h" |
|
11 |
+ |
|
12 |
+ |
|
13 |
+@interface TodayViewController : NSViewController <NCWidgetProviding, NCWidgetListViewDelegate> |
|
14 |
+ |
|
15 |
+@property (strong) IBOutlet NCWidgetListViewController *listViewController; |
|
16 |
+@property (strong) EditViewController *editViewController; |
|
17 |
+@property (strong) NSArrayController *arrayController; |
|
18 |
+ |
|
19 |
+@end |
|
20 |
+ |
|
21 |
+extern TodayViewController *todayViewController; |
0 | 22 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,131 @@ |
1 |
+// |
|
2 |
+// TodayViewController.m |
|
3 |
+// Scripts |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 8/14/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import <Cocoa/Cocoa.h> |
|
10 |
+ |
|
11 |
+#import "TodayScript.h" |
|
12 |
+#import "TodayViewController.h" |
|
13 |
+#import "EditViewController.h" |
|
14 |
+#import "ListRowViewController.h" |
|
15 |
+ |
|
16 |
+ |
|
17 |
+TodayViewController *todayViewController; |
|
18 |
+ |
|
19 |
+ |
|
20 |
+#pragma mark - Widget Implementation |
|
21 |
+ |
|
22 |
+ |
|
23 |
+@implementation TodayViewController |
|
24 |
+ |
|
25 |
+- (void)viewDidLoad |
|
26 |
+{ |
|
27 |
+ [super viewDidLoad]; |
|
28 |
+ |
|
29 |
+ todayViewController = self; |
|
30 |
+ |
|
31 |
+ // Set up the view controller for adding and editing scripts. |
|
32 |
+ self.editViewController = [[EditViewController alloc] init]; |
|
33 |
+ |
|
34 |
+ // Set up our array controller to manage the array which coordiates with our |
|
35 |
+ // persistent user defaults. |
|
36 |
+ self.arrayController = [NSArrayController.alloc initWithContent:TodayScriptArray.sharedScripts]; |
|
37 |
+ |
|
38 |
+ // If our user defaults contain no scripts, set up an introductory one. |
|
39 |
+ if (! TodayScriptArray.sharedScripts.count) |
|
40 |
+ { |
|
41 |
+ TodayScript *script = [[TodayScript alloc] init]; |
|
42 |
+ script.label = @"Welcome"; |
|
43 |
+ script.program = @"/bin/sh"; |
|
44 |
+ script.script = @"echo 'Click the Info button above to start adding scripts.'"; |
|
45 |
+ script.autoRun = YES; |
|
46 |
+ script.showStatus = YES; |
|
47 |
+ [self.arrayController addObject:script]; |
|
48 |
+ } |
|
49 |
+ |
|
50 |
+ // Set up the widget list view controller to get its content from our array |
|
51 |
+ // controller. |
|
52 |
+ self.listViewController.contents = self.arrayController.arrangedObjects; |
|
53 |
+} |
|
54 |
+ |
|
55 |
+ |
|
56 |
+ |
|
57 |
+#pragma mark - Getting and Displaying Updates |
|
58 |
+ |
|
59 |
+// Notification Center calls this method to give us an opportunity to provide |
|
60 |
+// updates. Refresh the widget's contents in preparation for a snapshot. |
|
61 |
+- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult result))handler |
|
62 |
+{ |
|
63 |
+ // Tell the script array to auto run the scripts as necessary, and return |
|
64 |
+ // the update status it indicates. |
|
65 |
+ handler(TodayScriptArray.sharedScripts.autoRun); |
|
66 |
+} |
|
67 |
+ |
|
68 |
+- (NSEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(NSEdgeInsets)defaultMarginInset { |
|
69 |
+ // Override the left margin so that the list view is flush with the edge. |
|
70 |
+ defaultMarginInset.left = 0; |
|
71 |
+ defaultMarginInset.right = 0; |
|
72 |
+ return defaultMarginInset; |
|
73 |
+} |
|
74 |
+ |
|
75 |
+ |
|
76 |
+ |
|
77 |
+#pragma mark - Editing the List of Scripts |
|
78 |
+ |
|
79 |
+- (NSViewController *)widgetList:(NCWidgetListViewController *)list viewControllerForRow:(NSUInteger)row |
|
80 |
+{ |
|
81 |
+ // Return a new view controller subclass for displaying an item of widget |
|
82 |
+ // content. The NCWidgetListViewController will set the representedObject |
|
83 |
+ // of this view controller to one of the objects in its contents array. |
|
84 |
+ return [[ListRowViewController alloc] init]; |
|
85 |
+} |
|
86 |
+ |
|
87 |
+- (BOOL)widgetList:(NCWidgetListViewController *)list shouldReorderRow:(NSUInteger)row { |
|
88 |
+ // Return YES to allow the item to be reordered in the list by the user. |
|
89 |
+ return YES; |
|
90 |
+} |
|
91 |
+ |
|
92 |
+- (void)widgetList:(NCWidgetListViewController *)list didReorderRow:(NSUInteger)row toRow:(NSUInteger)newIndex { |
|
93 |
+ // The user has reordered an item in the list. |
|
94 |
+ TodayScript *script = [self.arrayController.arrangedObjects objectAtIndex:row]; |
|
95 |
+ [self.arrayController removeObjectAtArrangedObjectIndex:row]; |
|
96 |
+ [self.arrayController insertObject:script atArrangedObjectIndex:newIndex]; |
|
97 |
+} |
|
98 |
+ |
|
99 |
+- (BOOL)widgetList:(NCWidgetListViewController *)list shouldRemoveRow:(NSUInteger)row { |
|
100 |
+ // Return YES to allow the item to be removed from the list by the user. |
|
101 |
+ return YES; |
|
102 |
+} |
|
103 |
+ |
|
104 |
+- (void)widgetList:(NCWidgetListViewController *)list didRemoveRow:(NSUInteger)row { |
|
105 |
+ // The user has removed an item from the list. |
|
106 |
+ [self.arrayController removeObjectAtArrangedObjectIndex:row]; |
|
107 |
+} |
|
108 |
+ |
|
109 |
+- (void)widgetListPerformAddAction:(NCWidgetListViewController *)list |
|
110 |
+{ |
|
111 |
+ // The user has clicked the add button in the list view. |
|
112 |
+ [self.editViewController createScript]; |
|
113 |
+} |
|
114 |
+ |
|
115 |
+- (BOOL)widgetAllowsEditing { |
|
116 |
+ return YES; |
|
117 |
+} |
|
118 |
+ |
|
119 |
+- (void)widgetDidBeginEditing { |
|
120 |
+ // The user has clicked the edit button. Put the list view into edit mode. |
|
121 |
+ self.listViewController.editing = YES; |
|
122 |
+} |
|
123 |
+ |
|
124 |
+- (void)widgetDidEndEditing { |
|
125 |
+ // The user has clicked the Done button, begun editing another widget, or NC |
|
126 |
+ // has been closed. Take the list view out of editing mode. |
|
127 |
+ self.listViewController.contents = self.arrayController.arrangedObjects; |
|
128 |
+ self.listViewController.editing = NO; |
|
129 |
+} |
|
130 |
+ |
|
131 |
+@end |
0 | 132 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,26 @@ |
1 |
+<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
|
2 |
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A389" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> |
|
3 |
+ <dependencies> |
|
4 |
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/> |
|
5 |
+ </dependencies> |
|
6 |
+ <objects> |
|
7 |
+ <customObject id="-2" userLabel="File's Owner" customClass="TodayViewController"> |
|
8 |
+ <connections> |
|
9 |
+ <outlet property="listViewController" destination="vLu-t9-fwO" id="U9u-yA-ZGU"/> |
|
10 |
+ <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/> |
|
11 |
+ </connections> |
|
12 |
+ </customObject> |
|
13 |
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> |
|
14 |
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/> |
|
15 |
+ <customView simulatedAppContext="notificationCenter" translatesAutoresizingMaskIntoConstraints="NO" id="Hz6-mo-xeY"> |
|
16 |
+ <rect key="frame" x="0.0" y="0.0" width="320" height="288"/> |
|
17 |
+ </customView> |
|
18 |
+ <viewController nibName="ListRowViewController" nibBundleIdentifier="org.samroth.Today-Scripts.Widget" id="vLu-t9-fwO" customClass="NCWidgetListViewController"> |
|
19 |
+ <connections> |
|
20 |
+ <outlet property="delegate" destination="-2" id="yeU-qg-EhZ"/> |
|
21 |
+ <outlet property="view" destination="Hz6-mo-xeY" id="nq9-gf-VjV"/> |
|
22 |
+ </connections> |
|
23 |
+ </viewController> |
|
24 |
+ <viewController id="V1t-Qa-lVl"/> |
|
25 |
+ </objects> |
|
26 |
+</document> |
0 | 27 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,8 @@ |
1 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
2 |
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|
3 |
+<plist version="1.0"> |
|
4 |
+<dict> |
|
5 |
+ <key>com.apple.security.app-sandbox</key> |
|
6 |
+ <true/> |
|
7 |
+</dict> |
|
8 |
+</plist> |
0 | 5 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,33 @@ |
1 |
+<?xml version="1.0" encoding="UTF-8"?> |
|
2 |
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|
3 |
+<plist version="1.0"> |
|
4 |
+<dict> |
|
5 |
+ <key>CFBundleDevelopmentRegion</key> |
|
6 |
+ <string>en</string> |
|
7 |
+ <key>CFBundleDisplayName</key> |
|
8 |
+ <string>XPC</string> |
|
9 |
+ <key>CFBundleExecutable</key> |
|
10 |
+ <string>$(EXECUTABLE_NAME)</string> |
|
11 |
+ <key>CFBundleIdentifier</key> |
|
12 |
+ <string>org.samroth.Today-Scripts.Widget.$(PRODUCT_NAME:rfc1034identifier)</string> |
|
13 |
+ <key>CFBundleInfoDictionaryVersion</key> |
|
14 |
+ <string>6.0</string> |
|
15 |
+ <key>CFBundleName</key> |
|
16 |
+ <string>$(PRODUCT_NAME)</string> |
|
17 |
+ <key>CFBundlePackageType</key> |
|
18 |
+ <string>XPC!</string> |
|
19 |
+ <key>CFBundleShortVersionString</key> |
|
20 |
+ <string>1.0</string> |
|
21 |
+ <key>CFBundleSignature</key> |
|
22 |
+ <string>????</string> |
|
23 |
+ <key>CFBundleVersion</key> |
|
24 |
+ <string>1</string> |
|
25 |
+ <key>NSHumanReadableCopyright</key> |
|
26 |
+ <string>Copyright © 2014 Sam Rothenberg. All rights reserved.</string> |
|
27 |
+ <key>XPCService</key> |
|
28 |
+ <dict> |
|
29 |
+ <key>ServiceType</key> |
|
30 |
+ <string>Application</string> |
|
31 |
+ </dict> |
|
32 |
+</dict> |
|
33 |
+</plist> |
0 | 34 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,138 @@ |
1 |
+// |
|
2 |
+// XPCHelper.m |
|
3 |
+// xpc |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 8/10/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import "TodayScripts.h" |
|
10 |
+#import "XPCMain.h" |
|
11 |
+#include <util.h> |
|
12 |
+ |
|
13 |
+ |
|
14 |
+@implementation XPCHelper |
|
15 |
+{ |
|
16 |
+ // The dictionary of environment variables for spawned tasks. |
|
17 |
+ NSMutableDictionary *environment; |
|
18 |
+ // Queue for performing asynchonous tasks. |
|
19 |
+ dispatch_queue_t queue; |
|
20 |
+ |
|
21 |
+ // A dictionary of the active tasks. |
|
22 |
+ NSMutableDictionary *tasks; |
|
23 |
+} |
|
24 |
+ |
|
25 |
+- (id)init |
|
26 |
+{ |
|
27 |
+ if (! (self = super.init)) return nil; |
|
28 |
+ |
|
29 |
+ // Create an environment for tasks based on our own, and add the details of |
|
30 |
+ // our "terminal." |
|
31 |
+ environment = NSProcessInfo.processInfo.environment.mutableCopy; |
|
32 |
+ environment[@"TERM" ] = @"ansi"; |
|
33 |
+ environment[@"COLUMNS"] = @"40"; |
|
34 |
+ environment[@"PAGER" ] = @"/bin/cat"; |
|
35 |
+ |
|
36 |
+ // We will perform asynchronous tasks on the default background queue. |
|
37 |
+ queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); |
|
38 |
+ |
|
39 |
+ // Set up the dictionary to keep records of our tasks. |
|
40 |
+ tasks = [[NSMutableDictionary alloc] init]; |
|
41 |
+ |
|
42 |
+ return self; |
|
43 |
+} |
|
44 |
+ |
|
45 |
+- (void)launchScript:(NSDictionary *)script forUUID:(NSString *)UUID handler:(XPCHandler)handler; |
|
46 |
+{ |
|
47 |
+// NSURL *programURL = [NSURL URLWithString:script[TodayScriptProgramKey]]; |
|
48 |
+// NSUserUnixTask *task = [[NSUserUnixTask alloc] initWithURL:programURL error:nil]; |
|
49 |
+ |
|
50 |
+ // Create a new task object and add it to our dictionary. |
|
51 |
+ NSTask *task = [[NSTask alloc] init]; |
|
52 |
+ tasks[UUID] = task; |
|
53 |
+ |
|
54 |
+ // Set the task's program to the user's shell. |
|
55 |
+ task.launchPath = script[TodayScriptProgramKey]; |
|
56 |
+ // Set the task's environment as previously determined. |
|
57 |
+ task.environment = environment; |
|
58 |
+ // Set our current directory to the user's home directory. |
|
59 |
+ task.currentDirectoryPath = environment[@"HOME"]; |
|
60 |
+ |
|
61 |
+ // Open a 40-character wide pseudo-TTY for this script, getting its file |
|
62 |
+ // descriptors. |
|
63 |
+ int master, slave; |
|
64 |
+ struct winsize size = { .ws_col = 40 }; |
|
65 |
+ openpty(&master, &slave, NULL, NULL, &size); |
|
66 |
+ |
|
67 |
+ // Set up handles for the TTY's file descriptors, such that they close them |
|
68 |
+ // once we're done. |
|
69 |
+ NSFileHandle *masterHandle = NSFileHandle.alloc, *slaveHandle = NSFileHandle.alloc; |
|
70 |
+ masterHandle = [masterHandle initWithFileDescriptor:master closeOnDealloc:YES]; |
|
71 |
+ slaveHandle = [slaveHandle initWithFileDescriptor:slave closeOnDealloc:YES]; |
|
72 |
+ |
|
73 |
+ // The script's output will be sent to the pseudo-TTY. |
|
74 |
+ task.standardOutput = task.standardError = slaveHandle; |
|
75 |
+ |
|
76 |
+ // If we were provided a script, convert it to UTF-8 to pass to the program. |
|
77 |
+ NSString *scriptString = script[TodayScriptScriptKey]; |
|
78 |
+ if (scriptString.length) { |
|
79 |
+ // We will be piping the script to the program via its standard input. |
|
80 |
+ // Write the script to the pipe so that it is ready for the interpreter. |
|
81 |
+ NSPipe *pipe = NSPipe.pipe; |
|
82 |
+ task.standardInput = pipe.fileHandleForReading; |
|
83 |
+ [pipe.fileHandleForWriting writeData:[scriptString dataUsingEncoding:NSUTF8StringEncoding]]; |
|
84 |
+ [pipe.fileHandleForWriting closeFile]; |
|
85 |
+ } |
|
86 |
+ |
|
87 |
+ // Create a data object which can be modified by the TTY reader block, as |
|
88 |
+ // well as a semaphore which it can use to signal the termination block. |
|
89 |
+ __block NSData *outputData = nil; |
|
90 |
+ dispatch_semaphore_t outputSemaphore = dispatch_semaphore_create(0); |
|
91 |
+ |
|
92 |
+ dispatch_async(queue, ^ |
|
93 |
+ { |
|
94 |
+ // Read the master handle in the background until the TTY is closed. |
|
95 |
+ outputData = [masterHandle readDataToEndOfFile]; |
|
96 |
+ // After we've set the data object, we may signal the termination block |
|
97 |
+ // that it is ready. |
|
98 |
+ dispatch_semaphore_signal(outputSemaphore); |
|
99 |
+ }); |
|
100 |
+ |
|
101 |
+ task.terminationHandler = ^(NSTask *task) |
|
102 |
+ { |
|
103 |
+ // If the task did not complete on its own volition, we we be returning |
|
104 |
+ // an invalid exit status and output. |
|
105 |
+ NSString *output = nil; |
|
106 |
+ int status = -1; |
|
107 |
+ |
|
108 |
+ // Close the TTY such that the reader thread stops waiting on it. |
|
109 |
+ [slaveHandle closeFile]; |
|
110 |
+ |
|
111 |
+ // If the task ran to completion on its own volition, we will need to |
|
112 |
+ // process its results. |
|
113 |
+ if (task.terminationReason == NSTaskTerminationReasonExit) |
|
114 |
+ { |
|
115 |
+ // Wait for the reader thread to set the data object and signal us. |
|
116 |
+ dispatch_semaphore_wait(outputSemaphore, DISPATCH_TIME_FOREVER); |
|
117 |
+ // We will return the actual exit status of the process. |
|
118 |
+ status = task.terminationStatus; |
|
119 |
+ // Convert the UTF-8 output from the reader block to a string. |
|
120 |
+ output = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding]; |
|
121 |
+ } |
|
122 |
+ |
|
123 |
+ // Return the results to the widget. |
|
124 |
+ handler(status, output); |
|
125 |
+ |
|
126 |
+ // Remove the task from our records. |
|
127 |
+ [tasks removeObjectForKey:UUID]; |
|
128 |
+ }; |
|
129 |
+ |
|
130 |
+ // Launch the task. |
|
131 |
+ [task launch]; |
|
132 |
+} |
|
133 |
+ |
|
134 |
+- (void)terminateScriptForUUID:(NSString *)UUID { |
|
135 |
+ [(NSTask *)tasks[UUID] terminate]; |
|
136 |
+} |
|
137 |
+ |
|
138 |
+@end |
0 | 139 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,22 @@ |
1 |
+// |
|
2 |
+// XPCMain.h |
|
3 |
+// Today Scripts |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 8/14/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import <Cocoa/Cocoa.h> |
|
10 |
+#import "TodayScripts.h" |
|
11 |
+ |
|
12 |
+@interface XPCListenerDelegate : NSObject <NSXPCListenerDelegate> @end |
|
13 |
+XPCListenerDelegate *listenerDelegate; |
|
14 |
+ |
|
15 |
+NSXPCListener *listener; |
|
16 |
+NSXPCConnection *connection; |
|
17 |
+ |
|
18 |
+// This object implements the protocol which we have defined. It provides the |
|
19 |
+// actual behavior for the service. It is 'exported' by the service to make it |
|
20 |
+// available to the process hosting the service over an NSXPCConnection. |
|
21 |
+@interface XPCHelper : NSObject <XPCHelping> @end |
|
22 |
+XPCHelper *helper; |
0 | 23 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,49 @@ |
1 |
+// |
|
2 |
+// XPCMain.m |
|
3 |
+// xpc |
|
4 |
+// |
|
5 |
+// Created by Sam Rothenberg on 8/10/14. |
|
6 |
+// Copyright (c) 2014 Sam Rothenberg. All rights reserved. |
|
7 |
+// |
|
8 |
+ |
|
9 |
+#import "XPCMain.h" |
|
10 |
+ |
|
11 |
+@implementation XPCListenerDelegate |
|
12 |
+ |
|
13 |
+- (BOOL)listener:(NSXPCListener *)aListener shouldAcceptNewConnection:(NSXPCConnection *)aConnection |
|
14 |
+{ |
|
15 |
+ helper = [XPCHelper.alloc init]; |
|
16 |
+ |
|
17 |
+ // This method is where the NSXPCListener configures, accepts, and resumes a new incoming NSXPCConnection. |
|
18 |
+ connection = aConnection; |
|
19 |
+ |
|
20 |
+ // Configure the connection. |
|
21 |
+ // First, set the interface that the exported object implements. |
|
22 |
+ connection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCHelping)]; |
|
23 |
+ |
|
24 |
+ // Next, set the object that the connection exports. All messages sent on the connection to this service will be sent to the exported object to handle. The connection retains the exported object. |
|
25 |
+ connection.exportedObject = helper; |
|
26 |
+ |
|
27 |
+ // Resuming the connection allows the system to deliver more incoming messages. |
|
28 |
+ [connection resume]; |
|
29 |
+ |
|
30 |
+ // Returning YES from this method tells the system that you have accepted this connection. If you want to reject the connection for some reason, call -invalidate on the connection and return NO. |
|
31 |
+ return YES; |
|
32 |
+} |
|
33 |
+ |
|
34 |
+@end |
|
35 |
+ |
|
36 |
+int main() |
|
37 |
+{ |
|
38 |
+ // Set up the one NSXPCListener for this service. It will handle all incoming connections. |
|
39 |
+ listener = NSXPCListener.serviceListener; |
|
40 |
+ |
|
41 |
+ // Create the delegate for the service. |
|
42 |
+ listenerDelegate = [XPCListenerDelegate.alloc init]; |
|
43 |
+ listener.delegate = listenerDelegate; |
|
44 |
+ |
|
45 |
+ // Resuming the serviceListener starts this service. This method does not return. |
|
46 |
+ [listener resume]; |
|
47 |
+ |
|
48 |
+ return 0; |
|
49 |
+} |