From 65679c27623512a79de7c6d92c75d1a9530fb756 Mon Sep 17 00:00:00 2001
From: Vlad Voicu <vladv@rosedu.org>
Date: Thu, 19 Jan 2012 23:49:08 +0200
Subject: Big spell checker chunk


diff --git a/SwifTools/HunspellChecker.cpp b/SwifTools/HunspellChecker.cpp
index a090311..ba7cedd 100644
--- a/SwifTools/HunspellChecker.cpp
+++ b/SwifTools/HunspellChecker.cpp
@@ -37,4 +37,17 @@ void HunspellChecker::getSuggestions(const std::string& word, std::vector<std::s
 	}
 }
 
+void HunspellChecker::checkFragment(const std::string& fragment, PositionPairVector& misspelledPositions) {
+	if (!fragment.empty()) {
+		parser_->check(fragment, misspelledPositions);
+		for (PositionPairVector::iterator it = misspelledPositions.begin(); it != misspelledPositions.end();) {
+			if (isCorrect(fragment.substr(boost::get<0>(*it), boost::get<1>(*it) - boost::get<0>(*it)))) {
+				misspelledPositions.erase(it++);
+			} else {
+				++it;
+			}
+		}
+	}
+}
+
 }
diff --git a/SwifTools/HunspellChecker.h b/SwifTools/HunspellChecker.h
index e016c08..bf56778 100644
--- a/SwifTools/HunspellChecker.h
+++ b/SwifTools/HunspellChecker.h
@@ -6,6 +6,7 @@
 
 #include <vector>
 #include <boost/algorithm/string.hpp>
+#include <boost/tuple/tuple.hpp>
 #include <SwifTools/SpellChecker.h>
 
 #pragma once
@@ -19,6 +20,7 @@ namespace Swift {
 			virtual ~HunspellChecker();
 			virtual bool isCorrect(const std::string& word);
 			virtual void getSuggestions(const std::string& word, std::vector<std::string>& list);
+			virtual void checkFragment(const std::string& fragment, PositionPairVector& misspelledPositions);
 		private:
 			Hunspell* speller_;
 	};
diff --git a/SwifTools/SConscript b/SwifTools/SConscript
index 2d5a657..41e5b74 100644
--- a/SwifTools/SConscript
+++ b/SwifTools/SConscript
@@ -30,6 +30,7 @@ if env["SCONS_STAGE"] == "build" :
 			"LastLineTracker.cpp",
 			"SpellCheckerFactory.cpp",
 			"HunspellChecker.cpp",
+			"SpellParser.cpp",
 		]
 
 	if swiftools_env.get("HAVE_SPARKLE", 0) :
diff --git a/SwifTools/SpellChecker.h b/SwifTools/SpellChecker.h
index a7272e9..a9cbe77 100644
--- a/SwifTools/SpellChecker.h
+++ b/SwifTools/SpellChecker.h
@@ -4,7 +4,10 @@
  * See Documentation/Licenses/BSD-simplified.txt for more information.
  */
 
+#include <SwifTools/SpellParser.h>
+
 #include <boost/algorithm/string.hpp>
+#include <boost/tuple/tuple.hpp>
 #include <vector>
 
 #pragma once
@@ -12,8 +15,16 @@
 namespace Swift {
 	class SpellChecker {
 		public:
-			virtual ~SpellChecker() { };
+			SpellChecker() {
+				parser_ = new SpellParser();
+			}
+			virtual ~SpellChecker() {
+				delete parser_;
+			};
 			virtual bool isCorrect(const std::string& word) = 0;
 			virtual void getSuggestions(const std::string& word, std::vector<std::string>& list) = 0;
+			virtual void checkFragment(const std::string& fragment, PositionPairVector& misspelledPositions) = 0;
+		protected:
+			SpellParser *parser_;
 	};
 }
