Kaynağa Gözat

Initial commit for Butterfly objc code editor.

Marco Bambini 8 yıl önce
ebeveyn
işleme
5e1f677565
25 değiştirilmiş dosya ile 2908 ekleme ve 0 silme
  1. 527 0
      projects/Butterfly/Butterfly.xcodeproj/project.pbxproj
  2. 15 0
      projects/Butterfly/Butterfly/AppDelegate.h
  3. 103 0
      projects/Butterfly/Butterfly/AppDelegate.m
  4. 58 0
      projects/Butterfly/Butterfly/Assets.xcassets/AppIcon.appiconset/Contents.json
  5. 703 0
      projects/Butterfly/Butterfly/Base.lproj/MainMenu.xib
  6. 32 0
      projects/Butterfly/Butterfly/Info.plist
  7. 33 0
      projects/Butterfly/Butterfly/Sources/BTFCodeEditor.h
  8. 286 0
      projects/Butterfly/Butterfly/Sources/BTFCodeEditor.m
  9. 16 0
      projects/Butterfly/Butterfly/Sources/BTFDelegate.h
  10. 25 0
      projects/Butterfly/Butterfly/Sources/BTFPopover.h
  11. 95 0
      projects/Butterfly/Butterfly/Sources/BTFPopover.m
  12. 18 0
      projects/Butterfly/Butterfly/Sources/BTFPopoverFrame.h
  13. 25 0
      projects/Butterfly/Butterfly/Sources/BTFPopoverFrame.m
  14. 13 0
      projects/Butterfly/Butterfly/Sources/BTFPopoverWindow.h
  15. 38 0
      projects/Butterfly/Butterfly/Sources/BTFPopoverWindow.m
  16. 21 0
      projects/Butterfly/Butterfly/Sources/BTFRulerView.h
  17. 150 0
      projects/Butterfly/Butterfly/Sources/BTFRulerView.m
  18. 17 0
      projects/Butterfly/Butterfly/Sources/BTFTextView.h
  19. 526 0
      projects/Butterfly/Butterfly/Sources/BTFTextView.m
  20. 54 0
      projects/Butterfly/Butterfly/Sources/BTFThemeKeys.h
  21. 13 0
      projects/Butterfly/Butterfly/main.m
  22. 36 0
      projects/Butterfly/Themes/Fourier.theme
  23. 34 0
      projects/Butterfly/Themes/Gauss.theme
  24. 36 0
      projects/Butterfly/Themes/Laplace.theme
  25. 34 0
      projects/Butterfly/Themes/Riemann.theme

+ 527 - 0
projects/Butterfly/Butterfly.xcodeproj/project.pbxproj

