Terminal.cpp 26 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this
  9. * list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include <AK/StringBuilder.h>
  27. #include <LibVT/Terminal.h>
  28. //#define TERMINAL_DEBUG
  29. namespace VT {
  30. Terminal::Terminal(TerminalClient& client)
  31. : m_client(client)
  32. {
  33. }
  34. Terminal::~Terminal()
  35. {
  36. }
  37. Terminal::Line::Line(u16 length)
  38. {
  39. set_length(length);
  40. }
  41. Terminal::Line::~Line()
  42. {
  43. delete[] characters;
  44. delete[] attributes;
  45. }
  46. void Terminal::Line::set_length(u16 new_length)
  47. {
  48. if (m_length == new_length)
  49. return;
  50. auto* new_characters = new u8[new_length];
  51. auto* new_attributes = new Attribute[new_length];
  52. memset(new_characters, ' ', new_length);
  53. if (characters && attributes) {
  54. memcpy(new_characters, characters, min(m_length, new_length));
  55. memcpy(new_attributes, attributes, min(m_length, new_length) * sizeof(Attribute));
  56. }
  57. delete[] characters;
  58. delete[] attributes;
  59. characters = new_characters;
  60. attributes = new_attributes;
  61. m_length = new_length;
  62. }
  63. void Terminal::Line::clear(Attribute attribute)
  64. {
  65. if (dirty) {
  66. memset(characters, ' ', m_length);
  67. for (u16 i = 0; i < m_length; ++i)
  68. attributes[i] = attribute;
  69. return;
  70. }
  71. for (unsigned i = 0; i < m_length; ++i) {
  72. if (characters[i] != ' ')
  73. dirty = true;
  74. characters[i] = ' ';
  75. }
  76. for (unsigned i = 0; i < m_length; ++i) {
  77. if (attributes[i] != attribute)
  78. dirty = true;
  79. attributes[i] = attribute;
  80. }
  81. }
  82. bool Terminal::Line::has_only_one_background_color() const
  83. {
  84. if (!m_length)
  85. return true;
  86. // FIXME: Cache this result?
  87. auto color = attributes[0].background_color;
  88. for (size_t i = 1; i < m_length; ++i) {
  89. if (attributes[i].background_color != color)
  90. return false;
  91. }
  92. return true;
  93. }
  94. void Terminal::clear()
  95. {
  96. for (size_t i = 0; i < rows(); ++i)
  97. line(i).clear(m_current_attribute);
  98. set_cursor(0, 0);
  99. }
  100. inline bool is_valid_parameter_character(u8 ch)
  101. {
  102. return ch >= 0x30 && ch <= 0x3f;
  103. }
  104. inline bool is_valid_intermediate_character(u8 ch)
  105. {
  106. return ch >= 0x20 && ch <= 0x2f;
  107. }
  108. inline bool is_valid_final_character(u8 ch)
  109. {
  110. return ch >= 0x40 && ch <= 0x7e;
  111. }
  112. void Terminal::escape$h_l(bool should_set, bool question_param, const ParamVector& params)
  113. {
  114. int mode = 2;
  115. if (params.size() > 0) {
  116. mode = params[0];
  117. }
  118. if (!question_param) {
  119. switch (mode) {
  120. // FIXME: implement *something* for this
  121. default:
  122. unimplemented_escape();
  123. break;
  124. }
  125. } else {
  126. switch (mode) {
  127. case 25:
  128. // Hide cursor command, but doesn't need to be run (for now, because
  129. // we don't do inverse control codes anyways)
  130. if (should_set)
  131. dbgprintf("Terminal: Hide Cursor escapecode recieved. Not needed: ignored.\n");
  132. else
  133. dbgprintf("Terminal: Show Cursor escapecode recieved. Not needed: ignored.\n");
  134. break;
  135. default:
  136. break;
  137. }
  138. }
  139. }
  140. void Terminal::SGR(const ParamVector& params)
  141. {
  142. // SGR – Select Graphic Rendition
  143. if (params.is_empty()) {
  144. m_current_attribute.reset();
  145. return;
  146. }
  147. if (params.size() == 3 && params[1] == 5) {
  148. if (params[0] == 38) {
  149. m_current_attribute.foreground_color = params[2];
  150. return;
  151. } else if (params[0] == 48) {
  152. m_current_attribute.background_color = params[2];
  153. return;
  154. }
  155. }
  156. for (auto param : params) {
  157. switch (param) {
  158. case 0:
  159. // Reset
  160. m_current_attribute.reset();
  161. break;
  162. case 1:
  163. m_current_attribute.flags |= Attribute::Bold;
  164. break;
  165. case 3:
  166. m_current_attribute.flags |= Attribute::Italic;
  167. break;
  168. case 4:
  169. m_current_attribute.flags |= Attribute::Underline;
  170. break;
  171. case 5:
  172. m_current_attribute.flags |= Attribute::Blink;
  173. break;
  174. case 7:
  175. m_current_attribute.flags |= Attribute::Negative;
  176. break;
  177. case 22:
  178. m_current_attribute.flags &= ~Attribute::Bold;
  179. break;
  180. case 23:
  181. m_current_attribute.flags &= ~Attribute::Italic;
  182. break;
  183. case 24:
  184. m_current_attribute.flags &= ~Attribute::Underline;
  185. break;
  186. case 25:
  187. m_current_attribute.flags &= ~Attribute::Blink;
  188. break;
  189. case 27:
  190. m_current_attribute.flags &= ~Attribute::Negative;
  191. break;
  192. case 30:
  193. case 31:
  194. case 32:
  195. case 33:
  196. case 34:
  197. case 35:
  198. case 36:
  199. case 37:
  200. // Foreground color
  201. if (m_current_attribute.flags & Attribute::Bold)
  202. param += 8;
  203. m_current_attribute.foreground_color = param - 30;
  204. break;
  205. case 39:
  206. // reset foreground
  207. m_current_attribute.foreground_color = Attribute::default_foreground_color;
  208. break;
  209. case 40:
  210. case 41:
  211. case 42:
  212. case 43:
  213. case 44:
  214. case 45:
  215. case 46:
  216. case 47:
  217. // Background color
  218. if (m_current_attribute.flags & Attribute::Bold)
  219. param += 8;
  220. m_current_attribute.background_color = param - 40;
  221. break;
  222. case 49:
  223. // reset background
  224. m_current_attribute.background_color = Attribute::default_background_color;
  225. break;
  226. default:
  227. dbgprintf("FIXME: SGR: p: %u\n", param);
  228. }
  229. }
  230. }
  231. void Terminal::escape$s(const ParamVector&)
  232. {
  233. m_saved_cursor_row = m_cursor_row;
  234. m_saved_cursor_column = m_cursor_column;
  235. }
  236. void Terminal::escape$u(const ParamVector&)
  237. {
  238. set_cursor(m_saved_cursor_row, m_saved_cursor_column);
  239. }
  240. void Terminal::escape$t(const ParamVector& params)
  241. {
  242. if (params.size() < 1)
  243. return;
  244. dbgprintf("FIXME: escape$t: Ps: %u (param count: %d)\n", params[0], params.size());
  245. }
  246. void Terminal::escape$r(const ParamVector& params)
  247. {
  248. unsigned top = 1;
  249. unsigned bottom = m_rows;
  250. if (params.size() >= 1)
  251. top = params[0];
  252. if (params.size() >= 2)
  253. bottom = params[1];
  254. if ((bottom - top) < 2 || bottom > m_rows) {
  255. dbgprintf("Error: escape$r: scrolling region invalid: %u-%u\n", top, bottom);
  256. return;
  257. }
  258. m_scroll_region_top = top - 1;
  259. m_scroll_region_bottom = bottom - 1;
  260. set_cursor(0, 0);
  261. }
  262. void Terminal::CUP(const ParamVector& params)
  263. {
  264. // CUP – Cursor Position
  265. unsigned row = 1;
  266. unsigned col = 1;
  267. if (params.size() >= 1)
  268. row = params[0];
  269. if (params.size() >= 2)
  270. col = params[1];
  271. set_cursor(row - 1, col - 1);
  272. }
  273. void Terminal::escape$f(const ParamVector& params)
  274. {
  275. // HVP – Horizontal and Vertical Position
  276. unsigned row = 1;
  277. unsigned col = 1;
  278. if (params.size() >= 1)
  279. row = params[0];
  280. if (params.size() >= 2)
  281. col = params[1];
  282. set_cursor(row - 1, col - 1);
  283. }
  284. void Terminal::CUU(const ParamVector& params)
  285. {
  286. // CUU – Cursor Up
  287. int num = 1;
  288. if (params.size() >= 1)
  289. num = params[0];
  290. if (num == 0)
  291. num = 1;
  292. int new_row = (int)m_cursor_row - num;
  293. if (new_row < 0)
  294. new_row = 0;
  295. set_cursor(new_row, m_cursor_column);
  296. }
  297. void Terminal::CUD(const ParamVector& params)
  298. {
  299. // CUD – Cursor Down
  300. int num = 1;
  301. if (params.size() >= 1)
  302. num = params[0];
  303. if (num == 0)
  304. num = 1;
  305. int new_row = (int)m_cursor_row + num;
  306. if (new_row >= m_rows)
  307. new_row = m_rows - 1;
  308. set_cursor(new_row, m_cursor_column);
  309. }
  310. void Terminal::CUF(const ParamVector& params)
  311. {
  312. // CUF – Cursor Forward
  313. int num = 1;
  314. if (params.size() >= 1)
  315. num = params[0];
  316. if (num == 0)
  317. num = 1;
  318. int new_column = (int)m_cursor_column + num;
  319. if (new_column >= m_columns)
  320. new_column = m_columns - 1;
  321. set_cursor(m_cursor_row, new_column);
  322. }
  323. void Terminal::CUB(const ParamVector& params)
  324. {
  325. // CUB – Cursor Backward
  326. int num = 1;
  327. if (params.size() >= 1)
  328. num = params[0];
  329. if (num == 0)
  330. num = 1;
  331. int new_column = (int)m_cursor_column - num;
  332. if (new_column < 0)
  333. new_column = 0;
  334. set_cursor(m_cursor_row, new_column);
  335. }
  336. void Terminal::escape$G(const ParamVector& params)
  337. {
  338. int new_column = 1;
  339. if (params.size() >= 1)
  340. new_column = params[0] - 1;
  341. if (new_column < 0)
  342. new_column = 0;
  343. set_cursor(m_cursor_row, new_column);
  344. }
  345. void Terminal::escape$b(const ParamVector& params)
  346. {
  347. if (params.size() < 1)
  348. return;
  349. for (unsigned i = 0; i < params[0]; ++i)
  350. put_character_at(m_cursor_row, m_cursor_column++, m_last_char);
  351. }
  352. void Terminal::escape$d(const ParamVector& params)
  353. {
  354. int new_row = 1;
  355. if (params.size() >= 1)
  356. new_row = params[0] - 1;
  357. if (new_row < 0)
  358. new_row = 0;
  359. set_cursor(new_row, m_cursor_column);
  360. }
  361. void Terminal::escape$X(const ParamVector& params)
  362. {
  363. // Erase characters (without moving cursor)
  364. int num = 1;
  365. if (params.size() >= 1)
  366. num = params[0];
  367. if (num == 0)
  368. num = 1;
  369. // Clear from cursor to end of line.
  370. for (int i = m_cursor_column; i < num; ++i) {
  371. put_character_at(m_cursor_row, i, ' ');
  372. }
  373. }
  374. void Terminal::EL(const ParamVector& params)
  375. {
  376. int mode = 0;
  377. if (params.size() >= 1)
  378. mode = params[0];
  379. switch (mode) {
  380. case 0:
  381. // Clear from cursor to end of line.
  382. for (int i = m_cursor_column; i < m_columns; ++i) {
  383. put_character_at(m_cursor_row, i, ' ');
  384. }
  385. break;
  386. case 1:
  387. // Clear from cursor to beginning of line.
  388. for (int i = 0; i <= m_cursor_column; ++i) {
  389. put_character_at(m_cursor_row, i, ' ');
  390. }
  391. break;
  392. case 2:
  393. // Clear the complete line
  394. for (int i = 0; i < m_columns; ++i) {
  395. put_character_at(m_cursor_row, i, ' ');
  396. }
  397. break;
  398. default:
  399. unimplemented_escape();
  400. break;
  401. }
  402. }
  403. void Terminal::ED(const ParamVector& params)
  404. {
  405. // ED - Erase in Display
  406. int mode = 0;
  407. if (params.size() >= 1)
  408. mode = params[0];
  409. switch (mode) {
  410. case 0:
  411. // Clear from cursor to end of screen.
  412. for (int i = m_cursor_column; i < m_columns; ++i)
  413. put_character_at(m_cursor_row, i, ' ');
  414. for (int row = m_cursor_row + 1; row < m_rows; ++row) {
  415. for (int column = 0; column < m_columns; ++column) {
  416. put_character_at(row, column, ' ');
  417. }
  418. }
  419. break;
  420. case 1:
  421. // Clear from cursor to beginning of screen.
  422. for (int i = m_cursor_column; i >= 0; --i)
  423. put_character_at(m_cursor_row, i, ' ');
  424. for (int row = m_cursor_row - 1; row >= 0; --row) {
  425. for (int column = 0; column < m_columns; ++column) {
  426. put_character_at(row, column, ' ');
  427. }
  428. }
  429. break;
  430. case 2:
  431. clear();
  432. break;
  433. case 3:
  434. // FIXME: <esc>[3J should also clear the scrollback buffer.
  435. clear();
  436. break;
  437. default:
  438. unimplemented_escape();
  439. break;
  440. }
  441. }
  442. void Terminal::escape$S(const ParamVector& params)
  443. {
  444. int count = 1;
  445. if (params.size() >= 1)
  446. count = params[0];
  447. for (u16 i = 0; i < count; i++)
  448. scroll_up();
  449. }
  450. void Terminal::escape$T(const ParamVector& params)
  451. {
  452. int count = 1;
  453. if (params.size() >= 1)
  454. count = params[0];
  455. for (u16 i = 0; i < count; i++)
  456. scroll_down();
  457. }
  458. void Terminal::escape$L(const ParamVector& params)
  459. {
  460. int count = 1;
  461. if (params.size() >= 1)
  462. count = params[0];
  463. invalidate_cursor();
  464. for (; count > 0; --count) {
  465. m_lines.insert(m_cursor_row + m_scroll_region_top, make<Line>(m_columns));
  466. if (m_scroll_region_bottom + 1 < m_lines.size())
  467. m_lines.remove(m_scroll_region_bottom + 1);
  468. else
  469. m_lines.remove(m_lines.size() - 1);
  470. }
  471. m_need_full_flush = true;
  472. }
  473. void Terminal::DA(const ParamVector&)
  474. {
  475. // DA - Device Attributes
  476. emit_string("\033[?1;0c");
  477. }
  478. void Terminal::escape$M(const ParamVector& params)
  479. {
  480. int count = 1;
  481. if (params.size() >= 1)
  482. count = params[0];
  483. if (count == 1 && m_cursor_row == 0) {
  484. scroll_up();
  485. return;
  486. }
  487. int max_count = m_rows - (m_scroll_region_top + m_cursor_row);
  488. count = min(count, max_count);
  489. for (int c = count; c > 0; --c) {
  490. m_lines.remove(m_cursor_row + m_scroll_region_top);
  491. if (m_scroll_region_bottom < m_lines.size())
  492. m_lines.insert(m_scroll_region_bottom, make<Line>(m_columns));
  493. else
  494. m_lines.append(make<Line>(m_columns));
  495. }
  496. }
  497. void Terminal::escape$P(const ParamVector& params)
  498. {
  499. int num = 1;
  500. if (params.size() >= 1)
  501. num = params[0];
  502. if (num == 0)
  503. num = 1;
  504. auto& line = this->line(m_cursor_row);
  505. // Move n characters of line to the left
  506. for (int i = m_cursor_column; i < line.m_length - num; i++)
  507. line.characters[i] = line.characters[i + num];
  508. // Fill remainder of line with blanks
  509. for (int i = line.m_length - num; i < line.m_length; i++)
  510. line.characters[i] = ' ';
  511. line.dirty = true;
  512. }
  513. void Terminal::execute_xterm_command()
  514. {
  515. m_final = '@';
  516. bool ok;
  517. unsigned value = String::copy(m_xterm_param1).to_uint(ok);
  518. if (ok) {
  519. switch (value) {
  520. case 0:
  521. case 1:
  522. case 2:
  523. m_client.set_window_title(String::copy(m_xterm_param2));
  524. break;
  525. default:
  526. unimplemented_xterm_escape();
  527. break;
  528. }
  529. }
  530. m_xterm_param1.clear_with_capacity();
  531. m_xterm_param2.clear_with_capacity();
  532. }
  533. void Terminal::execute_escape_sequence(u8 final)
  534. {
  535. bool question_param = false;
  536. m_final = final;
  537. ParamVector params;
  538. if (m_parameters.size() > 0 && m_parameters[0] == '?') {
  539. question_param = true;
  540. m_parameters.remove(0);
  541. }
  542. auto paramparts = String::copy(m_parameters).split(';');
  543. for (auto& parampart : paramparts) {
  544. bool ok;
  545. unsigned value = parampart.to_uint(ok);
  546. if (!ok) {
  547. // FIXME: Should we do something else?
  548. m_parameters.clear_with_capacity();
  549. m_intermediates.clear_with_capacity();
  550. return;
  551. }
  552. params.append(value);
  553. }
  554. #if defined(TERMINAL_DEBUG)
  555. dbgprintf("Terminal::execute_escape_sequence: Handled final '%c'\n", final);
  556. dbgprintf("Params: ");
  557. for (auto& p : params) {
  558. dbgprintf("%d ", p);
  559. }
  560. dbgprintf("\b\n");
  561. #endif
  562. switch (final) {
  563. case 'A':
  564. CUU(params);
  565. break;
  566. case 'B':
  567. CUD(params);
  568. break;
  569. case 'C':
  570. CUF(params);
  571. break;
  572. case 'D':
  573. CUB(params);
  574. break;
  575. case 'H':
  576. CUP(params);
  577. break;
  578. case 'J':
  579. ED(params);
  580. break;
  581. case 'K':
  582. EL(params);
  583. break;
  584. case 'M':
  585. escape$M(params);
  586. break;
  587. case 'P':
  588. escape$P(params);
  589. break;
  590. case 'S':
  591. escape$S(params);
  592. break;
  593. case 'T':
  594. escape$T(params);
  595. break;
  596. case 'L':
  597. escape$L(params);
  598. break;
  599. case 'G':
  600. escape$G(params);
  601. break;
  602. case 'X':
  603. escape$X(params);
  604. break;
  605. case 'b':
  606. escape$b(params);
  607. break;
  608. case 'd':
  609. escape$d(params);
  610. break;
  611. case 'm':
  612. SGR(params);
  613. break;
  614. case 's':
  615. escape$s(params);
  616. break;
  617. case 'u':
  618. escape$u(params);
  619. break;
  620. case 't':
  621. escape$t(params);
  622. break;
  623. case 'r':
  624. escape$r(params);
  625. break;
  626. case 'l':
  627. escape$h_l(true, question_param, params);
  628. break;
  629. case 'h':
  630. escape$h_l(false, question_param, params);
  631. break;
  632. case 'c':
  633. DA(params);
  634. break;
  635. case 'f':
  636. escape$f(params);
  637. break;
  638. default:
  639. dbgprintf("Terminal::execute_escape_sequence: Unhandled final '%c'\n", final);
  640. break;
  641. }
  642. #if defined(TERMINAL_DEBUG)
  643. dbgprintf("\n");
  644. for (auto& line : m_lines) {
  645. dbgprintf("Terminal: Line: ");
  646. for (int i = 0; i < line.m_length; i++) {
  647. dbgprintf("%c", line.characters[i]);
  648. }
  649. dbgprintf("\n");
  650. }
  651. #endif
  652. m_parameters.clear_with_capacity();
  653. m_intermediates.clear_with_capacity();
  654. }
  655. void Terminal::newline()
  656. {
  657. u16 new_row = m_cursor_row;
  658. if (m_cursor_row == m_scroll_region_bottom) {
  659. scroll_up();
  660. } else {
  661. ++new_row;
  662. }
  663. set_cursor(new_row, 0);
  664. }
  665. void Terminal::scroll_up()
  666. {
  667. // NOTE: We have to invalidate the cursor first.
  668. invalidate_cursor();
  669. if (m_scroll_region_top == 0) {
  670. auto line = move(m_lines.ptr_at(m_scroll_region_top));
  671. m_history.append(move(line));
  672. while (m_history.size() > max_history_size())
  673. m_history.take_first();
  674. m_client.terminal_history_changed();
  675. }
  676. m_lines.remove(m_scroll_region_top);
  677. m_lines.insert(m_scroll_region_bottom, make<Line>(m_columns));
  678. m_need_full_flush = true;
  679. }
  680. void Terminal::scroll_down()
  681. {
  682. // NOTE: We have to invalidate the cursor first.
  683. invalidate_cursor();
  684. m_lines.remove(m_scroll_region_bottom);
  685. m_lines.insert(m_scroll_region_top, make<Line>(m_columns));
  686. m_need_full_flush = true;
  687. }
  688. void Terminal::set_cursor(unsigned a_row, unsigned a_column)
  689. {
  690. unsigned row = min(a_row, m_rows - 1u);
  691. unsigned column = min(a_column, m_columns - 1u);
  692. if (row == m_cursor_row && column == m_cursor_column)
  693. return;
  694. ASSERT(row < rows());
  695. ASSERT(column < columns());
  696. invalidate_cursor();
  697. m_cursor_row = row;
  698. m_cursor_column = column;
  699. m_stomp = false;
  700. invalidate_cursor();
  701. }
  702. void Terminal::put_character_at(unsigned row, unsigned column, u8 ch)
  703. {
  704. ASSERT(row < rows());
  705. ASSERT(column < columns());
  706. auto& line = this->line(row);
  707. line.characters[column] = ch;
  708. line.attributes[column] = m_current_attribute;
  709. line.attributes[column].flags |= Attribute::Touched;
  710. line.dirty = true;
  711. m_last_char = ch;
  712. }
  713. void Terminal::NEL()
  714. {
  715. // NEL - Next Line
  716. newline();
  717. }
  718. void Terminal::IND()
  719. {
  720. // IND - Index (move down)
  721. CUD({});
  722. }
  723. void Terminal::RI()
  724. {
  725. // RI - Reverse Index (move up)
  726. CUU({});
  727. }
  728. void Terminal::on_char(u8 ch)
  729. {
  730. #ifdef TERMINAL_DEBUG
  731. dbgprintf("Terminal::on_char: %b (%c), fg=%u, bg=%u\n", ch, ch, m_current_attribute.foreground_color, m_current_attribute.background_color);
  732. #endif
  733. switch (m_escape_state) {
  734. case GotEscape:
  735. if (ch == '[') {
  736. m_escape_state = ExpectParameter;
  737. } else if (ch == '(') {
  738. m_swallow_current = true;
  739. m_escape_state = ExpectParameter;
  740. } else if (ch == ']') {
  741. m_escape_state = ExpectXtermParameter1;
  742. } else if (ch == '#') {
  743. m_escape_state = ExpectHashtagDigit;
  744. } else if (ch == 'D') {
  745. IND();
  746. m_escape_state = Normal;
  747. return;
  748. } else if (ch == 'M') {
  749. RI();
  750. m_escape_state = Normal;
  751. return;
  752. } else if (ch == 'E') {
  753. NEL();
  754. m_escape_state = Normal;
  755. return;
  756. } else {
  757. dbg() << "Unexpected character in GotEscape '" << (char)ch << "'";
  758. m_escape_state = Normal;
  759. }
  760. return;
  761. case ExpectHashtagDigit:
  762. if (ch >= '0' && ch <= '9') {
  763. execute_hashtag(ch);
  764. m_escape_state = Normal;
  765. }
  766. break;
  767. case ExpectXtermParameter1:
  768. if (ch != ';') {
  769. m_xterm_param1.append(ch);
  770. return;
  771. }
  772. m_escape_state = ExpectXtermParameter2;
  773. return;
  774. case ExpectXtermParameter2:
  775. if (ch != '\007') {
  776. m_xterm_param2.append(ch);
  777. return;
  778. }
  779. m_escape_state = ExpectXtermFinal;
  780. [[fallthrough]];
  781. case ExpectXtermFinal:
  782. m_escape_state = Normal;
  783. if (ch == '\007')
  784. execute_xterm_command();
  785. return;
  786. case ExpectParameter:
  787. if (is_valid_parameter_character(ch)) {
  788. m_parameters.append(ch);
  789. return;
  790. }
  791. m_escape_state = ExpectIntermediate;
  792. [[fallthrough]];
  793. case ExpectIntermediate:
  794. if (is_valid_intermediate_character(ch)) {
  795. m_intermediates.append(ch);
  796. return;
  797. }
  798. m_escape_state = ExpectFinal;
  799. [[fallthrough]];
  800. case ExpectFinal:
  801. if (is_valid_final_character(ch)) {
  802. m_escape_state = Normal;
  803. if (!m_swallow_current)
  804. execute_escape_sequence(ch);
  805. m_swallow_current = false;
  806. return;
  807. }
  808. m_escape_state = Normal;
  809. m_swallow_current = false;
  810. return;
  811. case Normal:
  812. break;
  813. }
  814. switch (ch) {
  815. case '\0':
  816. return;
  817. case '\033':
  818. m_escape_state = GotEscape;
  819. m_swallow_current = false;
  820. return;
  821. case 8: // Backspace
  822. if (m_cursor_column) {
  823. set_cursor(m_cursor_row, m_cursor_column - 1);
  824. return;
  825. }
  826. return;
  827. case '\a':
  828. m_client.beep();
  829. return;
  830. case '\t': {
  831. for (unsigned i = m_cursor_column + 1; i < columns(); ++i) {
  832. if (m_horizontal_tabs[i]) {
  833. set_cursor(m_cursor_row, i);
  834. return;
  835. }
  836. }
  837. return;
  838. }
  839. case '\r':
  840. set_cursor(m_cursor_row, 0);
  841. return;
  842. case '\n':
  843. newline();
  844. return;
  845. }
  846. auto new_column = m_cursor_column + 1;
  847. if (new_column < columns()) {
  848. put_character_at(m_cursor_row, m_cursor_column, ch);
  849. set_cursor(m_cursor_row, new_column);
  850. } else {
  851. if (m_stomp) {
  852. m_stomp = false;
  853. newline();
  854. put_character_at(m_cursor_row, m_cursor_column, ch);
  855. set_cursor(m_cursor_row, 1);
  856. } else {
  857. // Curious: We wait once on the right-hand side
  858. m_stomp = true;
  859. put_character_at(m_cursor_row, m_cursor_column, ch);
  860. }
  861. }
  862. }
  863. void Terminal::inject_string(const StringView& str)
  864. {
  865. for (size_t i = 0; i < str.length(); ++i)
  866. on_char(str[i]);
  867. }
  868. void Terminal::emit_string(const StringView& str)
  869. {
  870. for (size_t i = 0; i < str.length(); ++i)
  871. m_client.emit_char(str[i]);
  872. }
  873. void Terminal::unimplemented_escape()
  874. {
  875. StringBuilder builder;
  876. builder.appendf("((Unimplemented escape: %c", m_final);
  877. if (!m_parameters.is_empty()) {
  878. builder.append(" parameters:");
  879. for (int i = 0; i < m_parameters.size(); ++i)
  880. builder.append((char)m_parameters[i]);
  881. }
  882. if (!m_intermediates.is_empty()) {
  883. builder.append(" intermediates:");
  884. for (int i = 0; i < m_intermediates.size(); ++i)
  885. builder.append((char)m_intermediates[i]);
  886. }
  887. builder.append("))");
  888. inject_string(builder.to_string());
  889. }
  890. void Terminal::unimplemented_xterm_escape()
  891. {
  892. auto message = String::format("((Unimplemented xterm escape: %c))\n", m_final);
  893. inject_string(message);
  894. }
  895. void Terminal::set_size(u16 columns, u16 rows)
  896. {
  897. if (!columns)
  898. columns = 1;
  899. if (!rows)
  900. rows = 1;
  901. if (columns == m_columns && rows == m_rows)
  902. return;
  903. #if defined(TERMINAL_DEBUG)
  904. dbgprintf("Terminal: RESIZE to: %d rows\n", rows);
  905. #endif
  906. if (rows > m_rows) {
  907. while (m_lines.size() < rows)
  908. m_lines.append(make<Line>(columns));
  909. } else {
  910. m_lines.shrink(rows);
  911. }
  912. for (int i = 0; i < rows; ++i)
  913. m_lines[i].set_length(columns);
  914. m_columns = columns;
  915. m_rows = rows;
  916. m_scroll_region_top = 0;
  917. m_scroll_region_bottom = rows - 1;
  918. m_cursor_row = min((int)m_cursor_row, m_rows - 1);
  919. m_cursor_column = min((int)m_cursor_column, m_columns - 1);
  920. m_saved_cursor_row = min((int)m_saved_cursor_row, m_rows - 1);
  921. m_saved_cursor_column = min((int)m_saved_cursor_column, m_columns - 1);
  922. m_horizontal_tabs.resize(columns);
  923. for (unsigned i = 0; i < columns; ++i)
  924. m_horizontal_tabs[i] = (i % 8) == 0;
  925. // Rightmost column is always last tab on line.
  926. m_horizontal_tabs[columns - 1] = 1;
  927. m_client.terminal_did_resize(m_columns, m_rows);
  928. }
  929. void Terminal::invalidate_cursor()
  930. {
  931. line(m_cursor_row).dirty = true;
  932. }
  933. void Terminal::execute_hashtag(u8 hashtag)
  934. {
  935. switch (hashtag) {
  936. case '8':
  937. // Confidence Test - Fill screen with E's
  938. for (size_t row = 0; row < m_rows; ++row) {
  939. for (size_t column = 0; column < m_columns; ++column) {
  940. put_character_at(row, column, 'E');
  941. }
  942. }
  943. break;
  944. default:
  945. dbg() << "Unknown hashtag: '" << hashtag << "'";
  946. }
  947. }
  948. }