Add "Suggest emoji replacements" checkbox.

Also emoji suggestions insert an instant emoji replacement.
This commit is contained in:
John Preston 2018-05-13 18:56:08 +03:00
parent 4b763a76df
commit 168a7ce2e5
14 changed files with 121 additions and 44 deletions

View File

@ -296,6 +296,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_section_chat_settings" = "Chat Settings"; "lng_settings_section_chat_settings" = "Chat Settings";
"lng_settings_replace_emojis" = "Replace emoji"; "lng_settings_replace_emojis" = "Replace emoji";
"lng_settings_suggest_emoji" = "Suggest emoji replacements";
"lng_settings_suggest_by_emoji" = "Suggest popular stickers by emoji"; "lng_settings_suggest_by_emoji" = "Suggest popular stickers by emoji";
"lng_settings_view_emojis" = "View list"; "lng_settings_view_emojis" = "View list";
"lng_settings_send_enter" = "Send by Enter"; "lng_settings_send_enter" = "Send by Enter";

View File

@ -346,12 +346,15 @@ void SuggestionsWidget::leaveEventHook(QEvent *e) {
return TWidget::leaveEventHook(e); return TWidget::leaveEventHook(e);
} }
SuggestionsController::SuggestionsController(QWidget *parent, not_null<QTextEdit*> field) : QObject(nullptr) SuggestionsController::SuggestionsController(QWidget *parent, not_null<QTextEdit*> field)
: QObject(nullptr)
, _field(field) , _field(field)
, _container(parent, st::emojiSuggestionsDropdown) , _container(parent, st::emojiSuggestionsDropdown)
, _suggestions(_container->setOwnedWidget(object_ptr<Ui::Emoji::SuggestionsWidget>(parent, st::emojiSuggestionsMenu))) { , _suggestions(_container->setOwnedWidget(object_ptr<Ui::Emoji::SuggestionsWidget>(parent, st::emojiSuggestionsMenu))) {
_container->setAutoHiding(false); _container->setAutoHiding(false);
setReplaceCallback(nullptr);
_field->installEventFilter(this); _field->installEventFilter(this);
connect(_field, &QTextEdit::textChanged, this, [this] { handleTextChange(); }); connect(_field, &QTextEdit::textChanged, this, [this] { handleTextChange(); });
connect(_field, &QTextEdit::cursorPositionChanged, this, [this] { handleCursorPositionChange(); }); connect(_field, &QTextEdit::cursorPositionChanged, this, [this] { handleCursorPositionChange(); });
@ -363,6 +366,23 @@ SuggestionsController::SuggestionsController(QWidget *parent, not_null<QTextEdit
handleTextChange(); handleTextChange();
} }
void SuggestionsController::setReplaceCallback(
base::lambda<void(
int from,
int till,
const QString &replacement)> callback) {
if (callback) {
_replaceCallback = std::move(callback);
} else {
_replaceCallback = [=](int from, int till, const QString &replacement) {
auto cursor = _field->textCursor();
cursor.setPosition(from);
cursor.setPosition(till, QTextCursor::KeepAnchor);
cursor.insertText(replacement);
};
}
}
void SuggestionsController::handleTextChange() { void SuggestionsController::handleTextChange() {
_ignoreCursorPositionChange = true; _ignoreCursorPositionChange = true;
InvokeQueued(this, [this] { _ignoreCursorPositionChange = false; }); InvokeQueued(this, [this] { _ignoreCursorPositionChange = false; });
@ -374,7 +394,7 @@ void SuggestionsController::handleTextChange() {
} }
QString SuggestionsController::getEmojiQuery() { QString SuggestionsController::getEmojiQuery() {
if (!cReplaceEmojis()) { if (!Global::SuggestEmoji()) {
return QString(); return QString();
} }
@ -471,23 +491,14 @@ QString SuggestionsController::getEmojiQuery() {
} }
void SuggestionsController::replaceCurrent(const QString &replacement) { void SuggestionsController::replaceCurrent(const QString &replacement) {
auto cursor = _field->textCursor();
auto suggestion = getEmojiQuery(); auto suggestion = getEmojiQuery();
if (suggestion.isEmpty()) { if (suggestion.isEmpty()) {
_suggestions->showWithQuery(QString()); _suggestions->showWithQuery(QString());
} else { } else {
cursor.setPosition(cursor.position() - suggestion.size(), QTextCursor::KeepAnchor); const auto cursor = _field->textCursor();
cursor.insertText(replacement); const auto position = cursor.position();
} const auto from = position - suggestion.size();
_replaceCallback(from, position, replacement);
if (auto emoji = Find(replacement)) {
if (emoji->hasVariants()) {
auto it = cEmojiVariants().constFind(emoji->nonColoredId());
if (it != cEmojiVariants().cend()) {
emoji = emoji->variant(it.value());
}
}
AddRecent(emoji);
} }
} }

View File

@ -69,6 +69,10 @@ public:
SuggestionsController(QWidget *parent, not_null<QTextEdit*> field); SuggestionsController(QWidget *parent, not_null<QTextEdit*> field);
void raise(); void raise();
void setReplaceCallback(base::lambda<void(
int from,
int till,
const QString &replacement)> callback);
protected: protected:
bool eventFilter(QObject *object, QEvent *event) override; bool eventFilter(QObject *object, QEvent *event) override;
@ -88,6 +92,10 @@ private:
bool _ignoreCursorPositionChange = false; bool _ignoreCursorPositionChange = false;
bool _textChangeAfterKeyPress = false; bool _textChangeAfterKeyPress = false;
QPointer<QTextEdit> _field; QPointer<QTextEdit> _field;
base::lambda<void(
int from,
int till,
const QString &replacement)> _replaceCallback;
object_ptr<InnerDropdown> _container; object_ptr<InnerDropdown> _container;
QPointer<SuggestionsWidget> _suggestions; QPointer<SuggestionsWidget> _suggestions;

View File

@ -133,6 +133,10 @@ MessageField::MessageField(QWidget *parent, not_null<Window::Controller*> contro
Assert(emoji != nullptr); Assert(emoji != nullptr);
addInstantReplace(what, emoji->text()); addInstantReplace(what, emoji->text());
} }
enableInstantReplaces(Global::ReplaceEmoji());
subscribe(Global::RefReplaceEmojiChanged(), [=] {
enableInstantReplaces(Global::ReplaceEmoji());
});
} }
bool MessageField::hasSendText() const { bool MessageField::hasSendText() const {

View File

@ -553,7 +553,10 @@ struct Data {
QByteArray DownloadPathBookmark; QByteArray DownloadPathBookmark;
base::Observable<void> DownloadPathChanged; base::Observable<void> DownloadPathChanged;
bool ReplaceEmoji = true;
bool SuggestEmoji = true;
bool SuggestStickersByEmoji = true; bool SuggestStickersByEmoji = true;
base::Observable<void> ReplaceEmojiChanged;
bool SoundNotify = true; bool SoundNotify = true;
bool DesktopNotify = true; bool DesktopNotify = true;
bool RestoreSoundNotifyFromTray = false; bool RestoreSoundNotifyFromTray = false;
@ -678,7 +681,10 @@ DefineVar(Global, QString, DownloadPath);
DefineVar(Global, QByteArray, DownloadPathBookmark); DefineVar(Global, QByteArray, DownloadPathBookmark);
DefineRefVar(Global, base::Observable<void>, DownloadPathChanged); DefineRefVar(Global, base::Observable<void>, DownloadPathChanged);
DefineVar(Global, bool, ReplaceEmoji);
DefineVar(Global, bool, SuggestEmoji);
DefineVar(Global, bool, SuggestStickersByEmoji); DefineVar(Global, bool, SuggestStickersByEmoji);
DefineRefVar(Global, base::Observable<void>, ReplaceEmojiChanged);
DefineVar(Global, bool, SoundNotify); DefineVar(Global, bool, SoundNotify);
DefineVar(Global, bool, DesktopNotify); DefineVar(Global, bool, DesktopNotify);
DefineVar(Global, bool, RestoreSoundNotifyFromTray); DefineVar(Global, bool, RestoreSoundNotifyFromTray);

View File

@ -361,7 +361,10 @@ DeclareVar(QString, DownloadPath);
DeclareVar(QByteArray, DownloadPathBookmark); DeclareVar(QByteArray, DownloadPathBookmark);
DeclareRefVar(base::Observable<void>, DownloadPathChanged); DeclareRefVar(base::Observable<void>, DownloadPathChanged);
DeclareVar(bool, ReplaceEmoji);
DeclareVar(bool, SuggestEmoji);
DeclareVar(bool, SuggestStickersByEmoji); DeclareVar(bool, SuggestStickersByEmoji);
DeclareRefVar(base::Observable<void>, ReplaceEmojiChanged);
DeclareVar(bool, SoundNotify); DeclareVar(bool, SoundNotify);
DeclareVar(bool, DesktopNotify); DeclareVar(bool, DesktopNotify);
DeclareVar(bool, RestoreSoundNotifyFromTray); DeclareVar(bool, RestoreSoundNotifyFromTray);

View File

@ -512,6 +512,12 @@ HistoryWidget::HistoryWidget(
data->text()); data->text());
}); });
_emojiSuggestions.create(this, _field.data()); _emojiSuggestions.create(this, _field.data());
_emojiSuggestions->setReplaceCallback([=](
int from,
int till,
const QString &replacement) {
_field->commmitInstantReplacement(from, till, replacement);
});
updateFieldSubmitSettings(); updateFieldSubmitSettings();
_field->hide(); _field->hide();

