334 lines
10 KiB
Diff
334 lines
10 KiB
Diff
From 2caec486443ea86fc735be993f1eff457570e3f6 Mon Sep 17 00:00:00 2001
|
||
From: John Zimmermann <me@johnnynator.dev>
|
||
Date: Sat, 3 Aug 2024 17:47:00 +0200
|
||
Subject: [PATCH 3/3] Replace ispell with Sonnet
|
||
|
||
---
|
||
linphone-app/CMakeLists.txt | 12 +-
|
||
.../other/spell-checker/SpellChecker.hpp | 34 ++--
|
||
.../other/spell-checker/SpellCheckerLinux.cpp | 186 ++----------------
|
||
3 files changed, 26 insertions(+), 206 deletions(-)
|
||
|
||
diff --git a/linphone-app/CMakeLists.txt b/linphone-app/CMakeLists.txt
|
||
index d2b5dcde..770234db 100644
|
||
--- a/linphone-app/CMakeLists.txt
|
||
+++ b/linphone-app/CMakeLists.txt
|
||
@@ -126,13 +126,7 @@ if(ENABLE_QT_KEYCHAIN)
|
||
endif()
|
||
|
||
if(NOT APPLE AND NOT WIN32)
|
||
- if(NOT ISPELL_TARGET_NAME)
|
||
- set(ISPELL_TARGET_NAME "ISpell")
|
||
- endif()
|
||
- find_package(${ISPELL_TARGET_NAME})
|
||
- if(NOT ISpell_FOUND)
|
||
- find_package(${ISPELL_TARGET_NAME} CONFIG REQUIRED)
|
||
- endif()
|
||
+ find_package(KF5Sonnet REQUIRED)
|
||
endif()
|
||
|
||
if(ENABLE_BUILD_VERBOSE)
|
||
@@ -198,7 +192,7 @@ if( ENABLE_QT_KEYCHAIN)
|
||
list(APPEND APP_TARGETS ${QTKEYCHAIN_TARGET_NAME})
|
||
endif()
|
||
if(NOT APPLE AND NOT WIN32)
|
||
- list(APPEND APP_TARGETS ${ISPELL_TARGET_NAME})
|
||
+ list(APPEND APP_TARGETS KF5::SonnetCore)
|
||
endif()
|
||
if (UNIX AND NOT APPLE)
|
||
list(APPEND QT5_PACKAGES DBus)
|
||
diff --git a/linphone-app/src/components/other/spell-checker/SpellChecker.hpp b/linphone-app/src/components/other/spell-checker/SpellChecker.hpp
|
||
index 84d9abdf..37a1dd8b 100644
|
||
--- a/linphone-app/src/components/other/spell-checker/SpellChecker.hpp
|
||
+++ b/linphone-app/src/components/other/spell-checker/SpellChecker.hpp
|
||
@@ -39,7 +39,7 @@
|
||
#include "components/settings/SettingsModel.hpp"
|
||
|
||
#ifdef __linux__
|
||
-#include <thread>
|
||
+#include <Sonnet/Speller>
|
||
#endif
|
||
|
||
#define SUGGESTIONS_LIMIT 10
|
||
@@ -57,7 +57,7 @@ class SpellChecker : public QSyntaxHighlighter {
|
||
public:
|
||
SpellChecker(QObject* parent = nullptr);
|
||
~SpellChecker();
|
||
-
|
||
+
|
||
// Common
|
||
static QString currentLanguage() {
|
||
QString overrideLocale = CoreManager::getInstance()->getSettingsModel()->getSpellCheckerOverrideLocale();
|
||
@@ -72,21 +72,21 @@ public:
|
||
Q_INVOKABLE void ignoreAll(QString word);
|
||
Q_INVOKABLE void replace(QString word, QString byWord, int cursorPosition);
|
||
Q_PROPERTY(QStringList redLines MEMBER redLines NOTIFY redLinesChanged);
|
||
-
|
||
+
|
||
// Native (Mac/Windows) or ISpell
|
||
Q_INVOKABLE void learn(QString word);
|
||
Q_INVOKABLE QStringList suggestionsForWord(QString word);
|
||
bool isValid(QString word);
|
||
-
|
||
+
|
||
protected:
|
||
void highlightBlock(const QString &text) override;
|
||
-
|
||
+
|
||
public slots:
|
||
void highlightAfterGracePeriod();
|
||
-
|
||
+
|
||
signals:
|
||
void redLinesChanged();
|
||
-
|
||
+
|
||
private:
|
||
QTextCharFormat errorFormater;
|
||
QTimer *graceTimer;
|
||
@@ -111,24 +111,12 @@ private:
|
||
#ifdef _WIN32
|
||
ISpellChecker* mNativeSpellChecker = nullptr;
|
||
#endif
|
||
-
|
||
-// ISpell linux
|
||
+
|
||
+// nuspell linux
|
||
#ifdef __linux__
|
||
- static int gISpell_sc_read_fd;
|
||
- static int gISpell_sc_write_fd;
|
||
- static int gISpell_app_read_fd;
|
||
- static int gISpell_app_write_fd;
|
||
- static std::thread *gISpellCheckerThread;
|
||
- static QHash<QString,QStringList> gISpellSuggestions;
|
||
- static std::string gISpellCheckeCurrentLanguage;
|
||
- static std::string gIspellDictionariesFolder;
|
||
- void stopISpellChecker();
|
||
- static std::shared_ptr<linphone::Config> gISpellSelfDictionary;
|
||
- bool isLearnt(QString word);
|
||
- bool wordValidWithFrVariants(QString word);
|
||
- bool validSplittedOn(QString pattern, QString word);
|
||
+ Sonnet::Speller dict;
|
||
#endif
|
||
-
|
||
+
|
||
};
|
||
|
||
|
||
diff --git a/linphone-app/src/components/other/spell-checker/SpellCheckerLinux.cpp b/linphone-app/src/components/other/spell-checker/SpellCheckerLinux.cpp
|
||
index a9a56a48..4cb586dd 100644
|
||
--- a/linphone-app/src/components/other/spell-checker/SpellCheckerLinux.cpp
|
||
+++ b/linphone-app/src/components/other/spell-checker/SpellCheckerLinux.cpp
|
||
@@ -18,196 +18,34 @@
|
||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
-
|
||
#include "SpellChecker.hpp"
|
||
-#include <libispell.h>
|
||
-#include "app/paths/Paths.hpp"
|
||
-#include <unistd.h>
|
||
-#include <cstdio>
|
||
-#include <string>
|
||
+#include <Sonnet/Speller>
|
||
#include <linphone++/linphone.hh>
|
||
-#include "utils/Utils.hpp"
|
||
-#include <QCryptographicHash>
|
||
-
|
||
-int SpellChecker::gISpell_sc_read_fd = 0;
|
||
-int SpellChecker::gISpell_sc_write_fd = 0;
|
||
-int SpellChecker::gISpell_app_read_fd = 0;
|
||
-int SpellChecker::gISpell_app_write_fd = 0;
|
||
-std::thread *SpellChecker::gISpellCheckerThread = nullptr;
|
||
-QHash<QString,QStringList> SpellChecker::gISpellSuggestions;
|
||
-std::string SpellChecker::gISpellCheckeCurrentLanguage;
|
||
-std::shared_ptr<linphone::Config> SpellChecker::gISpellSelfDictionary = linphone::Factory::get()->createConfig(Paths::getISpellOwnDictsDirPath());
|
||
-std::string SpellChecker::gIspellDictionariesFolder;
|
||
-
|
||
-
|
||
-bool open_channel(int& read_fd, int& write_fd) {
|
||
- int vals[2];
|
||
- int errc = pipe(vals);
|
||
- if(errc) {
|
||
- return false;
|
||
- } else {
|
||
- read_fd = vals[0];
|
||
- write_fd = vals[1];
|
||
- return true;
|
||
- }
|
||
-}
|
||
-
|
||
-void SpellChecker::stopISpellChecker() {
|
||
- QString stop("__spellchecker_stop__");
|
||
- auto message = stop.toStdString();
|
||
- ssize_t amnt_written = write(gISpell_sc_write_fd, message.data(), message.size());
|
||
- if(amnt_written != message.size()) {
|
||
- qWarning() << LOG_TAG << "Linux ispell unable to stop spell checker";
|
||
- }
|
||
- gISpellCheckerThread->join();
|
||
- gISpellCheckerThread = nullptr;
|
||
- mAvailable = false;
|
||
-}
|
||
|
||
void SpellChecker::setLanguage() {
|
||
-
|
||
- QString locale = SpellChecker::currentLanguage().toLower().mid(0,2);
|
||
-
|
||
- if (gISpellCheckeCurrentLanguage == locale.toStdString() && gISpellCheckerThread != nullptr) {
|
||
- mAvailable = true;
|
||
- return;
|
||
- }
|
||
-
|
||
- if (gISpellCheckerThread != nullptr) // Language change
|
||
- stopISpellChecker();
|
||
-
|
||
- QString dict = Paths::getISpellDictsDirPath()+locale+".hash";
|
||
- gIspellDictionariesFolder = Paths::getISpellDictsDirPath().toStdString();
|
||
-
|
||
- if (!QFile::exists(dict)) {
|
||
- qWarning() << LOG_TAG << "Linux ispell language not supported " << SpellChecker::currentLanguage() << dict;
|
||
- mAvailable = false;
|
||
- return;
|
||
- }
|
||
-
|
||
- if (!open_channel(gISpell_sc_read_fd, gISpell_sc_write_fd) ||
|
||
- !open_channel(gISpell_app_read_fd, gISpell_app_write_fd)) {
|
||
- qWarning() << LOG_TAG << "Linux ispell language unable to open channels";
|
||
- mAvailable = false;
|
||
- return;
|
||
- }
|
||
-
|
||
- gISpellCheckeCurrentLanguage = locale.toStdString();
|
||
- gISpellCheckerThread = new std::thread(bc_spell_checker,
|
||
- gIspellDictionariesFolder.data(),
|
||
- gISpellCheckeCurrentLanguage.data(),
|
||
- gISpell_sc_read_fd,
|
||
- gISpell_app_write_fd);
|
||
-
|
||
- mAvailable = true;
|
||
- qDebug() << LOG_TAG << "Linux ispell language loaded from " << dict;
|
||
-}
|
||
-
|
||
-// Few special situation in French language not detected by the fr.hash.
|
||
-
|
||
-bool SpellChecker::wordValidWithFrVariants(QString word) {
|
||
- if (word.toLower().contains("qu'")) {
|
||
- QString replace = word.toLower().replace("qu'","que ");
|
||
- if (isValid(replace)||validSplittedOn(" ",replace))
|
||
- return true;
|
||
- }
|
||
- if (word.toLower().contains("s'")) {
|
||
- QString replace = word.toLower().replace("s'","se ");
|
||
- if (isValid(replace)||validSplittedOn(" ",replace))
|
||
- return true;
|
||
- }
|
||
- return false;
|
||
-}
|
||
|
||
-bool SpellChecker::validSplittedOn(QString pattern, QString word) {
|
||
- if (!word.contains(pattern))
|
||
- return false;
|
||
- auto split = word.split(pattern);
|
||
- return isValid(split[0]) && isValid(split[1]);
|
||
+ dict = Sonnet::Speller(SpellChecker::currentLanguage());
|
||
+ mAvailable = dict.isValid();
|
||
+ qDebug() << LOG_TAG << "Linux ispell language loaded: " << SpellChecker::currentLanguage();
|
||
}
|
||
|
||
bool SpellChecker::isValid(QString word) {
|
||
-
|
||
- if (!mAvailable || word.length() == 1 || isLearnt(word))
|
||
- return true;
|
||
-
|
||
- // no letters in word -> valid
|
||
- QString wordCopy = word;
|
||
- auto iterator = std::remove_if(wordCopy.begin(), wordCopy.end(), [](const QChar& c){ return !c.isLetter();});
|
||
- wordCopy.chop(std::distance(iterator, wordCopy.end()));
|
||
- if (wordCopy.isEmpty())
|
||
- return true;
|
||
-
|
||
- // Some preformating
|
||
-
|
||
- word = word.replace("’","'");
|
||
- word = word.replace("(","");
|
||
- word = word.replace(")","");
|
||
- word = word.replace("‘","'");
|
||
-
|
||
- while (word.endsWith(".") || word.endsWith("!") || word.endsWith(",") || word.endsWith(","))
|
||
- word.chop(1);
|
||
-
|
||
- while (word.startsWith(".") || word.startsWith("!") || word.startsWith(",") || word.startsWith(",")) {
|
||
- word = word.mid(1);
|
||
- }
|
||
-
|
||
- // submit word to ispell
|
||
- auto message = word.toStdString();
|
||
- ssize_t amnt_written = write(gISpell_sc_write_fd, message.data(), message.size());
|
||
- if(amnt_written != message.size()) {
|
||
- qWarning() << LOG_TAG << "Linux ispell unable to communicate with spell checker thread";
|
||
+
|
||
+ if (!mAvailable || word.length() == 1)
|
||
return true;
|
||
- }
|
||
-
|
||
- // wait and read ispell result
|
||
- constexpr int buffer_size = 1024;
|
||
- char buffer[buffer_size] = {0};
|
||
- ssize_t amnt_read = read(gISpell_app_read_fd, &buffer[0], buffer_size);
|
||
- QString returned = QString::fromUtf8(buffer);
|
||
- if (returned == "1") {
|
||
+
|
||
+ if(dict.isCorrect(word)) {
|
||
return true;
|
||
- } else {
|
||
- if (!gISpellSuggestions.contains(word)) { // Record returned suggestions if any
|
||
- QStringList returnedUggestions = returned.split(", ");
|
||
- returnedUggestions.removeFirst();
|
||
- gISpellSuggestions.insert(word,returnedUggestions);
|
||
- }
|
||
- return (gISpellCheckeCurrentLanguage == "fr" && wordValidWithFrVariants(word)) ||
|
||
- validSplittedOn("'",word) ||
|
||
- validSplittedOn("-",word);
|
||
- }
|
||
|
||
+ }
|
||
+ return false;
|
||
}
|
||
|
||
void SpellChecker::learn(QString word){
|
||
- QCryptographicHash hash( QCryptographicHash::Sha1 ); // Hash to avoid fancy character conflict with config format.
|
||
- hash.addData(word.toUtf8());
|
||
- auto hashString = Utils::appStringToCoreString(hash.result().toHex());
|
||
- gISpellSelfDictionary->setInt("words",hashString,1);
|
||
- gISpellSelfDictionary->sync();
|
||
+ dict.addToPersonal(word);
|
||
highlightDocument();
|
||
}
|
||
|
||
-bool SpellChecker::isLearnt(QString word){
|
||
- QCryptographicHash hash( QCryptographicHash::Sha1 ); // Hash to avoid fancy character conflict with config format.
|
||
- hash.addData(word.toUtf8());
|
||
- auto hashString = Utils::appStringToCoreString(hash.result().toHex());
|
||
- return gISpellSelfDictionary->getInt("words",hashString,0) == 1;
|
||
-}
|
||
-
|
||
QStringList SpellChecker::suggestionsForWord(QString word) {
|
||
- QStringList suggestions;
|
||
- if (!gISpellSuggestions.contains(word))
|
||
- return suggestions;
|
||
- QListIterator<QString> itr (gISpellSuggestions.value(word));
|
||
- while (itr.hasNext()) {
|
||
- QString suggestion = itr.next();
|
||
- if (!suggestion.contains("+"))
|
||
- suggestions << suggestion;
|
||
- if (suggestions.length() >= SUGGESTIONS_LIMIT) {
|
||
- return suggestions;
|
||
- }
|
||
- }
|
||
- return suggestions;
|
||
+ return dict.suggest(word);
|
||
}
|
||
--
|
||
2.46.0
|
||
|