Add and remove tags without clearing markup.
This commit is contained in:
parent
96b4c6c57a
commit
d3eff6f38a
2 changed files with 160 additions and 75 deletions
|
|
@ -98,6 +98,60 @@ bool IsNewline(QChar ch) {
|
||||||
return (kNewlineChars.indexOf(ch) >= 0);
|
return (kNewlineChars.indexOf(ch) >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsSeparateTag(const QStringRef &tag) {
|
||||||
|
return (tag == kTagCode.midRef(0)) || (tag == kTagPre.midRef(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsSeparateTag(const QString &tag) {
|
||||||
|
return IsSeparateTag(tag.midRef(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QString JoinTag(const QVector<QStringRef> &list) {
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
auto length = (list.size() - 1);
|
||||||
|
for (const auto &entry : list) {
|
||||||
|
length += entry.size();
|
||||||
|
}
|
||||||
|
auto result = QString();
|
||||||
|
result.reserve(length);
|
||||||
|
result.append(list.front());
|
||||||
|
for (auto i = 1, count = list.size(); i != count; ++i) {
|
||||||
|
if (!IsSeparateTag(list[i])) {
|
||||||
|
result.append('|').append(list[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QString TagWithRemoved(
|
||||||
|
const QString &tag,
|
||||||
|
const QString &removed) {
|
||||||
|
if (tag == removed) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
auto list = tag.splitRef('|');
|
||||||
|
list.erase(ranges::remove(list, removed.midRef(0)), list.end());
|
||||||
|
return JoinTag(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QString TagWithAdded(
|
||||||
|
const QString &tag,
|
||||||
|
const QString &added) {
|
||||||
|
if (tag == added) {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
auto list = tag.splitRef('|');
|
||||||
|
const auto ref = added.midRef(0);
|
||||||
|
if (list.contains(ref)) {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
list.push_back(ref);
|
||||||
|
ranges::sort(list);
|
||||||
|
return JoinTag(list);
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool IsValidMarkdownLink(const QStringRef &link) {
|
[[nodiscard]] bool IsValidMarkdownLink(const QStringRef &link) {
|
||||||
return (link.indexOf('.') >= 0) || (link.indexOf(':') >= 0);
|
return (link.indexOf('.') >= 0) || (link.indexOf(':') >= 0);
|
||||||
}
|
}
|
||||||
|
|
@ -133,9 +187,11 @@ bool IsNewline(QChar ch) {
|
||||||
resultLink = single.toString();
|
resultLink = single.toString();
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
} else if (resultLink.midRef(0) != single) {
|
} else if (resultLink.midRef(0) == single) {
|
||||||
return QString();
|
found = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
return QString();
|
||||||
} else if (!checkingLink && tag.midRef(0) == normalized) {
|
} else if (!checkingLink && tag.midRef(0) == normalized) {
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
|
|
@ -2922,6 +2978,10 @@ auto InputField::selectionEditLinkData(EditLinkSelection selection) const
|
||||||
const auto format = state.i.fragment().charFormat();
|
const auto format = state.i.fragment().charFormat();
|
||||||
return format.property(kTagProperty).toString();
|
return format.property(kTagProperty).toString();
|
||||||
};
|
};
|
||||||
|
const auto stateTagHasLink = [&](const State &state) {
|
||||||
|
const auto tag = stateTag(state);
|
||||||
|
return (tag == link) || tag.splitRef('|').contains(link.midRef(0));
|
||||||
|
};
|
||||||
const auto stateStart = [&](const State &state) {
|
const auto stateStart = [&](const State &state) {
|
||||||
return state.i.fragment().position();
|
return state.i.fragment().position();
|
||||||
};
|
};
|
||||||
|
|
@ -2941,14 +3001,14 @@ auto InputField::selectionEditLinkData(EditLinkSelection selection) const
|
||||||
} else if (fragmentStart >= selection.till) {
|
} else if (fragmentStart >= selection.till) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (stateTag(state) == link) {
|
if (stateTagHasLink(state)) {
|
||||||
auto start = fragmentStart;
|
auto start = fragmentStart;
|
||||||
auto finish = fragmentEnd;
|
auto finish = fragmentEnd;
|
||||||
auto copy = state;
|
auto copy = state;
|
||||||
while (moveToPrevious(copy) && (stateTag(copy) == link)) {
|
while (moveToPrevious(copy) && stateTagHasLink(copy)) {
|
||||||
start = stateStart(copy);
|
start = stateStart(copy);
|
||||||
}
|
}
|
||||||
while (skipInvalid(state) && (stateTag(state) == link)) {
|
while (skipInvalid(state) && stateTagHasLink(state)) {
|
||||||
finish = stateEnd(state);
|
finish = stateEnd(state);
|
||||||
moveToNext(state);
|
moveToNext(state);
|
||||||
}
|
}
|
||||||
|
|
@ -3270,73 +3330,87 @@ bool InputField::commitMarkdownReplacement(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputField::addMarkdownReplacement(
|
void InputField::addMarkdownTag(
|
||||||
int from,
|
int from,
|
||||||
int till,
|
int till,
|
||||||
const QString &tag) {
|
const QString &tag) {
|
||||||
const auto end = [&] {
|
|
||||||
auto cursor = QTextCursor(document()->docHandle(), 0);
|
|
||||||
cursor.movePosition(QTextCursor::End);
|
|
||||||
return cursor.position();
|
|
||||||
}();
|
|
||||||
|
|
||||||
const auto current = getTextWithTagsPart(from, till);
|
const auto current = getTextWithTagsPart(from, till);
|
||||||
const auto insert = current.text;
|
|
||||||
const auto tagRef = tag.midRef(0);
|
const auto tagRef = tag.midRef(0);
|
||||||
auto markdownTagApplies = std::vector<MarkdownTag>();
|
auto markdownTagApplies = std::vector<MarkdownTag>();
|
||||||
|
|
||||||
// #TODO Trim inserted tag, so that all newlines are left outside.
|
// #TODO Trim inserted tag, so that all newlines are left outside.
|
||||||
_insertedTags.clear();
|
auto tags = TagList();
|
||||||
auto filled = 0;
|
auto filled = 0;
|
||||||
for (const auto &existing : current.tags) {
|
const auto add = [&](const TextWithTags::Tag &existing) {
|
||||||
if (existing.offset >= till) {
|
const auto id = TagWithAdded(existing.id, tag);
|
||||||
break;
|
tags.push_back({ existing.offset, existing.length, id });
|
||||||
} else if (existing.offset > filled) {
|
filled = std::clamp(
|
||||||
_insertedTags.push_back({ filled, existing.offset - filled, tag });
|
existing.offset + existing.length,
|
||||||
auto &inserted = _insertedTags.back();
|
filled,
|
||||||
filled = existing.offset;
|
till - from);
|
||||||
markdownTagApplies.push_back({ from + inserted.offset, from + filled, -1, -1, false, inserted.id });
|
markdownTagApplies.push_back({
|
||||||
}
|
from + existing.offset,
|
||||||
_insertedTags.push_back(existing);
|
from + filled,
|
||||||
auto &inserted = _insertedTags.back();
|
-1,
|
||||||
auto list = existing.id.splitRef('|');
|
-1,
|
||||||
if (list.contains(tagRef)) {
|
false,
|
||||||
continue;
|
id,
|
||||||
}
|
});
|
||||||
list.push_back(tagRef);
|
};
|
||||||
ranges::sort(list);
|
if (!IsSeparateTag(tag)) {
|
||||||
inserted.id.clear();
|
for (const auto &existing : current.tags) {
|
||||||
for (const auto &single : list) {
|
if (existing.offset >= till) {
|
||||||
if (!inserted.id.isEmpty()) {
|
break;
|
||||||
inserted.id.append('|');
|
} else if (existing.offset > filled) {
|
||||||
|
add({ filled, existing.offset - filled, tag });
|
||||||
}
|
}
|
||||||
inserted.id.append(single);
|
add(existing);
|
||||||
}
|
}
|
||||||
filled = std::clamp(existing.offset + existing.length, filled, till - from);
|
|
||||||
markdownTagApplies.push_back({ from + inserted.offset, from + filled, -1, -1, false, inserted.id });
|
|
||||||
}
|
}
|
||||||
if (filled < till - from) {
|
if (filled < till - from) {
|
||||||
_insertedTags.push_back({ filled, till - from - filled, tag });
|
add({ filled, till - from - filled, tag });
|
||||||
auto &inserted = _insertedTags.back();
|
|
||||||
filled = till - from;
|
|
||||||
markdownTagApplies.push_back({ from + inserted.offset, from + filled, -1, -1, false, inserted.id });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace.
|
finishMarkdownTagChange(from, till, { current.text, tags });
|
||||||
|
|
||||||
|
// Fire the tags to the spellchecker.
|
||||||
|
for (auto &apply : markdownTagApplies) {
|
||||||
|
_markdownTagApplies.fire(std::move(apply));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputField::removeMarkdownTag(
|
||||||
|
int from,
|
||||||
|
int till,
|
||||||
|
const QString &tag) {
|
||||||
|
const auto current = getTextWithTagsPart(from, till);
|
||||||
|
const auto tagRef = tag.midRef(0);
|
||||||
|
|
||||||
|
auto tags = TagList();
|
||||||
|
for (const auto &existing : current.tags) {
|
||||||
|
const auto id = TagWithRemoved(existing.id, tag);
|
||||||
|
if (!id.isEmpty()) {
|
||||||
|
tags.push_back({ existing.offset, existing.length, id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finishMarkdownTagChange(from, till, { current.text, tags });
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputField::finishMarkdownTagChange(
|
||||||
|
int from,
|
||||||
|
int till,
|
||||||
|
const TextWithTags &textWithTags) {
|
||||||
auto cursor = _inner->textCursor();
|
auto cursor = _inner->textCursor();
|
||||||
cursor.setPosition(from);
|
cursor.setPosition(from);
|
||||||
cursor.setPosition(till, QTextCursor::KeepAnchor);
|
cursor.setPosition(till, QTextCursor::KeepAnchor);
|
||||||
|
_insertedTags = textWithTags.tags;
|
||||||
_insertedTagsAreFromMime = false;
|
_insertedTagsAreFromMime = false;
|
||||||
cursor.insertText(insert, _defaultCharFormat);
|
cursor.insertText(textWithTags.text, _defaultCharFormat);
|
||||||
_insertedTags.clear();
|
_insertedTags.clear();
|
||||||
|
|
||||||
cursor.setCharFormat(_defaultCharFormat);
|
cursor.setCharFormat(_defaultCharFormat);
|
||||||
_inner->setTextCursor(cursor);
|
_inner->setTextCursor(cursor);
|
||||||
|
|
||||||
// Fire the tag to the spellchecker.
|
|
||||||
for (auto &apply : markdownTagApplies) {
|
|
||||||
_markdownTagApplies.fire(std::move(apply));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InputField::IsValidMarkdownLink(const QStringRef &link) {
|
bool InputField::IsValidMarkdownLink(const QStringRef &link) {
|
||||||
|
|
@ -3380,34 +3454,40 @@ void InputField::toggleSelectionMarkdown(const QString &tag) {
|
||||||
if (from == till) {
|
if (from == till) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (tag.isEmpty()
|
if (tag.isEmpty()) {
|
||||||
|| HasFullTextTag(getTextWithTagsSelected(), tag)) {
|
|
||||||
//RemoveDocumentTag(_st, document(), from, till, tag);
|
|
||||||
RemoveDocumentTags(_st, document(), from, till);
|
RemoveDocumentTags(_st, document(), from, till);
|
||||||
return;
|
} else if (HasFullTextTag(getTextWithTagsSelected(), tag)) {
|
||||||
}
|
removeMarkdownTag(from, till, tag);
|
||||||
const auto commitTag = [&] {
|
} else {
|
||||||
if (tag != kTagCode) {
|
const auto useTag = [&] {
|
||||||
return tag;
|
if (tag != kTagCode) {
|
||||||
}
|
return tag;
|
||||||
const auto leftForBlock = [&] {
|
|
||||||
if (!from) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
const auto text = getTextWithTagsPart(from - 1, from + 1).text;
|
const auto leftForBlock = [&] {
|
||||||
return text.isEmpty()
|
if (!from) {
|
||||||
|| IsNewline(text[0])
|
return true;
|
||||||
|| IsNewline(text[text.size() - 1]);
|
}
|
||||||
|
const auto text = getTextWithTagsPart(
|
||||||
|
from - 1,
|
||||||
|
from + 1
|
||||||
|
).text;
|
||||||
|
return text.isEmpty()
|
||||||
|
|| IsNewline(text[0])
|
||||||
|
|| IsNewline(text[text.size() - 1]);
|
||||||
|
}();
|
||||||
|
const auto rightForBlock = [&] {
|
||||||
|
const auto text = getTextWithTagsPart(
|
||||||
|
till - 1,
|
||||||
|
till + 1
|
||||||
|
).text;
|
||||||
|
return text.isEmpty()
|
||||||
|
|| IsNewline(text[0])
|
||||||
|
|| IsNewline(text[text.size() - 1]);
|
||||||
|
}();
|
||||||
|
return (leftForBlock && rightForBlock) ? kTagPre : kTagCode;
|
||||||
}();
|
}();
|
||||||
const auto rightForBlock = [&] {
|
addMarkdownTag(from, till, useTag);
|
||||||
const auto text = getTextWithTagsPart(till - 1, till + 1).text;
|
}
|
||||||
return text.isEmpty()
|
|
||||||
|| IsNewline(text[0])
|
|
||||||
|| IsNewline(text[text.size() - 1]);
|
|
||||||
}();
|
|
||||||
return (leftForBlock && rightForBlock) ? kTagPre : kTagCode;
|
|
||||||
}();
|
|
||||||
addMarkdownReplacement(from, till, commitTag);
|
|
||||||
auto restorePosition = textCursor();
|
auto restorePosition = textCursor();
|
||||||
restorePosition.setPosition((position == till) ? from : till);
|
restorePosition.setPosition((position == till) ? from : till);
|
||||||
restorePosition.setPosition(position, QTextCursor::KeepAnchor);
|
restorePosition.setPosition(position, QTextCursor::KeepAnchor);
|
||||||
|
|
|
||||||
|
|
@ -453,7 +453,12 @@ private:
|
||||||
int till,
|
int till,
|
||||||
const QString &tag,
|
const QString &tag,
|
||||||
const QString &edge = QString());
|
const QString &edge = QString());
|
||||||
void addMarkdownReplacement(int from, int till, const QString &tag);
|
void addMarkdownTag(int from, int till, const QString &tag);
|
||||||
|
void removeMarkdownTag(int from, int till, const QString &tag);
|
||||||
|
void finishMarkdownTagChange(
|
||||||
|
int from,
|
||||||
|
int till,
|
||||||
|
const TextWithTags &textWithTags);
|
||||||
void toggleSelectionMarkdown(const QString &tag);
|
void toggleSelectionMarkdown(const QString &tag);
|
||||||
void clearSelectionMarkdown();
|
void clearSelectionMarkdown();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue