Fixed comma appended to username completion when not at the beginning of the message (#3060)

Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
Paweł 2021-07-24 12:01:50 +02:00 committed by GitHub
parent ae9f92ded9
commit 588ed557f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 97 additions and 22 deletions

View file

@ -8,6 +8,7 @@
- Minor: Received IRC messages use `time` message tag for timestamp if it's available. (#3021) - Minor: Received IRC messages use `time` message tag for timestamp if it's available. (#3021)
- Minor: Added informative messages for recent-messages API's errors. (#3029) - Minor: Added informative messages for recent-messages API's errors. (#3029)
- Bugfix: Fixed "smiley" emotes being unable to be "Tabbed" with autocompletion, introduced in v2.3.3. (#3010) - Bugfix: Fixed "smiley" emotes being unable to be "Tabbed" with autocompletion, introduced in v2.3.3. (#3010)
- Bugfix: Fixed comma appended to username completion when not at the beginning of the message. (#3060)
- Dev: Ubuntu packages are now available (#2936) - Dev: Ubuntu packages are now available (#2936)
## 2.3.3 ## 2.3.3

View file

@ -10,6 +10,7 @@
#include "providers/twitch/TwitchIrcServer.hpp" #include "providers/twitch/TwitchIrcServer.hpp"
#include "singletons/Emotes.hpp" #include "singletons/Emotes.hpp"
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
#include "util/Helpers.hpp"
#include "util/QStringHash.hpp" #include "util/QStringHash.hpp"
#include <QtAlgorithms> #include <QtAlgorithms>
@ -150,9 +151,6 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
} }
// Usernames // Usernames
QString usernamePostfix =
isFirstWord && getSettings()->mentionUsersWithComma ? "," : QString();
if (prefix.startsWith("@")) if (prefix.startsWith("@"))
{ {
QString usernamePrefix = prefix; QString usernamePrefix = prefix;
@ -162,8 +160,10 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
for (const auto &name : chatters) for (const auto &name : chatters)
{ {
addString("@" + name + usernamePostfix, addString(
TaggedString::Type::Username); "@" + formatUserMention(name, isFirstWord,
getSettings()->mentionUsersWithComma),
TaggedString::Type::Username);
} }
} }
else if (!getSettings()->userCompletionOnlyWithAt) else if (!getSettings()->userCompletionOnlyWithAt)
@ -172,7 +172,9 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
for (const auto &name : chatters) for (const auto &name : chatters)
{ {
addString(name + usernamePostfix, TaggedString::Type::Username); addString(formatUserMention(name, isFirstWord,
getSettings()->mentionUsersWithComma),
TaggedString::Type::Username);
} }
} }

View file

@ -68,4 +68,17 @@ QColor getRandomColor(const QString &userId)
return TWITCH_USERNAME_COLORS[colorIndex]; return TWITCH_USERNAME_COLORS[colorIndex];
} }
QString formatUserMention(const QString &userName, bool isFirstWord,
bool mentionUsersWithComma)
{
QString result = userName;
if (isFirstWord && mentionUsersWithComma)
{
result += ",";
}
return result;
}
} // namespace chatterino } // namespace chatterino

View file

@ -20,4 +20,14 @@ QString kFormatNumbers(const int &number);
QColor getRandomColor(const QString &userId); QColor getRandomColor(const QString &userId);
/**
* @brief Takes a user's name and some formatting parameter and spits out the standardized way to format it
*
* @param userName a user's name
* @param isFirstWord signifies whether this mention would be the first word in a message
* @param mentionUsersWithComma postfix mentions with a comma. generally powered by getSettings()->mentionUsersWithComma
**/
QString formatUserMention(const QString &userName, bool isFirstWord,
bool mentionUsersWithComma);
} // namespace chatterino } // namespace chatterino

View file

