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