curseOutput.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. #include <iostream>
  2. #include <sys/ioctl.h>
  3. #include <unistd.h>
  4. #include <signal.h>
  5. #include <string.h>
  6. #include "curseOutput.hh"
  7. #include "jsonObject.hh"
  8. static CurseOutput *runningInst = nullptr;
  9. CurseOutput::CurseOutput(JSonElement *root): data(root), selection(root), indentLevel(4)
  10. { }
  11. CurseOutput::~CurseOutput()
  12. { }
  13. void CurseOutput::run()
  14. {
  15. runningInst = this;
  16. init();
  17. loop();
  18. shutdown();
  19. runningInst = nullptr;
  20. }
  21. void CurseOutput::loop()
  22. {
  23. breakLoop = false;
  24. do
  25. {
  26. redraw();
  27. } while(readInput());
  28. }
  29. bool CurseOutput::onsig(int signo)
  30. {
  31. struct winsize size;
  32. switch (signo)
  33. {
  34. case SIGWINCH:
  35. if (ioctl(fileno(screen_fd ? screen_fd : stdout), TIOCGWINSZ, &size) == 0)
  36. resize_term(size.ws_row, size.ws_col);
  37. clear();
  38. redraw();
  39. break;
  40. case SIGKILL:
  41. case SIGINT:
  42. case SIGTERM:
  43. breakLoop = true;
  44. break;
  45. default:
  46. return false;
  47. }
  48. return true;
  49. }
  50. static void _resizeFnc(int signo)
  51. {
  52. if (!runningInst)
  53. return;
  54. runningInst->onsig(signo);
  55. }
  56. bool CurseOutput::redraw()
  57. {
  58. std::pair<unsigned int, unsigned int> screenSize;
  59. std::pair<int, int> cursor;
  60. bool result;
  61. select_up = select_down = nullptr;
  62. selectFound = selectIsLast = selectIsFirst = false;
  63. getScreenSize(screenSize, cursor);
  64. cursor.first = 0;
  65. cursor.second = 0;
  66. clear();
  67. result = redraw(cursor, screenSize, data, dynamic_cast<const JSonContainer *> (data));
  68. if (!result && !select_down)
  69. selectIsLast = true;
  70. if (!select_down)
  71. select_down = selection;
  72. if (!select_up)
  73. select_up = selection;
  74. refresh();
  75. return result;
  76. }
  77. bool CurseOutput::redraw(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, const JSonElement *item, const JSonContainer *parent)
  78. {
  79. checkSelection(item, parent, cursor);
  80. if (dynamic_cast<const JSonContainer*>(item))
  81. {
  82. if (!writeContainer(cursor, maxSize, (const JSonContainer *) item))
  83. return false;
  84. }
  85. else
  86. {
  87. cursor.second += write(cursor.first, cursor.second, item, maxSize.first, selection == item);
  88. if (cursor.second - topleft> maxSize.second -1)
  89. return false;
  90. }
  91. return true;
  92. }
  93. bool CurseOutput::writeContainer(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, const JSonContainer *item)
  94. {
  95. char childDelimiter[2];
  96. if (dynamic_cast<const JSonObject *>(item))
  97. memcpy(childDelimiter, "{}", sizeof(*childDelimiter) * 2);
  98. else
  99. memcpy(childDelimiter, "[]", sizeof(*childDelimiter) * 2);
  100. cursor.first += indentLevel /2;
  101. if (collapsed.find((const JSonContainer *)item) != collapsed.end())
  102. {
  103. std::string ss;
  104. ss.append(&childDelimiter[0], 1).append(" ... ").append(&childDelimiter[1], 1);
  105. cursor.second += write(cursor.first, cursor.second, ss, maxSize.first, selection == item);
  106. }
  107. else
  108. {
  109. cursor.second += write(cursor.first, cursor.second, childDelimiter[0], maxSize.first, selection == item);
  110. if (cursor.second - topleft > maxSize.second -1)
  111. return false;
  112. if (!writeContent(cursor, maxSize, (const std::list<JSonElement *> *)item))
  113. return false;
  114. cursor.second += write(cursor.first, cursor.second, childDelimiter[1], maxSize.first, selection == item);
  115. }
  116. cursor.first -= indentLevel /2;
  117. return (cursor.second - topleft <= maxSize.second -1);
  118. }
  119. bool CurseOutput::writeContent(std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, const std::list<JSonElement*> *_item)
  120. {
  121. const JSonContainer *item = (const JSonContainer *)_item;
  122. bool containerIsObject = (dynamic_cast<const JSonObject *>(item) != nullptr);
  123. cursor.first += indentLevel /2;
  124. for (std::list<JSonElement *>::const_iterator i = item->cbegin(); i != item->cend(); ++i)
  125. {
  126. bool isObject = (dynamic_cast<const JSonObject *>(*i) != nullptr);
  127. if (containerIsObject)
  128. {
  129. JSonObjectEntry *ent = (JSonObjectEntry*) *i;
  130. std::string key = ent->stringify();
  131. checkSelection(**ent, (JSonContainer*) item, cursor);
  132. if (collapsed.find((const JSonContainer*)(**ent)) != collapsed.cend())
  133. {
  134. if (isObject)
  135. cursor.second += write(cursor.first, cursor.second, key + ": { ... }", maxSize.first, selection == **ent);
  136. else
  137. cursor.second += write(cursor.first, cursor.second, key + ": [ ... ]", maxSize.first, selection == **ent);
  138. }
  139. else if (dynamic_cast<const JSonContainer*> (**ent) == nullptr)
  140. cursor.second += write(cursor.first, cursor.second, key + ": " +((**ent)->stringify()), maxSize.first, selection == **ent);
  141. else if (((JSonContainer*)(**ent))->size() == 0)
  142. {
  143. if (isObject)
  144. cursor.second += write(cursor.first, cursor.second, key + ": { }", maxSize.first, selection == **ent);
  145. else
  146. cursor.second += write(cursor.first, cursor.second, key + ": [ ]", maxSize.first, selection == **ent);
  147. }
  148. else
  149. {
  150. if (!writeKey(key, cursor, maxSize, selection == ent || selection == **ent))
  151. return false;
  152. cursor.first -= indentLevel /2;
  153. if (!redraw(cursor, maxSize, **ent, (const JSonContainer *)item))
  154. return false;
  155. cursor.first -= indentLevel /2;
  156. }
  157. }
  158. else
  159. {
  160. if (!redraw(cursor, maxSize, *i, (const JSonContainer *)item))
  161. return false;
  162. }
  163. }
  164. cursor.first -= indentLevel /2;
  165. return true;
  166. }
  167. bool CurseOutput::writeKey(const std::string &key, std::pair<int, int> &cursor, const std::pair<unsigned int, unsigned int> &maxSize, bool selected)
  168. {
  169. cursor.second += write(cursor.first, cursor.second, key +": ", maxSize.first, selected);
  170. cursor.first += indentLevel;
  171. return (cursor.second - topleft <= maxSize.second -1);
  172. }
  173. unsigned int CurseOutput::write(const int &x, const int &y, const JSonElement *item, unsigned int maxWidth, bool selected)
  174. {
  175. return write(x, y, item->stringify(), maxWidth, selected);
  176. }
  177. unsigned int CurseOutput::write(const int &x, const int &y, const char item, unsigned int maxWidth, bool selected)
  178. {
  179. int offsetY = y - topleft;
  180. if (offsetY < 0)
  181. return 1;
  182. if (selected)
  183. {
  184. attron(A_REVERSE | A_BOLD);
  185. mvprintw(offsetY, x, "%c", item);
  186. attroff(A_REVERSE | A_BOLD);
  187. }
  188. else
  189. mvprintw(offsetY, x, "%c", item);
  190. return getNbLines(x +1, maxWidth);
  191. }
  192. unsigned int CurseOutput::getNbLines(float nbChar, unsigned int maxWidth)
  193. {
  194. float nLine = nbChar / maxWidth;
  195. if (nLine == (unsigned int) nLine)
  196. return nLine;
  197. return nLine +1;
  198. }
  199. unsigned int CurseOutput::write(const int &x, const int &y, const char *str, unsigned int maxWidth, bool selected)
  200. {
  201. int offsetY = y - topleft;
  202. if (offsetY < 0)
  203. return 1;
  204. if (selected)
  205. {
  206. attron(A_REVERSE | A_BOLD);
  207. mvprintw(offsetY, x, "%s", str);
  208. attroff(A_REVERSE | A_BOLD);
  209. }
  210. else
  211. mvprintw(offsetY, x, "%s", str);
  212. return getNbLines(strlen(str) +x, maxWidth);
  213. }
  214. unsigned int CurseOutput::write(const int &x, const int &y, const std::string &str, unsigned int maxWidth, bool selected)
  215. {
  216. return write(x, y, str.c_str(), maxWidth, selected);
  217. }
  218. void CurseOutput::getScreenSize(std::pair<unsigned int, unsigned int> &screenSize, std::pair<int, int> &bs) const
  219. {
  220. getmaxyx(stdscr, screenSize.second, screenSize.first);
  221. getbegyx(stdscr, bs.second, bs.first);
  222. }
  223. const JSonElement *CurseOutput::findPrev(const JSonElement *item)
  224. {
  225. const JSonContainer *parent = item->getParent();
  226. if (parent == nullptr)
  227. return nullptr; // Root node, can't have brothers
  228. std::list<JSonElement *>::const_iterator it = parent->cbegin();
  229. const JSonObjectEntry *ent = dynamic_cast<const JSonObjectEntry *>(*it);
  230. const JSonElement *prevElem = ent ? **ent : (*it);
  231. if (prevElem == item || (ent && **ent == item))
  232. return nullptr; // First item
  233. while ((++it) != parent->cend())
  234. {
  235. ent = dynamic_cast<const JSonObjectEntry *>(*it);
  236. if (*it == item || (ent && **ent == item))
  237. return prevElem;
  238. prevElem = ent ? **ent : (*it);
  239. }
  240. return nullptr;
  241. }
  242. const JSonElement* CurseOutput::findNext(const JSonElement *item)
  243. {
  244. const JSonContainer *parent = item->getParent();
  245. if (parent == nullptr)
  246. return nullptr; // Root node, can't have brothers
  247. JSonContainer::const_iterator it = parent->cbegin();
  248. while (it != parent->cend())
  249. {
  250. const JSonObjectEntry *ent = dynamic_cast<const JSonObjectEntry *>(*it);
  251. if (*it == item || (ent && **ent == item))
  252. {
  253. it++;
  254. if (it == parent->cend())
  255. return findNext((const JSonElement*) parent); // Last item
  256. ent = dynamic_cast<const JSonObjectEntry *>(*it);
  257. if (ent)
  258. return **ent;
  259. return *it;
  260. }
  261. it++;
  262. }
  263. return findNext((const JSonElement*) parent);
  264. }
  265. void CurseOutput::checkSelection(const JSonElement *item, const JSonElement *parent, const std::pair<int, int> &cursor)
  266. {
  267. if (selection == item)
  268. {
  269. if (cursor.second <= topleft)
  270. selectIsFirst = true;
  271. selectFound = true;
  272. }
  273. else if (!selectFound)
  274. select_up = item;
  275. else if (!select_down)
  276. select_down = item;
  277. }
  278. /**
  279. * Read input and expect signal
  280. * @Return true on:
  281. * - Windows resized
  282. * - Key press and need redraw
  283. * false on:
  284. * - exit signal
  285. **/
  286. bool CurseOutput::readInput()
  287. {
  288. while (!breakLoop)
  289. {
  290. int c;
  291. c = getch();
  292. switch (c)
  293. {
  294. case 'q':
  295. case 'Q':
  296. return false;
  297. case KEY_UP:
  298. case 'K':
  299. case 'k':
  300. if (selectIsFirst && topleft)
  301. topleft = std::max(topleft -3, 0);
  302. else
  303. selection = select_up;
  304. return true;
  305. case KEY_DOWN:
  306. case 'j':
  307. case 'J':
  308. if (selectIsLast)
  309. topleft += 2;
  310. else
  311. selection = select_down;
  312. return true;
  313. case KEY_PPAGE:
  314. {
  315. const JSonElement *brother = findPrev(selection);
  316. if (brother == nullptr)
  317. {
  318. const JSonContainer *parent = selection->getParent();
  319. if (parent)
  320. selection = parent;
  321. else
  322. break;
  323. }
  324. else
  325. selection = brother;
  326. return true;
  327. break;
  328. }
  329. case KEY_NPAGE:
  330. {
  331. const JSonElement *brother = findNext(selection);
  332. if (brother)
  333. {
  334. selection = brother;
  335. return true;
  336. }
  337. break;
  338. }
  339. case 'l':
  340. case 'L':
  341. case KEY_RIGHT:
  342. {
  343. const JSonContainer *_selection = dynamic_cast<const JSonContainer *>(selection);
  344. if (!_selection)
  345. break;
  346. if (collapsed.erase((const JSonContainer *) selection))
  347. return true;
  348. if (!_selection->size())
  349. break;
  350. selection = _selection->firstChild();
  351. return true;
  352. }
  353. case 'h':
  354. case 'H':
  355. case KEY_LEFT:
  356. {
  357. const JSonContainer *_selection = dynamic_cast<const JSonContainer *>(selection);
  358. if (!_selection
  359. || collapsed.find((const JSonContainer *) selection) != collapsed.end()
  360. || (_selection && _selection->size() == 0))
  361. {
  362. const JSonContainer *parent = selection->getParent();
  363. selection = parent ? parent : selection;
  364. }
  365. else if (_selection)
  366. collapsed.insert((const JSonContainer *)selection);
  367. else
  368. break;
  369. return true;
  370. }
  371. }
  372. }
  373. return false;
  374. }
  375. void CurseOutput::init()
  376. {
  377. if (!isatty(fileno(stdin)) || !isatty(fileno(stdout)))
  378. {
  379. screen_fd = fopen("/dev/tty", "r+");
  380. setbuf(screen_fd, nullptr);
  381. screen = newterm(nullptr, screen_fd, screen_fd);
  382. }
  383. else
  384. {
  385. screen = newterm(nullptr, stdout, stdin);
  386. screen_fd = nullptr;
  387. }
  388. wtimeout(stdscr, 150);
  389. cbreak();
  390. clear();
  391. noecho();
  392. curs_set(false);
  393. keypad(stdscr, true);
  394. signal(SIGWINCH, _resizeFnc);
  395. signal(SIGINT, _resizeFnc);
  396. signal(SIGTERM, _resizeFnc);
  397. signal(SIGKILL, _resizeFnc);
  398. topleft = 0;
  399. }
  400. void CurseOutput::shutdown()
  401. {
  402. endwin();
  403. delscreen(screen);
  404. if (screen_fd)
  405. {
  406. fclose(screen_fd);
  407. screen_fd = nullptr;
  408. }
  409. screen = nullptr;
  410. }