@@ -0,0 +1,527 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		A96F4EAB1E7D8638000509B3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A96F4EAA1E7D8638000509B3 /* AppDelegate.m */; };
+		A96F4EAE1E7D8638000509B3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A96F4EAD1E7D8638000509B3 /* main.m */; };
+		A96F4EB01E7D8638000509B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A96F4EAF1E7D8638000509B3 /* Assets.xcassets */; };
+		A96F4EB31E7D8638000509B3 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = A96F4EB11E7D8638000509B3 /* MainMenu.xib */; };
+		A96F4EBB1E7D8696000509B3 /* Themes in Resources */ = {isa = PBXBuildFile; fileRef = A96F4EBA1E7D8696000509B3 /* Themes */; };
+		A96F4EEF1E7D86D8000509B3 /* gravity_ast.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4EBF1E7D86D8000509B3 /* gravity_ast.c */; };
+		A96F4EF01E7D86D8000509B3 /* gravity_codegen.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4EC11E7D86D8000509B3 /* gravity_codegen.c */; };
+		A96F4EF11E7D86D8000509B3 /* gravity_compiler.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4EC31E7D86D8000509B3 /* gravity_compiler.c */; };
+		A96F4EF21E7D86D8000509B3 /* gravity_ircode.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4EC51E7D86D8000509B3 /* gravity_ircode.c */; };
+		A96F4EF31E7D86D8000509B3 /* gravity_lexer.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4EC71E7D86D8000509B3 /* gravity_lexer.c */; };
+		A96F4EF41E7D86D8000509B3 /* gravity_optimizer.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4EC91E7D86D8000509B3 /* gravity_optimizer.c */; };
+		A96F4EF51E7D86D8000509B3 /* gravity_parser.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4ECB1E7D86D8000509B3 /* gravity_parser.c */; };
+		A96F4EF61E7D86D8000509B3 /* gravity_semacheck1.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4ECD1E7D86D8000509B3 /* gravity_semacheck1.c */; };
+		A96F4EF71E7D86D8000509B3 /* gravity_semacheck2.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4ECF1E7D86D8000509B3 /* gravity_semacheck2.c */; };
+		A96F4EF81E7D86D8000509B3 /* gravity_symboltable.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4ED11E7D86D8000509B3 /* gravity_symboltable.c */; };
+		A96F4EF91E7D86D8000509B3 /* gravity_token.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4ED31E7D86D8000509B3 /* gravity_token.c */; };
+		A96F4EFA1E7D86D8000509B3 /* gravity_visitor.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4ED51E7D86D8000509B3 /* gravity_visitor.c */; };
+		A96F4EFB1E7D86D8000509B3 /* gravity_core.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4ED81E7D86D8000509B3 /* gravity_core.c */; };
+		A96F4EFC1E7D86D8000509B3 /* gravity_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4EDA1E7D86D8000509B3 /* gravity_vm.c */; };
+		A96F4EFD1E7D86D8000509B3 /* gravity_hash.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4EE01E7D86D8000509B3 /* gravity_hash.c */; };
+		A96F4EFE1E7D86D8000509B3 /* gravity_memory.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4EE31E7D86D8000509B3 /* gravity_memory.c */; };
+		A96F4EFF1E7D86D8000509B3 /* gravity_value.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4EE61E7D86D8000509B3 /* gravity_value.c */; };
+		A96F4F001E7D86D8000509B3 /* gravity_debug.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4EE91E7D86D8000509B3 /* gravity_debug.c */; };
+		A96F4F011E7D86D8000509B3 /* gravity_json.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4EEB1E7D86D8000509B3 /* gravity_json.c */; };
+		A96F4F021E7D86D8000509B3 /* gravity_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = A96F4EED1E7D86D8000509B3 /* gravity_utils.c */; };
+		A96F4F121E7D8706000509B3 /* BTFCodeEditor.m in Sources */ = {isa = PBXBuildFile; fileRef = A96F4F051E7D8706000509B3 /* BTFCodeEditor.m */; };
+		A96F4F131E7D8706000509B3 /* BTFPopover.m in Sources */ = {isa = PBXBuildFile; fileRef = A96F4F081E7D8706000509B3 /* BTFPopover.m */; };
+		A96F4F141E7D8706000509B3 /* BTFPopoverFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = A96F4F0A1E7D8706000509B3 /* BTFPopoverFrame.m */; };
+		A96F4F151E7D8706000509B3 /* BTFPopoverWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = A96F4F0C1E7D8706000509B3 /* BTFPopoverWindow.m */; };
+		A96F4F161E7D8706000509B3 /* BTFRulerView.m in Sources */ = {isa = PBXBuildFile; fileRef = A96F4F0E1E7D8706000509B3 /* BTFRulerView.m */; };
+		A96F4F171E7D8706000509B3 /* BTFTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = A96F4F101E7D8706000509B3 /* BTFTextView.m */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+		A96F4EA61E7D8638000509B3 /* Butterfly.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Butterfly.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		A96F4EA91E7D8638000509B3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+		A96F4EAA1E7D8638000509B3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+		A96F4EAD1E7D8638000509B3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+		A96F4EAF1E7D8638000509B3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		A96F4EB21E7D8638000509B3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
+		A96F4EB41E7D8638000509B3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		A96F4EBA1E7D8696000509B3 /* Themes */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Themes; sourceTree = SOURCE_ROOT; };
+		A96F4EBE1E7D86D8000509B3 /* debug_macros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = debug_macros.h; sourceTree = "<group>"; };
+		A96F4EBF1E7D86D8000509B3 /* gravity_ast.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_ast.c; sourceTree = "<group>"; };
+		A96F4EC01E7D86D8000509B3 /* gravity_ast.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_ast.h; sourceTree = "<group>"; };
+		A96F4EC11E7D86D8000509B3 /* gravity_codegen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_codegen.c; sourceTree = "<group>"; };
+		A96F4EC21E7D86D8000509B3 /* gravity_codegen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_codegen.h; sourceTree = "<group>"; };
+		A96F4EC31E7D86D8000509B3 /* gravity_compiler.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_compiler.c; sourceTree = "<group>"; };
+		A96F4EC41E7D86D8000509B3 /* gravity_compiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_compiler.h; sourceTree = "<group>"; };
+		A96F4EC51E7D86D8000509B3 /* gravity_ircode.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_ircode.c; sourceTree = "<group>"; };
+		A96F4EC61E7D86D8000509B3 /* gravity_ircode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_ircode.h; sourceTree = "<group>"; };
+		A96F4EC71E7D86D8000509B3 /* gravity_lexer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_lexer.c; sourceTree = "<group>"; };
+		A96F4EC81E7D86D8000509B3 /* gravity_lexer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_lexer.h; sourceTree = "<group>"; };
+		A96F4EC91E7D86D8000509B3 /* gravity_optimizer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_optimizer.c; sourceTree = "<group>"; };
+		A96F4ECA1E7D86D8000509B3 /* gravity_optimizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_optimizer.h; sourceTree = "<group>"; };
+		A96F4ECB1E7D86D8000509B3 /* gravity_parser.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_parser.c; sourceTree = "<group>"; };
+		A96F4ECC1E7D86D8000509B3 /* gravity_parser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_parser.h; sourceTree = "<group>"; };
+		A96F4ECD1E7D86D8000509B3 /* gravity_semacheck1.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_semacheck1.c; sourceTree = "<group>"; };
+		A96F4ECE1E7D86D8000509B3 /* gravity_semacheck1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_semacheck1.h; sourceTree = "<group>"; };
+		A96F4ECF1E7D86D8000509B3 /* gravity_semacheck2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_semacheck2.c; sourceTree = "<group>"; };
+		A96F4ED01E7D86D8000509B3 /* gravity_semacheck2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_semacheck2.h; sourceTree = "<group>"; };
+		A96F4ED11E7D86D8000509B3 /* gravity_symboltable.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_symboltable.c; sourceTree = "<group>"; };
+		A96F4ED21E7D86D8000509B3 /* gravity_symboltable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_symboltable.h; sourceTree = "<group>"; };
+		A96F4ED31E7D86D8000509B3 /* gravity_token.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_token.c; sourceTree = "<group>"; };
+		A96F4ED41E7D86D8000509B3 /* gravity_token.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_token.h; sourceTree = "<group>"; };
+		A96F4ED51E7D86D8000509B3 /* gravity_visitor.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_visitor.c; sourceTree = "<group>"; };
+		A96F4ED61E7D86D8000509B3 /* gravity_visitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_visitor.h; sourceTree = "<group>"; };
+		A96F4ED81E7D86D8000509B3 /* gravity_core.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_core.c; sourceTree = "<group>"; };
+		A96F4ED91E7D86D8000509B3 /* gravity_core.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_core.h; sourceTree = "<group>"; };
+		A96F4EDA1E7D86D8000509B3 /* gravity_vm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_vm.c; sourceTree = "<group>"; };
+		A96F4EDB1E7D86D8000509B3 /* gravity_vm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_vm.h; sourceTree = "<group>"; };
+		A96F4EDC1E7D86D8000509B3 /* gravity_vmmacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_vmmacros.h; sourceTree = "<group>"; };
+		A96F4EDE1E7D86D8000509B3 /* gravity_array.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_array.h; sourceTree = "<group>"; };
+		A96F4EDF1E7D86D8000509B3 /* gravity_delegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_delegate.h; sourceTree = "<group>"; };
+		A96F4EE01E7D86D8000509B3 /* gravity_hash.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_hash.c; sourceTree = "<group>"; };
+		A96F4EE11E7D86D8000509B3 /* gravity_hash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_hash.h; sourceTree = "<group>"; };
+		A96F4EE21E7D86D8000509B3 /* gravity_macros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_macros.h; sourceTree = "<group>"; };
+		A96F4EE31E7D86D8000509B3 /* gravity_memory.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_memory.c; sourceTree = "<group>"; };
+		A96F4EE41E7D86D8000509B3 /* gravity_memory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_memory.h; sourceTree = "<group>"; };
+		A96F4EE51E7D86D8000509B3 /* gravity_opcodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_opcodes.h; sourceTree = "<group>"; };
+		A96F4EE61E7D86D8000509B3 /* gravity_value.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_value.c; sourceTree = "<group>"; };
+		A96F4EE71E7D86D8000509B3 /* gravity_value.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_value.h; sourceTree = "<group>"; };
+		A96F4EE91E7D86D8000509B3 /* gravity_debug.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_debug.c; sourceTree = "<group>"; };
+		A96F4EEA1E7D86D8000509B3 /* gravity_debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_debug.h; sourceTree = "<group>"; };
+		A96F4EEB1E7D86D8000509B3 /* gravity_json.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_json.c; sourceTree = "<group>"; };
+		A96F4EEC1E7D86D8000509B3 /* gravity_json.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_json.h; sourceTree = "<group>"; };
+		A96F4EED1E7D86D8000509B3 /* gravity_utils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gravity_utils.c; sourceTree = "<group>"; };
+		A96F4EEE1E7D86D8000509B3 /* gravity_utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gravity_utils.h; sourceTree = "<group>"; };
+		A96F4F041E7D8706000509B3 /* BTFCodeEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTFCodeEditor.h; sourceTree = "<group>"; };
+		A96F4F051E7D8706000509B3 /* BTFCodeEditor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTFCodeEditor.m; sourceTree = "<group>"; };
+		A96F4F061E7D8706000509B3 /* BTFDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTFDelegate.h; sourceTree = "<group>"; };
+		A96F4F071E7D8706000509B3 /* BTFPopover.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTFPopover.h; sourceTree = "<group>"; };
+		A96F4F081E7D8706000509B3 /* BTFPopover.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTFPopover.m; sourceTree = "<group>"; };
+		A96F4F091E7D8706000509B3 /* BTFPopoverFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTFPopoverFrame.h; sourceTree = "<group>"; };
+		A96F4F0A1E7D8706000509B3 /* BTFPopoverFrame.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTFPopoverFrame.m; sourceTree = "<group>"; };
+		A96F4F0B1E7D8706000509B3 /* BTFPopoverWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTFPopoverWindow.h; sourceTree = "<group>"; };
+		A96F4F0C1E7D8706000509B3 /* BTFPopoverWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTFPopoverWindow.m; sourceTree = "<group>"; };
+		A96F4F0D1E7D8706000509B3 /* BTFRulerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTFRulerView.h; sourceTree = "<group>"; };
+		A96F4F0E1E7D8706000509B3 /* BTFRulerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTFRulerView.m; sourceTree = "<group>"; };
+		A96F4F0F1E7D8706000509B3 /* BTFTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTFTextView.h; sourceTree = "<group>"; };
+		A96F4F101E7D8706000509B3 /* BTFTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTFTextView.m; sourceTree = "<group>"; };
+		A96F4F111E7D8706000509B3 /* BTFThemeKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTFThemeKeys.h; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		A96F4EA31E7D8638000509B3 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		A96F4E9D1E7D8638000509B3 = {
+			isa = PBXGroup;
+			children = (
+				A96F4EA81E7D8638000509B3 /* Main */,
+				A96F4F031E7D8706000509B3 /* Sources */,
+				A96F4EBC1E7D86BA000509B3 /* Gravity */,
+				A96F4EA71E7D8638000509B3 /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		A96F4EA71E7D8638000509B3 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				A96F4EA61E7D8638000509B3 /* Butterfly.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		A96F4EA81E7D8638000509B3 /* Main */ = {
+			isa = PBXGroup;
+			children = (
+				A96F4EA91E7D8638000509B3 /* AppDelegate.h */,
+				A96F4EAA1E7D8638000509B3 /* AppDelegate.m */,
+				A96F4EB11E7D8638000509B3 /* MainMenu.xib */,
+				A96F4EAC1E7D8638000509B3 /* Supporting Files */,
+			);
+			name = Main;
+			path = Butterfly;
+			sourceTree = "<group>";
+		};
+		A96F4EAC1E7D8638000509B3 /* Supporting Files */ = {
+			isa = PBXGroup;
+			children = (
+				A96F4EBA1E7D8696000509B3 /* Themes */,
+				A96F4EAF1E7D8638000509B3 /* Assets.xcassets */,
+				A96F4EB41E7D8638000509B3 /* Info.plist */,
+				A96F4EAD1E7D8638000509B3 /* main.m */,
+			);
+			name = "Supporting Files";
+			sourceTree = "<group>";
+		};
+		A96F4EBC1E7D86BA000509B3 /* Gravity */ = {
+			isa = PBXGroup;
+			children = (
+				A96F4EBD1E7D86D8000509B3 /* compiler */,
+				A96F4ED71E7D86D8000509B3 /* runtime */,
+				A96F4EDD1E7D86D8000509B3 /* shared */,
+				A96F4EE81E7D86D8000509B3 /* utils */,
+			);
+			name = Gravity;
+			sourceTree = "<group>";
+		};
+		A96F4EBD1E7D86D8000509B3 /* compiler */ = {
+			isa = PBXGroup;
+			children = (
+				A96F4EBE1E7D86D8000509B3 /* debug_macros.h */,
+				A96F4EBF1E7D86D8000509B3 /* gravity_ast.c */,
+				A96F4EC01E7D86D8000509B3 /* gravity_ast.h */,
+				A96F4EC11E7D86D8000509B3 /* gravity_codegen.c */,
+				A96F4EC21E7D86D8000509B3 /* gravity_codegen.h */,
+				A96F4EC31E7D86D8000509B3 /* gravity_compiler.c */,
+				A96F4EC41E7D86D8000509B3 /* gravity_compiler.h */,
+				A96F4EC51E7D86D8000509B3 /* gravity_ircode.c */,
+				A96F4EC61E7D86D8000509B3 /* gravity_ircode.h */,
+				A96F4EC71E7D86D8000509B3 /* gravity_lexer.c */,
+				A96F4EC81E7D86D8000509B3 /* gravity_lexer.h */,
+				A96F4EC91E7D86D8000509B3 /* gravity_optimizer.c */,
+				A96F4ECA1E7D86D8000509B3 /* gravity_optimizer.h */,
+				A96F4ECB1E7D86D8000509B3 /* gravity_parser.c */,
+				A96F4ECC1E7D86D8000509B3 /* gravity_parser.h */,
+				A96F4ECD1E7D86D8000509B3 /* gravity_semacheck1.c */,
+				A96F4ECE1E7D86D8000509B3 /* gravity_semacheck1.h */,
+				A96F4ECF1E7D86D8000509B3 /* gravity_semacheck2.c */,
+				A96F4ED01E7D86D8000509B3 /* gravity_semacheck2.h */,
+				A96F4ED11E7D86D8000509B3 /* gravity_symboltable.c */,
+				A96F4ED21E7D86D8000509B3 /* gravity_symboltable.h */,
+				A96F4ED31E7D86D8000509B3 /* gravity_token.c */,
+				A96F4ED41E7D86D8000509B3 /* gravity_token.h */,
+				A96F4ED51E7D86D8000509B3 /* gravity_visitor.c */,
+				A96F4ED61E7D86D8000509B3 /* gravity_visitor.h */,
+			);
+			name = compiler;
+			path = ../../src/compiler;
+			sourceTree = "<group>";
+		};
+		A96F4ED71E7D86D8000509B3 /* runtime */ = {
+			isa = PBXGroup;
+			children = (
+				A96F4ED81E7D86D8000509B3 /* gravity_core.c */,
+				A96F4ED91E7D86D8000509B3 /* gravity_core.h */,
+				A96F4EDA1E7D86D8000509B3 /* gravity_vm.c */,
+				A96F4EDB1E7D86D8000509B3 /* gravity_vm.h */,
+				A96F4EDC1E7D86D8000509B3 /* gravity_vmmacros.h */,
+			);
+			name = runtime;
+			path = ../../src/runtime;
+			sourceTree = "<group>";
+		};
+		A96F4EDD1E7D86D8000509B3 /* shared */ = {
+			isa = PBXGroup;
+			children = (
+				A96F4EDE1E7D86D8000509B3 /* gravity_array.h */,
+				A96F4EDF1E7D86D8000509B3 /* gravity_delegate.h */,
+				A96F4EE01E7D86D8000509B3 /* gravity_hash.c */,
+				A96F4EE11E7D86D8000509B3 /* gravity_hash.h */,
+				A96F4EE21E7D86D8000509B3 /* gravity_macros.h */,
+				A96F4EE31E7D86D8000509B3 /* gravity_memory.c */,
+				A96F4EE41E7D86D8000509B3 /* gravity_memory.h */,
+				A96F4EE51E7D86D8000509B3 /* gravity_opcodes.h */,
+				A96F4EE61E7D86D8000509B3 /* gravity_value.c */,
+				A96F4EE71E7D86D8000509B3 /* gravity_value.h */,
+			);
+			name = shared;
+			path = ../../src/shared;
+			sourceTree = "<group>";
+		};
+		A96F4EE81E7D86D8000509B3 /* utils */ = {
+			isa = PBXGroup;
+			children = (
+				A96F4EE91E7D86D8000509B3 /* gravity_debug.c */,
+				A96F4EEA1E7D86D8000509B3 /* gravity_debug.h */,
+				A96F4EEB1E7D86D8000509B3 /* gravity_json.c */,
+				A96F4EEC1E7D86D8000509B3 /* gravity_json.h */,
+				A96F4EED1E7D86D8000509B3 /* gravity_utils.c */,
+				A96F4EEE1E7D86D8000509B3 /* gravity_utils.h */,
+			);
+			name = utils;
+			path = ../../src/utils;
+			sourceTree = "<group>";
+		};
+		A96F4F031E7D8706000509B3 /* Sources */ = {
+			isa = PBXGroup;
+			children = (
+				A96F4F041E7D8706000509B3 /* BTFCodeEditor.h */,
+				A96F4F051E7D8706000509B3 /* BTFCodeEditor.m */,
+				A96F4F061E7D8706000509B3 /* BTFDelegate.h */,
+				A96F4F071E7D8706000509B3 /* BTFPopover.h */,
+				A96F4F081E7D8706000509B3 /* BTFPopover.m */,
+				A96F4F091E7D8706000509B3 /* BTFPopoverFrame.h */,
+				A96F4F0A1E7D8706000509B3 /* BTFPopoverFrame.m */,
+				A96F4F0B1E7D8706000509B3 /* BTFPopoverWindow.h */,
+				A96F4F0C1E7D8706000509B3 /* BTFPopoverWindow.m */,
+				A96F4F0D1E7D8706000509B3 /* BTFRulerView.h */,
+				A96F4F0E1E7D8706000509B3 /* BTFRulerView.m */,
+				A96F4F0F1E7D8706000509B3 /* BTFTextView.h */,
+				A96F4F101E7D8706000509B3 /* BTFTextView.m */,
+				A96F4F111E7D8706000509B3 /* BTFThemeKeys.h */,
+			);
+			name = Sources;
+			path = Butterfly/Sources;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		A96F4EA51E7D8638000509B3 /* Butterfly */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = A96F4EB71E7D8638000509B3 /* Build configuration list for PBXNativeTarget "Butterfly" */;
+			buildPhases = (
+				A96F4EA21E7D8638000509B3 /* Sources */,
+				A96F4EA31E7D8638000509B3 /* Frameworks */,
+				A96F4EA41E7D8638000509B3 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = Butterfly;
+			productName = Butterfly;
+			productReference = A96F4EA61E7D8638000509B3 /* Butterfly.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		A96F4E9E1E7D8638000509B3 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 0820;
+				ORGANIZATIONNAME = Creolabs;
+				TargetAttributes = {
+					A96F4EA51E7D8638000509B3 = {
+						CreatedOnToolsVersion = 8.2.1;
+						ProvisioningStyle = Automatic;
+					};
+				};
+			};
+			buildConfigurationList = A96F4EA11E7D8638000509B3 /* Build configuration list for PBXProject "Butterfly" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = English;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = A96F4E9D1E7D8638000509B3;
+			productRefGroup = A96F4EA71E7D8638000509B3 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				A96F4EA51E7D8638000509B3 /* Butterfly */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		A96F4EA41E7D8638000509B3 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				A96F4EBB1E7D8696000509B3 /* Themes in Resources */,
+				A96F4EB01E7D8638000509B3 /* Assets.xcassets in Resources */,
+				A96F4EB31E7D8638000509B3 /* MainMenu.xib in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		A96F4EA21E7D8638000509B3 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				A96F4EFF1E7D86D8000509B3 /* gravity_value.c in Sources */,
+				A96F4EF01E7D86D8000509B3 /* gravity_codegen.c in Sources */,
+				A96F4EFE1E7D86D8000509B3 /* gravity_memory.c in Sources */,
+				A96F4EFB1E7D86D8000509B3 /* gravity_core.c in Sources */,
+				A96F4F171E7D8706000509B3 /* BTFTextView.m in Sources */,
+				A96F4EEF1E7D86D8000509B3 /* gravity_ast.c in Sources */,
+				A96F4EF21E7D86D8000509B3 /* gravity_ircode.c in Sources */,
+				A96F4F131E7D8706000509B3 /* BTFPopover.m in Sources */,
+				A96F4EF61E7D86D8000509B3 /* gravity_semacheck1.c in Sources */,
+				A96F4EFC1E7D86D8000509B3 /* gravity_vm.c in Sources */,
+				A96F4F001E7D86D8000509B3 /* gravity_debug.c in Sources */,
+				A96F4EAE1E7D8638000509B3 /* main.m in Sources */,
+				A96F4F161E7D8706000509B3 /* BTFRulerView.m in Sources */,
+				A96F4F021E7D86D8000509B3 /* gravity_utils.c in Sources */,
+				A96F4F121E7D8706000509B3 /* BTFCodeEditor.m in Sources */,
+				A96F4F011E7D86D8000509B3 /* gravity_json.c in Sources */,
+				A96F4EF41E7D86D8000509B3 /* gravity_optimizer.c in Sources */,
+				A96F4EF31E7D86D8000509B3 /* gravity_lexer.c in Sources */,
+				A96F4EF91E7D86D8000509B3 /* gravity_token.c in Sources */,
+				A96F4EFD1E7D86D8000509B3 /* gravity_hash.c in Sources */,
+				A96F4EF71E7D86D8000509B3 /* gravity_semacheck2.c in Sources */,
+				A96F4EFA1E7D86D8000509B3 /* gravity_visitor.c in Sources */,
+				A96F4F151E7D8706000509B3 /* BTFPopoverWindow.m in Sources */,
+				A96F4EAB1E7D8638000509B3 /* AppDelegate.m in Sources */,
+				A96F4EF81E7D86D8000509B3 /* gravity_symboltable.c in Sources */,
+				A96F4EF51E7D86D8000509B3 /* gravity_parser.c in Sources */,
+				A96F4EF11E7D86D8000509B3 /* gravity_compiler.c in Sources */,
+				A96F4F141E7D8706000509B3 /* BTFPopoverFrame.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+		A96F4EB11E7D8638000509B3 /* MainMenu.xib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				A96F4EB21E7D8638000509B3 /* Base */,
+			);
+			name = MainMenu.xib;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		A96F4EB51E7D8638000509B3 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.12;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+			};
+			name = Debug;
+		};
+		A96F4EB61E7D8638000509B3 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.12;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+			};
+			name = Release;
+		};
+		A96F4EB81E7D8638000509B3 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Butterfly/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.creolabs.Butterfly;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		A96F4EB91E7D8638000509B3 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Butterfly/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.creolabs.Butterfly;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		A96F4EA11E7D8638000509B3 /* Build configuration list for PBXProject "Butterfly" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				A96F4EB51E7D8638000509B3 /* Debug */,
+				A96F4EB61E7D8638000509B3 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		A96F4EB71E7D8638000509B3 /* Build configuration list for PBXNativeTarget "Butterfly" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				A96F4EB81E7D8638000509B3 /* Debug */,
+				A96F4EB91E7D8638000509B3 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = A96F4E9E1E7D8638000509B3 /* Project object */;
+}

+ 15 - 0
projects/Butterfly/Butterfly/AppDelegate.h

@@ -0,0 +1,15 @@
+//
+//  AppDelegate.h
+//  Butterfly
+//
+//  Created by Marco Bambini on 18/03/2017.
+//  Copyright © 2017 Creolabs. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface AppDelegate : NSObject <NSApplicationDelegate>
+
+
+@end
+

