Document.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. /*
  2. * Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/ByteString.h>
  7. #include <AK/CharacterTypes.h>
  8. #include <AK/Debug.h>
  9. #include <AK/QuickSort.h>
  10. #include <AK/StringBuilder.h>
  11. #include <AK/Utf8View.h>
  12. #include <AK/Vector.h>
  13. #include <LibSyntax/Document.h>
  14. namespace Syntax {
  15. size_t TextDocumentLine::first_non_whitespace_column() const
  16. {
  17. for (size_t i = 0; i < length(); ++i) {
  18. auto code_point = code_points()[i];
  19. if (!is_ascii_space(code_point))
  20. return i;
  21. }
  22. return length();
  23. }
  24. Optional<size_t> TextDocumentLine::last_non_whitespace_column() const
  25. {
  26. for (ssize_t i = length() - 1; i >= 0; --i) {
  27. auto code_point = code_points()[i];
  28. if (!is_ascii_space(code_point))
  29. return i;
  30. }
  31. return {};
  32. }
  33. bool TextDocumentLine::ends_in_whitespace() const
  34. {
  35. if (!length())
  36. return false;
  37. return is_ascii_space(code_points()[length() - 1]);
  38. }
  39. bool TextDocumentLine::can_select() const
  40. {
  41. if (is_empty())
  42. return false;
  43. for (size_t i = 0; i < length(); ++i) {
  44. auto code_point = code_points()[i];
  45. if (code_point != '\n' && code_point != '\r' && code_point != '\f' && code_point != '\v')
  46. return true;
  47. }
  48. return false;
  49. }
  50. size_t TextDocumentLine::leading_spaces() const
  51. {
  52. size_t count = 0;
  53. for (; count < m_text.size(); ++count) {
  54. if (m_text[count] != ' ') {
  55. break;
  56. }
  57. }
  58. return count;
  59. }
  60. ByteString TextDocumentLine::to_utf8() const
  61. {
  62. StringBuilder builder;
  63. builder.append(view());
  64. return builder.to_byte_string();
  65. }
  66. TextDocumentLine::TextDocumentLine(Document& document)
  67. {
  68. clear(document);
  69. }
  70. TextDocumentLine::TextDocumentLine(Document& document, StringView text)
  71. {
  72. set_text(document, text);
  73. }
  74. void TextDocumentLine::clear(Document& document)
  75. {
  76. m_text.clear();
  77. document.update_views({});
  78. }
  79. void TextDocumentLine::set_text(Document& document, Vector<u32> const text)
  80. {
  81. m_text = move(text);
  82. document.update_views({});
  83. }
  84. bool TextDocumentLine::set_text(Document& document, StringView text)
  85. {
  86. if (text.is_empty()) {
  87. clear(document);
  88. return true;
  89. }
  90. m_text.clear();
  91. Utf8View utf8_view(text);
  92. if (!utf8_view.validate()) {
  93. return false;
  94. }
  95. for (auto code_point : utf8_view)
  96. m_text.append(code_point);
  97. document.update_views({});
  98. return true;
  99. }
  100. void TextDocumentLine::append(Document& document, u32 const* code_points, size_t length)
  101. {
  102. if (length == 0)
  103. return;
  104. m_text.append(code_points, length);
  105. document.update_views({});
  106. }
  107. void TextDocumentLine::append(Document& document, u32 code_point)
  108. {
  109. insert(document, length(), code_point);
  110. }
  111. void TextDocumentLine::prepend(Document& document, u32 code_point)
  112. {
  113. insert(document, 0, code_point);
  114. }
  115. void TextDocumentLine::insert(Document& document, size_t index, u32 code_point)
  116. {
  117. if (index == length()) {
  118. m_text.append(code_point);
  119. } else {
  120. m_text.insert(index, code_point);
  121. }
  122. document.update_views({});
  123. }
  124. void TextDocumentLine::remove(Document& document, size_t index)
  125. {
  126. if (index == length()) {
  127. m_text.take_last();
  128. } else {
  129. m_text.remove(index);
  130. }
  131. document.update_views({});
  132. }
  133. void TextDocumentLine::remove_range(Document& document, size_t start, size_t length)
  134. {
  135. VERIFY(length <= m_text.size());
  136. Vector<u32> new_data;
  137. new_data.ensure_capacity(m_text.size() - length);
  138. for (size_t i = 0; i < start; ++i)
  139. new_data.append(m_text[i]);
  140. for (size_t i = (start + length); i < m_text.size(); ++i)
  141. new_data.append(m_text[i]);
  142. m_text = move(new_data);
  143. document.update_views({});
  144. }
  145. void TextDocumentLine::keep_range(Document& document, size_t start_index, size_t length)
  146. {
  147. VERIFY(start_index + length < m_text.size());
  148. Vector<u32> new_data;
  149. new_data.ensure_capacity(m_text.size());
  150. for (size_t i = start_index; i <= (start_index + length); i++)
  151. new_data.append(m_text[i]);
  152. m_text = move(new_data);
  153. document.update_views({});
  154. }
  155. void TextDocumentLine::truncate(Document& document, size_t length)
  156. {
  157. m_text.resize(length);
  158. document.update_views({});
  159. }
  160. TextDocumentSpan const* Document::span_at(TextPosition const& position) const
  161. {
  162. for (auto& span : m_spans) {
  163. if (span.range.contains(position))
  164. return &span;
  165. }
  166. return nullptr;
  167. }
  168. void Document::set_spans(u32 span_collection_index, Vector<TextDocumentSpan> spans)
  169. {
  170. m_span_collections.set(span_collection_index, move(spans));
  171. merge_span_collections();
  172. }
  173. struct SpanAndCollectionIndex {
  174. TextDocumentSpan span;
  175. u32 collection_index { 0 };
  176. };
  177. void Document::merge_span_collections()
  178. {
  179. Vector<SpanAndCollectionIndex> sorted_spans;
  180. auto collection_indices = m_span_collections.keys();
  181. quick_sort(collection_indices);
  182. for (auto collection_index : collection_indices) {
  183. auto spans = m_span_collections.get(collection_index).value();
  184. for (auto span : spans) {
  185. sorted_spans.append({ move(span), collection_index });
  186. }
  187. }
  188. quick_sort(sorted_spans, [](SpanAndCollectionIndex const& a, SpanAndCollectionIndex const& b) {
  189. if (a.span.range.start() == b.span.range.start()) {
  190. return a.collection_index < b.collection_index;
  191. }
  192. return a.span.range.start() < b.span.range.start();
  193. });
  194. // The end of the TextRanges of spans are non-inclusive, i.e span range = [X,y).
  195. // This transforms the span's range to be inclusive, i.e [X,Y].
  196. auto adjust_end = [](TextDocumentSpan span) -> TextDocumentSpan {
  197. span.range.set_end({ span.range.end().line(), span.range.end().column() == 0 ? 0 : span.range.end().column() - 1 });
  198. return span;
  199. };
  200. Vector<SpanAndCollectionIndex> merged_spans;
  201. for (auto& span_and_collection_index : sorted_spans) {
  202. if (merged_spans.is_empty()) {
  203. merged_spans.append(span_and_collection_index);
  204. continue;
  205. }
  206. auto const& span = span_and_collection_index.span;
  207. auto last_span_and_collection_index = merged_spans.last();
  208. auto const& last_span = last_span_and_collection_index.span;
  209. if (adjust_end(span).range.start() > adjust_end(last_span).range.end()) {
  210. // Current span does not intersect with previous one, can simply append to merged list.
  211. merged_spans.append(span_and_collection_index);
  212. continue;
  213. }
  214. merged_spans.take_last();
  215. if (span.range.start() > last_span.range.start()) {
  216. SpanAndCollectionIndex first_part = last_span_and_collection_index;
  217. first_part.span.range.set_end(span.range.start());
  218. merged_spans.append(move(first_part));
  219. }
  220. SpanAndCollectionIndex merged_span;
  221. merged_span.collection_index = span_and_collection_index.collection_index;
  222. merged_span.span.range = { span.range.start(), min(span.range.end(), last_span.range.end()) };
  223. merged_span.span.is_skippable = span.is_skippable | last_span.is_skippable;
  224. merged_span.span.data = span.data ? span.data : last_span.data;
  225. merged_span.span.attributes.color = span_and_collection_index.collection_index > last_span_and_collection_index.collection_index ? span.attributes.color : last_span.attributes.color;
  226. merged_span.span.attributes.bold = span.attributes.bold | last_span.attributes.bold;
  227. merged_span.span.attributes.background_color = span.attributes.background_color.has_value() ? span.attributes.background_color.value() : last_span.attributes.background_color;
  228. merged_span.span.attributes.underline_color = span.attributes.underline_color.has_value() ? span.attributes.underline_color.value() : last_span.attributes.underline_color;
  229. merged_span.span.attributes.underline_style = span.attributes.underline_style.has_value() ? span.attributes.underline_style : last_span.attributes.underline_style;
  230. merged_spans.append(move(merged_span));
  231. if (span.range.end() == last_span.range.end())
  232. continue;
  233. if (span.range.end() > last_span.range.end()) {
  234. SpanAndCollectionIndex last_part = span_and_collection_index;
  235. last_part.span.range.set_start(last_span.range.end());
  236. merged_spans.append(move(last_part));
  237. continue;
  238. }
  239. SpanAndCollectionIndex last_part = last_span_and_collection_index;
  240. last_part.span.range.set_start(span.range.end());
  241. merged_spans.append(move(last_part));
  242. }
  243. m_spans.clear();
  244. TextDocumentSpan previous_span { .range = { TextPosition(0, 0), TextPosition(0, 0) }, .attributes = {} };
  245. for (auto span : merged_spans) {
  246. // Validate spans
  247. if (!span.span.range.is_valid()) {
  248. dbgln_if(TEXTEDITOR_DEBUG, "Invalid span {} => ignoring", span.span.range);
  249. continue;
  250. }
  251. if (span.span.range.end() < span.span.range.start()) {
  252. dbgln_if(TEXTEDITOR_DEBUG, "Span {} has negative length => ignoring", span.span.range);
  253. continue;
  254. }
  255. if (span.span.range.end() < previous_span.range.start()) {
  256. dbgln_if(TEXTEDITOR_DEBUG, "Spans not sorted (Span {} ends before previous span {}) => ignoring", span.span.range, previous_span.range);
  257. continue;
  258. }
  259. if (span.span.range.start() < previous_span.range.end()) {
  260. dbgln_if(TEXTEDITOR_DEBUG, "Span {} overlaps previous span {} => ignoring", span.span.range, previous_span.range);
  261. continue;
  262. }
  263. previous_span = span.span;
  264. m_spans.append(move(span.span));
  265. }
  266. }
  267. void Document::set_folding_regions(Vector<TextDocumentFoldingRegion> folding_regions)
  268. {
  269. // Remove any regions that don't span at least 3 lines.
  270. // Currently, we can't do anything useful with them, and our implementation gets very confused by
  271. // single-line regions, so drop them.
  272. folding_regions.remove_all_matching([](TextDocumentFoldingRegion const& region) {
  273. return region.range.line_count() < 3;
  274. });
  275. quick_sort(folding_regions, [](TextDocumentFoldingRegion const& a, TextDocumentFoldingRegion const& b) {
  276. return a.range.start() < b.range.start();
  277. });
  278. for (auto& folding_region : folding_regions) {
  279. folding_region.line_ptr = &line(folding_region.range.start().line());
  280. // Map the new folding region to an old one, to preserve which regions were folded.
  281. // FIXME: This is O(n*n).
  282. for (auto const& existing_folding_region : m_folding_regions) {
  283. // We treat two folding regions as the same if they start on the same TextDocumentLine,
  284. // and have the same line count. The actual line *numbers* might change, but the pointer
  285. // and count should not.
  286. if (existing_folding_region.line_ptr
  287. && existing_folding_region.line_ptr == folding_region.line_ptr
  288. && existing_folding_region.range.line_count() == folding_region.range.line_count()) {
  289. folding_region.is_folded = existing_folding_region.is_folded;
  290. break;
  291. }
  292. }
  293. }
  294. // FIXME: Remove any regions that partially overlap another region, since these are invalid.
  295. m_folding_regions = move(folding_regions);
  296. if constexpr (TEXTEDITOR_DEBUG) {
  297. dbgln("Document got {} fold regions:", m_folding_regions.size());
  298. for (auto const& item : m_folding_regions) {
  299. dbgln("- {} (ptr: {:p}, folded: {})", item.range, item.line_ptr, item.is_folded);
  300. }
  301. }
  302. }
  303. Optional<TextDocumentFoldingRegion&> Document::folding_region_starting_on_line(size_t line)
  304. {
  305. return m_folding_regions.first_matching([line](auto& region) {
  306. return region.range.start().line() == line;
  307. });
  308. }
  309. bool Document::line_is_visible(size_t line) const
  310. {
  311. // FIXME: line_is_visible() gets called a lot.
  312. // We could avoid a lot of repeated work if we saved this state on the TextDocumentLine.
  313. return !any_of(m_folding_regions, [line](auto& region) {
  314. return region.is_folded
  315. && line > region.range.start().line()
  316. && line < region.range.end().line();
  317. });
  318. }
  319. Vector<TextDocumentFoldingRegion const&> Document::currently_folded_regions() const
  320. {
  321. Vector<TextDocumentFoldingRegion const&> folded_regions;
  322. for (auto& region : m_folding_regions) {
  323. if (region.is_folded) {
  324. // Only add this region if it's not contained within a previous folded region.
  325. // Because regions are sorted by their start position, and regions cannot partially overlap,
  326. // we can just see if it starts inside the last region we appended.
  327. if (!folded_regions.is_empty() && folded_regions.last().range.contains(region.range.start()))
  328. continue;
  329. folded_regions.append(region);
  330. }
  331. }
  332. return folded_regions;
  333. }
  334. }