Serenity Operating System
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
27#pragma once
28
29#include <AK/HashTable.h>
30#include <AK/NonnullOwnPtrVector.h>
31#include <AK/NonnullRefPtr.h>
32#include <AK/Optional.h>
33#include <AK/RefCounted.h>
34#include <LibCore/Forward.h>
35#include <LibGUI/Command.h>
36#include <LibGUI/Forward.h>
37#include <LibGUI/TextRange.h>
38#include <LibGUI/UndoStack.h>
39#include <LibGfx/Color.h>
40#include <LibGfx/Forward.h>
41
42namespace GUI {
43
44struct TextDocumentSpan {
45 TextRange range;
46 Color color;
47 Optional<Color> background_color;
48 bool is_skippable { false };
49 const Gfx::Font* font { nullptr };
50 void* data { nullptr };
51};
52
53class TextDocument : public RefCounted<TextDocument> {
54public:
55 enum class SearchShouldWrap {
56 No = 0,
57 Yes
58 };
59
60 class Client {
61 public:
62 virtual ~Client();
63 virtual void document_did_append_line() = 0;
64 virtual void document_did_insert_line(size_t) = 0;
65 virtual void document_did_remove_line(size_t) = 0;
66 virtual void document_did_remove_all_lines() = 0;
67 virtual void document_did_change() = 0;
68 virtual void document_did_set_text() = 0;
69 virtual void document_did_set_cursor(const TextPosition&) = 0;
70
71 virtual bool is_automatic_indentation_enabled() const = 0;
72 virtual int soft_tab_width() const = 0;
73 };
74
75 static NonnullRefPtr<TextDocument> create(Client* client = nullptr);
76 ~TextDocument();
77
78 size_t line_count() const { return (size_t)m_lines.size(); }
79 const TextDocumentLine& line(size_t line_index) const { return m_lines[(int)line_index]; }
80 TextDocumentLine& line(size_t line_index) { return m_lines[(int)line_index]; }
81
82 void set_spans(const Vector<TextDocumentSpan>& spans) { m_spans = spans; }
83
84 void set_text(const StringView&);
85
86 const NonnullOwnPtrVector<TextDocumentLine>& lines() const { return m_lines; }
87 NonnullOwnPtrVector<TextDocumentLine>& lines() { return m_lines; }
88
89 bool has_spans() const { return !m_spans.is_empty(); }
90 const Vector<TextDocumentSpan>& spans() const { return m_spans; }
91 void set_span_at_index(size_t index, TextDocumentSpan span) { m_spans[(int)index] = move(span); }
92
93 void append_line(NonnullOwnPtr<TextDocumentLine>);
94 void remove_line(size_t line_index);
95 void remove_all_lines();
96 void insert_line(size_t line_index, NonnullOwnPtr<TextDocumentLine>);
97
98 void register_client(Client&);
99 void unregister_client(Client&);
100
101 void update_views(Badge<TextDocumentLine>);
102
103 String text_in_range(const TextRange&) const;
104
105 Vector<TextRange> find_all(const StringView& needle) const;
106
107 TextRange find_next(const StringView&, const TextPosition& start = {}, SearchShouldWrap = SearchShouldWrap::Yes) const;
108 TextRange find_previous(const StringView&, const TextPosition& start = {}, SearchShouldWrap = SearchShouldWrap::Yes) const;
109
110 TextPosition next_position_after(const TextPosition&, SearchShouldWrap = SearchShouldWrap::Yes) const;
111 TextPosition previous_position_before(const TextPosition&, SearchShouldWrap = SearchShouldWrap::Yes) const;
112
113 char character_at(const TextPosition&) const;
114
115 TextRange range_for_entire_line(size_t line_index) const;
116
117 Optional<TextDocumentSpan> first_non_skippable_span_before(const TextPosition&) const;
118 Optional<TextDocumentSpan> first_non_skippable_span_after(const TextPosition&) const;
119
120 void add_to_undo_stack(NonnullOwnPtr<TextDocumentUndoCommand>);
121
122 bool can_undo() const { return m_undo_stack.can_undo(); }
123 bool can_redo() const { return m_undo_stack.can_redo(); }
124 void undo();
125 void redo();
126
127 void notify_did_change();
128 void set_all_cursors(const TextPosition&);
129
130 TextPosition insert_at(const TextPosition&, char, const Client* = nullptr);
131 TextPosition insert_at(const TextPosition&, const StringView&, const Client* = nullptr);
132 void remove(const TextRange&);
133
134private:
135 explicit TextDocument(Client* client);
136
137 void update_undo_timer();
138
139 NonnullOwnPtrVector<TextDocumentLine> m_lines;
140 Vector<TextDocumentSpan> m_spans;
141
142 HashTable<Client*> m_clients;
143 bool m_client_notifications_enabled { true };
144
145 UndoStack m_undo_stack;
146 RefPtr<Core::Timer> m_undo_timer;
147};
148
149class TextDocumentLine {
150 friend class GTextEditor;
151 friend class TextDocument;
152
153public:
154 explicit TextDocumentLine(TextDocument&);
155 explicit TextDocumentLine(TextDocument&, const StringView&);
156
157 StringView view() const { return { characters(), (size_t)length() }; }
158 const char* characters() const { return m_text.data(); }
159 size_t length() const { return (size_t)m_text.size() - 1; }
160 void set_text(TextDocument&, const StringView&);
161 void append(TextDocument&, char);
162 void prepend(TextDocument&, char);
163 void insert(TextDocument&, size_t index, char);
164 void remove(TextDocument&, size_t index);
165 void append(TextDocument&, const char*, size_t);
166 void truncate(TextDocument&, size_t length);
167 void clear(TextDocument&);
168 size_t first_non_whitespace_column() const;
169
170private:
171 // NOTE: This vector is null terminated.
172 Vector<char> m_text;
173};
174
175class TextDocumentUndoCommand : public Command {
176public:
177 TextDocumentUndoCommand(TextDocument&);
178 virtual ~TextDocumentUndoCommand();
179
180 void execute_from(const TextDocument::Client& client)
181 {
182 m_client = &client;
183 redo();
184 m_client = nullptr;
185 }
186
187protected:
188 TextDocument& m_document;
189 const TextDocument::Client* m_client { nullptr };
190};
191
192class InsertTextCommand : public TextDocumentUndoCommand {
193public:
194 InsertTextCommand(TextDocument&, const String&, const TextPosition&);
195 virtual void undo() override;
196 virtual void redo() override;
197
198private:
199 String m_text;
200 TextRange m_range;
201};
202
203class RemoveTextCommand : public TextDocumentUndoCommand {
204public:
205 RemoveTextCommand(TextDocument&, const String&, const TextRange&);
206 virtual void undo() override;
207 virtual void redo() override;
208
209private:
210 String m_text;
211 TextRange m_range;
212};
213
214}