+ 103 - 0
projects/Butterfly/Butterfly/AppDelegate.m

@@ -0,0 +1,103 @@
+//
+//  AppDelegate.m
+//  Butterfly
+//
+//  Created by Marco Bambini on 18/03/2017.
+//  Copyright © 2017 Creolabs. All rights reserved.
+//
+
+#import "AppDelegate.h"
+#import "BTFThemeKeys.h"
+#import "BTFCodeEditor.h"
+#import "gravity_token.h"
+
+@interface AppDelegate () {
+	BTFCodeEditor                  *codeEditor;
+}
+
+@property (weak) IBOutlet NSWindow *window;
+@property (weak) IBOutlet NSView   *codeEditorView;
+@end
+
+@implementation AppDelegate
+
+// MARK: Utils functions
+
+static NSColor *colorFromHexadecimalValue(NSString *hex) {
+	if ([hex hasPrefix:@"#"]) {
+		hex = [hex substringWithRange:NSMakeRange(1, [hex length] - 1)];
+	}
+	
+	unsigned int colorCode = 0;
+	
+	// if string has only 6 characters append FF, opaque color
+	if (hex.length == 6)
+		hex = [NSString stringWithFormat:@"%@FF", hex];
+	
+	if (hex) {
+		NSScanner *scanner = [NSScanner scannerWithString:hex];
+		if (![scanner scanHexInt:&colorCode] || ! [scanner isAtEnd]) return nil;
+	}
+	
+	return [NSColor colorWithCalibratedRed:((colorCode>>24)&0xFF)/255.0 green:((colorCode>>16)&0xFF)/255.0 blue:((colorCode>>8)&0xFF)/255.0 alpha:((colorCode)&0xFF)/255.0];
+}
+
+static NSDictionary *deserializeTheme(NSDictionary *theme) {
+	NSMutableDictionary	*d = [[NSMutableDictionary alloc] initWithCapacity:theme.count];
+	
+	// ADJUST FONT
+	CGFloat		fontSize = 13;
+	NSString	*fontName = @"Menlo-Regular";
+	NSString	*boldFontName = @"Menlo-Bold";
+	NSFont		*fontRegular = nil;
+	NSFont		*fontBold = nil;
+	
+	if (theme[BTFKEY_FONT_SIZE]) fontSize = ((NSNumber*)theme[BTFKEY_FONT_SIZE]).floatValue;
+	if (theme[BTFKEY_FONT]) fontName = theme[BTFKEY_FONT];
+	if (theme[BTFKEY_BOLD_FONT]) boldFontName = theme[BTFKEY_BOLD_FONT];
+	fontRegular = [NSFont fontWithName:fontName size:fontSize];
+	fontBold = [NSFont fontWithName:boldFontName size:fontSize];
+	d[BTFKEY_FONT] = fontRegular;
+	d[BTFKEY_BOLD_FONT] = fontBold;
+	
+	// ADJUST all other values
+	for (NSString *key in theme.allKeys) {
+		if ([key hasSuffix:@"COLOR"]) d[key] = colorFromHexadecimalValue(theme[key]);
+		else if ([key hasSuffix:@"IMAGE"]) d[key] = [NSImage imageNamed:(theme[key])];
+		else if ([key containsString:@"FONT"]) continue;
+		else d[key] = theme[key];
+	}
+	
+	return d;
+}
+
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
+	codeEditor = [[BTFCodeEditor alloc] initWithFrame:_codeEditorView.frame inView:_window];
+	
+	// apply theme (available themes are: Gauss, Laplace, Fourier and Riemann)
+	NSURL *themeURL = [[NSBundle mainBundle] URLForResource:@"Gauss" withExtension:@"theme" subdirectory:@"Themes"];
+	NSData *data = [NSData dataWithContentsOfURL:themeURL];
+	NSDictionary *theme = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
+	// it is caller responsability to propertly deserize theme
+	[codeEditor applyTheme:deserializeTheme(theme)];
+	
+	// add reserved keywords to autocompletion engine
+	// set reserved keywords
+	uint32_t i, idx_end;
+	token_keywords_indexes(&i, &idx_end);
+	for (; i<=idx_end; ++i) {
+		const char *keyword = token_name(i);
+		if (keyword) [codeEditor addAutocompleteEntity:[NSString stringWithUTF8String:keyword] type:0];
+	}
+	
+	// UTF-8 EXAMPLE
+	codeEditor.string = @" // UTF-8 4 bytes\nvar a = \" 😀 a b\";\n\n// UTF-8 3 bytes\nvar b = \" 肉 a b\";\n\n// UTF-8 2 bytes\nvar c = \" © a b\";\n\n// UTF-8 1 byte\nvar d = \" a a b\";";
+}
+
+
+- (void)applicationWillTerminate:(NSNotification *)aNotification {
+	// Insert code here to tear down your application
+}
+
+
+@end

+ 58 - 0
projects/Butterfly/Butterfly/Assets.xcassets/AppIcon.appiconset/Contents.json