diff --git a/SwifTools/SpellParser.cpp b/SwifTools/SpellParser.cpp
new file mode 100644
index 0000000..8f5120b
--- /dev/null
+++ b/SwifTools/SpellParser.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2011 Vlad Voicu
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <SwifTools/SpellParser.h>
+
+#include <boost/spirit/include/lex_lexertl.hpp>
+#include <boost/bind.hpp>
+#include <boost/ref.hpp>
+
+#include <string>
+
+namespace lex = boost::spirit::lex;
+
+namespace Swift {
+
+template <typename Lexer>
+struct word_count_tokens : lex::lexer<Lexer>
+{
+	word_count_tokens()
+	{
+	// define tokens (regular expresions) to match strings
+	// order is important
+		this->self.add
+		("w{3}.[^ ]+", ID_WWW)
+		("http:\\/\\/[^ ]+", ID_HTTP)
+		("\\w{1,}['?|\\-?]?\\w{1,}", ID_WORD)
+		(".", ID_CHAR);
+	}
+};
+
+struct counter
+{
+	typedef bool result_type;
+	// the function operator gets called for each of the matched tokens
+	template <typename Token>
+	bool operator()(Token const& t, PositionPairVector& wordPositions, std::size_t& position) const
+	{
+		switch (t.id()) {
+			case ID_WWW:
+				position += t.value().size();
+				break;
+			case ID_HTTP:
+				position += t.value().size();
+				break;
+			case ID_WORD:       // matched a word
+				wordPositions.push_back(boost::tuples::make_tuple(position, position + t.value().size()));
+				position += t.value().size(); 
+				break;
+			case ID_CHAR:       // match a simple char
+				++position; 
+				break;
+		}
+		return true;        // always continue to tokenize
+	}
+};
+
+void SpellParser::check(const std::string& fragment, PositionPairVector& wordPositions) {
+	std::size_t position = 0;
+	// create the token definition instance needed to invoke the lexical analyzer
+	word_count_tokens<lex::lexertl::lexer<> > word_count_functor;
+	char const* first = fragment.c_str();
+	char const* last = &first[fragment.size()];
+	lex::tokenize(first, last, word_count_functor, boost::bind(counter(), _1, boost::ref(wordPositions), boost::ref(position)));
+}
+
+}
diff --git a/SwifTools/SpellParser.h b/SwifTools/SpellParser.h
new file mode 100644
index 0000000..2bc562d
--- /dev/null
+++ b/SwifTools/SpellParser.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2011 Vlad Voicu
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/algorithm/string.hpp>
+#include <boost/tuple/tuple.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <vector>
+
+namespace Swift {
+	enum token_ids
+	{
+		ID_WWW = 1,
+		ID_HTTP = 2,
+		ID_WORD = 3,
+		ID_CHAR = 4,
+	};
+	typedef std::list<boost::tuple<int, int> > PositionPairVector;
+
+	class SpellParser{
+		public:
+			void check(const std::string& fragment, PositionPairVector& wordPositions);
+	};
+}
diff --git a/SwifTools/UnitTest/SConscript b/SwifTools/UnitTest/SConscript
index e469deb..913ef37 100644
--- a/SwifTools/UnitTest/SConscript
+++ b/SwifTools/UnitTest/SConscript
@@ -4,4 +4,5 @@ env.Append(UNITTEST_SOURCES = [
 		File("LinkifyTest.cpp"),
 		File("TabCompleteTest.cpp"),
 		File("LastLineTrackerTest.cpp"),
+		File("SpellParserTest.cpp"),
 	])
