JSResourceEditor.cpp 14 KB

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