@@ -0,0 +1,58 @@
+{
+  "images" : [
+    {
+      "idiom" : "mac",
+      "size" : "16x16",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "mac",
+      "size" : "16x16",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "mac",
+      "size" : "32x32",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "mac",
+      "size" : "32x32",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "mac",
+      "size" : "128x128",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "mac",
+      "size" : "128x128",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "mac",
+      "size" : "256x256",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "mac",
+      "size" : "256x256",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "mac",
+      "size" : "512x512",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "mac",
+      "size" : "512x512",
+      "scale" : "2x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

+ 703 - 0
projects/Butterfly/Butterfly/Base.lproj/MainMenu.xib

@@ -0,0 +1,703 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+    <dependencies>
+        <deployment identifier="macosx"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
+            <connections>
+                <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
+            </connections>
+        </customObject>
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+        <customObject id="Voe-Tx-rLC" customClass="AppDelegate">
+            <connections>
+                <outlet property="codeEditorView" destination="SvY-0R-ahY" id="YV8-yM-HWN"/>
+                <outlet property="window" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
+            </connections>
+        </customObject>
+        <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
+        <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
+            <items>
+                <menuItem title="Butterfly" id="1Xt-HY-uBw">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Butterfly" systemMenu="apple" id="uQy-DD-JDr">
+                        <items>
+                            <menuItem title="About Butterfly" id="5kV-Vb-QxS">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
+                            <menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
+                            <menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
+                            <menuItem title="Services" id="NMo-om-nkz">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
+                            <menuItem title="Hide Butterfly" keyEquivalent="h" id="Olw-nP-bQN">
+                                <connections>
+                                    <action selector="hide:" target="-1" id="PnN-Uc-m68"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
+                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                <connections>
+                                    <action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Show All" id="Kd2-mp-pUS">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
+                            <menuItem title="Quit Butterfly" keyEquivalent="q" id="4sb-4s-VLi">
+                                <connections>
+                                    <action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="File" id="dMs-cI-mzQ">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="File" id="bib-Uj-vzu">
+                        <items>
+                            <menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
+                                <connections>
+                                    <action selector="newDocument:" target="-1" id="4Si-XN-c54"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
+                                <connections>
+                                    <action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Open Recent" id="tXI-mr-wws">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
+                                    <items>
+                                        <menuItem title="Clear Menu" id="vNY-rz-j42">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="clearRecentDocuments:" target="-1" id="Daa-9d-B3U"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
+                            <menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
+                                <connections>
+                                    <action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
+                                <connections>
+                                    <action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
+                                <connections>
+                                    <action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Revert to Saved" keyEquivalent="r" id="KaW-ft-85H">
+                                <connections>
+                                    <action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
+                            <menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
+                                <modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
+                                <connections>
+                                    <action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
+                                <connections>
+                                    <action selector="print:" target="-1" id="qaZ-4w-aoO"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="Edit" id="5QF-Oa-p0T">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Edit" id="W48-6f-4Dl">
+                        <items>
+                            <menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
+                                <connections>
+                                    <action selector="undo:" target="-1" id="M6e-cu-g7V"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
+                                <connections>
+                                    <action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
+                            <menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
+                                <connections>
+                                    <action selector="cut:" target="-1" id="YJe-68-I9s"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
+                                <connections>
+                                    <action selector="copy:" target="-1" id="G1f-GL-Joy"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
+                                <connections>
+                                    <action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
+                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                <connections>
+                                    <action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Delete" id="pa3-QI-u2k">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
+                                <connections>
+                                    <action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
+                            <menuItem title="Find" id="4EN-yA-p0u">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Find" id="1b7-l0-nxx">
+                                    <items>
+                                        <menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
+                                            <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
+                                            <connections>
+                                                <action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
+                                    <items>
+                                        <menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
+                                            <connections>
+                                                <action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
+                                            <connections>
+                                                <action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
+                                        <menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Substitutions" id="9ic-FL-obx">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
+                                    <items>
+                                        <menuItem title="Show Substitutions" id="z6F-FW-3nz">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
+                                        <menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Quotes" id="hQb-2v-fYv">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Dashes" id="rgM-f4-ycn">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Links" id="cwL-P1-jid">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Data Detectors" id="tRr-pd-1PS">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Text Replacement" id="HFQ-gK-NFA">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Transformations" id="2oI-Rn-ZJC">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Transformations" id="c8a-y6-VQd">
+                                    <items>
+                                        <menuItem title="Make Upper Case" id="vmV-6d-7jI">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Make Lower Case" id="d9M-CD-aMd">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Capitalize" id="UEZ-Bs-lqG">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Speech" id="xrE-MZ-jX0">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Speech" id="3rS-ZA-NoH">
+                                    <items>
+                                        <menuItem title="Start Speaking" id="Ynk-f8-cLZ">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Stop Speaking" id="Oyz-dy-DGm">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="Format" id="jxT-CU-nIS">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Format" id="GEO-Iw-cKr">
+                        <items>
+                            <menuItem title="Font" id="Gi5-1S-RQB">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Font" systemMenu="font" id="aXa-aM-Jaq">
+                                    <items>
+                                        <menuItem title="Show Fonts" keyEquivalent="t" id="Q5e-8K-NDq">
+                                            <connections>
+                                                <action selector="orderFrontFontPanel:" target="YLy-65-1bz" id="WHr-nq-2xA"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Bold" tag="2" keyEquivalent="b" id="GB9-OM-e27">
+                                            <connections>
+                                                <action selector="addFontTrait:" target="YLy-65-1bz" id="hqk-hr-sYV"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Italic" tag="1" keyEquivalent="i" id="Vjx-xi-njq">
+                                            <connections>
+                                                <action selector="addFontTrait:" target="YLy-65-1bz" id="IHV-OB-c03"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Underline" keyEquivalent="u" id="WRG-CD-K1S">
+                                            <connections>
+                                                <action selector="underline:" target="-1" id="FYS-2b-JAY"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="5gT-KC-WSO"/>
+                                        <menuItem title="Bigger" tag="3" keyEquivalent="+" id="Ptp-SP-VEL">
+                                            <connections>
+                                                <action selector="modifyFont:" target="YLy-65-1bz" id="Uc7-di-UnL"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smaller" tag="4" keyEquivalent="-" id="i1d-Er-qST">
+                                            <connections>
+                                                <action selector="modifyFont:" target="YLy-65-1bz" id="HcX-Lf-eNd"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="kx3-Dk-x3B"/>
+                                        <menuItem title="Kern" id="jBQ-r6-VK2">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <menu key="submenu" title="Kern" id="tlD-Oa-oAM">
+                                                <items>
+                                                    <menuItem title="Use Default" id="GUa-eO-cwY">
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="useStandardKerning:" target="-1" id="6dk-9l-Ckg"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                    <menuItem title="Use None" id="cDB-IK-hbR">
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="turnOffKerning:" target="-1" id="U8a-gz-Maa"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                    <menuItem title="Tighten" id="46P-cB-AYj">
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="tightenKerning:" target="-1" id="hr7-Nz-8ro"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                    <menuItem title="Loosen" id="ogc-rX-tC1">
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="loosenKerning:" target="-1" id="8i4-f9-FKE"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                </items>
+                                            </menu>
+                                        </menuItem>
+                                        <menuItem title="Ligatures" id="o6e-r0-MWq">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <menu key="submenu" title="Ligatures" id="w0m-vy-SC9">
+                                                <items>
+                                                    <menuItem title="Use Default" id="agt-UL-0e3">
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="useStandardLigatures:" target="-1" id="7uR-wd-Dx6"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                    <menuItem title="Use None" id="J7y-lM-qPV">
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="turnOffLigatures:" target="-1" id="iX2-gA-Ilz"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                    <menuItem title="Use All" id="xQD-1f-W4t">
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="useAllLigatures:" target="-1" id="KcB-kA-TuK"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                </items>
+                                            </menu>
+                                        </menuItem>
+                                        <menuItem title="Baseline" id="OaQ-X3-Vso">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <menu key="submenu" title="Baseline" id="ijk-EB-dga">
+                                                <items>
+                                                    <menuItem title="Use Default" id="3Om-Ey-2VK">
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="unscript:" target="-1" id="0vZ-95-Ywn"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                    <menuItem title="Superscript" id="Rqc-34-cIF">
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="superscript:" target="-1" id="3qV-fo-wpU"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                    <menuItem title="Subscript" id="I0S-gh-46l">
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="subscript:" target="-1" id="Q6W-4W-IGz"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                    <menuItem title="Raise" id="2h7-ER-AoG">
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="raiseBaseline:" target="-1" id="4sk-31-7Q9"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                    <menuItem title="Lower" id="1tx-W0-xDw">
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="lowerBaseline:" target="-1" id="OF1-bc-KW4"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                </items>
+                                            </menu>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="Ndw-q3-faq"/>
+                                        <menuItem title="Show Colors" keyEquivalent="C" id="bgn-CT-cEk">
+                                            <connections>
+                                                <action selector="orderFrontColorPanel:" target="-1" id="mSX-Xz-DV3"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="iMs-zA-UFJ"/>
+                                        <menuItem title="Copy Style" keyEquivalent="c" id="5Vv-lz-BsD">
+                                            <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                            <connections>
+                                                <action selector="copyFont:" target="-1" id="GJO-xA-L4q"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Paste Style" keyEquivalent="v" id="vKC-jM-MkH">
+                                            <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                            <connections>
+                                                <action selector="pasteFont:" target="-1" id="JfD-CL-leO"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Text" id="Fal-I4-PZk">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Text" id="d9c-me-L2H">
+                                    <items>
+                                        <menuItem title="Align Left" keyEquivalent="{" id="ZM1-6Q-yy1">
+                                            <connections>
+                                                <action selector="alignLeft:" target="-1" id="zUv-R1-uAa"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Center" keyEquivalent="|" id="VIY-Ag-zcb">
+                                            <connections>
+                                                <action selector="alignCenter:" target="-1" id="spX-mk-kcS"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Justify" id="J5U-5w-g23">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="alignJustified:" target="-1" id="ljL-7U-jND"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Align Right" keyEquivalent="}" id="wb2-vD-lq4">
+                                            <connections>
+                                                <action selector="alignRight:" target="-1" id="r48-bG-YeY"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="4s2-GY-VfK"/>
+                                        <menuItem title="Writing Direction" id="H1b-Si-o9J">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <menu key="submenu" title="Writing Direction" id="8mr-sm-Yjd">
+                                                <items>
+                                                    <menuItem title="Paragraph" enabled="NO" id="ZvO-Gk-QUH">
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                    </menuItem>
+                                                    <menuItem id="YGs-j5-SAR">
+                                                        <string key="title">	Default</string>
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="makeBaseWritingDirectionNatural:" target="-1" id="qtV-5e-UBP"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                    <menuItem id="Lbh-J2-qVU">
+                                                        <string key="title">	Left to Right</string>
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="makeBaseWritingDirectionLeftToRight:" target="-1" id="S0X-9S-QSf"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                    <menuItem id="jFq-tB-4Kx">
+                                                        <string key="title">	Right to Left</string>
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="makeBaseWritingDirectionRightToLeft:" target="-1" id="5fk-qB-AqJ"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                    <menuItem isSeparatorItem="YES" id="swp-gr-a21"/>
+                                                    <menuItem title="Selection" enabled="NO" id="cqv-fj-IhA">
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                    </menuItem>
+                                                    <menuItem id="Nop-cj-93Q">
+                                                        <string key="title">	Default</string>
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="makeTextWritingDirectionNatural:" target="-1" id="lPI-Se-ZHp"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                    <menuItem id="BgM-ve-c93">
+                                                        <string key="title">	Left to Right</string>
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="makeTextWritingDirectionLeftToRight:" target="-1" id="caW-Bv-w94"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                    <menuItem id="RB4-Sm-HuC">
+                                                        <string key="title">	Right to Left</string>
+                                                        <modifierMask key="keyEquivalentModifierMask"/>
+                                                        <connections>
+                                                            <action selector="makeTextWritingDirectionRightToLeft:" target="-1" id="EXD-6r-ZUu"/>
+                                                        </connections>
+                                                    </menuItem>
+                                                </items>
+                                            </menu>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="fKy-g9-1gm"/>
+                                        <menuItem title="Show Ruler" id="vLm-3I-IUL">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleRuler:" target="-1" id="FOx-HJ-KwY"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Copy Ruler" keyEquivalent="c" id="MkV-Pr-PK5">
+                                            <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+                                            <connections>
+                                                <action selector="copyRuler:" target="-1" id="71i-fW-3W2"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Paste Ruler" keyEquivalent="v" id="LVM-kO-fVI">
+                                            <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+                                            <connections>
+                                                <action selector="pasteRuler:" target="-1" id="cSh-wd-qM2"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="View" id="H8h-7b-M4v">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="View" id="HyV-fh-RgO">
+                        <items>
+                            <menuItem title="Show Toolbar" keyEquivalent="t" id="snW-S8-Cw5">
+                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                <connections>
+                                    <action selector="toggleToolbarShown:" target="-1" id="BXY-wc-z0C"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Customize Toolbar…" id="1UK-8n-QPP">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="runToolbarCustomizationPalette:" target="-1" id="pQI-g3-MTW"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="hB3-LF-h0Y"/>
+                            <menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
+                                <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+                                <connections>
+                                    <action selector="toggleSourceList:" target="-1" id="iwa-gc-5KM"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
+                                <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+                                <connections>
+                                    <action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="Window" id="aUF-d1-5bR">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
+                        <items>
+                            <menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
+                                <connections>
+                                    <action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Zoom" id="R4o-n2-Eq4">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
+                            <menuItem title="Bring All to Front" id="LE2-aR-0XJ">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="Help" id="wpr-3q-Mcd">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
+                        <items>
+                            <menuItem title="Butterfly Help" keyEquivalent="?" id="FKE-Sm-Kum">
+                                <connections>
+                                    <action selector="showHelp:" target="-1" id="y7X-2Q-9no"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+            </items>
+        </menu>
+        <window title="Butterfly" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g">
+            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
+            <windowPositionMask key="initialPositionMask" topStrut="YES" bottomStrut="YES"/>
+            <rect key="contentRect" x="476" y="359" width="580" height="420"/>
+            <rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
+            <value key="minSize" type="size" width="400" height="320"/>
+            <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
+                <rect key="frame" x="0.0" y="0.0" width="580" height="420"/>
+                <autoresizingMask key="autoresizingMask"/>
+                <subviews>
+                    <customView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="SvY-0R-ahY">
+                        <rect key="frame" x="0.0" y="0.0" width="580" height="420"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                    </customView>
+                </subviews>
+            </view>
+            <point key="canvasLocation" x="296.5" y="-79.5"/>
+        </window>
+    </objects>
+</document>

+ 32 - 0
projects/Butterfly/Butterfly/Info.plist

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIconFile</key>
+	<string></string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>LSMinimumSystemVersion</key>
+	<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>Copyright © 2017 Creolabs. All rights reserved.</string>
+	<key>NSMainNibFile</key>
+	<string>MainMenu</string>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+</dict>
+</plist>

+ 33 - 0
projects/Butterfly/Butterfly/Sources/BTFCodeEditor.h

@@ -0,0 +1,33 @@
+//
+//  BTFCodeEditor.h
+//  Butterfly
+//
+//  Created by Marco Bambini on 15/07/15.
+//  Copyright (c) 2015 Marco Bambini. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "BTFDelegate.h"
+
+@interface BTFCodeEditor : NSObject
+
+@property (nonatomic, weak) id<BTFCodeEditorDelegate>	delegate;
+@property (nonatomic)		NSString					*string;
+@property (nonatomic)		BOOL						editable;
+@property (readonly)		CGFloat						ruleThickness;
+@property (nonatomic)		BOOL						changed;
+
+- (instancetype) initWithFrame:(NSRect)frame inView:(id)aView;
+- (void) applyTheme:(NSDictionary *)theme;
+- (void) setFocus;
+
+- (void) resetAutocompleteEntities;
+- (void) addAutocompleteEntity:(NSString *)key type:(int)type;
+- (void) removeAutocompleteEntity:(NSString *)key;
+
+- (NSUInteger) mousePointToIndex:(NSPoint)p;
+- (void) tempString:(NSString *)s insertAtIndex:(NSUInteger)idx;
+- (void) tempStringFinalize;
+- (void) clearUndoManager:(NSUndoManager *)undoManager;
+
+@end

+ 286 - 0
projects/Butterfly/Butterfly/Sources/BTFCodeEditor.m

@@ -0,0 +1,286 @@
+//
+//  BTFCodeEditor.m
+//  Butterfly
+//
+//  Created by Marco Bambini on 15/07/15.
+//  Copyright (c) 2015 Marco Bambini. All rights reserved.
+//
+
+#import "BTFCodeEditor.h"
+#import "BTFRulerView.h"
+#import "BTFThemeKeys.h"
+#import "BTFTextView.h"
+
+#define BTF_DEFAULT_RIGHT_PADDING		10.0f
+
+@interface BTFCodeEditor() <NSTextDelegate, NSTextViewDelegate, BTFCodeEditorDelegate> {
+    NSScrollView		*scrollView;
+    BTFRulerView		*rulerView;
+    BTFTextView			*textView;
+    __weak NSWindow		*mainWindow;
+	BOOL				collectChanges;
+	
+	// autocomplete
+	NSMutableArray		*keys;
+	
+	// temp
+	NSUInteger			tempLength;
+	NSUInteger			tempIndex;
+}
+@end
+
+@implementation BTFCodeEditor
+@synthesize changed;
+
+- (instancetype) initWithFrame:(NSRect)frame inView:(id)aView; {
+    self = [super init];
+    if (self) {
+		collectChanges = NO;
+		
+        // setup scrollview
+        scrollView = [[NSScrollView alloc] initWithFrame:frame];
+        NSSize contentSize = [scrollView contentSize];
+        [scrollView setBorderType:NSNoBorder];
+        [scrollView setHasVerticalScroller:YES];
+        [scrollView setHasHorizontalScroller:NO];
+        [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+		
+        // setup textview
+        textView = [[BTFTextView alloc] initWithFrame:NSMakeRect(0, 0, contentSize.width, contentSize.height)];
+		
+		// setup rulerview
+		rulerView = [[BTFRulerView alloc] initWithBTFTextView:textView];
+		scrollView.verticalRulerView = rulerView;
+		scrollView.hasVerticalRuler = true;
+		scrollView.rulersVisible = true;
+		
+		// set textview settings
+        [textView setMinSize:NSMakeSize(0.0, contentSize.height)];
+        [textView setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
+        [textView setVerticallyResizable:YES];
+        [textView setHorizontallyResizable:NO];
+        [textView setAutoresizingMask:NSViewWidthSizable];
+        [[textView textContainer] setContainerSize:NSMakeSize(contentSize.width-(rulerView.ruleThickness + BTF_DEFAULT_RIGHT_PADDING), FLT_MAX)];
+        [[textView textContainer] setWidthTracksTextView:YES];
+		textView.BTFDelegate = self;
+		textView.rulerView = rulerView;
+		
+        // connect scrollView to textView
+        [scrollView setDocumentView:textView];
+        
+        // setup window connections (if any)
+		if ([aView isKindOfClass:[NSView class]]) {
+			[(NSView *)aView addSubview:scrollView];
+		} else {
+			NSWindow *aWindow = (NSWindow *)aView;
+			mainWindow = aWindow;
+			[aWindow setContentView:scrollView];
+			[aWindow makeKeyAndOrderFront:nil];
+			[aWindow makeFirstResponder:textView];
+		}
+		
+		// setup autocompletion
+		keys = [NSMutableArray array];
+		
+		// setup observers
+		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameDidChange:)
+													 name:NSViewFrameDidChangeNotification object:textView];
+		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:)
+													 name:NSTextDidChangeNotification object:textView];
+    }
+    return self;
+}
+
+- (void)dealloc {
+	[[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)setString:(NSString *)aString {
+	collectChanges = NO;
+	textView.string = (aString) ? aString : @"";
+	[rulerView setNeedsDisplay:YES];
+	collectChanges = YES;
+}
+
+- (NSString *)string {
+	return textView.string;
+}
+
+- (void)setEditable:(BOOL)editable {
+	[textView setEditable:editable];
+}
+
+- (void)setFocus {
+	[textView.window makeFirstResponder:textView];
+}
+
+- (BOOL)editable {
+	return textView.editable;
+}
+
+- (void) clearUndoManager:(NSUndoManager *)undoManager {
+	[undoManager removeAllActionsWithTarget:textView.textStorage];
+}
+
+#pragma mark - Autocompletion -
+
+- (void) resetAutocompleteEntities {
+	[keys removeAllObjects];
+}
+
+- (void) addAutocompleteEntity:(NSString *)key type:(int)type {
+	[keys addObject:key];
+}
+
+- (void) removeAutocompleteEntity:(NSString *)key {
+	[keys removeObject:key];
+}
+
+#pragma mark - Temp -
+
+- (void) tempString:(NSString *)s insertAtIndex:(NSUInteger)idx {
+	[self tempStringRemove];
+	if (!s) {[textView setNeedsDisplay:YES]; return;}
+	
+	// insert temp string into textview
+	[textView setSelectedRange:NSMakeRange(idx,0)];
+	[textView insertText:s];
+	
+	// set temp string coordinates
+	tempIndex = idx;
+	tempLength = s.length;
+	[textView setNeedsDisplay:YES];
+}
+
+- (void) tempStringFinalize {
+	tempLength = 0;
+	[textView setNeedsDisplay:YES];
+}
+
+- (void) tempStringRemove {
+	if (tempLength == 0) return;
+	NSRange range = NSMakeRange(tempIndex, tempLength);
+	NSString *string = [textView textStorage].string;
+	
+	// sanity check range before apply the change
+	if (range.location != NSNotFound && range.location + range.length <= string.length) {
+		[[textView textStorage] deleteCharactersInRange:range];
+	}
+	tempLength = 0;
+}
+
+- (NSUInteger) mousePointToIndex:(NSPoint)p {
+	// p is in screen coordinates
+	NSRect rect = [textView.window convertRectFromScreen:NSMakeRect(p.x, p.y, 0, 0)];
+	NSPoint point = [textView convertPoint:rect.origin fromView:nil];
+	
+	// sanity check
+	if ((point.x < 0.0f) || (point.y < 0.0) ||
+		(floor(point.x) == 0) || (floor(point.y) == 0)) return NSUIntegerMax;
+	
+	// compute insertion index
+	NSUInteger insertionIndex = [textView characterIndexForInsertionAtPoint:point];
+	if (tempLength) {
+		if (insertionIndex >= tempIndex && insertionIndex <= tempIndex+tempLength) insertionIndex = tempIndex;
+		else if (insertionIndex > tempIndex+tempLength) insertionIndex -= tempLength;
+	}
+	
+	return insertionIndex;
+}
+
+#pragma mark - Observers -
+
+- (void) frameDidChange:(NSNotification *)notification {
+	NSSize contentSize = [scrollView contentSize];
+	[[textView textContainer] setContainerSize:NSMakeSize(contentSize.width-(rulerView.ruleThickness + BTF_DEFAULT_RIGHT_PADDING), FLT_MAX)];
+	[rulerView setNeedsDisplay:YES];
+}
+
+- (void) textDidChange:(NSNotification *)notification {
+	if (collectChanges) changed = YES;
+	[rulerView setNeedsDisplay:YES];
+}
+
+#pragma mark - Others -
+
+- (void) applyTheme:(NSDictionary *)theme {
+	// process keys that need to be always evaluated
+	if (theme[BTFKEY_RULER_BACKGROUND_COLOR])
+		rulerView.backgroundColor = theme[BTFKEY_RULER_BACKGROUND_COLOR];
+	if (theme[BTFKEY_RULER_TEXT_COLOR])
+		rulerView.textColor = theme[BTFKEY_RULER_TEXT_COLOR];
+	if (theme[BTFKEY_RULER_BORDER_COLOR])
+		rulerView.borderColor = theme[BTFKEY_RULER_BORDER_COLOR];
+	else
+		rulerView.borderColor = [NSColor clearColor];
+	if (theme[BTFKEY_RULER_BORDER_WIDTH])
+		rulerView.borderWidth = ((NSNumber*)theme[BTFKEY_RULER_BORDER_WIDTH]).floatValue;
+	else
+		rulerView.borderWidth = 0;
+	
+	[rulerView setNeedsDisplay:YES];
+	textView.theme = theme;
+}
+
+- (CGFloat)ruleThickness {
+	return rulerView.ruleThickness;
+}
+
+#pragma mark - BTFCodeEditorDelegate -
+
+- (NSArray *)textView:(NSTextView *)aTextView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange
+			 location:(NSUInteger)location indexOfSelectedItem:(NSInteger *)index {
+	
+	NSMutableArray *matches = [NSMutableArray array];
+	NSString *text = [textView string];
+	NSString *target = [text substringWithRange:charRange];
+	
+//	NSLog(@"%@", target);
+//	NSLog(@"%@", words);
+	
+//	// do not autocomplete if in the middle of the string
+//	if (location < charRange.location + charRange.length) return matches;
+//	
+//	// autocomplete only if next character is not alpha
+//	if (text.length > charRange.location + charRange.length) {
+//		NSRange nextRange = NSMakeRange(charRange.location+1, 1);
+//		unichar nextChar = [[text substringWithRange:nextRange] characterAtIndex:0];
+//		bool isAlpha = ((nextChar >= 97 && nextChar <=122) || (nextChar >= 65 && nextChar <=90));
+//		NSLog(@"%d %d (%@)", isAlpha, nextChar, [text substringWithRange:nextRange]);
+//		if (!isAlpha) return matches;
+//	}
+	
+	if (words.count == 0) {
+		// scan keywords ONLY if there are no subtargets
+		for (NSString *string in keys) {
+			// do not autocomplete equal strings
+			if ([target isEqualToString:string]) continue;
+			
+			// search matches
+			if ([string rangeOfString:target options:NSAnchoredSearch | NSCaseInsensitiveSearch
+								range:NSMakeRange(0, [string length])].location != NSNotFound) {
+				[matches addObject:string];
+			}
+		}
+	}
+	[matches sortUsingSelector:@selector(compare:)];
+	return matches;
+}
+
+- (NSImage *)textView:(NSTextView *)textView imageForCompletion:(NSString *)word {
+//	NSImage *image = self.imageDict[word];
+//	if (image) {
+//		return image;
+//	}
+//	return self.imageDict[@"Unknown"];
+	return nil;
+}
+
+- (void)textDidEndEditing:(NSNotification *)notification {
+	if (!self.delegate) return;
+	if ([self.delegate respondsToSelector:@selector(textDidEndEditing:)]) {
+		[self.delegate textDidEndEditing:self];
+	}
+}
+
+@end

+ 16 - 0
projects/Butterfly/Butterfly/Sources/BTFDelegate.h

@@ -0,0 +1,16 @@
+//
+//  BTFDelegate.h
+//  Butterfly
+//
+//  Created by Marco Bambini on 11/09/15.
+//  Copyright (c) 2015 Marco Bambini. All rights reserved.
+//
+
+@protocol BTFCodeEditorDelegate <NSObject>
+@optional
+- (void) mouseMovedAtRange:(NSRange)range;
+- (void) textDidEndEditing:(id)sender;
+- (NSArray *)textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange
+			 location:(NSUInteger)location indexOfSelectedItem:(NSInteger *)index;
+//- (NSImage *)textView:(NSTextView *)textView imageForCompletion:(NSString *)word;
+@end

+ 25 - 0
projects/Butterfly/Butterfly/Sources/BTFPopover.h

@@ -0,0 +1,25 @@
+//
+//  BTFPopover.h
+//  Butterfly
+//
+//  Created by Marco Bambini on 21/07/15.
+//  Copyright (c) 2015 Marco Bambini. All rights reserved.
+//
+
+//
+// Although the majority of the code was written by me, the inspiration and some code comes from:
+// NCRAutocompleteTextView by Daniel Weber on 9/28/14 - Copyright (c) 2014 Null Creature.
+// SFBPopover - Copyright (C) 2011, 2012, 2013, 2014, 2015 Stephen F. Booth <[email protected]>
+//
+
+#import <Cocoa/Cocoa.h>
+#import	"BTFPopoverWindow.h"
+
+@interface BTFPopover : NSResponder
+
+- (id) initWithContentView:(NSView *)contentView;
+- (void) displayPopoverInWindow:(NSWindow *)window atPoint:(NSPoint)point;
+- (void) closePopover:(id)sender;
+- (BTFPopoverWindow *) window;
+- (BOOL) isVisible;
+@end

+ 95 - 0
projects/Butterfly/Butterfly/Sources/BTFPopover.m

@@ -0,0 +1,95 @@
+//
+//  BTFPopover.m
+//  Butterfly
+//
+//  Created by Marco Bambini on 21/07/15.
+//  Copyright (c) 2015 Marco Bambini. All rights reserved.
+//
+
+#import "BTFPopover.h"
+#import "BTFPopoverFrame.h"
+
+@interface BTFPopover() {
+	NSViewController	*contentViewController;
+	BTFPopoverWindow	*popoverWindow;
+	BTFPopoverFrame		*popoverFrame;
+}
+@end
+
+@implementation BTFPopover
+
+- (id) init {
+	return [self initWithContentViewController:nil];
+}
+
+- (id) initWithContentView:(NSView *)contentView {
+	NSViewController *aContentViewController = [[NSViewController alloc] init];
+	
+	popoverFrame = [[BTFPopoverFrame alloc] initWithFrame:NSZeroRect];
+	[popoverFrame addSubview:contentView];
+	
+	[aContentViewController setView:popoverFrame];
+	return [self initWithContentViewController:aContentViewController];
+}
+
+- (id) initWithContentViewController:(NSViewController *)aContentViewController {
+	if((self = [super init])) {
+		contentViewController = aContentViewController;
+		
+		NSView *contentView = [contentViewController view];
+		popoverWindow = [[BTFPopoverWindow alloc] initWithContentRect:[contentView frame] backing:NSBackingStoreBuffered defer:YES];
+		[popoverWindow setContentView:contentView];
+		[popoverWindow setMinSize:[contentView frame].size];
+		
+		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidResignKey:)
+													 name:NSWindowDidResignKeyNotification object:popoverWindow];
+		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidResignActive:)
+													 name:NSApplicationDidResignActiveNotification object:nil];
+	}
+	return self;
+}
+
+- (void) displayPopoverInWindow:(NSWindow *)window atPoint:(NSPoint)point {
+	BOOL wasVisible = [popoverWindow isVisible];
+	[popoverWindow setFrameOrigin:point];
+	if (wasVisible) return;
+	
+	[window addChildWindow:popoverWindow ordered:NSWindowAbove];
+	[popoverWindow makeKeyAndOrderFront:nil];
+}
+
+- (void) dealloc {
+	[[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (NSWindow *) window {
+	return popoverWindow;
+}
+
+- (BOOL) isVisible {
+	return [popoverWindow isVisible];
+}
+
+- (void) closePopover:(id)sender {
+	if(![popoverWindow isVisible]) return;
+	
+	NSWindow *parentWindow = [popoverWindow parentWindow];
+	[parentWindow removeChildWindow:popoverWindow];
+	[popoverWindow orderOut:sender];
+}
+@end
+
+@implementation BTFPopover (NSWindowDelegateMethods)
+
+- (void) windowDidResignKey:(NSNotification *)notification {
+	//NSLog(@"windowDidResignKey");
+	//if(self.closesWhenPopoverResignsKey)
+		//[self closePopover:notification];
+}
+
+- (void) applicationDidResignActive:(NSNotification *)notification{
+	//NSLog(@"applicationDidResignActive");
+		//[self closePopover:notification];
+}
+
+@end

+ 18 - 0
projects/Butterfly/Butterfly/Sources/BTFPopoverFrame.h

@@ -0,0 +1,18 @@
+//
+//  BTFPopoverFrame.h
+//  Butterfly
+//
+//  Created by Marco Bambini on 21/07/15.
+//  Copyright (c) 2015 Marco Bambini. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface BTFPopoverFrame : NSView
+
+@property (nonatomic, copy)	  NSColor *borderColor;
+@property (nonatomic, assign) CGFloat borderWidth;
+@property (nonatomic, assign) CGFloat cornerRadius;
+@property (nonatomic, copy)	  NSColor *backgroundColor;
+
+@end

+ 25 - 0
projects/Butterfly/Butterfly/Sources/BTFPopoverFrame.m

@@ -0,0 +1,25 @@
+//
+//  BTFPopoverFrame.m
+//  Butterfly
+//
+//  Created by Marco Bambini on 21/07/15.
+//  Copyright (c) 2015 Marco Bambini. All rights reserved.
+//
+
+#import "BTFPopoverFrame.h"
+
+@implementation BTFPopoverFrame
+
+- (id) initWithFrame:(NSRect)frame {
+	if ((self = [super initWithFrame:frame])) {
+		[self setWantsLayer:YES];
+		self.layer.backgroundColor = [NSColor colorWithCalibratedWhite:(CGFloat)1.0 alpha:(CGFloat)0.9].CGColor;
+		self.layer.borderColor = [NSColor darkGrayColor].CGColor;
+		self.layer.borderWidth = 0.5f;
+		self.layer.cornerRadius = 5;
+		self.layer.masksToBounds = YES;
+	}
+	return self;
+}
+
+@end

+ 13 - 0
projects/Butterfly/Butterfly/Sources/BTFPopoverWindow.h

@@ -0,0 +1,13 @@
+//
+//  BTFPopoverWindow.h
+//  Butterfly
+//
+//  Created by Marco Bambini on 21/07/15.
+//  Copyright (c) 2015 Marco Bambini. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface BTFPopoverWindow : NSWindow
+- (id) initWithContentRect:(NSRect)contentRect backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation;
+@end

+ 38 - 0
projects/Butterfly/Butterfly/Sources/BTFPopoverWindow.m

@@ -0,0 +1,38 @@
+//
+//  BTFPopoverWindow.m
+//  Butterfly
+//
+//  Created by Marco Bambini on 21/07/15.
+//  Copyright (c) 2015 Marco Bambini. All rights reserved.
+//
+
+#import "BTFPopoverWindow.h"
+
+@interface BTFPopoverWindow() {
+	NSView	*popoverContentView;
+}
+@end
+
+@implementation BTFPopoverWindow
+
+- (id) initWithContentRect:(NSRect)contentRect backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation {
+	if((self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:deferCreation])) {
+		[self setBackgroundColor:[NSColor clearColor]];
+		[self setMovableByWindowBackground:NO];
+		[self setExcludedFromWindowsMenu:YES];
+		[self setAlphaValue:1];
+		[self setOpaque:NO];
+		[self setHasShadow:YES];
+	}
+	return self;
+}
+
+- (BOOL) canBecomeKeyWindow {
+	return NO;
+}
+
+- (BOOL) canBecomeMainWindow {
+	return NO;
+}
+
+@end

+ 21 - 0
projects/Butterfly/Butterfly/Sources/BTFRulerView.h

@@ -0,0 +1,21 @@
+//
+//  BTFRulerView.h
+//  Butterfly
+//
+//  Created by Marco Bambini on 15/07/15.
+//  Copyright (c) 2015 Marco Bambini. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@class BTFTextView;
+@interface BTFRulerView : NSRulerView
+
+@property (nonatomic) NSColor *backgroundColor;
+@property (nonatomic) NSColor *textColor;
+@property (nonatomic) NSColor *borderColor;
+@property (nonatomic) CGFloat borderWidth;
+
+- (instancetype)initWithBTFTextView:(BTFTextView *)textView;
+
+@end

+ 150 - 0
projects/Butterfly/Butterfly/Sources/BTFRulerView.m

@@ -0,0 +1,150 @@
+//
+//  BTFRulerView.m
+//  Butterfly
+//
+//  Created by Marco Bambini on 15/07/15.
+//  Copyright (c) 2015 Marco Bambini. All rights reserved.
+//
+
+#import "BTFRulerView.h"
+#import "BTFTextView.h"
+
+#define BTF_RULER_WIDTH		40.0f
+#define BTF_RULER_PADDING	 5.0f
+
+#define MinX(FRAME)			CGRectGetMinX(FRAME)
+#define MinY(FRAME)			CGRectGetMinY(FRAME)
+#define MaxX(FRAME)			CGRectGetMaxX(FRAME)
+#define MaxY(FRAME)			CGRectGetMaxY(FRAME)
+
+static inline void drawLineNumberInRect(NSUInteger lineNumber, NSRect lineRect, NSDictionary *attributes, CGFloat ruleThickness) {
+	NSString *string = [[NSNumber numberWithUnsignedInteger:lineNumber] stringValue];
+	NSAttributedString *attString = [[NSAttributedString alloc] initWithString:string attributes:attributes];
+	NSUInteger x = ruleThickness - BTF_RULER_PADDING - attString.size.width;
+	
+	// Offsetting the drawing keeping into account the ascender (because we draw it without NSStringDrawingUsesLineFragmentOrigin)
+	NSFont *font = attributes[NSFontAttributeName];
+	lineRect.origin.x = x;
+	lineRect.origin.y += font.ascender;
+	
+	[attString drawWithRect:lineRect options:0 context:nil];
+}
+
+static inline NSUInteger countNewLines(NSString *s, NSUInteger location, NSUInteger length) {
+	CFStringInlineBuffer inlineBuffer;
+	CFStringInitInlineBuffer((__bridge CFStringRef)s, &inlineBuffer, CFRangeMake(location, length));
+	
+	NSUInteger counter = 0;
+	for (CFIndex i=0; i < length; ++i) {
+		UniChar c = CFStringGetCharacterFromInlineBuffer(&inlineBuffer, i);
+		if (c == (UniChar)'\n') ++counter;
+	}
+	return counter;
+}
+
+@implementation BTFRulerView
+
+- (instancetype)initWithBTFTextView:(BTFTextView *)textView {
+	self = [super initWithScrollView:textView.enclosingScrollView orientation:NSVerticalRuler];
+	if (self) {
+		self.clientView = textView;
+		
+		// default settings
+		self.ruleThickness = BTF_RULER_WIDTH;
+		self.textColor = [NSColor grayColor];
+	}
+	return self;
+}
+
+- (void)drawHashMarksAndLabelsInRect:(NSRect)rect {
+	// BACKGROUND
+	if (_backgroundColor) {
+		// do not use drawBackgroundInRect for background color otherwise a 1px right border with a different color appears
+		[_backgroundColor set];
+		[NSBezierPath fillRect:rect];
+	}
+		
+	if (_borderColor && _borderWidth > 0.0) {
+		NSBezierPath *borderBezierPath = [NSBezierPath bezierPath];
+		// LEFT
+		[borderBezierPath moveToPoint:NSMakePoint(NSMinX(rect), MinY(rect))];
+		[borderBezierPath lineToPoint:NSMakePoint(NSMinX(rect), MaxY(rect))];
+		
+		// RIGHT
+		[borderBezierPath moveToPoint:NSMakePoint(MaxX(rect), MinY(rect))];
+		[borderBezierPath lineToPoint:NSMakePoint(MaxX(rect), MaxY(rect))];
+		
+		[_borderColor setStroke];
+		[borderBezierPath setLineWidth:_borderWidth];
+		[borderBezierPath stroke];
+	}
+	
+	// MARKS AND LABELS
+	BTFTextView *textView = (BTFTextView *)self.clientView;
+	if (!textView) return;
+	
+	NSLayoutManager *layoutManager = textView.layoutManager;
+	if (!layoutManager) return;
+	
+	NSString *textString = textView.string;
+	if ((!textString) || (textString.length == 0)) return;
+	
+	CGFloat insetHeight = textView.textContainerInset.height;
+	CGPoint relativePoint = [self convertPoint:NSZeroPoint fromView:textView];
+	
+	// Gettign text attributes from the textview
+	NSMutableDictionary *lineNumberAttributes = [[textView.textStorage attributesAtIndex:0 effectiveRange:NULL] mutableCopy];
+	lineNumberAttributes[NSForegroundColorAttributeName] = self.textColor;
+	
+	NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:textView.visibleRect inTextContainer:textView.textContainer];
+	NSUInteger firstVisibleGlyphCharacterIndex = [layoutManager characterIndexForGlyphAtIndex:visibleGlyphRange.location];
+	
+	// line number for the first visible line
+	NSUInteger lineNumber = countNewLines(textString, 0, firstVisibleGlyphCharacterIndex)+1;
+	NSUInteger glyphIndexForStringLine = visibleGlyphRange.location;
+	
+	// go through each line in the string
+	while (glyphIndexForStringLine < NSMaxRange(visibleGlyphRange)) {
+		// range of current line in the string
+		NSRange characterRangeForStringLine = [textString lineRangeForRange:NSMakeRange([layoutManager characterIndexForGlyphAtIndex:glyphIndexForStringLine], 0)];
+		NSRange glyphRangeForStringLine = [layoutManager glyphRangeForCharacterRange: characterRangeForStringLine actualCharacterRange:nil];
+		
+		NSUInteger glyphIndexForGlyphLine = glyphIndexForStringLine;
+		NSUInteger glyphLineCount = 0;
+		
+		while (glyphIndexForGlyphLine < NSMaxRange(glyphRangeForStringLine)) {
+			// check if the current line in the string spread across several lines of glyphs
+			NSRange effectiveRange = NSMakeRange(0, 0);
+			
+			// range of current "line of glyphs". If a line is wrapped then it will have more than one "line of glyphs"
+			NSRect lineRect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndexForGlyphLine effectiveRange:&effectiveRange withoutAdditionalLayout:YES];
+			
+			// compute Y for line number
+			CGFloat y = ceil(NSMinY(lineRect) + relativePoint.y + insetHeight);
+			lineRect.origin.y = y;
+			
+			// draw line number only if string does not spread across several lines
+			if (glyphLineCount == 0) {
+				drawLineNumberInRect(lineNumber, lineRect, lineNumberAttributes, self.ruleThickness);
+			}
+			
+			// move to next glyph line
+			++glyphLineCount;
+			glyphIndexForGlyphLine = NSMaxRange(effectiveRange);
+		}
+		
+		glyphIndexForStringLine = NSMaxRange(glyphRangeForStringLine);
+		++lineNumber;
+	}
+	
+	// draw line number for the extra line at the end of the text
+	if (layoutManager.extraLineFragmentTextContainer) {
+		NSRect lineRect = layoutManager.extraLineFragmentRect;
+		CGFloat y = ceil(NSMinY(lineRect) + relativePoint.y + insetHeight);
+		lineRect.origin.y = y;
+		drawLineNumberInRect(lineNumber, lineRect, lineNumberAttributes, self.ruleThickness);
+	}
+}
+
+@end
+

