RootViewController.m 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. //
  2. // ViewController.m
  3. // TestApp
  4. //
  5. // Created by 邢铖 on 2023/6/1.
  6. //
  7. #import "RootViewController.h"
  8. #import <XCTreeLang/XCTreeLang.h>
  9. char *kRootViewControllerKVOKey = "kRootViewControllerKVOKey";
  10. @interface RootViewController () <UITextViewDelegate, XCTLStreamDelegate>
  11. @property (nonatomic, strong) UITextView *sourceTextView;
  12. @property (nonatomic, strong) UITextView *terminalTextView;
  13. @property (nonatomic, strong) UIButton *runButton;
  14. @property (nonatomic, strong) UIView *previewViewControllerContext;
  15. @property (nonatomic, strong) UIViewController *previewViewController;
  16. @property (nonatomic, copy) NSArray<NSLayoutConstraint *> *previewConstraints;
  17. @property (nonatomic, strong) UIView *testView;
  18. @end
  19. @implementation RootViewController
  20. - (UIView *)testView {
  21. if (!_testView) {
  22. _testView = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
  23. }
  24. return _testView;
  25. }
  26. - (void)viewDidLoad {
  27. [self.view addSubview:self.sourceTextView];
  28. [self.view addSubview:self.terminalTextView];
  29. [self.view addSubview:self.runButton];
  30. [self.view addSubview:self.previewViewControllerContext];
  31. [self.previewViewControllerContext.rightAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.rightAnchor].active = true;
  32. [self.previewViewControllerContext.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor].active = true;
  33. [self.previewViewControllerContext.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor].active = true;
  34. [self.previewViewControllerContext.widthAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.widthAnchor multiplier:0.3].active = true;
  35. [self.sourceTextView.leftAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leftAnchor].active = true;
  36. [self.sourceTextView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor].active = true;
  37. [self.sourceTextView.rightAnchor constraintEqualToAnchor:self.previewViewControllerContext.leftAnchor].active = true;
  38. [self.sourceTextView.heightAnchor constraintEqualToAnchor:self.terminalTextView.heightAnchor multiplier:2].active = true;
  39. [self.terminalTextView.topAnchor constraintEqualToAnchor:self.sourceTextView.bottomAnchor].active = true;
  40. [self.terminalTextView.leftAnchor constraintEqualToAnchor:self.sourceTextView.leftAnchor].active = true;
  41. [self.terminalTextView.rightAnchor constraintEqualToAnchor:self.sourceTextView.rightAnchor].active = true;
  42. [self.terminalTextView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor].active = true;
  43. [self.runButton.rightAnchor constraintEqualToAnchor:self.sourceTextView.rightAnchor constant:-10].active = true;
  44. [self.runButton.bottomAnchor constraintEqualToAnchor:self.sourceTextView.bottomAnchor constant:-10].active = true;
  45. [self.runButton.widthAnchor constraintEqualToConstant:108].active = true;
  46. [self.runButton.heightAnchor constraintEqualToConstant:38].active = true;
  47. [self addObserver:self
  48. forKeyPath:@"previewViewController"
  49. options:NSKeyValueObservingOptionOld
  50. context:kRootViewControllerKVOKey];
  51. [self addObserver:self
  52. forKeyPath:@"previewViewController"
  53. options:NSKeyValueObservingOptionNew
  54. context:kRootViewControllerKVOKey];
  55. [super viewDidLoad];
  56. }
  57. - (UITextView *)makeTextView {
  58. UITextView *textView = [UITextView new];
  59. textView.translatesAutoresizingMaskIntoConstraints = false;
  60. textView.font = [UIFont fontWithName:@"Menlo" size:16];
  61. textView.backgroundColor = UIColor.systemBackgroundColor;
  62. textView.allowsEditingTextAttributes = false;
  63. textView.contentMode = UIViewContentModeRedraw;
  64. textView.autocorrectionType = UITextAutocorrectionTypeNo;
  65. textView.smartQuotesType = UITextSmartQuotesTypeNo;
  66. textView.smartDashesType = UITextSmartDashesTypeNo;
  67. textView.smartInsertDeleteType = UITextSmartInsertDeleteTypeNo;
  68. textView.autocapitalizationType = UITextAutocapitalizationTypeNone;
  69. textView.spellCheckingType = UITextSpellCheckingTypeNo;
  70. textView.textContentType = nil;
  71. textView.tintColor = UIColor.systemOrangeColor;
  72. textView.editable = true;
  73. return textView;
  74. }
  75. - (UITextView *)sourceTextView {
  76. if (!_sourceTextView) {
  77. _sourceTextView = [self makeTextView];
  78. _sourceTextView.backgroundColor = UIColor.systemBackgroundColor;
  79. NSString *savedCode = [NSUserDefaults.standardUserDefaults stringForKey:@"code"];
  80. if (savedCode == nil || savedCode.length == 0) {
  81. savedCode = [NSString stringWithContentsOfFile:[NSBundle.mainBundle pathForResource:@"InitialContent" ofType:@"xct"]
  82. encoding:NSUTF8StringEncoding
  83. error:nil];
  84. }
  85. _sourceTextView.text = savedCode;
  86. _sourceTextView.delegate = self;
  87. }
  88. return _sourceTextView;
  89. }
  90. - (UITextView *)terminalTextView {
  91. if (!_terminalTextView) {
  92. _terminalTextView = [self makeTextView];
  93. _terminalTextView.backgroundColor = UIColor.tertiarySystemBackgroundColor;
  94. _terminalTextView.editable = false;
  95. }
  96. return _terminalTextView;
  97. }
  98. - (UIButton *)runButton {
  99. if (!_runButton) {
  100. _runButton = [UIButton new];
  101. _runButton.translatesAutoresizingMaskIntoConstraints = false;
  102. _runButton.layer.cornerRadius = 10;
  103. _runButton.backgroundColor = UIColor.systemFillColor;
  104. [_runButton setTitleColor:UIColor.systemOrangeColor forState:UIControlStateNormal];
  105. [_runButton setTitle:@"Run Code" forState:UIControlStateNormal];
  106. [_runButton addTarget:self
  107. action:@selector(runButtonDidTouchUp)
  108. forControlEvents:UIControlEventTouchUpInside];
  109. }
  110. return _runButton;
  111. }
  112. - (UIView *)previewViewControllerContext {
  113. if (!_previewViewControllerContext) {
  114. _previewViewControllerContext = [UIView new];
  115. _previewViewControllerContext.translatesAutoresizingMaskIntoConstraints = false;
  116. _previewViewControllerContext.backgroundColor = UIColor.secondarySystemBackgroundColor;
  117. }
  118. return _previewViewControllerContext;
  119. }
  120. - (void)runButtonDidTouchUp {
  121. [self clearPreviewWithViewController:self.previewViewController];
  122. _previewViewController = nil;
  123. self.terminalTextView.text = @"";
  124. XCTLAST *program = [XCTLEngine.shared compileWithCode:self.sourceTextView.text];
  125. if (program == NULL) {
  126. self.terminalTextView.text = [self.terminalTextView.text stringByAppendingString:@"Compile failed, see program log for more details."];
  127. return;
  128. }
  129. program.stdoutDelegate = self;
  130. NSError *error;
  131. [XCTLEngine.shared evaluateWithAst:program
  132. sourceObject:self
  133. error:&error];
  134. if (error) {
  135. self.terminalTextView.text = [self.terminalTextView.text stringByAppendingString:error.localizedDescription];
  136. self.terminalTextView.text = [self.terminalTextView.text stringByAppendingFormat:@"\n%@", error.description];
  137. }
  138. }
  139. - (void)updatePreview {
  140. if (self.previewViewController) {
  141. [self addChildViewController:self.previewViewController];
  142. [self.previewViewControllerContext addSubview:self.previewViewController.view];
  143. self.previewViewController.view.translatesAutoresizingMaskIntoConstraints = false;
  144. self.previewConstraints = @[
  145. [self.previewViewController.view.leftAnchor constraintEqualToAnchor:self.previewViewControllerContext.leftAnchor],
  146. [self.previewViewController.view.topAnchor constraintEqualToAnchor:self.previewViewControllerContext.topAnchor],
  147. [self.previewViewController.view.rightAnchor constraintEqualToAnchor:self.previewViewControllerContext.rightAnchor],
  148. [self.previewViewController.view.bottomAnchor constraintEqualToAnchor:self.previewViewControllerContext.bottomAnchor]
  149. ];
  150. for (NSLayoutConstraint *constraint in self.previewConstraints) {
  151. constraint.active = true;
  152. }
  153. }
  154. }
  155. - (void)clearPreviewWithViewController:(UIViewController *)viewController {
  156. if (self.previewConstraints) {
  157. [self.previewViewControllerContext removeConstraints:self.previewConstraints];
  158. self.previewConstraints = nil;
  159. }
  160. if (viewController) {
  161. [viewController.view removeFromSuperview];
  162. [viewController removeFromParentViewController];
  163. }
  164. }
  165. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  166. if (context == kRootViewControllerKVOKey) {
  167. if ([@"previewViewController" isEqualToString:keyPath]) {
  168. UIViewController *oldValue = change[NSKeyValueChangeOldKey];
  169. if (oldValue) {
  170. if ([oldValue isKindOfClass:UIViewController.class]) {
  171. [self clearPreviewWithViewController:oldValue];
  172. }
  173. } else {
  174. [self updatePreview];
  175. }
  176. }
  177. } else {
  178. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  179. }
  180. }
  181. - (void)stream:(XCTLStream *)stream appendText:(NSString *)text {
  182. self.terminalTextView.text = [self.terminalTextView.text stringByAppendingString:text];
  183. }
  184. - (void)textViewDidChange:(UITextView *)textView {
  185. [NSUserDefaults.standardUserDefaults setValue:textView.text forKey:@"code"];
  186. }
  187. - (void)dealloc {
  188. [self removeObserver:self
  189. forKeyPath:@"previewViewController"
  190. context:kRootViewControllerKVOKey];
  191. }
  192. @end