curseSimpleOutput.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. /**
  2. * curseOutput.cpp for jsonstroller
  3. *
  4. * Author: isundil <isundill@gmail.com>
  5. **/
  6. #include <sys/ioctl.h>
  7. #include <unistd.h>
  8. #include <signal.h>
  9. #include "curseSimpleOutput.hh"
  10. #include "searchPattern.hh"
  11. #include "jsonPrimitive.hh"
  12. #include "jsonObject.hh"
  13. #include "jsonArray.hh"
  14. CurseSimpleOutput::CurseSimpleOutput(const Params &p): CurseOutput(p)
  15. {
  16. init();
  17. }
  18. CurseSimpleOutput::~CurseSimpleOutput()
  19. {
  20. shutdown();
  21. }
  22. void CurseSimpleOutput::run(JSonElement *root)
  23. {
  24. selection = data = root;
  25. loop();
  26. }
  27. bool CurseSimpleOutput::redraw()
  28. {
  29. const std::pair<unsigned int, unsigned int> screenSize = getScreenSize();
  30. std::pair<int, int> cursor(0, 0);
  31. /**
  32. * Will be true if the json's last item is visible
  33. **/
  34. bool result;
  35. select_up = select_down = nullptr;
  36. selectFound = selectIsLast = false;
  37. clear();
  38. try {
  39. result = redraw(cursor, screenSize, data);
  40. }
  41. catch (SelectionOutOfRange &e)
  42. {
  43. return false;
  44. }
  45. if (!result && !selectFound)
  46. {
  47. scrollTop++;
  48. return false;
  49. }
  50. if (!result && !select_down)
  51. selectIsLast = true;
  52. if (!select_down)
  53. {
  54. const JSonContainer *pselect = dynamic_cast<const JSonContainer*>(selection);
  55. if (pselect && !pselect->empty())
  56. select_down = *(pselect->cbegin());
  57. else
  58. {
  59. const JSonElement *next = selection->findNext();
  60. select_down = next ? next : selection;
  61. }
  62. }
  63. if (!select_up)
  64. select_up = selection;
  65. refresh();
  66. return true;
  67. }
  68. inputResult CurseSimpleOutput::selectUp()
  69. {
  70. selection = select_up;
  71. return inputResult::redraw;
  72. }
  73. inputResult CurseSimpleOutput::selectDown()
  74. {
  75. if (selectIsLast)
  76. scrollTop += 2;
  77. else if (selection != select_down)
  78. selection = select_down;
  79. else
  80. return inputResult::nextInput;
  81. return inputResult::redraw;
  82. }
  83. inputResult CurseSimpleOutput::selectPUp()
  84. {
  85. const JSonElement *_selection = selection;
  86. const JSonElement *brother = _selection->findPrev();
  87. if (brother == nullptr)
  88. {
  89. const JSonElement *parent = _selection->getParent();
  90. if (parent && dynamic_cast<const JSonContainer*>(parent))
  91. {
  92. selection = _selection = parent;
  93. if (_selection->getParent() && dynamic_cast<const JSonObjectEntry*> (_selection->getParent()))
  94. selection = _selection->getParent();
  95. }
  96. else
  97. return inputResult::nextInput;
  98. }
  99. else
  100. selection = brother;
  101. return inputResult::redraw;
  102. }
  103. inputResult CurseSimpleOutput::selectPDown()
  104. {
  105. const JSonElement *brother = selection->findNext();
  106. if (brother)
  107. {
  108. selection = brother;
  109. return inputResult::redraw;
  110. }
  111. return inputResult::nextInput;
  112. }
  113. inputResult CurseSimpleOutput::expandSelection()
  114. {
  115. const JSonElement *_selection = selection;
  116. if (dynamic_cast<const JSonObjectEntry*>(_selection))
  117. _selection = **((const JSonObjectEntry*)_selection);
  118. if (!dynamic_cast<const JSonContainer*>(_selection))
  119. return inputResult::nextInput;
  120. if (collapsed.erase((const JSonContainer *)_selection))
  121. return inputResult::redraw;
  122. if (!((const JSonContainer*)_selection)->size())
  123. return inputResult::nextInput;
  124. selection = select_down;
  125. return inputResult::redraw;
  126. }
  127. inputResult CurseSimpleOutput::collapseSelection()
  128. {
  129. const JSonElement *_selection = selection;
  130. if (dynamic_cast<const JSonObjectEntry*>(_selection))
  131. _selection = **((const JSonObjectEntry*)_selection);
  132. if (_selection->getParent() && (!dynamic_cast<const JSonContainer*>(_selection)
  133. || collapsed.find((const JSonContainer *)_selection) != collapsed.end()
  134. || (dynamic_cast<const JSonContainer*>(_selection) && ((const JSonContainer*)_selection)->size() == 0)))
  135. {
  136. selection = selection->getParent();
  137. if (selection->getParent() && dynamic_cast<const JSonObjectEntry*>(selection->getParent()))
  138. selection = selection->getParent();
  139. }
  140. else
  141. collapsed.insert((const JSonContainer *)_selection);
  142. return inputResult::redraw;
  143. }
  144. inputResult CurseSimpleOutput::initSearch()
  145. {
  146. const SearchPattern *search_pattern = inputSearch();
  147. if (!search_pattern)
  148. return inputResult::redraw;
  149. search_result.clear();
  150. if (search_pattern->isEmpty())
  151. return inputResult::redraw;
  152. search(*search_pattern, data);
  153. delete search_pattern;
  154. return nextResult();
  155. }
  156. inputResult CurseSimpleOutput::nextResult()
  157. {
  158. if (search_result.empty())
  159. CurseOutput::redraw("Pattern not found");
  160. else if (jumpToNextSearch())
  161. return inputResult::redraw;
  162. return inputResult::nextInput;
  163. }
  164. bool CurseSimpleOutput::redraw(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, JSonElement *item)
  165. {
  166. checkSelection(item, cursor);
  167. if (dynamic_cast<const JSonContainer*>(item))
  168. {
  169. if (!writeContainer(cursor, maxSize, (const JSonContainer *) item))
  170. return false;
  171. }
  172. else
  173. {
  174. cursor.second += CurseOutput::write(cursor.first, cursor.second, item, maxSize.first, CurseSimpleOutput::getFlag(item));
  175. if (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1)
  176. return false;
  177. }
  178. return true;
  179. }
  180. bool CurseSimpleOutput::writeContainer(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, const JSonContainer *item)
  181. {
  182. char childDelimiter[2];
  183. if (dynamic_cast<const JSonObject *>(item))
  184. memcpy(childDelimiter, "{}", sizeof(*childDelimiter) * 2);
  185. else
  186. memcpy(childDelimiter, "[]", sizeof(*childDelimiter) * 2);
  187. if (collapsed.find((const JSonContainer *)item) != collapsed.end())
  188. {
  189. std::string ss;
  190. ss.append(&childDelimiter[0], 1).append(" ... ").append(&childDelimiter[1], 1);
  191. cursor.second += write(cursor.first, cursor.second, ss, 7, maxSize.first, CurseSimpleOutput::getFlag(item));
  192. }
  193. else
  194. {
  195. cursor.second += write(cursor.first, cursor.second, childDelimiter[0], maxSize.first, CurseSimpleOutput::getFlag(item));
  196. if (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1)
  197. return false;
  198. if (!writeContent(cursor, maxSize, (std::list<JSonElement *> *)item))
  199. return false;
  200. cursor.second += write(cursor.first, cursor.second, childDelimiter[1], maxSize.first, CurseSimpleOutput::getFlag(item));
  201. }
  202. return (cursor.second - scrollTop < 0 || (unsigned)(cursor.second - scrollTop) <= maxSize.second -1);
  203. }
  204. bool CurseSimpleOutput::writeContent(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, std::list<JSonElement*> *_item)
  205. {
  206. JSonContainer *item = (JSonContainer *)_item;
  207. bool containerIsObject = (dynamic_cast<JSonObject *>(item) != nullptr);
  208. bool result = true;
  209. cursor.first += INDENT_LEVEL;
  210. for (JSonElement *i : *item)
  211. {
  212. result = false;
  213. if (containerIsObject)
  214. {
  215. JSonObjectEntry *ent = (JSonObjectEntry*) i;
  216. bool isContainer = (dynamic_cast<JSonContainer *>(**ent) != nullptr);
  217. std::string key = ent->stringify();
  218. checkSelection(ent, cursor);
  219. if (isContainer && collapsed.find((JSonContainer*)(**ent)) != collapsed.cend())
  220. {
  221. if (dynamic_cast<JSonObject *>(**ent))
  222. {
  223. if (!CurseOutput::writeKey(key, ent->lazystrlen(), "{ ... }", cursor, maxSize, CurseSimpleOutput::getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
  224. break;
  225. }
  226. else if (!CurseOutput::writeKey(key, ent->lazystrlen(), "[ ... ]", cursor, maxSize, CurseSimpleOutput::getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
  227. break;
  228. }
  229. else if (!isContainer)
  230. {
  231. JSonElement *eContent = **ent;
  232. if (!writeKey(key, ent->lazystrlen(), eContent->stringify(), eContent->lazystrlen(), cursor, maxSize, CurseSimpleOutput::getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
  233. break;
  234. }
  235. else if (((JSonContainer*)(**ent))->size() == 0)
  236. {
  237. if (dynamic_cast<const JSonObject *>(**ent) )
  238. {
  239. if (!CurseOutput::writeKey(key, ent->lazystrlen(), "{ }", cursor, maxSize, CurseSimpleOutput::getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
  240. break;
  241. }
  242. else if (!CurseOutput::writeKey(key, ent->lazystrlen(), "[ ]", cursor, maxSize, CurseSimpleOutput::getFlag(ent)) || (cursor.second - scrollTop > 0 && (unsigned)(cursor.second - scrollTop) > maxSize.second -1))
  243. break;
  244. }
  245. else
  246. {
  247. if (!writeKey(key, ent->lazystrlen(), cursor, maxSize, CurseSimpleOutput::getFlag(ent)))
  248. break;
  249. const JSonElement *saveSelection = selection;
  250. if (selection == ent)
  251. selection = **ent;
  252. cursor.first += INDENT_LEVEL /2;
  253. if (!redraw(cursor, maxSize, **ent))
  254. {
  255. selection = saveSelection;
  256. cursor.first -= INDENT_LEVEL /2;
  257. return false;
  258. }
  259. selection = saveSelection;
  260. cursor.first -= INDENT_LEVEL /2;
  261. }
  262. }
  263. else
  264. {
  265. if (!redraw(cursor, maxSize, i))
  266. break;
  267. }
  268. result = true;
  269. }
  270. cursor.first -= INDENT_LEVEL;
  271. //result will be false if for loop break'd at some time, true otherwise
  272. return result;
  273. }
  274. bool CurseSimpleOutput::writeKey(const std::string &key, const size_t keylen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, OutputFlag flags, unsigned int extraLen)
  275. {
  276. if (cursor.second - scrollTop < 0)
  277. {
  278. cursor.second++;
  279. return true;
  280. }
  281. char oldType = flags.type();
  282. flags.type(OutputFlag::TYPE_OBJKEY);
  283. cursor.second += write(cursor.first, cursor.second, key, keylen, maxSize.first -extraLen -2, flags);
  284. flags.type(OutputFlag::TYPE_OBJ);
  285. write(": ", flags);
  286. flags.type(oldType);
  287. return (cursor.second - scrollTop < 0 || (unsigned)(cursor.second - scrollTop) <= maxSize.second);
  288. }
  289. bool CurseSimpleOutput::writeKey(const std::string &key, const size_t keylen, const std::string &after, size_t afterlen, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, OutputFlag flags)
  290. {
  291. if (cursor.second - scrollTop < 0)
  292. {
  293. cursor.second++;
  294. return true;
  295. }
  296. char oldType = flags.type();
  297. flags.type(OutputFlag::TYPE_OBJKEY);
  298. write(cursor.first, cursor.second, key, 0, 1, flags);
  299. flags.type(OutputFlag::TYPE_OBJ);
  300. write(": ", flags);
  301. flags.type(oldType);
  302. write(after.c_str(), flags);
  303. cursor.second += getNbLines(cursor.first +keylen +2 +afterlen, maxSize.first);
  304. return (cursor.second - scrollTop < 0 || (unsigned)(cursor.second - scrollTop) <= maxSize.second);
  305. }
  306. unsigned int CurseSimpleOutput::write(const int &x, const int &y, const char item, unsigned int maxWidth, OutputFlag flags)
  307. {
  308. int offsetY = y - scrollTop;
  309. char color = OutputFlag::SPECIAL_NONE;
  310. if (offsetY < 0)
  311. return 1;
  312. if (flags.selected())
  313. attron(A_REVERSE | A_BOLD);
  314. if (flags.searched())
  315. color = OutputFlag::SPECIAL_SEARCH;
  316. else if (colors.find(flags.type()) != colors.end())
  317. color = flags.type();
  318. if (color != OutputFlag::SPECIAL_NONE)
  319. attron(COLOR_PAIR(color));
  320. mvprintw(offsetY, x, "%c", item);
  321. attroff(A_REVERSE | A_BOLD);
  322. if (color != OutputFlag::SPECIAL_NONE)
  323. attroff(COLOR_PAIR(color));
  324. return getNbLines(x +1, maxWidth);
  325. }
  326. unsigned int CurseSimpleOutput::write(const int &x, const int &y, const std::string &str, const size_t strlen, unsigned int maxWidth, const OutputFlag flags)
  327. {
  328. int offsetY = y - scrollTop;
  329. if (offsetY < 0)
  330. return 1;
  331. move(offsetY, x);
  332. write(str, flags);
  333. return getNbLines(strlen +x, maxWidth);
  334. }
  335. void CurseSimpleOutput::write(const std::string &str, const OutputFlag flags) const
  336. {
  337. char color = OutputFlag::SPECIAL_NONE;
  338. if (flags.selected())
  339. attron(A_REVERSE | A_BOLD);
  340. if (flags.searched())
  341. color = OutputFlag::SPECIAL_SEARCH;
  342. else if (colors.find(flags.type()) != colors.end())
  343. color = flags.type();
  344. if (color != OutputFlag::SPECIAL_NONE)
  345. attron(COLOR_PAIR(color));
  346. printw("%s", str.c_str());
  347. attroff(A_REVERSE | A_BOLD);
  348. if (color != OutputFlag::SPECIAL_NONE)
  349. attroff(COLOR_PAIR(color));
  350. }
  351. bool CurseSimpleOutput::jumpToNextSearch(const JSonElement *current, bool &selectFound)
  352. {
  353. const JSonContainer *container = dynamic_cast<const JSonContainer *> (current);
  354. const JSonObjectEntry *objEntry = dynamic_cast<const JSonObjectEntry *> (current);
  355. if (selection == current)
  356. selectFound = true;
  357. if (container)
  358. {
  359. if (!container->empty())
  360. for (const JSonElement *it : *container)
  361. if (jumpToNextSearch(it, selectFound))
  362. return true;
  363. }
  364. else
  365. {
  366. if (current && std::find(search_result.cbegin(), search_result.cend(), current) != search_result.cend() && current != selection && selectFound)
  367. {
  368. selection = current;
  369. return true;
  370. }
  371. if (objEntry)
  372. if (jumpToNextSearch(**objEntry, selectFound))
  373. return true;
  374. }
  375. return false;
  376. }
  377. const OutputFlag CurseSimpleOutput::getFlag(const JSonElement *e) const
  378. {
  379. return getFlag(e, selection);
  380. }
  381. const OutputFlag CurseSimpleOutput::getFlag(const JSonElement *item, const JSonElement *selection) const
  382. {
  383. OutputFlag res;
  384. const JSonElement *i = dynamic_cast<const JSonObjectEntry*>(item) ? **((const JSonObjectEntry*)item) : item;
  385. res.selected(item == selection);
  386. res.searched(std::find(search_result.cbegin(), search_result.cend(), item) != search_result.cend());
  387. if (dynamic_cast<const JSonPrimitive<std::string> *>(i))
  388. res.type(OutputFlag::TYPE_STRING);
  389. else if (dynamic_cast<const JSonPrimitive<bool> *>(i))
  390. res.type(OutputFlag::TYPE_BOOL);
  391. else if (dynamic_cast<const JSonPrimitive<Null> *>(i))
  392. res.type(OutputFlag::TYPE_NULL);
  393. else if (dynamic_cast<const AJSonPrimitive *>(i))
  394. res.type(OutputFlag::TYPE_NUMBER);
  395. else if (dynamic_cast<const JSonObject*>(i))
  396. res.type(OutputFlag::TYPE_OBJ);
  397. else if (dynamic_cast<const JSonArray*>(i))
  398. res.type(OutputFlag::TYPE_ARR);
  399. return res;
  400. }
  401. unsigned int CurseSimpleOutput::search(const SearchPattern &search_pattern, const JSonElement *current)
  402. {
  403. const JSonContainer *container = dynamic_cast<const JSonContainer *> (current);
  404. const JSonObjectEntry *objEntry = dynamic_cast<const JSonObjectEntry *> (current);
  405. unsigned int result =0;
  406. if (container)
  407. {
  408. if (!container->empty())
  409. for (const JSonElement *it : *container)
  410. result += search(search_pattern, it);
  411. }
  412. else
  413. {
  414. if (current && current->match(search_pattern))
  415. {
  416. if (current->getParent() && dynamic_cast<const JSonObjectEntry*>(current->getParent()))
  417. search_result.push_back(current->getParent());
  418. else
  419. search_result.push_back(current);
  420. result++;
  421. }
  422. if (objEntry)
  423. result += search(search_pattern, **objEntry);
  424. }
  425. result = search_result.size();
  426. return result;
  427. }
  428. bool CurseSimpleOutput::jumpToNextSearch()
  429. {
  430. bool selectFound = false;
  431. bool res = jumpToNextSearch(data, selectFound);
  432. if (!res)
  433. {
  434. selection = *(search_result.cbegin());
  435. unfold(selection);
  436. CurseOutput::redraw("Search hit BOTTOM, continuing at TOP");
  437. return false;
  438. }
  439. unfold(selection);
  440. return true;
  441. }
  442. void CurseSimpleOutput::checkSelection(const JSonElement *item, const std::pair<int, int> &cursor)
  443. {
  444. if (!selectFound)
  445. {
  446. if (selection == item)
  447. {
  448. if (cursor.second < scrollTop) //Selection is above vp, move scroll pos to selection and start drawing
  449. scrollTop = cursor.second;
  450. selectFound = true;
  451. }
  452. else if (!item->getParent() || !dynamic_cast<const JSonObjectEntry*>(item->getParent()))
  453. select_up = item;
  454. }
  455. else if (!select_down)
  456. {
  457. const JSonElement *parent = item->getParent();
  458. if (!dynamic_cast<const JSonContainer*>(item) && parent && selection != parent && dynamic_cast<const JSonObjectEntry*>(parent))
  459. item = parent;
  460. if (!parent || !dynamic_cast<const JSonObjectEntry*>(parent))
  461. select_down = item;
  462. }
  463. }
  464. void CurseSimpleOutput::init()
  465. {
  466. if (!isatty(fileno(stdin)) || !isatty(fileno(stdout)))
  467. {
  468. screen_fd = fopen("/dev/tty", "r+");
  469. setbuf(screen_fd, nullptr);
  470. screen = newterm(nullptr, screen_fd, screen_fd);
  471. }
  472. else
  473. {
  474. screen = newterm(nullptr, stdout, stdin);
  475. screen_fd = nullptr;
  476. }
  477. wtimeout(stdscr, 150);
  478. cbreak();
  479. clear();
  480. noecho();
  481. curs_set(false);
  482. keypad(stdscr, true);
  483. if (params.colorEnabled())
  484. {
  485. start_color();
  486. init_pair(OutputFlag::TYPE_NUMBER, COLOR_GREEN, COLOR_BLACK);
  487. init_pair(OutputFlag::TYPE_BOOL, COLOR_RED, COLOR_BLACK);
  488. init_pair(OutputFlag::TYPE_NULL, COLOR_RED, COLOR_BLACK);
  489. init_pair(OutputFlag::TYPE_STRING, COLOR_CYAN, COLOR_BLACK);
  490. init_pair(OutputFlag::TYPE_OBJKEY, COLOR_CYAN, COLOR_BLACK);
  491. init_pair(OutputFlag::SPECIAL_SEARCH, COLOR_WHITE, COLOR_BLUE);
  492. init_pair(OutputFlag::SPECIAL_ERROR, COLOR_WHITE, COLOR_RED);
  493. colors.insert(OutputFlag::TYPE_NUMBER);
  494. colors.insert(OutputFlag::TYPE_BOOL);
  495. colors.insert(OutputFlag::TYPE_STRING);
  496. colors.insert(OutputFlag::TYPE_OBJKEY);
  497. colors.insert(OutputFlag::TYPE_NULL);
  498. }
  499. signal(SIGWINCH, _resizeFnc);
  500. signal(SIGINT, _resizeFnc);
  501. signal(SIGTERM, _resizeFnc);
  502. signal(SIGKILL, _resizeFnc);
  503. scrollTop = 0;
  504. }
  505. void CurseSimpleOutput::shutdown()
  506. {
  507. endwin();
  508. delscreen(screen);
  509. if (screen_fd)
  510. {
  511. fclose(screen_fd);
  512. screen_fd = nullptr;
  513. }
  514. screen = nullptr;
  515. }