diff --git a/SwifTools/UnitTest/SpellParserTest.cpp b/SwifTools/UnitTest/SpellParserTest.cpp
new file mode 100644
index 0000000..974f356
--- /dev/null
+++ b/SwifTools/UnitTest/SpellParserTest.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2012 Vlad Voicu
+ * Licensed under the Simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <boost/algorithm/string.hpp>
+
+#include <SwifTools/SpellParser.h>
+
+using namespace Swift;
+
+class SpellParserTest : public CppUnit::TestFixture {
+	CPPUNIT_TEST_SUITE(SpellParserTest);
+	CPPUNIT_TEST(testSimpleCheckFragment);
+	CPPUNIT_TEST(testWWWCheckFragment);
+	CPPUNIT_TEST_SUITE_END();
+	public:
+		SpellParserTest() {
+			parser_ = new SpellParser();
+		};
+		void tearDown() {
+			position_.clear();
+		}
+		void testSimpleCheckFragment() {
+			parser_->check("fragment test", position_);
+			int size = position_.size();
+			CPPUNIT_ASSERT_EQUAL(2, size);
+			CPPUNIT_ASSERT_EQUAL(0, boost::get<0>(position_[0]));
+			CPPUNIT_ASSERT_EQUAL(8, boost::get<1>(position_[0]));
+			CPPUNIT_ASSERT_EQUAL(9, boost::get<0>(position_[1]));
+			CPPUNIT_ASSERT_EQUAL(13, boost::get<1>(position_[1]));
+		}
+		void testWWWCheckFragment() {
+			parser_->check("www.link.com fragment test", position_);
+			int size = position_.size();
+			CPPUNIT_ASSERT_EQUAL(2, size);
+			CPPUNIT_ASSERT_EQUAL(13, boost::get<0>(position_[0]));
+			CPPUNIT_ASSERT_EQUAL(21, boost::get<1>(position_[0]));
+			CPPUNIT_ASSERT_EQUAL(22, boost::get<0>(position_[1]));
+			CPPUNIT_ASSERT_EQUAL(26, boost::get<1>(position_[1]));
+		}
+	private:
+		SpellParser *parser_;
+		PositionPairVector position_;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(SpellParserTest);
diff --git a/Swift/QtUI/QtTextEdit.cpp b/Swift/QtUI/QtTextEdit.cpp
index 0d0f31c..2dabd7e 100644
--- a/Swift/QtUI/QtTextEdit.cpp
+++ b/Swift/QtUI/QtTextEdit.cpp
@@ -4,6 +4,8 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
+#include <boost/tuple/tuple.hpp>
+
 #include <SwifTools/SpellCheckerFactory.h>
 #include <SwifTools/SpellChecker.h>
 
@@ -59,25 +61,24 @@ void QtTextEdit::keyPressEvent(QKeyEvent* event) {
 }
 
 void QtTextEdit::underlineMisspells() {
+	misspelledPositions_.clear();
 	QTextCharFormat spellingErrorFormat;
 	QTextCharFormat normalFormat;
 	spellingErrorFormat.setUnderlineColor(QColor(Qt::red));
 	spellingErrorFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline);
 	QTextCursor cursor = textCursor();
-	QString word;
-	cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 1);
-	cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1);
-	word = cursor.selectedText();
-	// Qt handles punctuation as words. Workaround that.
-	while (!word.isEmpty() && !word.at(0).isLetter()) {
-		cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 2);
-		cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1);
-		word = cursor.selectedText();
-	}
-	if ((!word.isEmpty()) && !checker_->isCorrect(word.toStdString())) {
-		cursor.mergeCharFormat(spellingErrorFormat);
-		cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::MoveAnchor, 1);
-		cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor, 1);
+	cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor, 1);
+	cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor, 1);
+	cursor.setCharFormat(normalFormat);
+	std::string fragment = Q2PSTRING(cursor.selectedText());
+	checker_->checkFragment(fragment, misspelledPositions_);
+	for (PositionPairVector::iterator it = misspelledPositions_.begin(); it != misspelledPositions_.end(); ++it) {
+		if (textCursor().position() > boost::get<1>(*it)) {
+			cursor.setPosition(boost::get<0>(*it), QTextCursor::MoveAnchor);
+			cursor.setPosition(boost::get<1>(*it), QTextCursor::KeepAnchor);
+			cursor.setCharFormat(spellingErrorFormat);
+			cursor.clearSelection();
+		}
 		cursor.setCharFormat(normalFormat);
 	}
 }
@@ -90,14 +91,23 @@ void QtTextEdit::handleTextChanged() {
 	}
 }
 