View File

@ -40,7 +40,6 @@ bool gRestartingUpdate = false, gRestarting = false, gRestartingToSettings = fal
int32 gLastUpdateCheck = 0; int32 gLastUpdateCheck = 0;
bool gNoStartUpdate = false; bool gNoStartUpdate = false;
bool gStartToSettings = false; bool gStartToSettings = false;
bool gReplaceEmojis = true;
bool gCtrlEnter = false; bool gCtrlEnter = false;

View File

@ -100,7 +100,6 @@ DeclareSetting(bool, WriteProtected);
DeclareSetting(int32, LastUpdateCheck); DeclareSetting(int32, LastUpdateCheck);
DeclareSetting(bool, NoStartUpdate); DeclareSetting(bool, NoStartUpdate);
DeclareSetting(bool, StartToSettings); DeclareSetting(bool, StartToSettings);
DeclareSetting(bool, ReplaceEmojis);
DeclareReadSetting(bool, ManyInstance); DeclareReadSetting(bool, ManyInstance);
DeclareSetting(QByteArray, LocalSalt); DeclareSetting(QByteArray, LocalSalt);

View File

@ -140,7 +140,8 @@ void ChatSettingsWidget::createControls() {
style::margins marginSub(0, 0, 0, st::settingsSubSkip); style::margins marginSub(0, 0, 0, st::settingsSubSkip);
style::margins slidedPadding(0, marginSub.bottom() / 2, 0, marginSub.bottom() - (marginSub.bottom() / 2)); style::margins slidedPadding(0, marginSub.bottom() / 2, 0, marginSub.bottom() - (marginSub.bottom() / 2));
createChildRow(_replaceEmoji, marginSmall, lang(lng_settings_replace_emojis), [this](bool) { toggleReplaceEmoji(); }, cReplaceEmojis()); createChildRow(_replaceEmoji, marginSmall, lang(lng_settings_replace_emojis), [this](bool) { toggleReplaceEmoji(); }, Global::ReplaceEmoji());
createChildRow(_suggestEmoji, marginSmall, lang(lng_settings_suggest_emoji), [this](bool) { toggleSuggestEmoji(); }, Global::SuggestEmoji());
createChildRow(_suggestByEmoji, marginSkip, lang(lng_settings_suggest_by_emoji), [this](bool) { toggleSuggestStickersByEmoji(); }, Global::SuggestStickersByEmoji()); createChildRow(_suggestByEmoji, marginSkip, lang(lng_settings_suggest_by_emoji), [this](bool) { toggleSuggestStickersByEmoji(); }, Global::SuggestStickersByEmoji());
#ifndef OS_WIN_STORE #ifndef OS_WIN_STORE
@ -170,7 +171,13 @@ void ChatSettingsWidget::createControls() {
} }
void ChatSettingsWidget::toggleReplaceEmoji() { void ChatSettingsWidget::toggleReplaceEmoji() {
cSetReplaceEmojis(_replaceEmoji->checked()); Global::SetReplaceEmoji(_replaceEmoji->checked());
Global::RefReplaceEmojiChanged().notify();
Local::writeUserSettings();
}
void ChatSettingsWidget::toggleSuggestEmoji() {
Global::SetSuggestEmoji(_suggestEmoji->checked());
Local::writeUserSettings(); Local::writeUserSettings();
} }

