Editor.cpp 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386
  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 "Editor.h"
  27. #include <AK/StringBuilder.h>
  28. #include <AK/Utf32View.h>
  29. #include <AK/Utf8View.h>
  30. #include <ctype.h>
  31. #include <stdio.h>
  32. #include <sys/ioctl.h>
  33. #include <sys/select.h>
  34. #include <sys/time.h>
  35. #include <unistd.h>
  36. // #define SUGGESTIONS_DEBUG
  37. namespace Line {
  38. Editor::Editor(Configuration configuration)
  39. : m_configuration(move(configuration))
  40. {
  41. m_always_refresh = configuration.refresh_behaviour == Configuration::RefreshBehaviour::Eager;
  42. m_pending_chars = ByteBuffer::create_uninitialized(0);
  43. struct winsize ws;
  44. if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) {
  45. m_num_columns = 80;
  46. m_num_lines = 25;
  47. } else {
  48. m_num_columns = ws.ws_col;
  49. m_num_lines = ws.ws_row;
  50. }
  51. m_suggestion_display = make<XtermSuggestionDisplay>(m_num_lines, m_num_columns);
  52. }
  53. Editor::~Editor()
  54. {
  55. if (m_initialized)
  56. restore();
  57. }
  58. void Editor::add_to_history(const String& line)
  59. {
  60. if ((m_history.size() + 1) > m_history_capacity)
  61. m_history.take_first();
  62. m_history.append(line);
  63. }
  64. void Editor::clear_line()
  65. {
  66. for (size_t i = 0; i < m_cursor; ++i)
  67. fputc(0x8, stdout);
  68. fputs("\033[K", stdout);
  69. fflush(stdout);
  70. m_buffer.clear();
  71. m_cursor = 0;
  72. m_inline_search_cursor = m_cursor;
  73. }
  74. void Editor::insert(const Utf32View& string)
  75. {
  76. for (size_t i = 0; i < string.length(); ++i)
  77. insert(string.codepoints()[i]);
  78. }
  79. void Editor::insert(const String& string)
  80. {
  81. for (auto ch : Utf8View { string })
  82. insert(ch);
  83. }
  84. void Editor::insert(const u32 cp)
  85. {
  86. StringBuilder builder;
  87. builder.append(Utf32View(&cp, 1));
  88. auto str = builder.build();
  89. m_pending_chars.append(str.characters(), str.length());
  90. readjust_anchored_styles(m_cursor, ModificationKind::Insertion);
  91. if (m_cursor == m_buffer.size()) {
  92. m_buffer.append(cp);
  93. m_cursor = m_buffer.size();
  94. m_inline_search_cursor = m_cursor;
  95. return;
  96. }
  97. m_buffer.insert(m_cursor, cp);
  98. ++m_chars_inserted_in_the_middle;
  99. ++m_cursor;
  100. m_inline_search_cursor = m_cursor;
  101. }
  102. void Editor::register_character_input_callback(char ch, Function<bool(Editor&)> callback)
  103. {
  104. if (m_key_callbacks.contains(ch)) {
  105. dbg() << "Key callback registered twice for " << ch;
  106. ASSERT_NOT_REACHED();
  107. }
  108. m_key_callbacks.set(ch, make<KeyCallback>(move(callback)));
  109. }
  110. static size_t codepoint_length_in_utf8(u32 codepoint)
  111. {
  112. if (codepoint <= 0x7f)
  113. return 1;
  114. if (codepoint <= 0x07ff)
  115. return 2;
  116. if (codepoint <= 0xffff)
  117. return 3;
  118. if (codepoint <= 0x10ffff)
  119. return 4;
  120. return 3;
  121. }
  122. // buffer [ 0 1 2 3 . . . A . . . B . . . M . . . N ]
  123. // ^ ^ ^ ^
  124. // | | | +- end of buffer
  125. // | | +- scan offset = M
  126. // | +- range end = M - B
  127. // +- range start = M - A
  128. // This method converts a byte range defined by [start_byte_offset, end_byte_offset] to a codepoint range [M - A, M - B] as shown in the diagram above.
  129. // If `reverse' is true, A and B are before M, if not, A and B are after M.
  130. Editor::CodepointRange Editor::byte_offset_range_to_codepoint_offset_range(size_t start_byte_offset, size_t end_byte_offset, size_t scan_codepoint_offset, bool reverse) const
  131. {
  132. size_t byte_offset = 0;
  133. size_t codepoint_offset = scan_codepoint_offset + (reverse ? 1 : 0);
  134. CodepointRange range;
  135. for (;;) {
  136. if (!reverse) {
  137. if (codepoint_offset >= m_buffer.size())
  138. break;
  139. } else {
  140. if (codepoint_offset == 0)
  141. break;
  142. }
  143. if (byte_offset > end_byte_offset)
  144. break;
  145. if (byte_offset < start_byte_offset)
  146. ++range.start;
  147. if (byte_offset < end_byte_offset)
  148. ++range.end;
  149. byte_offset += codepoint_length_in_utf8(m_buffer[reverse ? --codepoint_offset : codepoint_offset++]);
  150. }
  151. return range;
  152. }
  153. void Editor::stylize(const Span& span, const Style& style)
  154. {
  155. if (style.is_empty())
  156. return;
  157. auto start = span.beginning();
  158. auto end = span.end();
  159. if (span.mode() == Span::ByteOriented) {
  160. auto offsets = byte_offset_range_to_codepoint_offset_range(start, end, 0);
  161. start = offsets.start;
  162. end = offsets.end;
  163. }
  164. auto& spans_starting = style.is_anchored() ? m_anchored_spans_starting : m_spans_starting;
  165. auto& spans_ending = style.is_anchored() ? m_anchored_spans_ending : m_spans_ending;
  166. auto starting_map = spans_starting.get(start).value_or({});
  167. if (!starting_map.contains(end))
  168. m_refresh_needed = true;
  169. starting_map.set(end, style);
  170. spans_starting.set(start, starting_map);
  171. auto ending_map = spans_ending.get(end).value_or({});
  172. if (!ending_map.contains(start))
  173. m_refresh_needed = true;
  174. ending_map.set(start, style);
  175. spans_ending.set(end, ending_map);
  176. }
  177. void Editor::suggest(size_t invariant_offset, size_t static_offset, Span::Mode offset_mode) const
  178. {
  179. auto internal_static_offset = static_offset;
  180. auto internal_invariant_offset = invariant_offset;
  181. if (offset_mode == Span::Mode::ByteOriented) {
  182. // FIXME: We're assuming that invariant_offset points to the end of the available data
  183. // this is not necessarily true, but is true in most cases.
  184. auto offsets = byte_offset_range_to_codepoint_offset_range(internal_static_offset, internal_invariant_offset + internal_static_offset, m_cursor - 1, true);
  185. internal_static_offset = offsets.start;
  186. internal_invariant_offset = offsets.end - offsets.start;
  187. }
  188. m_suggestion_manager.set_suggestion_variants(internal_static_offset, internal_invariant_offset, 0);
  189. }
  190. Result<String, Editor::Error> Editor::get_line(const String& prompt)
  191. {
  192. initialize();
  193. m_is_editing = true;
  194. set_prompt(prompt);
  195. reset();
  196. set_origin();
  197. strip_styles(true);
  198. m_history_cursor = m_history.size();
  199. for (;;) {
  200. if (m_always_refresh)
  201. m_refresh_needed = true;
  202. refresh_display();
  203. if (m_input_error.has_value()) {
  204. return m_input_error.value();
  205. }
  206. if (m_finish) {
  207. m_finish = false;
  208. printf("\n");
  209. fflush(stdout);
  210. auto string = line();
  211. m_buffer.clear();
  212. m_is_editing = false;
  213. restore();
  214. return string;
  215. }
  216. char keybuf[16];
  217. ssize_t nread = 0;
  218. if (!m_incomplete_data.size())
  219. nread = read(0, keybuf, sizeof(keybuf));
  220. if (nread < 0) {
  221. if (errno == EINTR) {
  222. if (!m_was_interrupted) {
  223. if (m_was_resized)
  224. continue;
  225. finish();
  226. continue;
  227. }
  228. m_was_interrupted = false;
  229. if (!m_buffer.is_empty())
  230. printf("^C");
  231. m_buffer.clear();
  232. m_cursor = 0;
  233. if (on_interrupt_handled)
  234. on_interrupt_handled();
  235. m_refresh_needed = true;
  236. continue;
  237. }
  238. ScopedValueRollback errno_restorer(errno);
  239. perror("read failed");
  240. return Error::ReadFailure;
  241. }
  242. m_incomplete_data.append(keybuf, nread);
  243. nread = m_incomplete_data.size();
  244. if (nread == 0)
  245. return Error::Empty;
  246. auto reverse_tab = false;
  247. auto ctrl_held = false;
  248. // Discard starting bytes until they make sense as utf-8.
  249. size_t valid_bytes = 0;
  250. while (nread) {
  251. Utf8View { StringView { m_incomplete_data.data(), (size_t)nread } }.validate(valid_bytes);
  252. if (valid_bytes)
  253. break;
  254. m_incomplete_data.take_first();
  255. --nread;
  256. }
  257. Utf8View input_view { StringView { m_incomplete_data.data(), valid_bytes } };
  258. size_t consumed_codepoints = 0;
  259. for (auto codepoint : input_view) {
  260. if (m_finish)
  261. break;
  262. ++consumed_codepoints;
  263. if (codepoint == 0)
  264. continue;
  265. switch (m_state) {
  266. case InputState::ExpectBracket:
  267. if (codepoint == '[') {
  268. m_state = InputState::ExpectFinal;
  269. continue;
  270. } else {
  271. m_state = InputState::Free;
  272. break;
  273. }
  274. case InputState::ExpectFinal:
  275. switch (codepoint) {
  276. case 'O': // mod_ctrl
  277. ctrl_held = true;
  278. continue;
  279. case 'A': // up
  280. {
  281. m_searching_backwards = true;
  282. auto inline_search_cursor = m_inline_search_cursor;
  283. StringBuilder builder;
  284. builder.append(Utf32View { m_buffer.data(), inline_search_cursor });
  285. String search_phrase = builder.to_string();
  286. if (search(search_phrase, true, true)) {
  287. ++m_search_offset;
  288. } else {
  289. insert(search_phrase);
  290. }
  291. m_inline_search_cursor = inline_search_cursor;
  292. m_state = InputState::Free;
  293. ctrl_held = false;
  294. continue;
  295. }
  296. case 'B': // down
  297. {
  298. auto inline_search_cursor = m_inline_search_cursor;
  299. StringBuilder builder;
  300. builder.append(Utf32View { m_buffer.data(), inline_search_cursor });
  301. String search_phrase = builder.to_string();
  302. auto search_changed_directions = m_searching_backwards;
  303. m_searching_backwards = false;
  304. if (m_search_offset > 0) {
  305. m_search_offset -= 1 + search_changed_directions;
  306. if (!search(search_phrase, true, true)) {
  307. insert(search_phrase);
  308. }
  309. } else {
  310. m_search_offset = 0;
  311. m_cursor = 0;
  312. m_buffer.clear();
  313. insert(search_phrase);
  314. m_refresh_needed = true;
  315. }
  316. m_inline_search_cursor = inline_search_cursor;
  317. m_state = InputState::Free;
  318. ctrl_held = false;
  319. continue;
  320. }
  321. case 'D': // left
  322. if (m_cursor > 0) {
  323. if (ctrl_held) {
  324. auto skipped_at_least_one_character = false;
  325. for (;;) {
  326. if (m_cursor == 0)
  327. break;
  328. if (skipped_at_least_one_character && isspace(m_buffer[m_cursor - 1])) // stop *after* a space, but only if it changes the position
  329. break;
  330. skipped_at_least_one_character = true;
  331. --m_cursor;
  332. }
  333. } else {
  334. --m_cursor;
  335. }
  336. }
  337. m_inline_search_cursor = m_cursor;
  338. m_state = InputState::Free;
  339. ctrl_held = false;
  340. continue;
  341. case 'C': // right
  342. if (m_cursor < m_buffer.size()) {
  343. if (ctrl_held) {
  344. // Temporarily put a space at the end of our buffer,
  345. // doing this greatly simplifies the logic below.
  346. m_buffer.append(' ');
  347. for (;;) {
  348. if (m_cursor >= m_buffer.size())
  349. break;
  350. if (isspace(m_buffer[++m_cursor]))
  351. break;
  352. }
  353. m_buffer.take_last();
  354. } else {
  355. ++m_cursor;
  356. }
  357. }
  358. m_inline_search_cursor = m_cursor;
  359. m_search_offset = 0;
  360. m_state = InputState::Free;
  361. ctrl_held = false;
  362. continue;
  363. case 'H':
  364. m_cursor = 0;
  365. m_inline_search_cursor = m_cursor;
  366. m_search_offset = 0;
  367. m_state = InputState::Free;
  368. ctrl_held = false;
  369. continue;
  370. case 'F':
  371. m_cursor = m_buffer.size();
  372. m_state = InputState::Free;
  373. m_inline_search_cursor = m_cursor;
  374. m_search_offset = 0;
  375. ctrl_held = false;
  376. continue;
  377. case 'Z': // shift+tab
  378. reverse_tab = true;
  379. m_state = InputState::Free;
  380. ctrl_held = false;
  381. break;
  382. case '3':
  383. if (m_cursor == m_buffer.size()) {
  384. fputc('\a', stdout);
  385. fflush(stdout);
  386. continue;
  387. }
  388. remove_at_index(m_cursor);
  389. m_refresh_needed = true;
  390. m_search_offset = 0;
  391. m_state = InputState::ExpectTerminator;
  392. ctrl_held = false;
  393. continue;
  394. default:
  395. dbgprintf("Shell: Unhandled final: %02x (%c)\r\n", codepoint, codepoint);
  396. m_state = InputState::Free;
  397. ctrl_held = false;
  398. continue;
  399. }
  400. break;
  401. case InputState::ExpectTerminator:
  402. m_state = InputState::Free;
  403. continue;
  404. case InputState::Free:
  405. if (codepoint == 27) {
  406. m_state = InputState::ExpectBracket;
  407. continue;
  408. }
  409. break;
  410. }
  411. auto cb = m_key_callbacks.get(codepoint);
  412. if (cb.has_value()) {
  413. if (!cb.value()->callback(*this)) {
  414. continue;
  415. }
  416. }
  417. m_search_offset = 0; // reset search offset on any key
  418. if (codepoint == '\t' || reverse_tab) {
  419. if (!on_tab_complete)
  420. continue;
  421. // Reverse tab can count as regular tab here.
  422. m_times_tab_pressed++;
  423. int token_start = m_cursor;
  424. // Ask for completions only on the first tab
  425. // and scan for the largest common prefix to display,
  426. // further tabs simply show the cached completions.
  427. if (m_times_tab_pressed == 1) {
  428. m_suggestion_manager.set_suggestions(on_tab_complete(*this));
  429. m_prompt_lines_at_suggestion_initiation = num_lines();
  430. if (m_suggestion_manager.count() == 0) {
  431. // There are no suggestions, beep.
  432. putchar('\a');
  433. fflush(stdout);
  434. }
  435. }
  436. // Adjust already incremented / decremented index when switching tab direction.
  437. if (reverse_tab && m_tab_direction != TabDirection::Backward) {
  438. m_suggestion_manager.previous();
  439. m_suggestion_manager.previous();
  440. m_tab_direction = TabDirection::Backward;
  441. }
  442. if (!reverse_tab && m_tab_direction != TabDirection::Forward) {
  443. m_suggestion_manager.next();
  444. m_suggestion_manager.next();
  445. m_tab_direction = TabDirection::Forward;
  446. }
  447. reverse_tab = false;
  448. auto completion_mode = m_times_tab_pressed == 1 ? SuggestionManager::CompletePrefix : m_times_tab_pressed == 2 ? SuggestionManager::ShowSuggestions : SuggestionManager::CycleSuggestions;
  449. auto completion_result = m_suggestion_manager.attempt_completion(completion_mode, token_start);
  450. auto new_cursor = m_cursor + completion_result.new_cursor_offset;
  451. for (size_t i = completion_result.offset_region_to_remove.start; i < completion_result.offset_region_to_remove.end; ++i)
  452. remove_at_index(new_cursor);
  453. m_cursor = new_cursor;
  454. m_inline_search_cursor = new_cursor;
  455. m_refresh_needed = true;
  456. for (auto& view : completion_result.insert)
  457. insert(view);
  458. if (completion_result.style_to_apply.has_value()) {
  459. // Apply the style of the last suggestion.
  460. readjust_anchored_styles(m_suggestion_manager.current_suggestion().start_index, ModificationKind::ForcedOverlapRemoval);
  461. stylize({ m_suggestion_manager.current_suggestion().start_index, m_cursor, Span::Mode::CodepointOriented }, completion_result.style_to_apply.value());
  462. }
  463. switch (completion_result.new_completion_mode) {
  464. case SuggestionManager::DontComplete:
  465. m_times_tab_pressed = 0;
  466. break;
  467. case SuggestionManager::CompletePrefix:
  468. break;
  469. default:
  470. ++m_times_tab_pressed;
  471. break;
  472. }
  473. if (m_times_tab_pressed > 1) {
  474. if (m_suggestion_manager.count() > 0) {
  475. if (m_suggestion_display->cleanup())
  476. reposition_cursor();
  477. m_suggestion_display->set_initial_prompt_lines(m_prompt_lines_at_suggestion_initiation);
  478. m_suggestion_display->display(m_suggestion_manager);
  479. m_origin_x = m_suggestion_display->origin_x();
  480. }
  481. }
  482. if (m_times_tab_pressed > 2) {
  483. if (m_tab_direction == TabDirection::Forward)
  484. m_suggestion_manager.next();
  485. else
  486. m_suggestion_manager.previous();
  487. }
  488. if (m_suggestion_manager.count() < 2) {
  489. // We have none, or just one suggestion,
  490. // we should just commit that and continue
  491. // after it, as if it were auto-completed.
  492. suggest(0, 0, Span::CodepointOriented);
  493. m_times_tab_pressed = 0;
  494. m_suggestion_manager.reset();
  495. m_suggestion_display->finish();
  496. }
  497. continue;
  498. }
  499. if (m_times_tab_pressed) {
  500. // Apply the style of the last suggestion.
  501. readjust_anchored_styles(m_suggestion_manager.current_suggestion().start_index, ModificationKind::ForcedOverlapRemoval);
  502. stylize({ m_suggestion_manager.current_suggestion().start_index, m_cursor, Span::Mode::CodepointOriented }, m_suggestion_manager.current_suggestion().style);
  503. // We probably have some suggestions drawn,
  504. // let's clean them up.
  505. if (m_suggestion_display->cleanup()) {
  506. reposition_cursor();
  507. m_refresh_needed = true;
  508. }
  509. m_suggestion_manager.reset();
  510. suggest(0, 0, Span::CodepointOriented);
  511. m_suggestion_display->finish();
  512. }
  513. m_times_tab_pressed = 0; // Safe to say if we get here, the user didn't press TAB
  514. auto do_backspace = [&] {
  515. if (m_is_searching) {
  516. return;
  517. }
  518. if (m_cursor == 0) {
  519. fputc('\a', stdout);
  520. fflush(stdout);
  521. return;
  522. }
  523. remove_at_index(m_cursor - 1);
  524. --m_cursor;
  525. m_inline_search_cursor = m_cursor;
  526. // We will have to redraw :(
  527. m_refresh_needed = true;
  528. };
  529. if (codepoint == 8 || codepoint == m_termios.c_cc[VERASE]) {
  530. do_backspace();
  531. continue;
  532. }
  533. if (codepoint == m_termios.c_cc[VWERASE]) {
  534. bool has_seen_nonspace = false;
  535. while (m_cursor > 0) {
  536. if (isspace(m_buffer[m_cursor - 1])) {
  537. if (has_seen_nonspace)
  538. break;
  539. } else {
  540. has_seen_nonspace = true;
  541. }
  542. do_backspace();
  543. }
  544. continue;
  545. }
  546. if (codepoint == m_termios.c_cc[VKILL]) {
  547. for (size_t i = 0; i < m_cursor; ++i)
  548. remove_at_index(0);
  549. m_cursor = 0;
  550. m_refresh_needed = true;
  551. continue;
  552. }
  553. // ^L
  554. if (codepoint == 0xc) {
  555. printf("\033[3J\033[H\033[2J"); // Clear screen.
  556. VT::move_absolute(1, 1);
  557. set_origin(1, 1);
  558. m_refresh_needed = true;
  559. continue;
  560. }
  561. // ^A
  562. if (codepoint == 0x01) {
  563. m_cursor = 0;
  564. continue;
  565. }
  566. // ^R
  567. if (codepoint == 0x12) {
  568. if (m_is_searching) {
  569. // how did we get here?
  570. ASSERT_NOT_REACHED();
  571. } else {
  572. m_is_searching = true;
  573. m_search_offset = 0;
  574. m_pre_search_buffer.clear();
  575. for (auto codepoint : m_buffer)
  576. m_pre_search_buffer.append(codepoint);
  577. m_pre_search_cursor = m_cursor;
  578. m_search_editor = make<Editor>(Configuration { Configuration::Eager, m_configuration.split_mechanism }); // Has anyone seen 'Inception'?
  579. m_search_editor->on_display_refresh = [this](Editor& search_editor) {
  580. StringBuilder builder;
  581. builder.append(Utf32View { search_editor.buffer().data(), search_editor.buffer().size() });
  582. search(builder.build());
  583. refresh_display();
  584. return;
  585. };
  586. // Whenever the search editor gets a ^R, cycle between history entries.
  587. m_search_editor->register_character_input_callback(0x12, [this](Editor& search_editor) {
  588. ++m_search_offset;
  589. search_editor.m_refresh_needed = true;
  590. return false; // Do not process this key event
  591. });
  592. // Whenever the search editor gets a backspace, cycle back between history entries
  593. // unless we're at the zeroth entry, in which case, allow the deletion.
  594. m_search_editor->register_character_input_callback(m_termios.c_cc[VERASE], [this](Editor& search_editor) {
  595. if (m_search_offset > 0) {
  596. --m_search_offset;
  597. search_editor.m_refresh_needed = true;
  598. return false; // Do not process this key event
  599. }
  600. return true;
  601. });
  602. // ^L - This is a source of issues, as the search editor refreshes first,
  603. // and we end up with the wrong order of prompts, so we will first refresh
  604. // ourselves, then refresh the search editor, and then tell him not to process
  605. // this event.
  606. m_search_editor->register_character_input_callback(0x0c, [this](auto& search_editor) {
  607. printf("\033[3J\033[H\033[2J"); // Clear screen.
  608. // refresh our own prompt
  609. set_origin(1, 1);
  610. m_refresh_needed = true;
  611. refresh_display();
  612. // move the search prompt below ours
  613. // and tell it to redraw itself
  614. search_editor.set_origin(2, 1);
  615. search_editor.m_refresh_needed = true;
  616. return false;
  617. });
  618. // quit without clearing the current buffer
  619. m_search_editor->register_character_input_callback('\t', [this](Editor& search_editor) {
  620. search_editor.finish();
  621. m_reset_buffer_on_search_end = false;
  622. return false;
  623. });
  624. printf("\n");
  625. fflush(stdout);
  626. auto search_prompt = "\x1b[32msearch:\x1b[0m ";
  627. auto search_string_result = m_search_editor->get_line(search_prompt);
  628. m_search_editor = nullptr;
  629. m_is_searching = false;
  630. m_search_offset = 0;
  631. if (search_string_result.is_error()) {
  632. // Somethine broke, fail
  633. return search_string_result;
  634. }
  635. auto& search_string = search_string_result.value();
  636. // Manually cleanup the search line.
  637. reposition_cursor();
  638. auto search_string_codepoint_length = Utf8View { search_string }.length_in_codepoints();
  639. VT::clear_lines(0, (search_string_codepoint_length + actual_rendered_string_length(search_prompt) + m_num_columns - 1) / m_num_columns);
  640. reposition_cursor();
  641. if (!m_reset_buffer_on_search_end || search_string_codepoint_length == 0) {
  642. // If the entry was empty, or we purposely quit without a newline,
  643. // do not return anything; instead, just end the search.
  644. end_search();
  645. continue;
  646. }
  647. // Return the string,
  648. finish();
  649. continue;
  650. }
  651. continue;
  652. }
  653. // Normally ^D
  654. if (codepoint == m_termios.c_cc[VEOF]) {
  655. if (m_buffer.is_empty()) {
  656. printf("<EOF>\n");
  657. if (!m_always_refresh) {
  658. return Error::Eof;
  659. }
  660. }
  661. continue;
  662. }
  663. // ^E
  664. if (codepoint == 0x05) {
  665. m_cursor = m_buffer.size();
  666. continue;
  667. }
  668. if (codepoint == '\n') {
  669. finish();
  670. continue;
  671. }
  672. insert(codepoint);
  673. }
  674. if (consumed_codepoints == m_incomplete_data.size()) {
  675. m_incomplete_data.clear();
  676. } else {
  677. for (size_t i = 0; i < consumed_codepoints; ++i)
  678. m_incomplete_data.take_first();
  679. }
  680. }
  681. }
  682. bool Editor::search(const StringView& phrase, bool allow_empty, bool from_beginning)
  683. {
  684. int last_matching_offset = -1;
  685. // Do not search for empty strings.
  686. if (allow_empty || phrase.length() > 0) {
  687. size_t search_offset = m_search_offset;
  688. for (size_t i = m_history_cursor; i > 0; --i) {
  689. auto contains = from_beginning ? m_history[i - 1].starts_with(phrase) : m_history[i - 1].contains(phrase);
  690. if (contains) {
  691. last_matching_offset = i - 1;
  692. if (search_offset == 0)
  693. break;
  694. --search_offset;
  695. }
  696. }
  697. if (last_matching_offset == -1) {
  698. fputc('\a', stdout);
  699. fflush(stdout);
  700. }
  701. }
  702. m_buffer.clear();
  703. m_cursor = 0;
  704. if (last_matching_offset >= 0) {
  705. insert(m_history[last_matching_offset]);
  706. }
  707. // Always needed, as we have cleared the buffer above.
  708. m_refresh_needed = true;
  709. return last_matching_offset >= 0;
  710. }
  711. void Editor::recalculate_origin()
  712. {
  713. // Changing the columns can affect our origin if
  714. // the new size is smaller than our prompt, which would
  715. // cause said prompt to take up more space, so we should
  716. // compensate for that.
  717. if (m_cached_prompt_length >= m_num_columns) {
  718. auto added_lines = (m_cached_prompt_length + 1) / m_num_columns - 1;
  719. m_origin_x += added_lines;
  720. }
  721. // We also need to recalculate our cursor position,
  722. // but that will be calculated and applied at the next
  723. // refresh cycle.
  724. }
  725. void Editor::cleanup()
  726. {
  727. VT::move_relative(0, m_pending_chars.size() - m_chars_inserted_in_the_middle);
  728. auto current_line = cursor_line();
  729. VT::clear_lines(current_line - 1, num_lines() - current_line);
  730. VT::move_relative(-num_lines() + 1, -offset_in_line() - m_old_prompt_length - m_pending_chars.size() + m_chars_inserted_in_the_middle);
  731. };
  732. void Editor::refresh_display()
  733. {
  734. auto has_cleaned_up = false;
  735. // Someone changed the window size, figure it out
  736. // and react to it, we might need to redraw.
  737. if (m_was_resized) {
  738. auto previous_num_columns = m_num_columns;
  739. struct winsize ws;
  740. if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) {
  741. m_num_columns = 80;
  742. m_num_lines = 25;
  743. } else {
  744. m_num_columns = ws.ws_col;
  745. m_num_lines = ws.ws_row;
  746. }
  747. m_suggestion_display->set_vt_size(m_num_lines, m_num_columns);
  748. if (previous_num_columns != m_num_columns) {
  749. // We need to cleanup and redo everything.
  750. m_cached_prompt_valid = false;
  751. m_refresh_needed = true;
  752. swap(previous_num_columns, m_num_columns);
  753. recalculate_origin();
  754. cleanup();
  755. swap(previous_num_columns, m_num_columns);
  756. has_cleaned_up = true;
  757. }
  758. }
  759. // Do not call hook on pure cursor movement.
  760. if (m_cached_prompt_valid && !m_refresh_needed && m_pending_chars.size() == 0) {
  761. // Probably just moving around.
  762. reposition_cursor();
  763. m_cached_buffer_size = m_buffer.size();
  764. return;
  765. }
  766. if (on_display_refresh)
  767. on_display_refresh(*this);
  768. if (m_cached_prompt_valid) {
  769. if (!m_refresh_needed && m_cursor == m_buffer.size()) {
  770. // Just write the characters out and continue,
  771. // no need to refresh the entire line.
  772. char null = 0;
  773. m_pending_chars.append(&null, 1);
  774. fputs((char*)m_pending_chars.data(), stdout);
  775. m_pending_chars.clear();
  776. m_drawn_cursor = m_cursor;
  777. m_cached_buffer_size = m_buffer.size();
  778. fflush(stdout);
  779. return;
  780. }
  781. }
  782. // Ouch, reflow entire line.
  783. // FIXME: handle multiline stuff
  784. if (!has_cleaned_up) {
  785. cleanup();
  786. }
  787. VT::move_absolute(m_origin_x, m_origin_y);
  788. fputs(m_new_prompt.characters(), stdout);
  789. VT::clear_to_end_of_line();
  790. HashMap<u32, Style> empty_styles {};
  791. StringBuilder builder;
  792. for (size_t i = 0; i < m_buffer.size(); ++i) {
  793. auto ends = m_spans_ending.get(i).value_or(empty_styles);
  794. auto starts = m_spans_starting.get(i).value_or(empty_styles);
  795. auto anchored_ends = m_anchored_spans_ending.get(i).value_or(empty_styles);
  796. auto anchored_starts = m_anchored_spans_starting.get(i).value_or(empty_styles);
  797. if (ends.size() || anchored_ends.size()) {
  798. Style style;
  799. for (auto& applicable_style : ends)
  800. style.unify_with(applicable_style.value);
  801. for (auto& applicable_style : anchored_ends)
  802. style.unify_with(applicable_style.value);
  803. // Disable any style that should be turned off.
  804. VT::apply_style(style, false);
  805. // Reapply styles for overlapping spans that include this one.
  806. style = find_applicable_style(i);
  807. VT::apply_style(style, true);
  808. }
  809. if (starts.size() || anchored_starts.size()) {
  810. Style style;
  811. for (auto& applicable_style : starts)
  812. style.unify_with(applicable_style.value);
  813. for (auto& applicable_style : anchored_starts)
  814. style.unify_with(applicable_style.value);
  815. // Set new styles.
  816. VT::apply_style(style, true);
  817. }
  818. builder.clear();
  819. builder.append(Utf32View { &m_buffer[i], 1 });
  820. fputs(builder.to_string().characters(), stdout);
  821. }
  822. VT::apply_style(Style::reset_style()); // don't bleed to EOL
  823. m_pending_chars.clear();
  824. m_refresh_needed = false;
  825. m_cached_buffer_size = m_buffer.size();
  826. m_chars_inserted_in_the_middle = 0;
  827. if (!m_cached_prompt_valid) {
  828. m_cached_prompt_valid = true;
  829. }
  830. reposition_cursor();
  831. fflush(stdout);
  832. }
  833. void Editor::strip_styles(bool strip_anchored)
  834. {
  835. m_spans_starting.clear();
  836. m_spans_ending.clear();
  837. if (strip_anchored) {
  838. m_anchored_spans_starting.clear();
  839. m_anchored_spans_ending.clear();
  840. }
  841. m_refresh_needed = true;
  842. }
  843. void Editor::reposition_cursor()
  844. {
  845. m_drawn_cursor = m_cursor;
  846. auto line = cursor_line() - 1;
  847. auto column = offset_in_line();
  848. VT::move_absolute(line + m_origin_x, column + m_origin_y);
  849. }
  850. void VT::move_absolute(u32 x, u32 y)
  851. {
  852. printf("\033[%d;%dH", x, y);
  853. fflush(stdout);
  854. }
  855. void VT::move_relative(int x, int y)
  856. {
  857. char x_op = 'A', y_op = 'D';
  858. if (x > 0)
  859. x_op = 'B';
  860. else
  861. x = -x;
  862. if (y > 0)
  863. y_op = 'C';
  864. else
  865. y = -y;
  866. if (x > 0)
  867. printf("\033[%d%c", x, x_op);
  868. if (y > 0)
  869. printf("\033[%d%c", y, y_op);
  870. }
  871. Style Editor::find_applicable_style(size_t offset) const
  872. {
  873. // Walk through our styles and merge all that fit in the offset.
  874. auto style = Style::reset_style();
  875. auto unify = [&](auto& entry) {
  876. if (entry.key >= offset)
  877. return;
  878. for (auto& style_value : entry.value) {
  879. if (style_value.key <= offset)
  880. return;
  881. style.unify_with(style_value.value, true);
  882. }
  883. };
  884. for (auto& entry : m_spans_starting) {
  885. unify(entry);
  886. }
  887. for (auto& entry : m_anchored_spans_starting) {
  888. unify(entry);
  889. }
  890. return style;
  891. }
  892. String Style::Background::to_vt_escape() const
  893. {
  894. if (is_default())
  895. return "";
  896. if (m_is_rgb) {
  897. return String::format("\033[48;2;%d;%d;%dm", m_rgb_color[0], m_rgb_color[1], m_rgb_color[2]);
  898. } else {
  899. return String::format("\033[%dm", (u8)m_xterm_color + 40);
  900. }
  901. }
  902. String Style::Foreground::to_vt_escape() const
  903. {
  904. if (is_default())
  905. return "";
  906. if (m_is_rgb) {
  907. return String::format("\033[38;2;%d;%d;%dm", m_rgb_color[0], m_rgb_color[1], m_rgb_color[2]);
  908. } else {
  909. return String::format("\033[%dm", (u8)m_xterm_color + 30);
  910. }
  911. }
  912. String Style::Hyperlink::to_vt_escape(bool starting) const
  913. {
  914. if (is_empty())
  915. return "";
  916. return String::format("\033]8;;%s\033\\", starting ? m_link.characters() : "");
  917. }
  918. void Style::unify_with(const Style& other, bool prefer_other)
  919. {
  920. // Unify colors.
  921. if (prefer_other || m_background.is_default())
  922. m_background = other.background();
  923. if (prefer_other || m_foreground.is_default())
  924. m_foreground = other.foreground();
  925. // Unify graphic renditions.
  926. if (other.bold())
  927. set(Bold);
  928. if (other.italic())
  929. set(Italic);
  930. if (other.underline())
  931. set(Underline);
  932. // Unify links.
  933. if (prefer_other || m_hyperlink.is_empty())
  934. m_hyperlink = other.hyperlink();
  935. }
  936. String Style::to_string() const
  937. {
  938. StringBuilder builder;
  939. builder.append("Style { ");
  940. if (!m_foreground.is_default()) {
  941. builder.append("Foreground(");
  942. if (m_foreground.m_is_rgb) {
  943. builder.join(", ", m_foreground.m_rgb_color);
  944. } else {
  945. builder.appendf("(XtermColor) %d", m_foreground.m_xterm_color);
  946. }
  947. builder.append("), ");
  948. }
  949. if (!m_background.is_default()) {
  950. builder.append("Background(");
  951. if (m_background.m_is_rgb) {
  952. builder.join(' ', m_background.m_rgb_color);
  953. } else {
  954. builder.appendf("(XtermColor) %d", m_background.m_xterm_color);
  955. }
  956. builder.append("), ");
  957. }
  958. if (bold())
  959. builder.append("Bold, ");
  960. if (underline())
  961. builder.append("Underline, ");
  962. if (italic())
  963. builder.append("Italic, ");
  964. if (!m_hyperlink.is_empty())
  965. builder.appendf("Hyperlink(\"%s\"), ", m_hyperlink.m_link.characters());
  966. builder.append("}");
  967. return builder.build();
  968. }
  969. void VT::apply_style(const Style& style, bool is_starting)
  970. {
  971. if (is_starting) {
  972. printf(
  973. "\033[%d;%d;%dm%s%s%s",
  974. style.bold() ? 1 : 22,
  975. style.underline() ? 4 : 24,
  976. style.italic() ? 3 : 23,
  977. style.background().to_vt_escape().characters(),
  978. style.foreground().to_vt_escape().characters(),
  979. style.hyperlink().to_vt_escape(true).characters());
  980. } else {
  981. printf("%s", style.hyperlink().to_vt_escape(false).characters());
  982. }
  983. }
  984. void VT::clear_lines(size_t count_above, size_t count_below)
  985. {
  986. // Go down count_below lines.
  987. if (count_below > 0)
  988. printf("\033[%dB", (int)count_below);
  989. // Then clear lines going upwards.
  990. for (size_t i = count_below + count_above; i > 0; --i)
  991. fputs(i == 1 ? "\033[2K" : "\033[2K\033[A", stdout);
  992. }
  993. void VT::save_cursor()
  994. {
  995. fputs("\033[s", stdout);
  996. fflush(stdout);
  997. }
  998. void VT::restore_cursor()
  999. {
  1000. fputs("\033[u", stdout);
  1001. fflush(stdout);
  1002. }
  1003. void VT::clear_to_end_of_line()
  1004. {
  1005. fputs("\033[K", stdout);
  1006. fflush(stdout);
  1007. }
  1008. size_t Editor::actual_rendered_string_length(const StringView& string) const
  1009. {
  1010. size_t length { 0 };
  1011. enum VTState {
  1012. Free = 1,
  1013. Escape = 3,
  1014. Bracket = 5,
  1015. BracketArgsSemi = 7,
  1016. Title = 9,
  1017. } state { Free };
  1018. Utf8View view { string };
  1019. auto it = view.begin();
  1020. for (size_t i = 0; i < view.length_in_codepoints(); ++i, ++it) {
  1021. auto c = *it;
  1022. switch (state) {
  1023. case Free:
  1024. if (c == '\x1b') { // escape
  1025. state = Escape;
  1026. continue;
  1027. }
  1028. if (c == '\r' || c == '\n') { // return or carriage return
  1029. // Reset length to 0, since we either overwrite, or are on a newline.
  1030. length = 0;
  1031. continue;
  1032. }
  1033. // FIXME: This will not support anything sophisticated
  1034. ++length;
  1035. break;
  1036. case Escape:
  1037. if (c == ']') {
  1038. ++i;
  1039. ++it;
  1040. if (*it == '0')
  1041. state = Title;
  1042. continue;
  1043. }
  1044. if (c == '[') {
  1045. state = Bracket;
  1046. continue;
  1047. }
  1048. // FIXME: This does not support non-VT (aside from set-title) escapes
  1049. break;
  1050. case Bracket:
  1051. if (isdigit(c)) {
  1052. state = BracketArgsSemi;
  1053. continue;
  1054. }
  1055. break;
  1056. case BracketArgsSemi:
  1057. if (c == ';') {
  1058. state = Bracket;
  1059. continue;
  1060. }
  1061. if (!isdigit(c))
  1062. state = Free;
  1063. break;
  1064. case Title:
  1065. if (c == 7)
  1066. state = Free;
  1067. break;
  1068. }
  1069. }
  1070. return length;
  1071. }
  1072. Vector<size_t, 2> Editor::vt_dsr()
  1073. {
  1074. char buf[16];
  1075. u32 length { 0 };
  1076. // Read whatever junk there is before talking to the terminal
  1077. // and insert them later when we're reading user input.
  1078. bool more_junk_to_read { false };
  1079. timeval timeout { 0, 0 };
  1080. fd_set readfds;
  1081. FD_ZERO(&readfds);
  1082. FD_SET(0, &readfds);
  1083. do {
  1084. more_junk_to_read = false;
  1085. (void)select(1, &readfds, nullptr, nullptr, &timeout);
  1086. if (FD_ISSET(0, &readfds)) {
  1087. auto nread = read(0, buf, 16);
  1088. if (nread < 0) {
  1089. m_input_error = Error::ReadFailure;
  1090. break;
  1091. }
  1092. if (nread == 0)
  1093. break;
  1094. m_incomplete_data.append(buf, nread);
  1095. more_junk_to_read = true;
  1096. }
  1097. } while (more_junk_to_read);
  1098. if (m_input_error.has_value())
  1099. return { 1, 1 };
  1100. fputs("\033[6n", stdout);
  1101. fflush(stdout);
  1102. do {
  1103. auto nread = read(0, buf + length, 16 - length);
  1104. if (nread < 0) {
  1105. if (errno == 0) {
  1106. // ????
  1107. continue;
  1108. }
  1109. dbg() << "Error while reading DSR: " << strerror(errno);
  1110. m_input_error = Error::ReadFailure;
  1111. return { 1, 1 };
  1112. }
  1113. if (nread == 0) {
  1114. m_input_error = Error::Empty;
  1115. dbg() << "Terminal DSR issue; received no response";
  1116. return { 1, 1 };
  1117. }
  1118. length += nread;
  1119. } while (buf[length - 1] != 'R' && length < 16);
  1120. size_t x { 1 }, y { 1 };
  1121. if (buf[0] == '\033' && buf[1] == '[') {
  1122. auto parts = StringView(buf + 2, length - 3).split_view(';');
  1123. bool ok;
  1124. x = parts[0].to_int(ok);
  1125. if (!ok) {
  1126. dbg() << "Terminal DSR issue; received garbage x";
  1127. }
  1128. y = parts[1].to_int(ok);
  1129. if (!ok) {
  1130. dbg() << "Terminal DSR issue; received garbage y";
  1131. }
  1132. }
  1133. return { x, y };
  1134. }
  1135. String Editor::line(size_t up_to_index) const
  1136. {
  1137. StringBuilder builder;
  1138. builder.append(Utf32View { m_buffer.data(), min(m_buffer.size(), up_to_index) });
  1139. return builder.build();
  1140. }
  1141. bool Editor::should_break_token(Vector<u32, 1024>& buffer, size_t index)
  1142. {
  1143. switch (m_configuration.split_mechanism) {
  1144. case Configuration::TokenSplitMechanism::Spaces:
  1145. return buffer[index] == ' ';
  1146. case Configuration::TokenSplitMechanism::UnescapedSpaces:
  1147. return buffer[index] == ' ' && (index == 0 || buffer[index - 1] != '\\');
  1148. }
  1149. ASSERT_NOT_REACHED();
  1150. return true;
  1151. };
  1152. void Editor::remove_at_index(size_t index)
  1153. {
  1154. // See if we have any anchored styles, and reposition them if needed.
  1155. readjust_anchored_styles(index, ModificationKind::Removal);
  1156. m_buffer.remove(index);
  1157. }
  1158. void Editor::readjust_anchored_styles(size_t hint_index, ModificationKind modification)
  1159. {
  1160. struct Anchor {
  1161. Span old_span;
  1162. Span new_span;
  1163. Style style;
  1164. };
  1165. Vector<Anchor> anchors_to_relocate;
  1166. auto index_shift = modification == ModificationKind::Insertion ? 1 : -1;
  1167. auto forced_removal = modification == ModificationKind::ForcedOverlapRemoval;
  1168. for (auto& start_entry : m_anchored_spans_starting) {
  1169. for (auto& end_entry : start_entry.value) {
  1170. if (forced_removal) {
  1171. if (start_entry.key <= hint_index && end_entry.key > hint_index) {
  1172. // Remove any overlapping regions.
  1173. continue;
  1174. }
  1175. }
  1176. if (start_entry.key >= hint_index) {
  1177. if (start_entry.key == hint_index && end_entry.key == hint_index + 1 && modification == ModificationKind::Removal) {
  1178. // Remove the anchor, as all its text was wiped.
  1179. continue;
  1180. }
  1181. // Shift everything.
  1182. anchors_to_relocate.append({ { start_entry.key, end_entry.key, Span::Mode::CodepointOriented }, { start_entry.key + index_shift, end_entry.key + index_shift, Span::Mode::CodepointOriented }, end_entry.value });
  1183. continue;
  1184. }
  1185. if (end_entry.key > hint_index) {
  1186. // Shift just the end.
  1187. anchors_to_relocate.append({ { start_entry.key, end_entry.key, Span::Mode::CodepointOriented }, { start_entry.key, end_entry.key + index_shift, Span::Mode::CodepointOriented }, end_entry.value });
  1188. continue;
  1189. }
  1190. anchors_to_relocate.append({ { start_entry.key, end_entry.key, Span::Mode::CodepointOriented }, { start_entry.key, end_entry.key, Span::Mode::CodepointOriented }, end_entry.value });
  1191. }
  1192. }
  1193. m_anchored_spans_ending.clear();
  1194. m_anchored_spans_starting.clear();
  1195. // Pass over the relocations and update the stale entries.
  1196. for (auto& relocation : anchors_to_relocate) {
  1197. stylize(relocation.new_span, relocation.style);
  1198. }
  1199. }
  1200. }