@ -37,6 +37,7 @@
#include "singletons/WindowManager.hpp" #include "singletons/WindowManager.hpp"
#include "util/Clipboard.hpp" #include "util/Clipboard.hpp"
#include "util/DistanceBetweenPoints.hpp" #include "util/DistanceBetweenPoints.hpp"
#include "util/Helpers.hpp"
#include "util/IncognitoBrowser.hpp" #include "util/IncognitoBrowser.hpp"
#include "util/StreamerMode.hpp" #include "util/StreamerMode.hpp"
#include "util/Twitch.hpp" #include "util/Twitch.hpp"
@ -48,6 +49,7 @@
#include "widgets/helper/EffectLabel.hpp" #include "widgets/helper/EffectLabel.hpp"
#include "widgets/helper/SearchPopup.hpp" #include "widgets/helper/SearchPopup.hpp"
#include "widgets/splits/Split.hpp" #include "widgets/splits/Split.hpp"
#include "widgets/splits/SplitInput.hpp"
#define DRAW_WIDTH (this->width()) #define DRAW_WIDTH (this->width())
#define SELECTION_RESUME_SCROLLING_MSG_THRESHOLD 3 #define SELECTION_RESUME_SCROLLING_MSG_THRESHOLD 3
@ -1804,8 +1806,9 @@ void ChannelView::handleMouseClick(QMouseEvent *event,
} }
break; break;
case Qt::RightButton: { case Qt::RightButton: {
auto split = dynamic_cast<Split *>(this->parentWidget());
auto insertText = [=](QString text) { auto insertText = [=](QString text) {
if (auto split = dynamic_cast<Split *>(this->parentWidget())) if (split)
{ {
split->insertTextToInput(text); split->insertTextToInput(text);
} }
@ -1815,7 +1818,11 @@ void ChannelView::handleMouseClick(QMouseEvent *event,
if (link.type == Link::UserInfo) if (link.type == Link::UserInfo)
{ {
const bool commaMention = getSettings()->mentionUsersWithComma; const bool commaMention = getSettings()->mentionUsersWithComma;
insertText("@" + link.value + (commaMention ? ", " : " ")); const bool isFirstWord =
split && split->getInput().isEditFirstWord();
auto userMention =
formatUserMention(link.value, isFirstWord, commaMention);
insertText("@" + userMention + " ");
} }
else if (link.type == Link::UserWhisper) else if (link.type == Link::UserWhisper)
{ {

View file

@ -39,6 +39,19 @@ bool ResizingTextEdit::hasHeightForWidth() const
return true; return true;
} }
bool ResizingTextEdit::isFirstWord() const
{
QString plainText = this->toPlainText();
for (int i = this->textCursor().position(); i >= 0; i--)
{
if (plainText[i] == ' ')
{
return false;
}
}
return true;
};
int ResizingTextEdit::heightForWidth(int) const int ResizingTextEdit::heightForWidth(int) const
{ {
auto margins = this->contentsMargins(); auto margins = this->contentsMargins();
@ -108,17 +121,6 @@ void ResizingTextEdit::keyPressEvent(QKeyEvent *event)
} }
QString currentCompletionPrefix = this->textUnderCursor(); QString currentCompletionPrefix = this->textUnderCursor();
bool isFirstWord = [&] {
QString plainText = this->toPlainText();
for (int i = this->textCursor().position(); i >= 0; i--)
{
if (plainText[i] == ' ')
{
return false;
}
}
return true;
}();
// check if there is something to complete // check if there is something to complete
if (currentCompletionPrefix.size() <= 1) if (currentCompletionPrefix.size() <= 1)
@ -134,7 +136,8 @@ void ResizingTextEdit::keyPressEvent(QKeyEvent *event)
// First type pressing tab after modifying a message, we refresh our // First type pressing tab after modifying a message, we refresh our
// completion model // completion model
this->completer_->setModel(completionModel); this->completer_->setModel(completionModel);
completionModel->refresh(currentCompletionPrefix, isFirstWord); completionModel->refresh(currentCompletionPrefix,
this->isFirstWord());
this->completionInProgress_ = true; this->completionInProgress_ = true;
this->completer_->setCompletionPrefix(currentCompletionPrefix); this->completer_->setCompletionPrefix(currentCompletionPrefix);
this->completer_->complete(); this->completer_->complete();

View file

@ -15,6 +15,7 @@ public:
QSize sizeHint() const override; QSize sizeHint() const override;
bool hasHeightForWidth() const override; bool hasHeightForWidth() const override;
bool isFirstWord() const;
pajlada::Signals::Signal<QKeyEvent *> keyPressed; pajlada::Signals::Signal<QKeyEvent *> keyPressed;
pajlada::Signals::NoArgSignal focused; pajlada::Signals::NoArgSignal focused;

View file

@ -306,6 +306,11 @@ ChannelView &Split::getChannelView()
return *this->view_; return *this->view_;
} }
SplitInput &Split::getInput()
{
return *this->input_;
}
void Split::updateInputPlaceholder() void Split::updateInputPlaceholder()
{ {
if (!this->getChannel()->isTwitchChannel()) if (!this->getChannel()->isTwitchChannel())

View file

@ -47,6 +47,7 @@ public:
pajlada::Signals::NoArgSignal focusLost; pajlada::Signals::NoArgSignal focusLost;
ChannelView &getChannelView(); ChannelView &getChannelView();
SplitInput &getInput();
IndirectChannel getIndirectChannel(); IndirectChannel getIndirectChannel();
ChannelPtr getChannel(); ChannelPtr getChannel();

View file

@ -8,6 +8,7 @@
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
#include "singletons/Theme.hpp" #include "singletons/Theme.hpp"
#include "util/Clamp.hpp" #include "util/Clamp.hpp"
#include "util/Helpers.hpp"
#include "util/LayoutCreator.hpp" #include "util/LayoutCreator.hpp"
#include "widgets/Notebook.hpp" #include "widgets/Notebook.hpp"
#include "widgets/Scrollbar.hpp" #include "widgets/Scrollbar.hpp"
@ -568,8 +569,10 @@ void SplitInput::insertCompletionText(const QString &input_)
} }
else if (text[i] == '@') else if (text[i] == '@')
{ {
input = "@" + input_ + const auto userMention =
(getSettings()->mentionUsersWithComma ? ", " : " "); formatUserMention(input_, edit.isFirstWord(),
getSettings()->mentionUsersWithComma);
input = "@" + userMention + " ";
done = true; done = true;
} }
@ -595,6 +598,11 @@ void SplitInput::clearSelection()
this->ui_.textEdit->setTextCursor(c); this->ui_.textEdit->setTextCursor(c);
} }
bool SplitInput::isEditFirstWord() const
{
return this->ui_.textEdit->isFirstWord();
}
QString SplitInput::getInputText() const QString SplitInput::getInputText() const
{ {
return this->ui_.textEdit->toPlainText(); return this->ui_.textEdit->toPlainText();

View file

@ -28,6 +28,7 @@ public:
SplitInput(Split *_chatWidget); SplitInput(Split *_chatWidget);
void clearSelection(); void clearSelection();
bool isEditFirstWord() const;
QString getInputText() const; QString getInputText() const;
void insertText(const QString &text); void insertText(const QString &text);

View file

@ -10,6 +10,7 @@ set(test_SOURCES
${CMAKE_CURRENT_LIST_DIR}/src/Emojis.cpp ${CMAKE_CURRENT_LIST_DIR}/src/Emojis.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ExponentialBackoff.cpp ${CMAKE_CURRENT_LIST_DIR}/src/ExponentialBackoff.cpp
${CMAKE_CURRENT_LIST_DIR}/src/TwitchAccount.cpp ${CMAKE_CURRENT_LIST_DIR}/src/TwitchAccount.cpp
${CMAKE_CURRENT_LIST_DIR}/src/Helpers.cpp
) )
add_executable(${PROJECT_NAME} ${test_SOURCES}) add_executable(${PROJECT_NAME} ${test_SOURCES})

22
tests/src/Helpers.cpp Normal file
View file

@ -0,0 +1,22 @@
#include "util/Helpers.hpp"
#include <gtest/gtest.h>
using namespace chatterino;
TEST(Helpers, formatUserMention)
{
const auto userName = "pajlada";
// A user mention that is the first word, that has 'mention with comma' enabled should have a comma appended at the end.
EXPECT_EQ(formatUserMention(userName, true, true), "pajlada,");
// A user mention that is not the first word, but has 'mention with comma' enabled should not have a comma appended at the end.
EXPECT_EQ(formatUserMention(userName, false, true), "pajlada");
// A user mention that is the first word, but has 'mention with comma' disabled should not have a comma appended at the end.
EXPECT_EQ(formatUserMention(userName, true, false), "pajlada");
// A user mention that is neither the first word, nor has 'mention with comma' enabled should not have a comma appended at the end.
EXPECT_EQ(formatUserMention(userName, false, false), "pajlada");
}