+ 17 - 0
projects/Butterfly/Butterfly/Sources/BTFTextView.h

@@ -0,0 +1,17 @@
+//
+//  BTFTextView.h
+//  Butterfly
+//
+//  Created by Marco Bambini on 15/07/15.
+//  Copyright (c) 2015 Marco Bambini. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "BTFDelegate.h"
+#import "BTFRulerView.h"
+
+@interface BTFTextView : NSTextView
+@property (nonatomic, weak) id <BTFCodeEditorDelegate>	BTFDelegate;
+@property (nonatomic, weak) BTFRulerView				*rulerView;
+@property (nonatomic)		NSDictionary				*theme;
+@end

+ 526 - 0
projects/Butterfly/Butterfly/Sources/BTFTextView.m

@@ -0,0 +1,526 @@
+//
+//  BTFTextView.m
+//  Butterfly
+//
+//  Created by Marco Bambini on 15/07/15.
+//  Copyright (c) 2015 Marco Bambini. All rights reserved.
+//
+
+#import "BTFThemeKeys.h"
+#import "BTFTextView.h"
+#import "BTFPopover.h"
+#import "gravity_parser.h"
+
+#define BTF_DEFAULT_INSET_VALUE		10.0f
+#define BTF_MAX_RESULTS				10
+#define BTF_INTERCELL_SPACING		NSMakeSize(20.0f, 3.0f)
+#define BTF_DEFAULT_FONT			[NSFont fontWithName:@"Menlo" size:12.0f]
+#define BTF_DEFAULT_BOLDFONT		[NSFont fontWithName:@"Menlo-Bold" size:12.0f]
+
+#define BTF_POPOVER_DISTANCE		5.0f
+#define BTF_POPOVER_WIDTH			375.0f
+#define BTF_POPOVER_PADDING			0.0f
+#define BTF_POPOVER_FONT			BTF_DEFAULT_FONT
+#define BTF_POPOVER_BOLDFOND		BTF_DEFAULT_BOLDFONT
+#define BTF_POPOVER_TEXTCOLOR		[NSColor blackColor]
+
+#define BTF_HIGHLIGHT_STROKE_COLOR	[NSColor selectedMenuItemColor]
+#define BTF_HIGHLIGHT_FILL_COLOR	[NSColor selectedMenuItemColor]
+#define BTF_HIGHLIGHT_RADIUS		0.0f
+
+#pragma mark -
+
+@interface BTFAutocompleteTableRowView : NSTableRowView
+@end
+@implementation BTFAutocompleteTableRowView
+- (void)drawSelectionInRect:(NSRect)dirtyRect {
+	if (self.selectionHighlightStyle != NSTableViewSelectionHighlightStyleNone) {
+		NSRect selectionRect = NSInsetRect(self.bounds, 0.5, 0.5);
+		[BTF_HIGHLIGHT_STROKE_COLOR setStroke];
+		[BTF_HIGHLIGHT_FILL_COLOR setFill];
+		NSBezierPath *selectionPath = [NSBezierPath bezierPathWithRoundedRect:selectionRect xRadius:BTF_HIGHLIGHT_RADIUS yRadius:BTF_HIGHLIGHT_RADIUS];
+		[selectionPath fill];
+		[selectionPath stroke];
+	}
+}
+- (NSBackgroundStyle)interiorBackgroundStyle {
+	if (self.isSelected) {
+		return NSBackgroundStyleDark;
+	} else {
+		return NSBackgroundStyleLight;
+	}
+}
+@end
+
+#pragma mark -
+
+@interface BTFTextView() <NSTableViewDataSource, NSTableViewDelegate, NSTextViewDelegate, NSTextDelegate> {
+	BTFPopover			*autocompletePopover;
+	NSTableView			*autocompleteTableView;
+	BOOL				autocompleteSupported;
+	NSCharacterSet		*characterSet;
+	
+	NSArray				*matches;
+	NSString			*substring;
+	NSInteger			lastPosition;
+	NSDictionary		*theme;
+	
+	NSFont				*fontRegular;
+	NSFont				*fontBold;
+	
+	gravity_delegate_t	compiler_delegate;
+}
+@end
+
+static void	errorCallback (error_type_t error_type, const char *description, error_desc_t error_desc, void *xdata) {
+//	BTFTextView *textView = (__bridge BTFTextView *)xdata;
+//	
+//	const char *type = "N/A";
+//	switch (error_type) {
+//		case GRAVITY_ERROR_NONE: type = "NONE"; break;
+//		case GRAVITY_ERROR_SYNTAX: type = "SYNTAX"; break;
+//		case GRAVITY_ERROR_SEMANTIC: type = "SEMANTIC"; break;
+//		case GRAVITY_ERROR_IO: type = "I/O"; break;
+//		case GRAVITY_ERROR_RUNTIME: type = "RUNTIME"; break;
+//		case GRAVITY_WARNING: type = "WARNING"; break;
+//	}
+//	
+//	if (error_type == GRAVITY_ERROR_RUNTIME) printf("RUNTIME ERROR: ");
+//	else printf("%s ERROR (code %d) on %s (%d,%d)\n", type, error_desc.error_code, error_desc.filename, error_desc.lineno, error_desc.colno);
+//	printf("%s\n", description);
+}
+
+static void parserCallback (void *xtoken, void *xdata) {
+	BTFTextView		*textView = (__bridge BTFTextView *) xdata;
+	NSDictionary	*theme = textView.theme;
+	gtoken_s		*token = (gtoken_s *)xtoken;
+	
+	[textView.textStorage beginEditing];
+	NSRange range = NSMakeRange(token->position, token->length);
+	@try {
+		switch (token->type) {
+			case TOK_COMMENT:
+				[textView setTextColor:theme[BTFKEY_COMMENTS_COLOR] range:range];
+				break;
+				
+			case TOK_IDENTIFIER:
+				[textView setTextColor:theme[BTFKEY_PLAIN_COLOR] range:range];
+				break;
+			
+			case TOK_MACRO:
+			case TOK_KEY_IMPORT:
+				[textView setTextColor:theme[BTFKEY_MACROS_COLOR] range:range];
+				break;
+			
+			case TOK_KEY_ENUM:
+			case TOK_KEY_MODULE:
+			case TOK_KEY_VAR:
+			case TOK_KEY_CONST:
+			case TOK_KEY_CLASS:
+			case TOK_KEY_FUNC:
+			case TOK_KEY_SUPER:
+			case TOK_KEY_DEFAULT:
+			case TOK_KEY_TRUE:
+			case TOK_KEY_FALSE:
+			case TOK_KEY_IF:
+			case TOK_KEY_ELSE:
+			case TOK_KEY_SWITCH:
+			case TOK_KEY_BREAK:
+			case TOK_KEY_CONTINUE:
+			case TOK_KEY_RETURN:
+			case TOK_KEY_WHILE:
+			case TOK_KEY_REPEAT:
+			case TOK_KEY_FOR:
+			case TOK_KEY_IN:
+			case TOK_KEY_PRIVATE:
+			case TOK_KEY_INTERNAL:
+			case TOK_KEY_PUBLIC:
+			case TOK_KEY_STATIC:
+			case TOK_KEY_EXTERN:
+			case TOK_KEY_CASE:
+			case TOK_KEY_EVENT:
+			case TOK_KEY_FILE:
+			case TOK_KEY_NULL:
+			case TOK_KEY_UNDEFINED:
+			case TOK_KEY_CURRARGS:
+				[textView setTextColor:theme[BTFKEY_KEYWORDS_COLOR] range:range];
+				break;
+				
+			case TOK_STRING:
+				// in case of literal string, returned token is the string itself and
+				// not the string surronded by the quotes, so let's fix it!
+				range.location-=1; range.length+=2;
+				[textView setTextColor:theme[BTFKEY_STRINGS_COLOR] range:range];
+				break;
+				
+			case TOK_NUMBER:
+				[textView setTextColor:theme[BTFKEY_NUMBERS_COLOR] range:range];
+				break;
+				
+			case TOK_SPECIAL:
+				[textView setTextColor:theme[BTFKEY_SPECIAL_COLOR] range:range];
+				break;
+				
+			case TOK_ERROR: {
+				[textView setTextColor:theme[BTFKEY_PLAIN_COLOR] range:range];
+				}
+				break;
+				
+			default:
+				[textView setTextColor:theme[BTFKEY_PLAIN_COLOR] range:range];
+				break;
+		}		
+	}
+	@catch (NSException *exception) {
+		NSLog(@"%@", exception.reason);
+	}
+	@finally {
+		//NSLog(@"(%d, %d)\t%s\t%@", token->lineno, token->colno, token_name(token->type), NSStringFromRange(range));
+		//NSLog(@"%@", [textView.string substringWithRange:range]);
+		[textView.textStorage endEditing];
+	}
+}
+
+static BOOL is_word_boundary (unichar c) {
+	if (c == '_') return NO;
+	if (isspace(c)) return YES;
+	if (iscntrl(c)) return YES;
+	if (ispunct(c)) return YES;
+	return NO;
+}
+
+@implementation BTFTextView
+
+- (instancetype)initWithFrame:(NSRect)frameRect textContainer:(NSTextContainer *)aTextContainer {
+	self = [super initWithFrame:frameRect textContainer:aTextContainer];
+	if (self) {
+		// default TextView settings
+		[self setTextContainerInset:NSMakeSize(BTF_DEFAULT_INSET_VALUE, BTF_DEFAULT_INSET_VALUE)];
+		[self setFont:BTF_DEFAULT_FONT];
+		[self setRichText:YES];
+		
+		[self setAutomaticTextReplacementEnabled:NO];
+		[self setAutomaticSpellingCorrectionEnabled:NO];
+		[self setAutomaticDataDetectionEnabled:NO];
+		[self setAutomaticDashSubstitutionEnabled:NO];
+		[self setEnabledTextCheckingTypes:NO];
+		
+		[self setDelegate:self];
+		[self setUpAutocompletion];
+		characterSet = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
+		
+		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidEndEditing:)
+													 name:NSControlTextDidEndEditingNotification object:nil];
+		
+		self.allowsUndo = YES;
+		
+		compiler_delegate.xdata = (__bridge void *)(self);
+		compiler_delegate.parser_callback = parserCallback;
+		compiler_delegate.error_callback = errorCallback;
+	}
+	return self;
+}
+
+- (void) dealloc {
+	[[NSNotificationCenter defaultCenter] removeObserver:self name:NSControlTextDidEndEditingNotification object:nil];
+}
+
+- (void) setTheme:(NSDictionary *)aTheme {
+	// process keys that need to be always evaluated (it should work for NSString {v1,v2}
+	if (aTheme[BTFKEY_INSET_VALUE]) [self setTextContainerInset:[aTheme[BTFKEY_INSET_VALUE] sizeValue]];
+	if (aTheme[BTFKEY_BACKGROUND_COLOR]) [self setBackgroundColor:aTheme[BTFKEY_BACKGROUND_COLOR]];
+	if (aTheme[BTFKEY_SELECTION_COLOR]) [self setSelectedTextAttributes:@{NSBackgroundColorAttributeName:aTheme[BTFKEY_SELECTION_COLOR]}];
+	if (aTheme[BTFKEY_CURSOR_COLOR]) [self setInsertionPointColor:aTheme[BTFKEY_CURSOR_COLOR]];
+	if (aTheme[BTFKEY_FONT]) [self setFont:aTheme[BTFKEY_FONT]];
+	if (aTheme[BTFKEY_PLAIN_COLOR]) [self setTextColor:aTheme[BTFKEY_PLAIN_COLOR]];
+	theme = aTheme;
+}
+
+- (NSDictionary *)theme {
+	return theme;
+}
+
+- (void) setUpAutocompletion {
+	// Make a table view with 1 column and enclosing scroll view. It doesn't
+	// matter what the frames are here because they are set when the popover
+	// is displayed
+	NSTableColumn *column1 = [[NSTableColumn alloc] initWithIdentifier:@"text"];
+	[column1 setEditable:NO];
+	[column1 setWidth:BTF_POPOVER_WIDTH - 2 * BTF_POPOVER_PADDING];
+	
+	NSTableView *tableView = [[NSTableView alloc] initWithFrame:NSZeroRect];
+	[tableView setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleRegular];
+	[tableView setBackgroundColor:[NSColor clearColor]];
+	[tableView setRowSizeStyle:NSTableViewRowSizeStyleSmall];
+	[tableView setIntercellSpacing:BTF_INTERCELL_SPACING];
+	[tableView setHeaderView:nil];
+	[tableView setRefusesFirstResponder:YES];
+	[tableView setTarget:self];
+	[tableView setDoubleAction:@selector(insert:)];
+	[tableView addTableColumn:column1];
+	[tableView setDelegate:self];
+	[tableView setDataSource:self];
+	autocompleteTableView = tableView;
+	
+	NSScrollView *tableScrollView = [[NSScrollView alloc] initWithFrame:NSZeroRect];
+	[tableScrollView setDrawsBackground:NO];
+	[tableScrollView setDocumentView:tableView];
+	[tableScrollView setHasVerticalScroller:YES];
+		
+	// Setup custom popover
+	autocompletePopover = [[BTFPopover alloc] initWithContentView:tableScrollView];
+	
+	// Finish setup
+	matches = @[];
+	lastPosition = -1;
+	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeSelection:) name:@"NSTextViewDidChangeSelectionNotification" object:nil];
+}
+
+-(void)setString:(NSString *)aString {
+	[super setString:aString];
+	#pragma clang diagnostic push
+	#pragma clang diagnostic ignored "-Wnonnull"
+	[self textDidChange:nil];
+	#pragma clang diagnostic pop
+}
+
+- (void)textDidChange:(NSNotification*)notification {
+	NSString *code = [self string];
+	NSRange range = NSMakeRange(0, [code length]);
+	
+	[self setTextColor:theme[BTFKEY_PLAIN_COLOR] range:range];
+	// DO NOT USE [code lenght] here, otherwise UTF-8 characters are not counted!
+	gravity_parser_t *parser = gravity_parser_create([code UTF8String], [code lengthOfBytesUsingEncoding:NSUTF8StringEncoding], 0, true);
+	gravity_parser_run(parser, &compiler_delegate);
+	gravity_parser_free(parser);
+}
+
+- (void)textDidEndEditing:(NSNotification *)notification {
+	if (!self.BTFDelegate) return;
+	if ([self.BTFDelegate respondsToSelector:@selector(textDidEndEditing:)]) {
+		[self.BTFDelegate textDidEndEditing:self];
+	}
+}
+
+- (void)setBTFDelegate:(id<BTFCodeEditorDelegate>)aBTFDelegate {
+	_BTFDelegate = aBTFDelegate;
+	autocompleteSupported = [self.BTFDelegate respondsToSelector:@selector(textView:completions:forPartialWordRange:location:indexOfSelectedItem:)];
+}
+
+#pragma mark - Autocompletion -
+
+- (void)keyDown:(NSEvent *)theEvent {
+	NSInteger row = autocompleteTableView.selectedRow;
+	BOOL shouldComplete = YES;
+	
+	switch (theEvent.keyCode) {
+		case 51:	// Delete key
+			[autocompletePopover closePopover:self];
+			break;
+		case 53:	// Esc key
+			if (autocompletePopover.isVisible)
+				[autocompletePopover closePopover:self];
+			return; // Skip default behavior
+		case 125:	// Down arrow
+			if (autocompletePopover.isVisible) {
+				[autocompleteTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:row+1] byExtendingSelection:NO];
+				[autocompleteTableView scrollRowToVisible:autocompleteTableView.selectedRow];
+				return; // Skip default behavior
+			}
+			break;
+		case 126:	// Up arrow
+			if (autocompletePopover.isVisible) {
+				[autocompleteTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:row-1] byExtendingSelection:NO];
+				[autocompleteTableView scrollRowToVisible:autocompleteTableView.selectedRow];
+				return; // Skip default behavior
+			}
+			break;
+		case 36:	// Return key
+		case 48:	// Tab key
+			if (autocompletePopover.isVisible) {
+				[self insert:self];
+				return; // Skip default behavior
+			}
+		case 124:	// Right arrow
+		case 123:	// Left arrow
+		case 49:	// Space key
+			if (autocompletePopover.isVisible) {
+				[autocompletePopover closePopover:self];
+			}
+			break;
+	}
+
+	[super keyDown:theEvent];
+	if (shouldComplete) {
+		[self complete:self];
+	}
+}
+
+- (void)didChangeSelection:(NSNotification *)notification {
+	if ((self.selectedRange.location - lastPosition) > 1) {
+		// if selection moves by more than just one character, hide autocomplete
+		[autocompletePopover closePopover:self];
+	}
+}
+
+- (void)paste:(id)sender {
+	[self pasteAsPlainText:sender];
+	[self complete:nil];
+}
+
+- (void)copy:(id)sender {
+	NSRange range = self.selectedRange;
+	NSString *text = (range.location != NSNotFound) ? [self.string substringWithRange:self.selectedRange] : self.string;
+	NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
+	[pasteBoard declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] owner:nil];
+	[pasteBoard setString:text forType:NSStringPboardType];
+}
+
+- (void)insert:(id)sender {
+	if (autocompleteTableView.selectedRow >= 0 && autocompleteTableView.selectedRow < matches.count) {
+		NSString *string = [matches objectAtIndex:autocompleteTableView.selectedRow];
+		NSInteger beginningOfWord = self.selectedRange.location - substring.length;
+		NSRange range = NSMakeRange(beginningOfWord, substring.length);
+		if ([self shouldChangeTextInRange:range replacementString:string]) {
+			[self replaceCharactersInRange:range withString:string];
+			[self didChangeText];
+		}
+	}
+	[autocompletePopover closePopover:self];
+}
+
+- (void)complete:(id)sender {
+	NSInteger startOfWord = self.selectedRange.location;
+	
+	// extract member access (if any) Navigation1.Window1.Label1.text
+	NSMutableArray *completions = [NSMutableArray array];
+	NSInteger dotIndex = 0;
+	for (NSInteger i = startOfWord - 1; i >= 0; --i) {
+		unichar c = [self.string characterAtIndex:i];
+		if (c == '.') {
+			// check if it is a multi member case
+			if (dotIndex != 0) {
+				NSString *member = [self.string substringWithRange:NSMakeRange(i+1, dotIndex-(i+1))];
+				if ((member == nil) || member.length == 0) {[completions removeAllObjects]; break;}
+				[completions addObject:member];
+			}
+			dotIndex = i;
+			continue;
+		}
+		
+		// member access lookup break condition
+		BOOL isBoundary = is_word_boundary(c);
+		if (isBoundary && dotIndex == 0) break;
+		
+		// member case
+		if (((i == 0) || isBoundary) && dotIndex != 0) {
+			NSString *member = [self.string substringWithRange:NSMakeRange(i, dotIndex-i)];
+			if ((member == nil) || member.length == 0) {[completions removeAllObjects]; break;}
+			[completions addObject:member];
+			dotIndex = 0;
+		}
+	}
+	
+	// extract current word
+	for (NSInteger i = startOfWord - 1; i >= 0; --i) {
+		if ([characterSet characterIsMember:[self.string characterAtIndex:i]]) break;
+		--startOfWord;
+	}
+	
+	// extract current lenght
+	NSInteger lengthOfWord = 0;
+	for (NSInteger i = startOfWord; i < self.string.length; ++i) {
+		if ([characterSet characterIsMember:[self.string characterAtIndex:i]]) break;
+		++lengthOfWord;
+	}
+	
+	substring = [self.string substringWithRange:NSMakeRange(startOfWord, lengthOfWord)];
+	NSRange substringRange = NSMakeRange(startOfWord, self.selectedRange.location - startOfWord);
+	
+	// NSLog(@"%@ %ld %ld %lu", substring, (long)startOfWord, (long)lengthOfWord, (unsigned long)self.selectedRange.location);
+	
+	// This happens when we just started a new word or if we have already typed the entire word
+	if ((substringRange.length == 0 || lengthOfWord == 0) && (completions.count == 0)) {
+		[autocompletePopover closePopover:self];
+		return;
+	}
+	
+	NSInteger index = 0;
+	if (autocompleteSupported) {
+		NSArray *r = [[completions reverseObjectEnumerator] allObjects];
+		matches = [self.BTFDelegate textView:self completions:r forPartialWordRange:substringRange
+									location:self.selectedRange.location indexOfSelectedItem:&index];
+	} else matches = @[];
+	
+	if (matches.count > 0) {
+		lastPosition = self.selectedRange.location;
+		[autocompleteTableView reloadData];
+		
+		[autocompleteTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:NO];
+		[autocompleteTableView scrollRowToVisible:index];
+		
+		// Make the frame for the popover. We want it to shrink with a small number
+		// of items to autocomplete but never grow above a certain limit when there
+		// are a lot of items. The limit is set by MAX_RESULTS.
+		NSInteger numberOfRows = MIN(autocompleteTableView.numberOfRows, BTF_MAX_RESULTS);
+		CGFloat height = (autocompleteTableView.rowHeight + autocompleteTableView.intercellSpacing.height) * numberOfRows + 2 * BTF_POPOVER_PADDING;
+		NSRect frame = NSMakeRect(0, 0, BTF_POPOVER_WIDTH, height);
+		[autocompleteTableView.enclosingScrollView setFrame:NSInsetRect(frame, BTF_POPOVER_PADDING, BTF_POPOVER_PADDING)];
+		[[autocompletePopover window] setContentSize:NSMakeSize(NSWidth(frame), NSHeight(frame))];
+		
+		// Find best coord to display the popover
+		NSRect rect = [self firstRectForCharacterRange:substringRange actualRange:NULL];
+		rect.origin.y -= (height + BTF_POPOVER_DISTANCE);
+		
+		[autocompletePopover displayPopoverInWindow:self.window	atPoint:rect.origin];
+	} else {
+		[autocompletePopover closePopover:self];
+	}
+}
+
+#pragma mark - NSTableView Data Source -
+
+- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
+	return matches.count;
+}
+
+- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
+	NSTableCellView *cellView = [tableView makeViewWithIdentifier:@"BTFCellView" owner:self];
+	if (cellView == nil) {
+		cellView = [[NSTableCellView alloc] initWithFrame:NSZeroRect];
+		NSTextField *textField = [[NSTextField alloc] initWithFrame:NSZeroRect];
+		[textField setBezeled:NO];
+		[textField setDrawsBackground:NO];
+		[textField setEditable:NO];
+		[textField setSelectable:NO];
+		[cellView addSubview:textField];
+		cellView.textField = textField;
+//		if ([self.BTFDelegate respondsToSelector:@selector(textView:imageForCompletion:)]) {
+//			NSImageView *imageView = [[NSImageView alloc] initWithFrame:NSZeroRect];
+//			[imageView setImageFrameStyle:NSImageFrameNone];
+//			[imageView setImageScaling:NSImageScaleNone];
+//			[cellView addSubview:imageView];
+//			cellView.imageView = imageView;
+//		}
+		
+		cellView.identifier = @"BTFCellView";
+	}
+	
+	NSDictionary *attributes = @{NSFontAttributeName:BTF_POPOVER_FONT, NSForegroundColorAttributeName:BTF_POPOVER_TEXTCOLOR};
+	NSMutableAttributedString *s = [[NSMutableAttributedString alloc] initWithString:matches[row] attributes:attributes];
+	
+	if (substring) {
+		NSRange range = [s.string rangeOfString:substring options:NSAnchoredSearch|NSCaseInsensitiveSearch];
+		[s addAttribute:NSFontAttributeName value:BTF_POPOVER_BOLDFOND range:range];
+	}
+	
+	[cellView.textField setAttributedStringValue:s];
+	return cellView;
+}
+
+- (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row {
+	return [[BTFAutocompleteTableRowView alloc] init];
+}
+
+@end
+

+ 54 - 0
projects/Butterfly/Butterfly/Sources/BTFThemeKeys.h

@@ -0,0 +1,54 @@
+//
+//  BTFThemeKeys.h
+//  Butterfly
+//
+//  Created by Marco Bambini on 15/07/15.
+//  Copyright (c) 2015 Marco Bambini. All rights reserved.
+//
+
+// HEADER
+#define BTFKEY_HEADER_BORDER_COLOR			@"HEADER_BORDER_COLOR"
+#define BTFKEY_HEADER_BACKGROUND_COLOR		@"HEADER_BACKGROUND_COLOR"
+#define BTFKEY_HEADER_DETACH_IMAGE			@"HEADER_DETACH_IMAGE"
+#define BTFKEY_HEADER_ATTACH_IMAGE			@"HEADER_ATTACH_IMAGE"
+
+// NAVIGATION
+#define BTFKEY_NAV_TEXT_COLOR				@"NAVIGATION_TEXTPLAIN_COLOR"
+#define BTFKEY_NAV_BOLD_COLOR				@"NAVIGATION_TEXTBOLD_COLOR"
+#define BTFKEY_NAV_SELECTION_COLOR			@"NAVIGATION_SELECTION_COLOR"
+#define BTFKEY_NAV_BACKGROUND_COLOR			@"NAVIGATION_BACKGROUND_COLOR"
+
+// RULER
+#define BTFKEY_RULER_BACKGROUND_COLOR		@"RULER_BACKGROUND_COLOR"
+#define BTFKEY_RULER_TEXT_COLOR				@"RULER_TEXTPLAIN_COLOR"
+#define BTFKEY_RULER_ERROR_COLOR			@"RULER_TEXTERROR_COLOR"
+#define BTFKEY_RULER_BORDER_COLOR			@"RULER_BORDER_COLOR"
+#define BTFKEY_RULER_BORDER_WIDTH			@"RULER_BORDER_WIDTH"
+#define BTFKEY_RULER_WIDTH					@"RULER_WIDTH"
+
+// SCOPE
+#define BTFKEY_SCOPE_ALPHA					@"SCOPE_ALPHA"
+
+// CODEEDITOR
+#define BTFKEY_INSET_VALUE					@"CODEEDITOR_INSET_VALUE"
+#define BTFKEY_BACKGROUND_COLOR				@"CODEEDITOR_BACKGROUND_COLOR"
+#define BTFKEY_FONT							@"CODEEDITOR_PLAIN_FONT"
+#define BTFKEY_BOLD_FONT					@"CODEEDITOR_BOLD_FONT"
+#define BTFKEY_FONT_SIZE					@"CODEEDITOR_FONT_SIZE"
+#define BTFKEY_SELECTION_COLOR				@"CODEEDITOR_SELECTION_COLOR"
+#define BTFKEY_CURSOR_COLOR					@"CODEEDITOR_CURSOR_COLOR"
+#define BTFKEY_PLAIN_COLOR					@"CODEEDITOR_PLAIN_COLOR"
+#define BTFKEY_IDENTIFIER_COLOR				@"CODEEDITOR_IDENTIFIER_COLOR"
+#define BTFKEY_COMMENTS_COLOR				@"CODEEDITOR_COMMENTS_COLOR"
+#define BTFKEY_STRINGS_COLOR				@"CODEEDITOR_STRINGS_COLOR"
+#define BTFKEY_NUMBERS_COLOR				@"CODEEDITOR_NUMBERS_COLOR"
+#define BTFKEY_KEYWORDS_COLOR				@"CODEEDITOR_KEYWORDS_COLOR"
+#define BTFKEY_MACROS_COLOR					@"CODEEDITOR_MACROS_COLOR"
+#define BTFKEY_SPECIAL_COLOR				@"CODEEDITOR_SPECIAL_COLOR"
+#define BTFKEY_CLASS_COLOR					@"CODEEDITOR_CLASS_COLOR"
+#define BTFKEY_FUNCTION_COLOR				@"CODEEDITOR_FUNCTION_COLOR"
+#define BTFKEY_VAR_COLOR					@"CODEEDITOR_VAR_COLOR"
+#define BTFKEY_CONST_COLOR					@"CODEEDITOR_CONST_COLOR"
+#define BTFKEY_ENUM_COLOR					@"CODEEDITOR_ENUM_COLOR"
+#define BTFKEY_MODULES_COLOR				@"CODEEDITOR_MODULES_COLOR"
+#define BTFKEY_ERROR_COLOR					@"CODEEDITOR_ERROR_COLOR"

+ 13 - 0
projects/Butterfly/Butterfly/main.m

@@ -0,0 +1,13 @@
+//
+//  main.m
+//  Butterfly
+//
+//  Created by Marco Bambini on 18/03/2017.
+//  Copyright © 2017 Creolabs. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+int main(int argc, const char * argv[]) {
+	return NSApplicationMain(argc, argv);
+}

