Bug 474573

Summary: runners/services: look for the same keystrokes search sequence in other keyboard language layouts
Product: [Plasma] krunner Reporter: Sergey Katunin <sulmpx60>
Component: generalAssignee: Plasma Bugs List <plasma-bugs-null>
Status: CONFIRMED ---    
Severity: wishlist CC: alexander.lohnau, eugene.savitsky, geekxx10, natalie_clarius, nate, sulmpx60
Priority: NOR    
Version First Reported In: unspecified   
Target Milestone: ---   
Platform: unspecified   
OS: Linux   
See Also: https://bugs.kde.org/show_bug.cgi?id=467924
Latest Commit: Version Fixed/Implemented In:
Sentry Crash Report:
Attachments: Search example

Description Sergey Katunin 2023-09-15 19:32:21 UTC
Created attachment 161649 [details]
Search example

I would like to discuss this feature and how it can be implemented for all languages supported in KDE.

### How it is work
For example, keystroke sequence like `лфеу` in `Cyrilic ЙЦУКЕН` is `kate` in `QWERTY`. And vice versa, not only the `RU -> EN` search is implemented, but also the `EN -> RU`.

This is useful for systems with multiple keyboard language layouts in case when you forget to switch layout.

### Problem
However, the task of implementation for all languages looks rather non-trivial. I assume that it is possible to use some kind of system library in this conversion, like `xkb`.

So, I would like to know if the community is interested in this feature, and in what ways it could be implemented.

### How it is implemented now
At the moment, I have implemented a prototype that has the desired behavior for EN (English) - RU (Russian) layouts by using simple convertation table.

Convertation table:
```
class LayoutRuEnConverter
{
    enum class LayoutType {
        RU_TO_EN = 0,
        EN_TO_RU = 1
    };

    using LayoutMap = QMap<QChar, QChar>;
    using LayoutMaps = QMap<LayoutType, LayoutMap>;
public:
    LayoutRuEnConverter() = default;

    bool isStrHasRuChar(const QString &str) const
    {
        for (auto c: str)
        {
            if (ruCharsMap[c]) {
                return true;
            }
        }

        return false;
    }

    QString invertLayout(const QString &str) const
    {
        return convertLayout(str, isStrHasRuChar(str) ? LayoutType::RU_TO_EN : LayoutType::EN_TO_RU);
    }

private:
    QMap<QChar, bool> getRuCharsMap() const
    {
        const QString ruChars = "абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ";

        QMap<QChar, bool> t;
        for (auto c: ruChars)
        {
            t[c] = true;
        }

        return t;
    }

    LayoutMaps initLayouts() const
    {
        const QString qwertyEn = "`qwertyuiop[]asdfghjkl;'zxcvbnm,./~@#$^&QWERTYUIOP{}|ASDFGHJKL:\"ZXCVBNM<>?";
        const QString qwertyRu = "ёйцукенгшщзхъфывапролджэячсмитьбю.Ё\"№;:?ЙЦУКЕНГШЩЗХЪ/ФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,";

        const int N = qwertyEn.count();

        LayoutMap layoutRuToEnMap;
        LayoutMap layoutEnToRuMap;

        for (int i = 0; i < N; ++i)
        {
            layoutRuToEnMap[qwertyRu[i]] = qwertyEn[i];
            layoutEnToRuMap[qwertyEn[i]] = qwertyRu[i];
        }

        LayoutMaps res;
        res[LayoutType::RU_TO_EN] = layoutRuToEnMap;
        res[LayoutType::EN_TO_RU] = layoutEnToRuMap;

        return res;
    }

    QString convertLayout(const QString &str, const LayoutType layoutType) const
    {
        QString res = "";

        for (auto c: str)
        {
            res += !layouts[layoutType][c].isNull() ? layouts[layoutType][c] : c;
        }

        return res;
    }

    const QMap<QChar, bool> ruCharsMap = getRuCharsMap();
    const LayoutMaps layouts = initLayouts();
};
```

And in match() function, converted search query is executed.
```
void match(Plasma::RunnerContext &context)
{
    const auto doMatch = [this]() {
        matchNameKeywordAndGenericName();
        matchCategories();
        matchJumpListActions();
    };

    term = context.query();
    // Splitting the query term to match using subsequences
    queryList = term.split(QLatin1Char(' '));
    weightedTermLength = weightedLength(term);

    doMatch();

    // Invert language layout (RU <-> EN) in queryList.
    std::transform(queryList.cbegin(), queryList.cend(), queryList.begin(),
                    [this](const QString& str) { return m_layoutConverter->invertLayout(str); });

    // Do match again with converted queryList.
    doMatch();

    context.addMatches(matches);
}
```
Comment 1 Nate Graham 2023-09-19 18:02:24 UTC
What a fascinatingly cool idea.
Comment 2 Nate Graham 2023-11-29 21:42:03 UTC
*** Bug 477652 has been marked as a duplicate of this bug. ***
Comment 3 Nate Graham 2024-04-08 20:55:05 UTC
*** Bug 485202 has been marked as a duplicate of this bug. ***