TextDocument.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Badge.h>
  7. #include <AK/CharacterTypes.h>
  8. #include <AK/ScopeGuard.h>
  9. #include <AK/StringBuilder.h>
  10. #include <AK/Utf8View.h>
  11. #include <LibCore/Timer.h>
  12. #include <LibGUI/TextDocument.h>
  13. #include <LibGUI/TextEditor.h>
  14. #include <LibRegex/Regex.h>
  15. namespace GUI {
  16. NonnullRefPtr<TextDocument> TextDocument::create(Client* client)
  17. {
  18. return adopt_ref(*new TextDocument(client));
  19. }
  20. TextDocument::TextDocument(Client* client)
  21. {
  22. if (client)
  23. m_clients.set(client);
  24. append_line(make<TextDocumentLine>(*this));
  25. set_unmodified();
  26. m_undo_stack.on_state_change = [this] {
  27. if (m_client_notifications_enabled) {
  28. for (auto* client : m_clients)
  29. client->document_did_update_undo_stack();
  30. }
  31. };
  32. }
  33. TextDocument::~TextDocument()
  34. {
  35. }
  36. bool TextDocument::set_text(const StringView& text)
  37. {
  38. m_client_notifications_enabled = false;
  39. m_undo_stack.clear();
  40. m_spans.clear();
  41. remove_all_lines();
  42. ArmedScopeGuard clear_text_guard([this]() {
  43. set_text({});
  44. });
  45. size_t start_of_current_line = 0;
  46. auto add_line = [&](size_t current_position) -> bool {
  47. size_t line_length = current_position - start_of_current_line;
  48. auto line = make<TextDocumentLine>(*this);
  49. bool success = true;
  50. if (line_length)
  51. success = line->set_text(*this, text.substring_view(start_of_current_line, current_position - start_of_current_line));
  52. if (!success)
  53. return false;
  54. append_line(move(line));
  55. start_of_current_line = current_position + 1;
  56. return true;
  57. };
  58. size_t i = 0;
  59. for (i = 0; i < text.length(); ++i) {
  60. if (text[i] != '\n')
  61. continue;
  62. auto success = add_line(i);
  63. if (!success)
  64. return false;
  65. }
  66. auto success = add_line(i);
  67. if (!success)
  68. return false;
  69. // Don't show the file's trailing newline as an actual new line.
  70. if (line_count() > 1 && line(line_count() - 1).is_empty())
  71. m_lines.take_last();
  72. m_client_notifications_enabled = true;
  73. for (auto* client : m_clients)
  74. client->document_did_set_text();
  75. clear_text_guard.disarm();
  76. // FIXME: Should the modified state be cleared on some of the earlier returns as well?
  77. set_unmodified();
  78. return true;
  79. }
  80. size_t TextDocumentLine::first_non_whitespace_column() const
  81. {
  82. for (size_t i = 0; i < length(); ++i) {
  83. auto code_point = code_points()[i];
  84. if (!is_ascii_space(code_point))
  85. return i;
  86. }
  87. return length();
  88. }
  89. Optional<size_t> TextDocumentLine::last_non_whitespace_column() const
  90. {
  91. for (ssize_t i = length() - 1; i >= 0; --i) {
  92. auto code_point = code_points()[i];
  93. if (!is_ascii_space(code_point))
  94. return i;
  95. }
  96. return {};
  97. }
  98. bool TextDocumentLine::ends_in_whitespace() const
  99. {
  100. if (!length())
  101. return false;
  102. return is_ascii_space(code_points()[length() - 1]);
  103. }
  104. bool TextDocumentLine::can_select() const
  105. {
  106. if (is_empty())
  107. return false;
  108. for (size_t i = 0; i < length(); ++i) {
  109. auto code_point = code_points()[i];
  110. if (code_point != '\n' && code_point != '\r' && code_point != '\f' && code_point != '\v')
  111. return true;
  112. }
  113. return false;
  114. }
  115. size_t TextDocumentLine::leading_spaces() const
  116. {
  117. size_t count = 0;
  118. for (; count < m_text.size(); ++count) {
  119. if (m_text[count] != ' ') {
  120. break;
  121. }
  122. }
  123. return count;
  124. }
  125. String TextDocumentLine::to_utf8() const
  126. {
  127. StringBuilder builder;
  128. builder.append(view());
  129. return builder.to_string();
  130. }
  131. TextDocumentLine::TextDocumentLine(TextDocument& document)
  132. {
  133. clear(document);
  134. }
  135. TextDocumentLine::TextDocumentLine(TextDocument& document, const StringView& text)
  136. {
  137. set_text(document, text);
  138. }
  139. void TextDocumentLine::clear(TextDocument& document)
  140. {
  141. m_text.clear();
  142. document.update_views({});
  143. }
  144. void TextDocumentLine::set_text(TextDocument& document, const Vector<u32> text)
  145. {
  146. m_text = move(text);
  147. document.update_views({});
  148. }
  149. bool TextDocumentLine::set_text(TextDocument& document, const StringView& text)
  150. {
  151. if (text.is_empty()) {
  152. clear(document);
  153. return true;
  154. }
  155. m_text.clear();
  156. Utf8View utf8_view(text);
  157. if (!utf8_view.validate()) {
  158. return false;
  159. }
  160. for (auto code_point : utf8_view)
  161. m_text.append(code_point);
  162. document.update_views({});
  163. return true;
  164. }
  165. void TextDocumentLine::append(TextDocument& document, const u32* code_points, size_t length)
  166. {
  167. if (length == 0)
  168. return;
  169. m_text.append(code_points, length);
  170. document.update_views({});
  171. }
  172. void TextDocumentLine::append(TextDocument& document, u32 code_point)
  173. {
  174. insert(document, length(), code_point);
  175. }
  176. void TextDocumentLine::prepend(TextDocument& document, u32 code_point)
  177. {
  178. insert(document, 0, code_point);
  179. }
  180. void TextDocumentLine::insert(TextDocument& document, size_t index, u32 code_point)
  181. {
  182. if (index == length()) {
  183. m_text.append(code_point);
  184. } else {
  185. m_text.insert(index, code_point);
  186. }
  187. document.update_views({});
  188. }
  189. void TextDocumentLine::remove(TextDocument& document, size_t index)
  190. {
  191. if (index == length()) {
  192. m_text.take_last();
  193. } else {
  194. m_text.remove(index);
  195. }
  196. document.update_views({});
  197. }
  198. void TextDocumentLine::remove_range(TextDocument& document, size_t start, size_t length)
  199. {
  200. VERIFY(length <= m_text.size());
  201. Vector<u32> new_data;
  202. new_data.ensure_capacity(m_text.size() - length);
  203. for (size_t i = 0; i < start; ++i)
  204. new_data.append(m_text[i]);
  205. for (size_t i = (start + length); i < m_text.size(); ++i)
  206. new_data.append(m_text[i]);
  207. m_text = move(new_data);
  208. document.update_views({});
  209. }
  210. void TextDocumentLine::truncate(TextDocument& document, size_t length)
  211. {
  212. m_text.resize(length);
  213. document.update_views({});
  214. }
  215. void TextDocument::append_line(NonnullOwnPtr<TextDocumentLine> line)
  216. {
  217. lines().append(move(line));
  218. if (m_client_notifications_enabled) {
  219. for (auto* client : m_clients)
  220. client->document_did_append_line();
  221. }
  222. }
  223. void TextDocument::insert_line(size_t line_index, NonnullOwnPtr<TextDocumentLine> line)
  224. {
  225. lines().insert((int)line_index, move(line));
  226. if (m_client_notifications_enabled) {
  227. for (auto* client : m_clients)
  228. client->document_did_insert_line(line_index);
  229. }
  230. }
  231. void TextDocument::remove_line(size_t line_index)
  232. {
  233. lines().remove((int)line_index);
  234. if (m_client_notifications_enabled) {
  235. for (auto* client : m_clients)
  236. client->document_did_remove_line(line_index);
  237. }
  238. }
  239. void TextDocument::remove_all_lines()
  240. {
  241. lines().clear();
  242. if (m_client_notifications_enabled) {
  243. for (auto* client : m_clients)
  244. client->document_did_remove_all_lines();
  245. }
  246. }
  247. TextDocument::Client::~Client()
  248. {
  249. }
  250. void TextDocument::register_client(Client& client)
  251. {
  252. m_clients.set(&client);
  253. }
  254. void TextDocument::unregister_client(Client& client)
  255. {
  256. m_clients.remove(&client);
  257. }
  258. void TextDocument::update_views(Badge<TextDocumentLine>)
  259. {
  260. notify_did_change();
  261. }
  262. void TextDocument::notify_did_change()
  263. {
  264. if (m_client_notifications_enabled) {
  265. for (auto* client : m_clients)
  266. client->document_did_change();
  267. }
  268. m_regex_needs_update = true;
  269. }
  270. void TextDocument::set_all_cursors(const TextPosition& position)
  271. {
  272. if (m_client_notifications_enabled) {
  273. for (auto* client : m_clients)
  274. client->document_did_set_cursor(position);
  275. }
  276. }
  277. String TextDocument::text() const
  278. {
  279. StringBuilder builder;
  280. for (size_t i = 0; i < line_count(); ++i) {
  281. auto& line = this->line(i);
  282. builder.append(line.view());
  283. if (i != line_count() - 1)
  284. builder.append('\n');
  285. }
  286. return builder.to_string();
  287. }
  288. String TextDocument::text_in_range(const TextRange& a_range) const
  289. {
  290. auto range = a_range.normalized();
  291. if (is_empty() || line_count() < range.end().line() - range.start().line())
  292. return String("");
  293. StringBuilder builder;
  294. for (size_t i = range.start().line(); i <= range.end().line(); ++i) {
  295. auto& line = this->line(i);
  296. size_t selection_start_column_on_line = range.start().line() == i ? range.start().column() : 0;
  297. size_t selection_end_column_on_line = range.end().line() == i ? range.end().column() : line.length();
  298. builder.append(Utf32View(line.code_points() + selection_start_column_on_line, selection_end_column_on_line - selection_start_column_on_line));
  299. if (i != range.end().line())
  300. builder.append('\n');
  301. }
  302. return builder.to_string();
  303. }
  304. u32 TextDocument::code_point_at(const TextPosition& position) const
  305. {
  306. VERIFY(position.line() < line_count());
  307. auto& line = this->line(position.line());
  308. if (position.column() == line.length())
  309. return '\n';
  310. return line.code_points()[position.column()];
  311. }
  312. TextPosition TextDocument::next_position_after(const TextPosition& position, SearchShouldWrap should_wrap) const
  313. {
  314. auto& line = this->line(position.line());
  315. if (position.column() == line.length()) {
  316. if (position.line() == line_count() - 1) {
  317. if (should_wrap == SearchShouldWrap::Yes)
  318. return { 0, 0 };
  319. return {};
  320. }
  321. return { position.line() + 1, 0 };
  322. }
  323. return { position.line(), position.column() + 1 };
  324. }
  325. TextPosition TextDocument::previous_position_before(const TextPosition& position, SearchShouldWrap should_wrap) const
  326. {
  327. if (position.column() == 0) {
  328. if (position.line() == 0) {
  329. if (should_wrap == SearchShouldWrap::Yes) {
  330. auto& last_line = this->line(line_count() - 1);
  331. return { line_count() - 1, last_line.length() };
  332. }
  333. return {};
  334. }
  335. auto& prev_line = this->line(position.line() - 1);
  336. return { position.line() - 1, prev_line.length() };
  337. }
  338. return { position.line(), position.column() - 1 };
  339. }
  340. void TextDocument::update_regex_matches(const StringView& needle)
  341. {
  342. if (m_regex_needs_update || needle != m_regex_needle) {
  343. Regex<PosixExtended> re(needle);
  344. Vector<RegexStringView> views;
  345. for (size_t line = 0; line < m_lines.size(); ++line) {
  346. views.append(m_lines.at(line).view());
  347. }
  348. re.search(views, m_regex_result);
  349. m_regex_needs_update = false;
  350. m_regex_needle = String { needle };
  351. m_regex_result_match_index = -1;
  352. m_regex_result_match_capture_group_index = -1;
  353. }
  354. }
  355. TextRange TextDocument::find_next(const StringView& needle, const TextPosition& start, SearchShouldWrap should_wrap, bool regmatch, bool match_case)
  356. {
  357. if (needle.is_empty())
  358. return {};
  359. if (regmatch) {
  360. if (!m_regex_result.matches.size())
  361. return {};
  362. regex::Match match;
  363. bool use_whole_match { false };
  364. auto next_match = [&] {
  365. m_regex_result_match_capture_group_index = 0;
  366. if (m_regex_result_match_index == m_regex_result.matches.size() - 1) {
  367. if (should_wrap == SearchShouldWrap::Yes)
  368. m_regex_result_match_index = 0;
  369. else
  370. ++m_regex_result_match_index;
  371. } else
  372. ++m_regex_result_match_index;
  373. };
  374. if (m_regex_result.n_capture_groups) {
  375. if (m_regex_result_match_index >= m_regex_result.capture_group_matches.size())
  376. next_match();
  377. else {
  378. // check if last capture group has been reached
  379. if (m_regex_result_match_capture_group_index >= m_regex_result.capture_group_matches.at(m_regex_result_match_index).size()) {
  380. next_match();
  381. } else {
  382. // get to the next capture group item
  383. ++m_regex_result_match_capture_group_index;
  384. }
  385. }
  386. // use whole match, if there is no capture group for current index
  387. if (m_regex_result_match_index >= m_regex_result.capture_group_matches.size())
  388. use_whole_match = true;
  389. else if (m_regex_result_match_capture_group_index >= m_regex_result.capture_group_matches.at(m_regex_result_match_index).size())
  390. next_match();
  391. } else {
  392. next_match();
  393. }
  394. if (use_whole_match || !m_regex_result.capture_group_matches.at(m_regex_result_match_index).size())
  395. match = m_regex_result.matches.at(m_regex_result_match_index);
  396. else
  397. match = m_regex_result.capture_group_matches.at(m_regex_result_match_index).at(m_regex_result_match_capture_group_index);
  398. return TextRange { { match.line, match.column }, { match.line, match.column + match.view.length() } };
  399. }
  400. TextPosition position = start.is_valid() ? start : TextPosition(0, 0);
  401. TextPosition original_position = position;
  402. TextPosition start_of_potential_match;
  403. size_t needle_index = 0;
  404. do {
  405. auto ch = code_point_at(position);
  406. // FIXME: This is not the right way to use a Unicode needle!
  407. if (match_case ? ch == (u32)needle[needle_index] : tolower(ch) == tolower((u32)needle[needle_index])) {
  408. if (needle_index == 0)
  409. start_of_potential_match = position;
  410. ++needle_index;
  411. if (needle_index >= needle.length())
  412. return { start_of_potential_match, next_position_after(position, should_wrap) };
  413. } else {
  414. if (needle_index > 0)
  415. position = start_of_potential_match;
  416. needle_index = 0;
  417. }
  418. position = next_position_after(position, should_wrap);
  419. } while (position.is_valid() && position != original_position);
  420. return {};
  421. }
  422. TextRange TextDocument::find_previous(const StringView& needle, const TextPosition& start, SearchShouldWrap should_wrap, bool regmatch, bool match_case)
  423. {
  424. if (needle.is_empty())
  425. return {};
  426. if (regmatch) {
  427. if (!m_regex_result.matches.size())
  428. return {};
  429. regex::Match match;
  430. bool use_whole_match { false };
  431. auto next_match = [&] {
  432. if (m_regex_result_match_index == 0) {
  433. if (should_wrap == SearchShouldWrap::Yes)
  434. m_regex_result_match_index = m_regex_result.matches.size() - 1;
  435. else
  436. --m_regex_result_match_index;
  437. } else
  438. --m_regex_result_match_index;
  439. m_regex_result_match_capture_group_index = m_regex_result.capture_group_matches.at(m_regex_result_match_index).size() - 1;
  440. };
  441. if (m_regex_result.n_capture_groups) {
  442. if (m_regex_result_match_index >= m_regex_result.capture_group_matches.size())
  443. next_match();
  444. else {
  445. // check if last capture group has been reached
  446. if (m_regex_result_match_capture_group_index >= m_regex_result.capture_group_matches.at(m_regex_result_match_index).size()) {
  447. next_match();
  448. } else {
  449. // get to the next capture group item
  450. --m_regex_result_match_capture_group_index;
  451. }
  452. }
  453. // use whole match, if there is no capture group for current index
  454. if (m_regex_result_match_index >= m_regex_result.capture_group_matches.size())
  455. use_whole_match = true;
  456. else if (m_regex_result_match_capture_group_index >= m_regex_result.capture_group_matches.at(m_regex_result_match_index).size())
  457. next_match();
  458. } else {
  459. next_match();
  460. }
  461. if (use_whole_match || !m_regex_result.capture_group_matches.at(m_regex_result_match_index).size())
  462. match = m_regex_result.matches.at(m_regex_result_match_index);
  463. else
  464. match = m_regex_result.capture_group_matches.at(m_regex_result_match_index).at(m_regex_result_match_capture_group_index);
  465. return TextRange { { match.line, match.column }, { match.line, match.column + match.view.length() } };
  466. }
  467. TextPosition position = start.is_valid() ? start : TextPosition(0, 0);
  468. position = previous_position_before(position, should_wrap);
  469. if (position.line() >= line_count())
  470. return {};
  471. TextPosition original_position = position;
  472. TextPosition end_of_potential_match;
  473. size_t needle_index = needle.length() - 1;
  474. do {
  475. auto ch = code_point_at(position);
  476. // FIXME: This is not the right way to use a Unicode needle!
  477. if (match_case ? ch == (u32)needle[needle_index] : tolower(ch) == tolower((u32)needle[needle_index])) {
  478. if (needle_index == needle.length() - 1)
  479. end_of_potential_match = position;
  480. if (needle_index == 0)
  481. return { position, next_position_after(end_of_potential_match, should_wrap) };
  482. --needle_index;
  483. } else {
  484. if (needle_index < needle.length() - 1)
  485. position = end_of_potential_match;
  486. needle_index = needle.length() - 1;
  487. }
  488. position = previous_position_before(position, should_wrap);
  489. } while (position.is_valid() && position != original_position);
  490. return {};
  491. }
  492. Vector<TextRange> TextDocument::find_all(const StringView& needle, bool regmatch)
  493. {
  494. Vector<TextRange> ranges;
  495. TextPosition position;
  496. for (;;) {
  497. auto range = find_next(needle, position, SearchShouldWrap::No, regmatch);
  498. if (!range.is_valid())
  499. break;
  500. ranges.append(range);
  501. position = range.end();
  502. }
  503. return ranges;
  504. }
  505. Optional<TextDocumentSpan> TextDocument::first_non_skippable_span_before(const TextPosition& position) const
  506. {
  507. for (int i = m_spans.size() - 1; i >= 0; --i) {
  508. if (!m_spans[i].range.contains(position))
  509. continue;
  510. while ((i - 1) >= 0 && m_spans[i - 1].is_skippable)
  511. --i;
  512. if (i <= 0)
  513. return {};
  514. return m_spans[i - 1];
  515. }
  516. return {};
  517. }
  518. Optional<TextDocumentSpan> TextDocument::first_non_skippable_span_after(const TextPosition& position) const
  519. {
  520. for (size_t i = 0; i < m_spans.size(); ++i) {
  521. if (!m_spans[i].range.contains(position))
  522. continue;
  523. while ((i + 1) < m_spans.size() && m_spans[i + 1].is_skippable)
  524. ++i;
  525. if (i >= (m_spans.size() - 1))
  526. return {};
  527. return m_spans[i + 1];
  528. }
  529. return {};
  530. }
  531. TextPosition TextDocument::first_word_break_before(const TextPosition& position, bool start_at_column_before) const
  532. {
  533. if (position.column() == 0) {
  534. if (position.line() == 0) {
  535. return TextPosition(0, 0);
  536. }
  537. auto previous_line = this->line(position.line() - 1);
  538. return TextPosition(position.line() - 1, previous_line.length());
  539. }
  540. auto target = position;
  541. auto line = this->line(target.line());
  542. auto modifier = start_at_column_before ? 1 : 0;
  543. if (target.column() == line.length())
  544. modifier = 1;
  545. auto is_start_alphanumeric = is_ascii_alphanumeric(line.code_points()[target.column() - modifier]);
  546. while (target.column() > 0) {
  547. auto prev_code_point = line.code_points()[target.column() - 1];
  548. if ((is_start_alphanumeric && !is_ascii_alphanumeric(prev_code_point)) || (!is_start_alphanumeric && is_ascii_alphanumeric(prev_code_point)))
  549. break;
  550. target.set_column(target.column() - 1);
  551. }
  552. return target;
  553. }
  554. TextPosition TextDocument::first_word_break_after(const TextPosition& position) const
  555. {
  556. auto target = position;
  557. auto line = this->line(target.line());
  558. if (position.column() >= line.length()) {
  559. if (position.line() >= this->line_count() - 1) {
  560. return position;
  561. }
  562. return TextPosition(position.line() + 1, 0);
  563. }
  564. auto is_start_alphanumeric = is_ascii_alphanumeric(line.code_points()[target.column()]);
  565. while (target.column() < line.length()) {
  566. auto next_code_point = line.code_points()[target.column()];
  567. if ((is_start_alphanumeric && !is_ascii_alphanumeric(next_code_point)) || (!is_start_alphanumeric && is_ascii_alphanumeric(next_code_point)))
  568. break;
  569. target.set_column(target.column() + 1);
  570. }
  571. return target;
  572. }
  573. void TextDocument::undo()
  574. {
  575. if (!can_undo())
  576. return;
  577. m_undo_stack.undo();
  578. notify_did_change();
  579. }
  580. void TextDocument::redo()
  581. {
  582. if (!can_redo())
  583. return;
  584. m_undo_stack.redo();
  585. notify_did_change();
  586. }
  587. void TextDocument::add_to_undo_stack(NonnullOwnPtr<TextDocumentUndoCommand> undo_command)
  588. {
  589. m_undo_stack.push(move(undo_command));
  590. }
  591. TextDocumentUndoCommand::TextDocumentUndoCommand(TextDocument& document)
  592. : m_document(document)
  593. {
  594. }
  595. TextDocumentUndoCommand::~TextDocumentUndoCommand()
  596. {
  597. }
  598. InsertTextCommand::InsertTextCommand(TextDocument& document, const String& text, const TextPosition& position)
  599. : TextDocumentUndoCommand(document)
  600. , m_text(text)
  601. , m_range({ position, position })
  602. {
  603. }
  604. String InsertTextCommand::action_text() const
  605. {
  606. return "Insert Text";
  607. }
  608. bool InsertTextCommand::merge_with(GUI::Command const& other)
  609. {
  610. if (!is<InsertTextCommand>(other))
  611. return false;
  612. auto& typed_other = static_cast<InsertTextCommand const&>(other);
  613. if (m_range.end() != typed_other.m_range.start())
  614. return false;
  615. if (m_range.start().line() != m_range.end().line())
  616. return false;
  617. StringBuilder builder(m_text.length() + typed_other.m_text.length());
  618. builder.append(m_text);
  619. builder.append(typed_other.m_text);
  620. m_text = builder.to_string();
  621. m_range.set_end(typed_other.m_range.end());
  622. return true;
  623. }
  624. void InsertTextCommand::perform_formatting(const TextDocument::Client& client)
  625. {
  626. const size_t tab_width = client.soft_tab_width();
  627. const auto& dest_line = m_document.line(m_range.start().line());
  628. const bool should_auto_indent = client.is_automatic_indentation_enabled();
  629. StringBuilder builder;
  630. size_t column = m_range.start().column();
  631. size_t line_indentation = dest_line.leading_spaces();
  632. bool at_start_of_line = line_indentation == column;
  633. for (auto input_char : m_text) {
  634. if (input_char == '\n') {
  635. builder.append('\n');
  636. column = 0;
  637. if (should_auto_indent) {
  638. for (; column < line_indentation; ++column) {
  639. builder.append(' ');
  640. }
  641. }
  642. at_start_of_line = true;
  643. } else if (input_char == '\t') {
  644. size_t next_soft_tab_stop = ((column + tab_width) / tab_width) * tab_width;
  645. size_t spaces_to_insert = next_soft_tab_stop - column;
  646. for (size_t i = 0; i < spaces_to_insert; ++i) {
  647. builder.append(' ');
  648. }
  649. column = next_soft_tab_stop;
  650. if (at_start_of_line) {
  651. line_indentation = column;
  652. }
  653. } else {
  654. if (input_char == ' ') {
  655. if (at_start_of_line) {
  656. ++line_indentation;
  657. }
  658. } else {
  659. at_start_of_line = false;
  660. }
  661. builder.append(input_char);
  662. ++column;
  663. }
  664. }
  665. m_text = builder.build();
  666. }
  667. void InsertTextCommand::redo()
  668. {
  669. auto new_cursor = m_document.insert_at(m_range.start(), m_text, m_client);
  670. // NOTE: We don't know where the range ends until after doing redo().
  671. // This is okay since we always do redo() after adding this to the undo stack.
  672. m_range.set_end(new_cursor);
  673. m_document.set_all_cursors(new_cursor);
  674. }
  675. void InsertTextCommand::undo()
  676. {
  677. m_document.remove(m_range);
  678. m_document.set_all_cursors(m_range.start());
  679. }
  680. RemoveTextCommand::RemoveTextCommand(TextDocument& document, const String& text, const TextRange& range)
  681. : TextDocumentUndoCommand(document)
  682. , m_text(text)
  683. , m_range(range)
  684. {
  685. }
  686. String RemoveTextCommand::action_text() const
  687. {
  688. return "Remove Text";
  689. }
  690. bool RemoveTextCommand::merge_with(GUI::Command const& other)
  691. {
  692. if (!is<RemoveTextCommand>(other))
  693. return false;
  694. auto& typed_other = static_cast<RemoveTextCommand const&>(other);
  695. if (m_range.start() != typed_other.m_range.end())
  696. return false;
  697. if (m_range.start().line() != m_range.end().line())
  698. return false;
  699. // Merge backspaces
  700. StringBuilder builder(m_text.length() + typed_other.m_text.length());
  701. builder.append(typed_other.m_text);
  702. builder.append(m_text);
  703. m_text = builder.to_string();
  704. m_range.set_start(typed_other.m_range.start());
  705. return true;
  706. return false;
  707. }
  708. void RemoveTextCommand::redo()
  709. {
  710. m_document.remove(m_range);
  711. m_document.set_all_cursors(m_range.start());
  712. }
  713. void RemoveTextCommand::undo()
  714. {
  715. auto new_cursor = m_document.insert_at(m_range.start(), m_text);
  716. m_document.set_all_cursors(new_cursor);
  717. }
  718. TextPosition TextDocument::insert_at(const TextPosition& position, const StringView& text, const Client* client)
  719. {
  720. TextPosition cursor = position;
  721. Utf8View utf8_view(text);
  722. for (auto code_point : utf8_view)
  723. cursor = insert_at(cursor, code_point, client);
  724. return cursor;
  725. }
  726. TextPosition TextDocument::insert_at(const TextPosition& position, u32 code_point, const Client*)
  727. {
  728. if (code_point == '\n') {
  729. auto new_line = make<TextDocumentLine>(*this);
  730. new_line->append(*this, line(position.line()).code_points() + position.column(), line(position.line()).length() - position.column());
  731. line(position.line()).truncate(*this, position.column());
  732. insert_line(position.line() + 1, move(new_line));
  733. notify_did_change();
  734. return { position.line() + 1, 0 };
  735. } else {
  736. line(position.line()).insert(*this, position.column(), code_point);
  737. notify_did_change();
  738. return { position.line(), position.column() + 1 };
  739. }
  740. }
  741. void TextDocument::remove(const TextRange& unnormalized_range)
  742. {
  743. if (!unnormalized_range.is_valid())
  744. return;
  745. auto range = unnormalized_range.normalized();
  746. // First delete all the lines in between the first and last one.
  747. for (size_t i = range.start().line() + 1; i < range.end().line();) {
  748. remove_line(i);
  749. range.end().set_line(range.end().line() - 1);
  750. }
  751. if (range.start().line() == range.end().line()) {
  752. // Delete within same line.
  753. auto& line = this->line(range.start().line());
  754. if (line.length() == 0)
  755. return;
  756. bool whole_line_is_selected = range.start().column() == 0 && range.end().column() == line.length();
  757. if (whole_line_is_selected) {
  758. line.clear(*this);
  759. } else {
  760. line.remove_range(*this, range.start().column(), range.end().column() - range.start().column());
  761. }
  762. } else {
  763. // Delete across a newline, merging lines.
  764. VERIFY(range.start().line() == range.end().line() - 1);
  765. auto& first_line = line(range.start().line());
  766. auto& second_line = line(range.end().line());
  767. Vector<u32> code_points;
  768. code_points.append(first_line.code_points(), range.start().column());
  769. code_points.append(second_line.code_points() + range.end().column(), second_line.length() - range.end().column());
  770. first_line.set_text(*this, move(code_points));
  771. remove_line(range.end().line());
  772. }
  773. if (lines().is_empty()) {
  774. append_line(make<TextDocumentLine>(*this));
  775. }
  776. notify_did_change();
  777. }
  778. bool TextDocument::is_empty() const
  779. {
  780. return line_count() == 1 && line(0).is_empty();
  781. }
  782. TextRange TextDocument::range_for_entire_line(size_t line_index) const
  783. {
  784. if (line_index >= line_count())
  785. return {};
  786. return { { line_index, 0 }, { line_index, line(line_index).length() } };
  787. }
  788. const TextDocumentSpan* TextDocument::span_at(const TextPosition& position) const
  789. {
  790. for (auto& span : m_spans) {
  791. if (span.range.contains(position))
  792. return &span;
  793. }
  794. return nullptr;
  795. }
  796. void TextDocument::set_unmodified()
  797. {
  798. m_undo_stack.set_current_unmodified();
  799. }
  800. }