+ 36 - 0
projects/Butterfly/Themes/Fourier.theme

@@ -0,0 +1,36 @@
+{
+	"HEADER_BORDER_COLOR": "#BEBEBE",
+	"HEADER_BACKGROUND_COLOR": "#FFFEF5",
+	"HEADER_DETACH_IMAGE": "CodeEditorDetach",
+	"HEADER_ATTACH_IMAGE": "CodeEditorAttach",
+	"NAVIGATION_TEXTPLAIN_COLOR": "#454545",
+	"NAVIGATION_TEXTBOLD_COLOR": "#454545",
+	"NAVIGATION_SELECTION_COLOR": "#E2F0FC",
+	"NAVIGATION_BACKGROUND_COLOR": "#FFFEF5",
+	"RULER_BACKGROUND_COLOR": "#F7F4E2",
+	"RULER_TEXTPLAIN_COLOR": "#636363",
+	"RULER_BORDER_COLOR": "#979797",
+	"RULER_BORDER_WIDTH": 0.5,
+	"SCOPE_ALPHA": 1.0,
+	"CODEEDITOR_BACKGROUND_COLOR":"#FFFEF5",
+	"CODEEDITOR_SELECTION_COLOR": "#E2F0FC",
+	"CODEEDITOR_PLAIN_COLOR": "#000000",
+	"CODEEDITOR_IDENTIFIER_COLOR": "#000000",
+	"CODEEDITOR_CURSOR_COLOR": "#000000",
+	"CODEEDITOR_COMMENTS_COLOR": "#758F7E",
+	"CODEEDITOR_STRINGS_COLOR": "#FFAC02",
+	"CODEEDITOR_NUMBERS_COLOR": "#31CA41",
+	"CODEEDITOR_KEYWORDS_COLOR": "#FF2600",
+	"CODEEDITOR_MACROS_COLOR": "#CA45B3",
+	"CODEEDITOR_SPECIAL_COLOR": "#CA45B3",
+	"CODEEDITOR_CLASS_COLOR": "#FF0202",
+	"CODEEDITOR_FUNCTION_COLOR": "#FF0202",
+	"CODEEDITOR_VAR_COLOR": "#FF2600",
+	"CODEEDITOR_CONST_COLOR": "#FF2600",
+	"CODEEDITOR_ENUM_COLOR": "#FF2600",
+	"CODEEDITOR_MODULES_COLOR": "#FF2600",
+	"CODEEDITOR_ERROR_COLOR": "#000000",
+	"CODEEDITOR_PLAIN_FONT": "Menlo-Regular",
+	"CODEEDITOR_BOLD_FONT": "Menlo-Bold",
+	"CODEEDITOR_FONT_SIZE": 13
+}

