1 // This file is part of Desktop App Toolkit,
2 // a set of libraries for developing nice desktop applications.
3 //
4 // For license and copyright information please follow this link:
5 // https://github.com/desktop-app/legal/blob/master/LEGAL
6 //
7 #pragma once
8
9 #include "base/basic_types.h"
10 #include "base/algorithm.h"
11
12 #include <QtCore/QList>
13 #include <QtCore/QVector>
14 #include <QtGui/QClipboard>
15
16 enum class EntityType : uchar {
17 Invalid = 0,
18
19 Url,
20 CustomUrl,
21 Email,
22 Hashtag,
23 Cashtag,
24 Mention,
25 MentionName,
26 BotCommand,
27 MediaTimestamp,
28
29 Bold,
30 Semibold,
31 Italic,
32 Underline,
33 StrikeOut,
34 Code, // inline
35 Pre, // block
36 };
37
38 enum class EntityLinkShown : uchar {
39 Full,
40 Partial,
41 };
42
43 struct EntityLinkData {
44 QString text;
45 QString data;
46 EntityType type = EntityType::Invalid;
47 EntityLinkShown shown = EntityLinkShown::Full;
48 };
49
50 class EntityInText;
51 using EntitiesInText = QList<EntityInText>;
52
53 class EntityInText {
54 public:
55 EntityInText(
56 EntityType type,
57 int offset,
58 int length,
59 const QString &data = QString());
60
type()61 EntityType type() const {
62 return _type;
63 }
offset()64 int offset() const {
65 return _offset;
66 }
length()67 int length() const {
68 return _length;
69 }
data()70 QString data() const {
71 return _data;
72 }
73
extendToLeft(int extent)74 void extendToLeft(int extent) {
75 _offset -= extent;
76 _length += extent;
77 }
shrinkFromRight(int shrink)78 void shrinkFromRight(int shrink) {
79 _length -= shrink;
80 }
shiftLeft(int shift)81 void shiftLeft(int shift) {
82 _offset -= shift;
83 if (_offset < 0) {
84 _length += _offset;
85 _offset = 0;
86 if (_length < 0) {
87 _length = 0;
88 }
89 }
90 }
shiftRight(int shift)91 void shiftRight(int shift) {
92 _offset += shift;
93 }
updateTextEnd(int textEnd)94 void updateTextEnd(int textEnd) {
95 if (_offset > textEnd) {
96 _offset = textEnd;
97 _length = 0;
98 } else if (_offset + _length > textEnd) {
99 _length = textEnd - _offset;
100 }
101 }
102
103 static int FirstMonospaceOffset(
104 const EntitiesInText &entities,
105 int textLength);
106
107 explicit operator bool() const {
108 return type() != EntityType::Invalid;
109 }
110
111 private:
112 EntityType _type = EntityType::Invalid;
113 int _offset = 0;
114 int _length = 0;
115 QString _data;
116
117 };
118
119 inline bool operator==(const EntityInText &a, const EntityInText &b) {
120 return (a.type() == b.type())
121 && (a.offset() == b.offset())
122 && (a.length() == b.length())
123 && (a.data() == b.data());
124 }
125
126 inline bool operator!=(const EntityInText &a, const EntityInText &b) {
127 return !(a == b);
128 }
129
130 struct TextWithEntities {
131 QString text;
132 EntitiesInText entities;
133
emptyTextWithEntities134 bool empty() const {
135 return text.isEmpty();
136 }
137
138 void reserve(int size, int entitiesCount = 0) {
139 text.reserve(size);
140 entities.reserve(entitiesCount);
141 }
142
appendTextWithEntities143 TextWithEntities &append(TextWithEntities &&other) {
144 const auto shift = text.size();
145 for (auto &entity : other.entities) {
146 entity.shiftRight(shift);
147 }
148 text.append(other.text);
149 entities.append(other.entities);
150 return *this;
151 }
appendTextWithEntities152 TextWithEntities &append(const QString &other) {
153 text.append(other);
154 return *this;
155 }
appendTextWithEntities156 TextWithEntities &append(QLatin1String other) {
157 text.append(other);
158 return *this;
159 }
appendTextWithEntities160 TextWithEntities &append(QChar other) {
161 text.append(other);
162 return *this;
163 }
164
SimpleTextWithEntities165 static TextWithEntities Simple(const QString &simple) {
166 auto result = TextWithEntities();
167 result.text = simple;
168 return result;
169 }
170 };
171
172 inline bool operator==(
173 const TextWithEntities &a,
174 const TextWithEntities &b) {
175 return (a.text == b.text) && (a.entities == b.entities);
176 }
177
178 inline bool operator!=(
179 const TextWithEntities &a,
180 const TextWithEntities &b) {
181 return !(a == b);
182 }
183
184 struct TextForMimeData {
185 QString expanded;
186 TextWithEntities rich;
187
emptyTextForMimeData188 bool empty() const {
189 return expanded.isEmpty();
190 }
191
192 void reserve(int size, int entitiesCount = 0) {
193 expanded.reserve(size);
194 rich.reserve(size, entitiesCount);
195 }
appendTextForMimeData196 TextForMimeData &append(TextForMimeData &&other) {
197 expanded.append(other.expanded);
198 rich.append(std::move(other.rich));
199 return *this;
200 }
appendTextForMimeData201 TextForMimeData &append(TextWithEntities &&other) {
202 expanded.append(other.text);
203 rich.append(std::move(other));
204 return *this;
205 }
appendTextForMimeData206 TextForMimeData &append(const QString &other) {
207 expanded.append(other);
208 rich.append(other);
209 return *this;
210 }
appendTextForMimeData211 TextForMimeData &append(QLatin1String other) {
212 expanded.append(other);
213 rich.append(other);
214 return *this;
215 }
appendTextForMimeData216 TextForMimeData &append(QChar other) {
217 expanded.append(other);
218 rich.append(other);
219 return *this;
220 }
221
RichTextForMimeData222 static TextForMimeData Rich(TextWithEntities &&rich) {
223 auto result = TextForMimeData();
224 result.expanded = rich.text;
225 result.rich = std::move(rich);
226 return result;
227 }
SimpleTextForMimeData228 static TextForMimeData Simple(const QString &simple) {
229 auto result = TextForMimeData();
230 result.expanded = result.rich.text = simple;
231 return result;
232 }
233 };
234
235 enum {
236 TextParseMultiline = 0x001,
237 TextParseLinks = 0x002,
238 TextParseRichText = 0x004,
239 TextParseMentions = 0x008,
240 TextParseHashtags = 0x010,
241 TextParseBotCommands = 0x020,
242 TextParseMarkdown = 0x040,
243 };
244
245 struct TextWithTags {
246 struct Tag {
247 int offset = 0;
248 int length = 0;
249 QString id;
250 };
251 using Tags = QVector<Tag>;
252
253 QString text;
254 Tags tags;
255 };
256
257 inline bool operator==(const TextWithTags::Tag &a, const TextWithTags::Tag &b) {
258 return (a.offset == b.offset) && (a.length == b.length) && (a.id == b.id);
259 }
260 inline bool operator!=(const TextWithTags::Tag &a, const TextWithTags::Tag &b) {
261 return !(a == b);
262 }
263
264 inline bool operator==(const TextWithTags &a, const TextWithTags &b) {
265 return (a.text == b.text) && (a.tags == b.tags);
266 }
267 inline bool operator!=(const TextWithTags &a, const TextWithTags &b) {
268 return !(a == b);
269 }
270
271 // Parsing helpers.
272
273 namespace TextUtilities {
274
275 bool IsValidProtocol(const QString &protocol);
276 bool IsValidTopDomain(const QString &domain);
277
278 const QRegularExpression &RegExpMailNameAtEnd();
279 const QRegularExpression &RegExpHashtag();
280 const QRegularExpression &RegExpHashtagExclude();
281 const QRegularExpression &RegExpMention();
282 const QRegularExpression &RegExpBotCommand();
283 QString MarkdownBoldGoodBefore();
284 QString MarkdownBoldBadAfter();
285 QString MarkdownItalicGoodBefore();
286 QString MarkdownItalicBadAfter();
287 QString MarkdownStrikeOutGoodBefore();
288 QString MarkdownStrikeOutBadAfter();
289 QString MarkdownCodeGoodBefore();
290 QString MarkdownCodeBadAfter();
291 QString MarkdownPreGoodBefore();
292 QString MarkdownPreBadAfter();
293
294 // Text preprocess.
295 QString Clean(const QString &text);
296 QString EscapeForRichParsing(const QString &text);
297 QString SingleLine(const QString &text);
298 TextWithEntities SingleLine(const TextWithEntities &text);
299 QString RemoveAccents(const QString &text);
300 QString RemoveEmoji(const QString &text);
301 QStringList PrepareSearchWords(const QString &query, const QRegularExpression *SplitterOverride = nullptr);
302 bool CutPart(TextWithEntities &sending, TextWithEntities &left, int limit);
303
304 struct MentionNameFields {
305 MentionNameFields(uint64 userId = 0, uint64 accessHash = 0)
userIdMentionNameFields306 : userId(userId), accessHash(accessHash) {
307 }
308 uint64 userId = 0;
309 uint64 accessHash = 0;
310 };
311
MentionNameDataToFields(const QString & data)312 inline MentionNameFields MentionNameDataToFields(const QString &data) {
313 auto components = data.split('.');
314 if (!components.isEmpty()) {
315 return {
316 components.at(0).toULongLong(),
317 (components.size() > 1) ? components.at(1).toULongLong() : 0
318 };
319 }
320 return MentionNameFields{};
321 }
322
MentionNameDataFromFields(const MentionNameFields & fields)323 inline QString MentionNameDataFromFields(const MentionNameFields &fields) {
324 auto result = QString::number(fields.userId);
325 if (fields.accessHash) {
326 result += '.' + QString::number(fields.accessHash);
327 }
328 return result;
329 }
330
331 // New entities are added to the ones that are already in result.
332 // Changes text if (flags & TextParseMarkdown).
333 TextWithEntities ParseEntities(const QString &text, int32 flags);
334 void ParseEntities(TextWithEntities &result, int32 flags, bool rich = false);
335
336 void PrepareForSending(TextWithEntities &result, int32 flags);
337 void Trim(TextWithEntities &result);
338
339 enum class PrepareTextOption {
340 IgnoreLinks,
341 CheckLinks,
342 };
343 inline QString PrepareForSending(const QString &text, PrepareTextOption option = PrepareTextOption::IgnoreLinks) {
344 auto result = TextWithEntities { text };
345 auto prepareFlags = (option == PrepareTextOption::CheckLinks) ? (TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands) : 0;
346 PrepareForSending(result, prepareFlags);
347 return result.text;
348 }
349
350 // Replace bad symbols with space and remove '\r'.
351 void ApplyServerCleaning(TextWithEntities &result);
352
353 [[nodiscard]] int SerializeTagsSize(const TextWithTags::Tags &tags);
354 [[nodiscard]] QByteArray SerializeTags(const TextWithTags::Tags &tags);
355 [[nodiscard]] TextWithTags::Tags DeserializeTags(
356 QByteArray data,
357 int textLength);
358 [[nodiscard]] QString TagsMimeType();
359 [[nodiscard]] QString TagsTextMimeType();
360
361 inline const auto kMentionTagStart = qstr("mention://user.");
362
363 [[nodiscard]] bool IsMentionLink(QStringView link);
364 [[nodiscard]] bool IsSeparateTag(QStringView tag);
365 [[nodiscard]] QString JoinTag(const QList<QStringView> &list);
366 [[nodiscard]] QString TagWithRemoved(
367 const QString &tag,
368 const QString &removed);
369 [[nodiscard]] QString TagWithAdded(const QString &tag, const QString &added);
370
371 EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags);
372 TextWithTags::Tags ConvertEntitiesToTextTags(
373 const EntitiesInText &entities);
374 std::unique_ptr<QMimeData> MimeDataFromText(const TextForMimeData &text);
375 std::unique_ptr<QMimeData> MimeDataFromText(TextWithTags &&text);
376 void SetClipboardText(
377 const TextForMimeData &text,
378 QClipboard::Mode mode = QClipboard::Clipboard);
379
380 } // namespace TextUtilities
381