Bug 474573 - runners/services: look for the same keystrokes search sequence in other keyboard language layouts
Summary: runners/services: look for the same keystrokes search sequence in other keybo...
Status: CONFIRMED
Alias: None
Product: krunner
Classification: Plasma
Component: general (show other bugs)
Version: unspecified
Platform: unspecified Linux
: NOR wishlist
Target Milestone: ---
Assignee: Plasma Bugs List
URL:
Keywords:
: 477652 485202 (view as bug list)
Depends on:
Blocks:
 
Reported: 2023-09-15 19:32 UTC by Sergey Katunin
Modified: 2024-04-08 20:55 UTC (History)
6 users (show)

See Also:
Latest Commit:
Version Fixed In:


Attachments
Search example (22.33 KB, image/png)
2023-09-15 19:32 UTC, Sergey Katunin
Details

Note You need to log in before you can comment on or make changes to this bug.
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. ***