View File

@ -94,9 +94,11 @@ private:
void createControls(); void createControls();
void toggleReplaceEmoji(); void toggleReplaceEmoji();
void toggleSuggestEmoji();
void toggleSuggestStickersByEmoji(); void toggleSuggestStickersByEmoji();
Ui::Checkbox *_replaceEmoji = nullptr; Ui::Checkbox *_replaceEmoji = nullptr;
Ui::Checkbox *_suggestEmoji = nullptr;
Ui::Checkbox *_suggestByEmoji = nullptr; Ui::Checkbox *_suggestByEmoji = nullptr;
Ui::Checkbox *_dontAskDownloadPath = nullptr; Ui::Checkbox *_dontAskDownloadPath = nullptr;

View File

@ -522,7 +522,7 @@ enum {
// 0x10 reserved // 0x10 reserved
dbiDefaultAttach = 0x11, dbiDefaultAttach = 0x11,
dbiCatsAndDogs = 0x12, dbiCatsAndDogs = 0x12,
dbiReplaceEmojis = 0x13, dbiReplaceEmoji = 0x13,
dbiAskDownloadPath = 0x14, dbiAskDownloadPath = 0x14,
dbiDownloadPathOld = 0x15, dbiDownloadPathOld = 0x15,
dbiScale = 0x16, dbiScale = 0x16,
@ -575,6 +575,7 @@ enum {
dbiConnectionType = 0x4f, dbiConnectionType = 0x4f,
dbiStickersFavedLimit = 0x50, dbiStickersFavedLimit = 0x50,
dbiSuggestStickersByEmoji = 0x51, dbiSuggestStickersByEmoji = 0x51,
dbiSuggestEmoji = 0x52,
dbiEncryptedWithSalt = 333, dbiEncryptedWithSalt = 333,
dbiEncrypted = 444, dbiEncrypted = 444,
@ -1019,14 +1020,6 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
Global::SetSoundNotify(v == 1); Global::SetSoundNotify(v == 1);
} break; } break;
case dbiSuggestStickersByEmoji: {
qint32 v;
stream >> v;
if (!_checkStreamStatus(stream)) return false;
Global::SetSuggestStickersByEmoji(v == 1);
} break;
case dbiAutoDownload: { case dbiAutoDownload: {
qint32 photo, audio, gif; qint32 photo, audio, gif;
stream >> photo >> audio >> gif; stream >> photo >> audio >> gif;
@ -1434,12 +1427,28 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
Global::RefLocalPasscodeChanged().notify(); Global::RefLocalPasscodeChanged().notify();
} break; } break;
case dbiReplaceEmojis: { case dbiReplaceEmoji: {
qint32 v; qint32 v;
stream >> v; stream >> v;
if (!_checkStreamStatus(stream)) return false; if (!_checkStreamStatus(stream)) return false;
cSetReplaceEmojis(v == 1); Global::SetReplaceEmoji(v == 1);
} break;
case dbiSuggestEmoji: {
qint32 v;
stream >> v;
if (!_checkStreamStatus(stream)) return false;
Global::SetSuggestEmoji(v == 1);
} break;
case dbiSuggestStickersByEmoji: {
qint32 v;
stream >> v;
if (!_checkStreamStatus(stream)) return false;
Global::SetSuggestStickersByEmoji(v == 1);
} break; } break;
case dbiDefaultAttach: { case dbiDefaultAttach: {
@ -1852,7 +1861,7 @@ void _writeUserSettings() {
? userDataInstance->serialize() ? userDataInstance->serialize()
: QByteArray(); : QByteArray();
uint32 size = 22 * (sizeof(quint32) + sizeof(qint32)); uint32 size = 23 * (sizeof(quint32) + sizeof(qint32));
size += sizeof(quint32) + Serialize::stringSize(Global::AskDownloadPath() ? QString() : Global::DownloadPath()) + Serialize::bytearraySize(Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark()); size += sizeof(quint32) + Serialize::stringSize(Global::AskDownloadPath() ? QString() : Global::DownloadPath()) + Serialize::bytearraySize(Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark());
size += sizeof(quint32) + sizeof(qint32); size += sizeof(quint32) + sizeof(qint32);
@ -1877,7 +1886,9 @@ void _writeUserSettings() {
data.stream << quint32(dbiTileBackground) << qint32(Window::Theme::Background()->tileForSave() ? 1 : 0); data.stream << quint32(dbiTileBackground) << qint32(Window::Theme::Background()->tileForSave() ? 1 : 0);
data.stream << quint32(dbiAdaptiveForWide) << qint32(Global::AdaptiveForWide() ? 1 : 0); data.stream << quint32(dbiAdaptiveForWide) << qint32(Global::AdaptiveForWide() ? 1 : 0);
data.stream << quint32(dbiAutoLock) << qint32(Global::AutoLock()); data.stream << quint32(dbiAutoLock) << qint32(Global::AutoLock());
data.stream << quint32(dbiReplaceEmojis) << qint32(cReplaceEmojis() ? 1 : 0); data.stream << quint32(dbiReplaceEmoji) << qint32(Global::ReplaceEmoji() ? 1 : 0);
data.stream << quint32(dbiSuggestEmoji) << qint32(Global::SuggestEmoji() ? 1 : 0);
data.stream << quint32(dbiSuggestStickersByEmoji) << qint32(Global::SuggestStickersByEmoji() ? 1 : 0);
data.stream << quint32(dbiSoundNotify) << qint32(Global::SoundNotify()); data.stream << quint32(dbiSoundNotify) << qint32(Global::SoundNotify());
data.stream << quint32(dbiIncludeMuted) << qint32(Global::IncludeMuted()); data.stream << quint32(dbiIncludeMuted) << qint32(Global::IncludeMuted());
data.stream << quint32(dbiDesktopNotify) << qint32(Global::DesktopNotify()); data.stream << quint32(dbiDesktopNotify) << qint32(Global::DesktopNotify());
@ -1895,7 +1906,6 @@ void _writeUserSettings() {
data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0); data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0);
data.stream << quint32(dbiAutoPlay) << qint32(cAutoPlayGif() ? 1 : 0); data.stream << quint32(dbiAutoPlay) << qint32(cAutoPlayGif() ? 1 : 0);
data.stream << quint32(dbiUseExternalVideoPlayer) << qint32(cUseExternalVideoPlayer()); data.stream << quint32(dbiUseExternalVideoPlayer) << qint32(cUseExternalVideoPlayer());
data.stream << quint32(dbiSuggestStickersByEmoji) << qint32(Global::SuggestStickersByEmoji() ? 1 : 0);
if (!userData.isEmpty()) { if (!userData.isEmpty()) {
data.stream << quint32(dbiAuthSessionSettings) << userData; data.stream << quint32(dbiAuthSessionSettings) << userData;
} }

View File

@ -211,6 +211,10 @@ void FlatTextarea::addInstantReplace(
accumulate_max(_instantReplaceMaxLength, int(what.size())); accumulate_max(_instantReplaceMaxLength, int(what.size()));
} }
void FlatTextarea::enableInstantReplaces(bool enabled) {
_instantReplacesEnabled = enabled;
}
void FlatTextarea::updatePalette() { void FlatTextarea::updatePalette() {
auto p = palette(); auto p = palette();
p.setColor(QPalette::Text, _st.textColor->c); p.setColor(QPalette::Text, _st.textColor->c);
@ -1499,7 +1503,9 @@ void FlatTextarea::keyPressEvent(QKeyEvent *e) {
} }
void FlatTextarea::processInstantReplaces(const QString &text) { void FlatTextarea::processInstantReplaces(const QString &text) {
if (text.size() != 1 || !_instantReplaceMaxLength) { if (text.size() != 1
|| !_instantReplaceMaxLength
|| !_instantReplacesEnabled) {
return; return;
} }
const auto it = _reverseInstantReplaces.tail.find(text[0]); const auto it = _reverseInstantReplaces.tail.find(text[0]);
@ -1540,9 +1546,18 @@ void FlatTextarea::applyInstantReplace(
} else if (position < length) { } else if (position < length) {
return; return;
} }
commmitInstantReplacement(position - length, position, with, what);
}
void FlatTextarea::commmitInstantReplacement(
int from,
int till,
const QString &with,
base::optional<QString> checkOriginal) {
auto tags = QVector<TextWithTags::Tag>(); auto tags = QVector<TextWithTags::Tag>();
const auto original = getTextPart(position - length, position, &tags); const auto original = getTextPart(from, till, &tags);
if (what.compare(original, Qt::CaseInsensitive) != 0) { if (checkOriginal
&& checkOriginal->compare(original, Qt::CaseInsensitive) != 0) {
return; return;
} }
@ -1550,7 +1565,7 @@ void FlatTextarea::applyInstantReplace(
auto emojiLength = 0; auto emojiLength = 0;
const auto emoji = Ui::Emoji::Find(with, &emojiLength); const auto emoji = Ui::Emoji::Find(with, &emojiLength);
if (!emoji || with.size() != emojiLength) { if (!emoji || with.size() != emojiLength) {
return cursor.charFormat(); return _defaultCharFormat;
} }
const auto use = [&] { const auto use = [&] {
if (!emoji->hasVariants()) { if (!emoji->hasVariants()) {
@ -1562,6 +1577,7 @@ void FlatTextarea::applyInstantReplace(
? emoji->variant(it.value()) ? emoji->variant(it.value())
: emoji; : emoji;
}(); }();
Ui::Emoji::AddRecent(use);
return PrepareEmojiFormat(use, _st.font); return PrepareEmojiFormat(use, _st.font);
}(); }();
const auto replacement = format.isImageFormat() const auto replacement = format.isImageFormat()
@ -1570,12 +1586,10 @@ void FlatTextarea::applyInstantReplace(
format.setProperty(kInstantReplaceWhatId, original); format.setProperty(kInstantReplaceWhatId, original);
format.setProperty(kInstantReplaceWithId, replacement); format.setProperty(kInstantReplaceWithId, replacement);
format.setProperty(kInstantReplaceRandomId, rand_value<uint32>()); format.setProperty(kInstantReplaceRandomId, rand_value<uint32>());
auto replaceCursor = cursor; auto cursor = textCursor();
replaceCursor.setPosition(position - length); cursor.setPosition(from);
replaceCursor.setPosition(position, QTextCursor::KeepAnchor); cursor.setPosition(till, QTextCursor::KeepAnchor);
replaceCursor.insertText( cursor.insertText(replacement, format);
replacement,
format);
} }
bool FlatTextarea::revertInstantReplace() { bool FlatTextarea::revertInstantReplace() {

View File

@ -16,7 +16,7 @@ namespace Ui {
static UserData * const LookingUpInlineBot = SharedMemoryLocation<UserData, 0>(); static UserData * const LookingUpInlineBot = SharedMemoryLocation<UserData, 0>();
class FlatTextarea : public TWidgetHelper<QTextEdit>, private base::Subscriber { class FlatTextarea : public TWidgetHelper<QTextEdit>, protected base::Subscriber {
Q_OBJECT Q_OBJECT
public: public:
@ -32,7 +32,13 @@ public:
void setMinHeight(int minHeight); void setMinHeight(int minHeight);
void setMaxHeight(int maxHeight); void setMaxHeight(int maxHeight);
void enableInstantReplaces(bool enabled);
void addInstantReplace(const QString &what, const QString &with); void addInstantReplace(const QString &what, const QString &with);
void commmitInstantReplacement(
int from,
int till,
const QString &with,
base::optional<QString> checkOriginal = base::none);
void setPlaceholder(base::lambda<QString()> placeholderFactory, int afterSymbols = 0); void setPlaceholder(base::lambda<QString()> placeholderFactory, int afterSymbols = 0);
void updatePlaceholder(); void updatePlaceholder();
@ -230,6 +236,7 @@ private:
int _instantReplaceMaxLength = 0; int _instantReplaceMaxLength = 0;
InstantReplaceNode _reverseInstantReplaces; InstantReplaceNode _reverseInstantReplaces;
bool _instantReplacesEnabled = true;
}; };