JSResourceEditor.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. //
  2. // Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
  3. // LICENSE: Atomic Game Engine Editor and Tools EULA
  4. // Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
  5. // license information: https://github.com/AtomicGameEngine/AtomicGameEngine
  6. //
  7. #include <Atomic/Container/ArrayPtr.h>
  8. #include <Atomic/UI/UI.h>
  9. #include <Atomic/IO/Log.h>
  10. #include <Atomic/IO/File.h>
  11. #include <Atomic/IO/FileSystem.h>
  12. #include <Atomic/Resource/ResourceCache.h>
  13. #include <Atomic/Core/CoreEvents.h>
  14. #include <AtomicJS/Javascript/JSVM.h>
  15. #include "JSResourceEditor.h"
  16. #include "../Javascript/JSTheme.h"
  17. #include "../Javascript/JSASTSyntaxColorVisitor.h"
  18. #include <TurboBadger/tb_message_window.h>
  19. #include <TurboBadger/tb_editfield.h>
  20. #include <TurboBadger/tb_style_edit.h>
  21. #include <TurboBadger/tb_style_edit_content.h>
  22. using namespace tb;
  23. namespace AtomicEditor
  24. {
  25. JSResourceEditor ::JSResourceEditor(Context* context, const String &fullpath, UITabContainer *container) :
  26. ResourceEditor(context, fullpath, container),
  27. styleEdit_(0),
  28. lineNumberList_(0),
  29. editField_(0),
  30. autocomplete_(0),
  31. textDirty_(true),
  32. textDelta_(0.0f),
  33. currentFindPos_(-1)
  34. {
  35. TBLayout* layout = new TBLayout();
  36. layout->SetLayoutSize(LAYOUT_SIZE_GRAVITY);
  37. layout->SetGravity(WIDGET_GRAVITY_ALL);
  38. layout->SetLayoutDistribution(LAYOUT_DISTRIBUTION_GRAVITY);
  39. rootContentWidget_->GetInternalWidget()->AddChild(layout);
  40. TBContainer* c = new TBContainer();
  41. c->SetGravity(WIDGET_GRAVITY_ALL);
  42. TBEditField* text = editField_ = new TBEditField();
  43. text->SetMultiline(true);
  44. text->SetWrapping(true);
  45. text->SetGravity(WIDGET_GRAVITY_ALL);
  46. text->SetStyling(true);
  47. text->SetSkinBg(TBIDC("TextCode"));
  48. TBFontDescription fd;
  49. fd.SetID(TBIDC("Monaco"));
  50. fd.SetSize(12);
  51. text->SetFontDescription(fd);
  52. SharedPtr<File> jsFile(GetSubsystem<ResourceCache>()->GetFile(fullpath));
  53. assert(jsFile);
  54. String source;
  55. jsFile->ReadText(source);
  56. String json;
  57. JSASTProgram* program = NULL;
  58. if (ParseJavascriptToJSON(source.CString(), json))
  59. {
  60. program = JSASTProgram::ParseFromJSON(fullpath, json);
  61. }
  62. text->SetText(source.CString());
  63. lineNumberList_ = new TBSelectList();
  64. lineNumberList_->SetFontDescription(fd);
  65. lineNumberList_->SetSkinBg(TBIDC("LineNumberSelectList"));
  66. lineNumberList_->GetScrollContainer()->SetScrollMode(SCROLL_MODE_OFF);
  67. //lineNumberList_->GetScrollContainer()->SetIgnoreScrollEvents(true);
  68. lineNumberList_->SetGravity(WIDGET_GRAVITY_ALL);
  69. LayoutParams lp;
  70. lp.max_w = 48;
  71. lineNumberList_->SetLayoutParams(lp);
  72. c->AddChild(text);
  73. layout->AddChild(lineNumberList_);
  74. layout->AddChild(c);
  75. layout->SetSpacing(0);
  76. TBStyleEdit* sedit = text->GetStyleEdit();
  77. TBTextTheme* theme = new TBTextTheme();
  78. for (unsigned i = 0; i < TB_MAX_TEXT_THEME_COLORS; i++)
  79. theme->themeColors[i] = TBColor(255, 255, 255);
  80. theme->themeColors[JSTHEME_LITERAL_STRING].SetFromString("#E6DB74", 7);
  81. theme->themeColors[JSTHEME_LITERAL_NUMBER].SetFromString("#AE81FF", 7);
  82. theme->themeColors[JSTHEME_LITERAL_REGEX].SetFromString("#AE81FF", 7);
  83. theme->themeColors[JSTHEME_LITERAL_BOOLEAN].SetFromString("#AE81FF", 7);
  84. theme->themeColors[JSTHEME_LITERAL_NULL].SetFromString("#AE81FF", 7);
  85. theme->themeColors[JSTHEME_FUNCTION].SetFromString("#66D9EF", 7);
  86. theme->themeColors[JSTHEME_VAR].SetFromString("#66D9EF", 7);
  87. theme->themeColors[JSTHEME_KEYWORD].SetFromString("#f92672", 7);
  88. theme->themeColors[JSTHEME_OPERATOR].SetFromString("#f92672", 7);
  89. theme->themeColors[JSTHEME_CODE].SetFromString("#a6e22e", 7);
  90. theme->themeColors[JSTHEME_COMMENT].SetFromString("#75715e", 7);
  91. theme->themeColors[JSTHEME_FUNCTIONDECLARG].SetFromString("#FF9800", 7);
  92. sedit->SetTextTheme(theme);
  93. sedit->text_change_listener = this;
  94. styleEdit_ = sedit;
  95. UpdateLineNumbers();
  96. if (program)
  97. {
  98. JSASTSyntaxColorVisitor syntaxColor(sedit);
  99. syntaxColor.visit(program);
  100. }
  101. SubscribeToEvent(E_UPDATE, HANDLER(JSResourceEditor, HandleUpdate));
  102. // FIXME: Set the size at the end of setup, so all children are updated accordingly
  103. // future size changes will be handled automatically
  104. IntRect rect = container_->GetContentRoot()->GetRect();
  105. rootContentWidget_->SetSize(rect.Width(), rect.Height());
  106. }
  107. JSResourceEditor::~JSResourceEditor()
  108. {
  109. }
  110. void JSResourceEditor::FormatCode()
  111. {
  112. TBStr text;
  113. styleEdit_->GetText(text);
  114. if (text.Length())
  115. {
  116. String output;
  117. if (BeautifyJavascript(text.CStr(), output))
  118. {
  119. if (output.Length())
  120. {
  121. styleEdit_->selection.SelectAll();
  122. styleEdit_->InsertText(output.CString(), output.Length());
  123. }
  124. }
  125. }
  126. }
  127. void JSResourceEditor::UpdateLineNumbers()
  128. {
  129. if (!styleEdit_)
  130. return;
  131. TBGenericStringItemSource* lineSource = lineNumberList_->GetDefaultSource();
  132. int lines = lineSource->GetNumItems();
  133. int lineCount = styleEdit_->blocks.CountLinks();
  134. if (lines == lineCount)
  135. return;
  136. while (lines > lineCount)
  137. {
  138. lineSource->DeleteItem(lineSource->GetNumItems() - 1);
  139. lines --;
  140. }
  141. for (int i = lines; i < lineCount; i++)
  142. {
  143. String sline;
  144. sline.AppendWithFormat("%i ", i + 1);
  145. TBGenericStringItem* item = new TBGenericStringItem(sline.CString());
  146. lineSource->AddItem(item);
  147. }
  148. // item widgets don't exist until ValidateList
  149. lineNumberList_->ValidateList();
  150. for (int i = 0; i < lineCount; i++)
  151. {
  152. TBTextField* textField = (TBTextField* )lineNumberList_->GetItemWidget(i);
  153. if (textField)
  154. {
  155. textField->SetTextAlign(TB_TEXT_ALIGN_RIGHT);
  156. textField->SetSkinBg(TBIDC("TBSelectItemLineNumber"));
  157. }
  158. }
  159. }
  160. void JSResourceEditor::OnChange(TBStyleEdit* styleEdit)
  161. {
  162. textDelta_ = 0.25f;
  163. textDirty_ = true;
  164. SetModified(true);
  165. UpdateLineNumbers();
  166. }
  167. bool JSResourceEditor::OnEvent(const TBWidgetEvent &ev)
  168. {
  169. if (ev.type == EVENT_TYPE_KEY_DOWN)
  170. {
  171. if (ev.special_key == TB_KEY_ESC)
  172. {
  173. //SendEvent(E_FINDTEXTCLOSE);
  174. }
  175. }
  176. if (ev.type == EVENT_TYPE_SHORTCUT)
  177. {
  178. if (ev.ref_id == TBIDC("close"))
  179. {
  180. RequestClose();
  181. }
  182. if (ev.ref_id == TBIDC("find"))
  183. {
  184. //using namespace FindTextOpen;
  185. //SendEvent(E_FINDTEXTOPEN);
  186. }
  187. else if (ev.ref_id == TBIDC("findnext") || ev.ref_id == TBIDC("findprev"))
  188. {
  189. /*
  190. String text;
  191. FindTextWidget* finder = GetSubsystem<FindTextWidget>();
  192. finder->GetFindText(text);
  193. // TODO: get flags from finder
  194. unsigned flags = FINDTEXT_FLAG_NONE;
  195. if (ev.ref_id == TBIDC("findnext"))
  196. flags |= FINDTEXT_FLAG_NEXT;
  197. else if (ev.ref_id == TBIDC("findprev"))
  198. flags |= FINDTEXT_FLAG_PREV;
  199. flags |= FINDTEXT_FLAG_WRAP;
  200. finder->Find(text, flags);
  201. */
  202. }
  203. else if (ev.ref_id == TBIDC("cut") || ev.ref_id == TBIDC("copy") || ev.ref_id == TBIDC("paste")
  204. || ev.ref_id == TBIDC("selectall") || ev.ref_id == TBIDC("undo") || ev.ref_id == TBIDC("redo") )
  205. {
  206. editField_->OnEvent(ev);
  207. }
  208. }
  209. return false;
  210. }
  211. void JSResourceEditor::HandleUpdate(StringHash eventType, VariantMap& eventData)
  212. {
  213. if (!styleEdit_)
  214. return;
  215. // sync line number
  216. lineNumberList_->GetScrollContainer()->ScrollTo(0, styleEdit_->scroll_y);
  217. lineNumberList_->SetValue(styleEdit_->GetCaretLine());
  218. // Timestep parameter is same no matter what event is being listened to
  219. float timeStep = eventData[Update::P_TIMESTEP].GetFloat();
  220. if (!textDirty_)
  221. return;
  222. if (textDelta_ > 0.0f)
  223. {
  224. textDelta_ -= timeStep;
  225. if (textDelta_ < 0.0f)
  226. {
  227. textDelta_ = 0.0f;
  228. }
  229. else
  230. {
  231. return;
  232. }
  233. }
  234. TBStr text;
  235. styleEdit_->GetText(text);
  236. JSASTProgram* program = NULL;
  237. String json;
  238. if (ParseJavascriptToJSON(text.CStr(), json))
  239. {
  240. program = JSASTProgram::ParseFromJSON("fullpath", json);
  241. if (program)
  242. {
  243. JSASTSyntaxColorVisitor syntaxColor(styleEdit_);
  244. syntaxColor.visit(program);
  245. delete program;
  246. }
  247. }
  248. textDirty_ = false;
  249. editField_->SetFocus(WIDGET_FOCUS_REASON_UNKNOWN);
  250. }
  251. void JSResourceEditor::FindTextClose()
  252. {
  253. editField_->SetFocus(WIDGET_FOCUS_REASON_UNKNOWN);
  254. styleEdit_->selection.SelectNothing();
  255. }
  256. bool JSResourceEditor::FindText(const String& findText, unsigned flags)
  257. {
  258. /*
  259. unsigned findLength = findText.Length();
  260. if (!findLength)
  261. return true;
  262. TBStr _source;
  263. styleEdit_->GetText(_source);
  264. String source = _source.CStr();
  265. unsigned pos = String::NPOS;
  266. int startPos = currentFindPos_;
  267. if (currentFindPos_ == -1)
  268. startPos = styleEdit_->caret.GetGlobalOfs();
  269. else
  270. {
  271. if (flags & FINDTEXT_FLAG_NEXT)
  272. startPos += findLength;
  273. }
  274. if (flags & FINDTEXT_FLAG_PREV)
  275. {
  276. String pretext = source.Substring(0, startPos);
  277. pos = pretext.FindLast(findText, String::NPOS, flags & FINDTEXT_FLAG_CASESENSITIVE ? true : false);
  278. }
  279. else
  280. {
  281. pos = source.Find(findText, startPos, flags & FINDTEXT_FLAG_CASESENSITIVE ? true : false);
  282. }
  283. if (pos == String::NPOS)
  284. {
  285. if (flags & FINDTEXT_FLAG_WRAP)
  286. {
  287. if (flags & FINDTEXT_FLAG_PREV)
  288. {
  289. pos = source.FindLast(findText, String::NPOS, flags & FINDTEXT_FLAG_CASESENSITIVE ? true : false);
  290. }
  291. else
  292. {
  293. pos = source.Find(findText, 0, flags & FINDTEXT_FLAG_CASESENSITIVE ? true : false);
  294. }
  295. }
  296. if (pos == String::NPOS)
  297. {
  298. styleEdit_->selection.SelectNothing();
  299. return true;
  300. }
  301. }
  302. currentFindPos_ = pos;
  303. styleEdit_->caret.SetGlobalOfs((int) pos + findLength);
  304. int height = styleEdit_->layout_height;
  305. int newy = styleEdit_->caret.y - height/2;
  306. styleEdit_->SetScrollPos(styleEdit_->scroll_x, newy);
  307. styleEdit_->selection.Select(pos, pos + findLength);
  308. */
  309. return true;
  310. }
  311. void JSResourceEditor::SetFocus()
  312. {
  313. editField_->SetFocus(WIDGET_FOCUS_REASON_UNKNOWN);
  314. }
  315. void JSResourceEditor::GotoTokenPos(int tokenPos)
  316. {
  317. styleEdit_->caret.SetGlobalOfs(tokenPos);
  318. int height = styleEdit_->layout_height;
  319. int newy = styleEdit_->caret.y - height/2;
  320. styleEdit_->SetScrollPos(styleEdit_->scroll_x, newy);
  321. }
  322. void JSResourceEditor::GotoLineNumber(int lineNumber)
  323. {
  324. int line = 0;
  325. TBBlock *block = NULL;
  326. for (block = styleEdit_->blocks.GetFirst(); block; block = block->GetNext())
  327. {
  328. if (lineNumber == line)
  329. break;
  330. line++;
  331. }
  332. if (!block)
  333. return;
  334. styleEdit_->caret.Place(block, 0);
  335. int height = styleEdit_->layout_height;
  336. int newy = styleEdit_->caret.y - height/2;
  337. styleEdit_->SetScrollPos(styleEdit_->scroll_x, newy);
  338. }
  339. bool JSResourceEditor::ParseJavascriptToJSON(const char* source, String& json, bool loose)
  340. {
  341. JSVM* vm = JSVM::GetJSVM(NULL);
  342. duk_context* ctx = vm->GetJSContext();
  343. int top = duk_get_top(ctx);
  344. json.Clear();
  345. duk_get_global_string(ctx, "require");
  346. duk_push_string(ctx, "AtomicEditor/JavaScript/Lib/jsutils");
  347. if (duk_pcall(ctx, 1))
  348. {
  349. printf("Error: %s\n", duk_safe_to_string(ctx, -1));
  350. duk_set_top(ctx, top);
  351. return false;
  352. }
  353. duk_get_prop_string(ctx, -1, "parseToJSON");
  354. duk_push_string(ctx, source);
  355. bool ok = true;
  356. if (duk_pcall(ctx, 1))
  357. {
  358. ok = false;
  359. printf("Error: %s\n", duk_safe_to_string(ctx, -1));
  360. }
  361. else
  362. {
  363. json = duk_to_string(ctx, -1);
  364. }
  365. duk_set_top(ctx, top);
  366. return ok;
  367. }
  368. bool JSResourceEditor::BeautifyJavascript(const char* source, String& output)
  369. {
  370. JSVM* vm = JSVM::GetJSVM(NULL);
  371. duk_context* ctx = vm->GetJSContext();
  372. int top = duk_get_top(ctx);
  373. output.Clear();
  374. duk_get_global_string(ctx, "require");
  375. duk_push_string(ctx, "AtomicEditor/JavaScript/Lib/jsutils");
  376. if (duk_pcall(ctx, 1))
  377. {
  378. printf("Error: %s\n", duk_safe_to_string(ctx, -1));
  379. duk_set_top(ctx, top);
  380. return false;
  381. }
  382. duk_get_prop_string(ctx, -1, "jsBeautify");
  383. duk_push_string(ctx, source);
  384. bool ok = true;
  385. if (duk_pcall(ctx, 1))
  386. {
  387. ok = false;
  388. printf("Error: %s\n", duk_safe_to_string(ctx, -1));
  389. }
  390. else
  391. {
  392. output = duk_to_string(ctx, -1);
  393. }
  394. // ignore result
  395. duk_set_top(ctx, top);
  396. return ok;
  397. }
  398. bool JSResourceEditor::Save()
  399. {
  400. if (!modified_)
  401. return true;
  402. TBStr text;
  403. styleEdit_->GetText(text);
  404. File file(context_, fullpath_, FILE_WRITE);
  405. file.Write((void*) text.CStr(), text.Length());
  406. file.Close();
  407. SetModified(false);
  408. return true;
  409. }
  410. }