+ 34 - 0
projects/Butterfly/Themes/Gauss.theme

@@ -0,0 +1,34 @@
+{
+	"HEADER_BORDER_COLOR": "#4B4B4B",
+	"HEADER_BACKGROUND_COLOR": "#212224",
+	"HEADER_DETACH_IMAGE": "CodeEditorDetachWhite",
+	"HEADER_ATTACH_IMAGE": "CodeEditorAttachWhite",	
+	"NAVIGATION_TEXTPLAIN_COLOR": "#888A8F",
+	"NAVIGATION_TEXTBOLD_COLOR": "#FFFFFF",
+	"NAVIGATION_SELECTION_COLOR": "#424244",
+	"NAVIGATION_BACKGROUND_COLOR": "#212224",
+	"RULER_BACKGROUND_COLOR": "#141414",
+	"RULER_TEXTPLAIN_COLOR": "#55575B",
+	"SCOPE_ALPHA": 0.8,
+	"CODEEDITOR_BACKGROUND_COLOR":"#212224",
+	"CODEEDITOR_SELECTION_COLOR": "#68634D",
+	"CODEEDITOR_PLAIN_COLOR": "#FFFFFF",
+	"CODEEDITOR_IDENTIFIER_COLOR": "#FFFFFF",
+	"CODEEDITOR_CURSOR_COLOR": "#FFFFFF",
+	"CODEEDITOR_COMMENTS_COLOR": "#666666",
+	"CODEEDITOR_STRINGS_COLOR": "#DD2249",
+	"CODEEDITOR_NUMBERS_COLOR": "#786DFF",
+	"CODEEDITOR_KEYWORDS_COLOR": "#00E475",
+	"CODEEDITOR_MACROS_COLOR": "#AAAAAA",
+	"CODEEDITOR_SPECIAL_COLOR": "#AAAAAA",
+	"CODEEDITOR_CLASS_COLOR": "#FF00DF",
+	"CODEEDITOR_FUNCTION_COLOR": "#FF00DF",
+	"CODEEDITOR_VAR_COLOR": "#00E475",
+	"CODEEDITOR_CONST_COLOR": "#00E475",
+	"CODEEDITOR_ENUM_COLOR": "#005BF1",
+	"CODEEDITOR_MODULES_COLOR": "#005BF1",
+	"CODEEDITOR_ERROR_COLOR": "#888A8F",
+	"CODEEDITOR_PLAIN_FONT": "Menlo-Regular",
+	"CODEEDITOR_BOLD_FONT": "Menlo-Bold",
+	"CODEEDITOR_FONT_SIZE": 13
+}