-void QtTextEdit::handleReplaceMisspellWord(const QString& word, const QPoint& position) {
-	QTextCursor cursor = cursorForPosition(position);
-	cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 1);
-	cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1);
+void QtTextEdit::handleReplaceMisspellWord(const QString& word, const boost::tuple<int, int>& wordPosition) {
+	QTextCursor cursor = textCursor();
+	cursor.setPosition(boost::get<0>(wordPosition), QTextCursor::MoveAnchor);
+	cursor.setPosition(boost::get<1>(wordPosition), QTextCursor::KeepAnchor);
 	QTextCharFormat normalFormat;
 	cursor.insertText(word, normalFormat);
 }
 
+boost::tuple<int, int> QtTextEdit::getWordFromCursor(int cursorPosition) {
+	for (PositionPairVector::iterator it = misspelledPositions_.begin(); it != misspelledPositions_.end(); ++it) {
+		if (cursorPosition >= boost::get<0>(*it) && cursorPosition <= boost::get<1>(*it)) {
+			return *it;
+		}
+	}
+	return boost::make_tuple(-1,-1);
+}
+
 QSize QtTextEdit::sizeHint() const {
 	QFontMetrics inputMetrics(currentFont());
 	QRect horizontalBounds = contentsRect().adjusted(0,0,0,9999);
@@ -113,11 +123,14 @@ QSize QtTextEdit::sizeHint() const {
 
 void QtTextEdit::contextMenuEvent(QContextMenuEvent* event) {
 	QTextCursor cursor = cursorForPosition(event->pos());
-	cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 1);
-	cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1);
+	boost::tuple<int, int> wordPosition = getWordFromCursor(cursor.position());
+	if (boost::get<0>(wordPosition) < 0) {
+		return;
+	}
+	cursor.setPosition(boost::get<0>(wordPosition), QTextCursor::MoveAnchor);
+	cursor.setPosition(boost::get<1>(wordPosition), QTextCursor::KeepAnchor);
 	std::vector<std::string> wordList;
 	checker_->getSuggestions(Q2PSTRING(cursor.selectedText()), wordList);
-
 	std::vector<QAction*> replaceWordActions;
 	QMenu *menu = createStandardContextMenu();
 	for (std::vector<std::string>::iterator it = wordList.begin(); it != wordList.end(); ++it) {
@@ -127,7 +140,7 @@ void QtTextEdit::contextMenuEvent(QContextMenuEvent* event) {
 	QAction* result = menu->exec(event->globalPos());
 	for (std::vector<QAction*>::iterator it = replaceWordActions.begin(); it != replaceWordActions.end(); ++it) {
 		if (*it == result) {
-			handleReplaceMisspellWord((*it)->text(), event->pos());
+			handleReplaceMisspellWord((*it)->text(), wordPosition);
 		}
 	}
 	delete menu;
diff --git a/Swift/QtUI/QtTextEdit.h b/Swift/QtUI/QtTextEdit.h
index a940879..76087c9 100644
--- a/Swift/QtUI/QtTextEdit.h
+++ b/Swift/QtUI/QtTextEdit.h
@@ -7,6 +7,7 @@
 #pragma once
 
 #include <QTextEdit>
+#include <SwifTools/SpellParser.h>
 
 namespace Swift {
 	class SpellChecker;
@@ -21,7 +22,7 @@ namespace Swift {
 		void returnPressed();
 		void unhandledKeyPressEvent(QKeyEvent* event);
 	public slots:
-		void handleReplaceMisspellWord(const QString& word, const QPoint& position);
+		void handleReplaceMisspellWord(const QString& word, const boost::tuple<int,int>& wordPosition);
 	protected:
 		virtual void keyPressEvent(QKeyEvent* event);
 		virtual void contextMenuEvent(QContextMenuEvent* event);
@@ -29,6 +30,8 @@ namespace Swift {
 		void handleTextChanged();
 	private:
 		SpellChecker *checker_;
+		PositionPairVector misspelledPositions_;
 		void underlineMisspells();
+		boost::tuple<int,int> getWordFromCursor(int cursorPosition);
 	};
 }
-- 
cgit v0.10.2-6-g49f6