+ 36 - 0
projects/Butterfly/Themes/Laplace.theme

@@ -0,0 +1,36 @@
+{
+	"HEADER_BORDER_COLOR": "#BEBEBE",
+	"HEADER_BACKGROUND_COLOR": "#FFFFFF",
+	"HEADER_DETACH_IMAGE": "CodeEditorDetach",
+	"HEADER_ATTACH_IMAGE": "CodeEditorAttach",	
+	"NAVIGATION_TEXTPLAIN_COLOR": "#454545",
+	"NAVIGATION_TEXTBOLD_COLOR": "#454545",
+	"NAVIGATION_SELECTION_COLOR": "#E2F0FC",
+	"NAVIGATION_BACKGROUND_COLOR": "#FFFFFF",
+	"RULER_BACKGROUND_COLOR": "#F7F7F7",
+	"RULER_TEXTPLAIN_COLOR": "#636363",
+	"RULER_BORDER_COLOR": "#979797",
+	"RULER_BORDER_WIDTH": 0.5,
+	"SCOPE_ALPHA": 1.0,
+	"CODEEDITOR_BACKGROUND_COLOR":"#FFFFFF",
+	"CODEEDITOR_SELECTION_COLOR": "#E2F0FC",
+	"CODEEDITOR_PLAIN_COLOR": "#000000",
+	"CODEEDITOR_IDENTIFIER_COLOR": "#000000",
+	"CODEEDITOR_CURSOR_COLOR": "#000000",
+	"CODEEDITOR_COMMENTS_COLOR": "#00852C",
+	"CODEEDITOR_STRINGS_COLOR": "#DE4205",
+	"CODEEDITOR_NUMBERS_COLOR": "#3047E4",
+	"CODEEDITOR_KEYWORDS_COLOR": "#CA45B3",
+	"CODEEDITOR_MACROS_COLOR": "#CA45B3",
+	"CODEEDITOR_SPECIAL_COLOR": "#CA45B3",
+	"CODEEDITOR_CLASS_COLOR": "#CA45B3",
+	"CODEEDITOR_FUNCTION_COLOR": "#CA45B3",
+	"CODEEDITOR_VAR_COLOR": "#CA45B3",
+	"CODEEDITOR_CONST_COLOR": "#CA45B3",
+	"CODEEDITOR_ENUM_COLOR": "#CA45B3",
+	"CODEEDITOR_MODULES_COLOR": "#CA45B3",
+	"CODEEDITOR_ERROR_COLOR": "#000000",
+	"CODEEDITOR_PLAIN_FONT": "Menlo-Regular",
+	"CODEEDITOR_BOLD_FONT": "Menlo-Bold",
+	"CODEEDITOR_FONT_SIZE": 13
+}

+ 34 - 0
projects/Butterfly/Themes/Riemann.theme

@@ -0,0 +1,34 @@
+{
+	"HEADER_BORDER_COLOR": "#4B4B4B",
+	"HEADER_BACKGROUND_COLOR": "#1A1F29",
+	"HEADER_DETACH_IMAGE": "CodeEditorDetachWhite",
+	"HEADER_ATTACH_IMAGE": "CodeEditorAttachWhite",	
+	"NAVIGATION_TEXTPLAIN_COLOR": "#888A8F",
+	"NAVIGATION_TEXTBOLD_COLOR": "#FFFFFF",
+	"NAVIGATION_SELECTION_COLOR": "#424244",
+	"NAVIGATION_BACKGROUND_COLOR": "#1A1F29",
+	"RULER_BACKGROUND_COLOR": "#10151E",
+	"RULER_TEXTPLAIN_COLOR": "#55575B",
+	"SCOPE_ALPHA": 0.8,
+	"CODEEDITOR_BACKGROUND_COLOR":"#1A1F29",
+	"CODEEDITOR_SELECTION_COLOR": "#68634D",
+	"CODEEDITOR_PLAIN_COLOR": "#FFFFFF",
+	"CODEEDITOR_IDENTIFIER_COLOR": "#FFFFFF",
+	"CODEEDITOR_CURSOR_COLOR": "#FFFFFF",
+	"CODEEDITOR_COMMENTS_COLOR": "#797979",
+	"CODEEDITOR_STRINGS_COLOR": "#12D2EF",
+	"CODEEDITOR_NUMBERS_COLOR": "#00FA41",
+	"CODEEDITOR_KEYWORDS_COLOR": "#FFB532",
+	"CODEEDITOR_MACROS_COLOR": "#AAAAAA",
+	"CODEEDITOR_SPECIAL_COLOR": "#AAAAAA",
+	"CODEEDITOR_CLASS_COLOR": "#FF9800",
+	"CODEEDITOR_FUNCTION_COLOR": "#FF9800",
+	"CODEEDITOR_VAR_COLOR": "#FEA727",
+	"CODEEDITOR_CONST_COLOR": "#FEA727",
+	"CODEEDITOR_ENUM_COLOR": "#FEA727",
+	"CODEEDITOR_MODULES_COLOR": "#FEA727",
+	"CODEEDITOR_ERROR_COLOR": "#C31717",
+	"CODEEDITOR_PLAIN_FONT": "Menlo-Regular",
+	"CODEEDITOR_BOLD_FONT": "Menlo-Bold",
+	"CODEEDITOR_FONT_SIZE": 13
+}