[rkward/work/unified_hinting2] rkward/windows: Next attempt at unifying text hints (function args, code completion, filename completion, and auto-text hints; script-editor and console).

Thomas Friedrichsmeier null at kde.org
Fri Feb 8 21:35:02 GMT 2019


Git commit 985db5e6ac852b70b94ab4033f0ccbe0836e98ba by Thomas Friedrichsmeier.
Committed on 08/02/2019 at 19:53.
Pushed by tfry into branch 'work/unified_hinting2'.

Next attempt at unifying text hints (function args, code completion, filename completion, and auto-text hints; script-editor and console).

We start by moving existing code-completion code into a dedicated manager class separate from script editor.

M  +161  -124  rkward/windows/rkcommandeditorwindow.cpp
M  +57   -21   rkward/windows/rkcommandeditorwindow.h

https://commits.kde.org/rkward/985db5e6ac852b70b94ab4033f0ccbe0836e98ba

diff --git a/rkward/windows/rkcommandeditorwindow.cpp b/rkward/windows/rkcommandeditorwindow.cpp
index 97f64d43..1f93905f 100644
--- a/rkward/windows/rkcommandeditorwindow.cpp
+++ b/rkward/windows/rkcommandeditorwindow.cpp
@@ -221,30 +221,11 @@ RKCommandEditorWindow::RKCommandEditorWindow (QWidget *parent, const QUrl _url,
 	// somehow the katepart loses the context menu each time it loses focus
 	connect (m_view, &KTextEditor::View::focusIn, this, &RKCommandEditorWindow::focusIn);
 
-	completion_model = 0;
-	kate_keyword_completion_model = 0;
-	cc_iface = 0;
 	hinter = 0;
 	if (use_r_highlighting) {
 		RKCommandHighlighter::setHighlighting (m_doc, RKCommandHighlighter::RScript);
 		if (use_codehinting) {
-			cc_iface = qobject_cast<KTextEditor::CodeCompletionInterface*> (m_view);
-			if (cc_iface) {
-				cc_iface->setAutomaticInvocationEnabled (false);
-				completion_model = new RKCodeCompletionModel (this);
-				completion_timer = new QTimer (this);
-				completion_timer->setSingleShot (true);
-				connect (completion_timer, &QTimer::timeout, this, &RKCommandEditorWindow::tryCompletion);
-				connect (m_doc, &KTextEditor::Document::textChanged, this, &RKCommandEditorWindow::tryCompletionProxy);
-
-				// HACK: I just can't see to make the object name completion model play nice with automatic invocation.
-				//       However, there is no official way to invoke all registered models, manually. So we try to hack our way
-				//       to a pointer to the default kate keyword completion model
-				kate_keyword_completion_model = editor->findChild<KTextEditor::CodeCompletionModel *> ();
-				if (!kate_keyword_completion_model) kate_keyword_completion_model = m_view->findChild<KTextEditor::CodeCompletionModel *> (QString());
-			} else {
-				RK_ASSERT (false);
-			}
+			new RKCompletionManager (m_view);
 			hinter = new RKFunctionArgHinter (this, m_view);
 		}
 	} else {
@@ -740,75 +721,6 @@ void RKCommandEditorWindow::currentHelpContext (QString *symbol, QString *packag
 	*symbol = RKCommonFunctions::getCurrentSymbol (line, c.column ());
 }
 
-void RKCommandEditorWindow::tryCompletionProxy (KTextEditor::Document*) {
-	if (RKSettingsModuleCommandEditor::completionEnabled ()) {
-		if (cc_iface && cc_iface->isCompletionActive ()) {
-			tryCompletion ();
-		} else if (cc_iface) {
-			completion_timer->start (RKSettingsModuleCommandEditor::completionTimeout ());
-		}
-	}
-}
-
-QString RKCommandEditorWindow::currentCompletionWord () const {
-	RK_TRACE (COMMANDEDITOR);
-// KDE4 TODO: This may no longer be needed, if the katepart gets fixed not to abort completions when the range
-// contains dots or other special characters
-	KTextEditor::Cursor c = m_view->cursorPosition();
-	if (!c.isValid ()) return QString ();
-	uint para=c.line(); uint cursor_pos=c.column();
-
-	QString current_line = m_doc->line (para);
-	if (current_line.lastIndexOf ("#", cursor_pos) >= 0) return QString ();	// do not hint while in comments
-
-	return RKCommonFunctions::getCurrentSymbol (current_line, cursor_pos, false);
-}
-
-KTextEditor::Range RKCodeCompletionModel::completionRange (KTextEditor::View *view, const KTextEditor::Cursor &position) {
-	if (!position.isValid ()) return KTextEditor::Range ();
-	QString current_line = view->document ()->line (position.line ());
-	int start;
-	int end;
-	RKCommonFunctions::getCurrentSymbolOffset (current_line, position.column (), false, &start, &end);
-	return KTextEditor::Range (position.line (), start, position.line (), end);
-}
-
-void RKCommandEditorWindow::tryCompletion () {
-	// TODO: merge this with RKConsole::doTabCompletion () somehow
-	RK_TRACE (COMMANDEDITOR);
-	if ((!cc_iface) || (!completion_model)) {
-		RK_ASSERT (false);
-		return;
-	}
-
-	KTextEditor::Cursor c = m_view->cursorPosition();
-	uint para=c.line(); int cursor_pos=c.column();
-
-	QString current_line = m_doc->line (para);
-	int start;
-	int end;
-	RKCommonFunctions::getCurrentSymbolOffset (current_line, cursor_pos, false, &start, &end);
-	if (end > cursor_pos) return;   // Only hint when at the end of a word/symbol: https://mail.kde.org/pipermail/rkward-devel/2015-April/004122.html
-
-	KTextEditor::Range range = KTextEditor::Range (para, start, para, end);
-	QString word;
-	if (range.isValid ()) word = m_doc->text (range);
-	if (current_line.lastIndexOf ("#", cursor_pos) >= 0) word.clear ();	// do not hint while in comments
-	if (word.length () >= RKSettingsModuleCommandEditor::completionMinChars ()) {
-		completion_model->updateCompletionList (word);
-		if (completion_model->isEmpty ()) {
-			if (kate_keyword_completion_model && kate_keyword_completion_model->rowCount () < 1) cc_iface->abortCompletion ();
-		} else {
-			if (!cc_iface->isCompletionActive ()) {
-				cc_iface->startCompletion (range, completion_model);
-				if (kate_keyword_completion_model) cc_iface->startCompletion (range, kate_keyword_completion_model);
-			}
-		}
-	} else {
-		cc_iface->abortCompletion ();
-	}
-}
-
 QString RKCommandEditorWindow::provideContext (int line_rev) {
 	RK_TRACE (COMMANDEDITOR);
 
@@ -1269,13 +1181,156 @@ bool RKFunctionArgHinter::eventFilter (QObject *, QEvent *e) {
 	return false;
 }
 
+//////////////////////// RKCompletionManager //////////////////////
+
+RKCompletionManager::RKCompletionManager (KTextEditor::View* view) : QObject (view) {
+	RK_TRACE (COMMANDEDITOR);
+
+	RKCompletionManager::view = view;
+	completion_model = 0;
+	kate_keyword_completion_model = 0;
+
+	cc_iface = qobject_cast<KTextEditor::CodeCompletionInterface*> (view);
+	if (cc_iface) {
+		cc_iface->setAutomaticInvocationEnabled (false);
+		completion_model = new RKCodeCompletionModel (this);
+		completion_timer = new QTimer (this);
+		completion_timer->setSingleShot (true);
+		connect (completion_timer, &QTimer::timeout, this, &RKCompletionManager::tryCompletion);
+		connect (view->document (), &KTextEditor::Document::textInserted, this, &RKCompletionManager::textInserted);
+		connect (view->document (), &KTextEditor::Document::textRemoved, this, &RKCompletionManager::textRemoved);
+		connect (view->document (), &KTextEditor::Document::lineWrapped, this, &RKCompletionManager::lineWrapped);
+		connect (view->document (), &KTextEditor::Document::lineUnwrapped, this, &RKCompletionManager::lineUnwrapped);
+		connect (view, &KTextEditor::View::cursorPositionChanged, this, &RKCompletionManager::cursorPositionChanged);
+
+		// HACK: I just can't see to make the object name completion model play nice with automatic invocation.
+		//       However, there is no official way to invoke all registered models, manually. So we try to hack our way
+		//       to a pointer to the default kate keyword completion model
+		kate_keyword_completion_model = KTextEditor::Editor::instance ()->findChild<KTextEditor::CodeCompletionModel *> ();
+		if (!kate_keyword_completion_model) kate_keyword_completion_model = view->findChild<KTextEditor::CodeCompletionModel *> (QString());
+	} else {
+		RK_ASSERT (false);  // Not a katepart?
+	}
+}
+
+RKCompletionManager::~RKCompletionManager () {
+	RK_TRACE (COMMANDEDITOR);
+}
+
+void RKCompletionManager::tryCompletionProxy () {
+	if (RKSettingsModuleCommandEditor::completionEnabled ()) {
+		if (cc_iface && cc_iface->isCompletionActive ()) {
+			tryCompletion ();
+		} else if (cc_iface) {
+			completion_timer->start (RKSettingsModuleCommandEditor::completionTimeout ());
+		}
+	}
+}
+
+QString RKCompletionManager::currentCompletionWord () const {
+	RK_TRACE (COMMANDEDITOR);
+
+	if (symbol_range.isValid ()) return view->document ()->text (symbol_range);
+	return QString ();
+}
+
+void RKCompletionManager::tryCompletion () {
+	// TODO: merge this with RKConsole::doTabCompletion () somehow
+	RK_TRACE (COMMANDEDITOR);
+	if (!cc_iface) {
+		RK_ASSERT (cc_iface);
+		symbol_range = KTextEditor::Range ();
+		return;
+	}
+
+	KTextEditor::Document *doc = view->document ();
+	KTextEditor::Cursor c = view->cursorPosition();
+	uint para=c.line(); int cursor_pos=c.column();
+
+	QString current_line = doc->line (para);
+	int start;
+	int end;
+	RKCommonFunctions::getCurrentSymbolOffset (current_line, cursor_pos, false, &start, &end);
+	if (end > cursor_pos) return;   // Only hint when at the end of a word/symbol: https://mail.kde.org/pipermail/rkward-devel/2015-April/004122.html
+	if (current_line.lastIndexOf ("#", cursor_pos) >= 0) symbol_range = KTextEditor::Range ();	// do not hint while in comments
+	else symbol_range = KTextEditor::Range (para, start, para, end);
+
+	QString word = currentCompletionWord ();
+	if (word.length () >= RKSettingsModuleCommandEditor::completionMinChars ()) {
+		completion_model->updateCompletionList (word);
+		if (completion_model->isEmpty ()) {
+			if ((!kate_keyword_completion_model) || (kate_keyword_completion_model->rowCount () < 1)) cc_iface->abortCompletion ();
+		} else {
+			if (!cc_iface->isCompletionActive ()) {
+				cc_iface->startCompletion (symbol_range, completion_model);
+				if (kate_keyword_completion_model) cc_iface->startCompletion (symbol_range, kate_keyword_completion_model);
+			}
+		}
+	} else {
+		cc_iface->abortCompletion ();
+	}
+}
+
+void RKCompletionManager::textInserted(KTextEditor::Document* document, const KTextEditor::Cursor& position, const QString& text) {
+	tryCompletionProxy();
+}
+
+void RKCompletionManager::textRemoved(KTextEditor::Document* document, const KTextEditor::Range& range, const QString& text) {
+	tryCompletionProxy();
+}
+
+void RKCompletionManager::lineWrapped(KTextEditor::Document* document, const KTextEditor::Cursor& position) {
+	tryCompletionProxy();
+}
+
+void RKCompletionManager::lineUnwrapped(KTextEditor::Document* document, int line) {
+	tryCompletionProxy();
+}
+
+void RKCompletionManager::cursorPositionChanged(KTextEditor::View* view, const KTextEditor::Cursor& newPosition) {
+	tryCompletionProxy();
+}
+
+
+//////////////////////// RKCompletionModelBase ////////////////////
+
+RKCompletionModelBase::RKCompletionModelBase (RKCompletionManager *manager) : KTextEditor::CodeCompletionModel (manager) {
+	RK_TRACE (COMMANDEDITOR);
+	n_completions = 0;
+	RKCompletionModelBase::manager = manager;
+}
+
+RKCompletionModelBase::~RKCompletionModelBase () {
+	RK_TRACE (COMMANDEDITOR);
+}
+
+QModelIndex RKCompletionModelBase::index (int row, int column, const QModelIndex& parent) const {
+	if (!parent.isValid ()) { // root
+		if (row == 0) return createIndex (row, column, quintptr (HeaderItem));
+	} else if (isHeaderItem (parent)) {
+		return createIndex (row, column, quintptr (LeafItem));
+	}
+	return QModelIndex ();
+}
+
+QModelIndex RKCompletionModelBase::parent (const QModelIndex& index) const {
+	if (index.isValid () && !isHeaderItem (index)) {
+		return createIndex (0, 0, quintptr (HeaderItem));
+	}
+	return QModelIndex ();
+}
+
+int RKCompletionModelBase::rowCount (const QModelIndex& parent) const {
+	if (!parent.isValid ()) return (n_completions ? 1 : 0); // header item, if list not empty
+	if (isHeaderItem (parent)) return n_completions;
+	return 0;  // no children on completion entries
+}
+
 //////////////////////// RKCodeCompletionModel ////////////////////
 
-RKCodeCompletionModel::RKCodeCompletionModel (RKCommandEditorWindow *parent) : KTextEditor::CodeCompletionModel (parent) {
+RKCodeCompletionModel::RKCodeCompletionModel (RKCompletionManager *manager) : RKCompletionModelBase (manager) {
 	RK_TRACE (COMMANDEDITOR);
 
-	setRowCount (0);
-	command_editor = parent;
 	setHasGroups (true);
 }
 
@@ -1297,11 +1352,11 @@ void RKCodeCompletionModel::updateCompletionList (const QString& symbol) {
 
 	// copy the map to two lists. For one thing, we need an int indexable storage, for another, caching this information is safer
 	// in case objects are removed while the completion mode is active.
-	int count = matches.size ();
+	n_completions = matches.size ();
 	icons.clear ();
-	icons.reserve (count);
+	icons.reserve (n_completions);
 	names = RObject::getFullNames (matches, RKSettingsModuleCommandEditor::completionOptions());
-	for (int i = 0; i < count; ++i) {
+	for (int i = 0; i < n_completions; ++i) {
 		icons.append (RKStandardIcons::iconForObject (matches[i]));
 	}
 
@@ -1310,6 +1365,15 @@ void RKCodeCompletionModel::updateCompletionList (const QString& symbol) {
 	endResetModel ();
 }
 
+KTextEditor::Range RKCodeCompletionModel::completionRange (KTextEditor::View *view, const KTextEditor::Cursor &position) {
+	if (!position.isValid ()) return KTextEditor::Range ();
+	QString current_line = view->document ()->line (position.line ());
+	int start;
+	int end;
+	RKCommonFunctions::getCurrentSymbolOffset (current_line, position.column (), false, &start, &end);
+	return KTextEditor::Range (position.line (), start, position.line (), end);
+}
+
 void RKCodeCompletionModel::completionInvoked (KTextEditor::View*, const KTextEditor::Range&, InvocationType) {
 	RK_TRACE (COMMANDEDITOR);
 
@@ -1317,7 +1381,7 @@ void RKCodeCompletionModel::completionInvoked (KTextEditor::View*, const KTextEd
 	// it is often wrong, esp, when there are dots in the symbol
 // KDE4 TODO: This may no longer be needed, if the katepart gets fixed not to abort completions when the range
 // contains dots or other special characters
-	updateCompletionList (command_editor->currentCompletionWord ());
+	updateCompletionList (manager->currentCompletionWord ());
 }
 
 void RKCodeCompletionModel::executeCompletionItem (KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const {
@@ -1328,36 +1392,9 @@ void RKCodeCompletionModel::executeCompletionItem (KTextEditor::View *view, cons
 	view->document ()->replaceText (word, names[index.row ()]);
 }
 
-QModelIndex RKCodeCompletionModel::index (int row, int column, const QModelIndex& parent) const {
-	if (!parent.isValid ()) { // header item
-		if (row == 0) return createIndex (row, column, quintptr (0));
-		return QModelIndex ();
-	} else if (parent.parent ().isValid ()) {
-		return QModelIndex ();
-	}
-
-	if (row < 0 || row >= names.count () || column < 0 || column >= ColumnCount) {
-		return QModelIndex ();
-	}
-
-	return createIndex (row, column, 1);  // regular item
-}
-
-QModelIndex RKCodeCompletionModel::parent (const QModelIndex& index) const {
-	if (index.internalId ()) return createIndex (0, 0, quintptr (0));
-	return QModelIndex ();
-}
-
-int RKCodeCompletionModel::rowCount (const QModelIndex& parent) const {
-	if (parent.parent ().isValid ()) return 0;  // no children on completion entries
-	if (parent.isValid ()) return names.count ();
-	return (!names.isEmpty ()); // header item, if list not empty
-}
-
 QVariant RKCodeCompletionModel::data (const QModelIndex& index, int role) const {
-	if (!index.parent ().isValid ()) {  // group header
+	if (isHeaderItem (index)) {
 		if (role == Qt::DisplayRole) return i18n ("Objects on search path");
-		if (role == GroupRole) return Qt::DisplayRole;
 		return QVariant ();
 	}
 
diff --git a/rkward/windows/rkcommandeditorwindow.h b/rkward/windows/rkcommandeditorwindow.h
index e83ed654..9d42c397 100644
--- a/rkward/windows/rkcommandeditorwindow.h
+++ b/rkward/windows/rkcommandeditorwindow.h
@@ -104,32 +104,78 @@ private:
 	QLabel *arghints_popup;
 };
 
-class RKCodeCompletionModel : public KTextEditor::CodeCompletionModel, public KTextEditor::CodeCompletionModelControllerInterface {
+class RKCodeCompletionModel;
+class RKCompletionManager : public QObject {
 	Q_OBJECT
-	Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface)
 public:
-	explicit RKCodeCompletionModel (RKCommandEditorWindow* parent);
-	~RKCodeCompletionModel ();
+	RKCompletionManager (KTextEditor::View *view);
+	~RKCompletionManager ();
+
+	QString currentCompletionWord () const;
+private slots:
+	void lineWrapped (KTextEditor::Document *document, const KTextEditor::Cursor &position);
+	void lineUnwrapped (KTextEditor::Document *document, int line);
+	void textInserted (KTextEditor::Document *document, const KTextEditor::Cursor &position, const QString &text);
+	void textRemoved (KTextEditor::Document *document, const KTextEditor::Range &range, const QString &text);
+	void cursorPositionChanged (KTextEditor::View *view, const KTextEditor::Cursor &newPosition);
+/** show a code completion box if appropriate. Use tryCompletionProxy () instead, which will call this function after a timeout */
+	void tryCompletion ();
+private:
+/** called whenever it might be appropriate to show a code completion box. The box is not shown immediately, but only after a timeout (if at all) */
+	void tryCompletionProxy ();
+	KTextEditor::CodeCompletionInterface *cc_iface;
+	RKCodeCompletionModel *completion_model;
+	KTextEditor::CodeCompletionModel* kate_keyword_completion_model;
+	QTimer *completion_timer;
+
+	KTextEditor::View *view;
+	KTextEditor::Cursor cached_position;
+	KTextEditor::Range symbol_range;
+};
+
+/** Base class for the completion models employed in script editor. Essentially it takes care of the bureaucratic overhead involved in providing a group header */
+class RKCompletionModelBase : public KTextEditor::CodeCompletionModel, public KTextEditor::CodeCompletionModelControllerInterface {
+public:
+	explicit RKCompletionModelBase (RKCompletionManager *manager);
+	~RKCompletionModelBase ();
 
-	KTextEditor::Range completionRange (KTextEditor::View *view, const KTextEditor::Cursor &position) override;
 	QString filterString (KTextEditor::View *, const KTextEditor::Range &, const KTextEditor::Cursor &) override { return QString (); };
 	bool shouldAbortCompletion (KTextEditor::View *, const KTextEditor::Range &, const QString &) override { return false; }
 	KTextEditor::CodeCompletionModelControllerInterface::MatchReaction matchingItem (const QModelIndex &) override { return KTextEditor::CodeCompletionModelControllerInterface::None; };
 
-	void updateCompletionList (const QString& symbol);
-	void completionInvoked (KTextEditor::View *, const KTextEditor::Range &, InvocationType) override;
-	void executeCompletionItem (KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const override;
-	QVariant data (const QModelIndex& index, int role=Qt::DisplayRole) const override;
 	int rowCount (const QModelIndex &parent) const override;
 	QModelIndex index (int row, int column, const QModelIndex &parent = QModelIndex ()) const override;
 	QModelIndex parent (const QModelIndex &index) const override;
 
-	bool isEmpty () const { return names.isEmpty (); };
+	bool isHeaderItem (const QModelIndex &parent) const { return (parent.internalId () == HeaderItem); };
+	bool isEmpty () const { return (n_completions == 0); };
+protected:
+	int n_completions;
+	RKCompletionManager *manager;
+private:
+	enum {
+		// forcing non-0, so function will not return true on null-QModelIndex
+		HeaderItem = 1,
+		LeafItem = 2
+	};
+};
+
+class RKCodeCompletionModel : public RKCompletionModelBase {
+	Q_OBJECT
+public:
+	explicit RKCodeCompletionModel (RKCompletionManager *manager);
+	~RKCodeCompletionModel ();
+
+	KTextEditor::Range completionRange (KTextEditor::View *view, const KTextEditor::Cursor &position) override;
+
+	void updateCompletionList (const QString& symbol);
+	void completionInvoked (KTextEditor::View *, const KTextEditor::Range &, InvocationType) override;
+	void executeCompletionItem (KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const override;
+	QVariant data (const QModelIndex& index, int role=Qt::DisplayRole) const override;
 private:
 	QList<QIcon> icons;
 	QStringList names;
 	QString current_symbol;
-	RKCommandEditorWindow *command_editor;
 };
 
 class RKJobSequence;
@@ -180,16 +226,11 @@ public:
 
 	QString provideContext (int line_rev) override;
 	void currentHelpContext (QString* symbol, QString* package) override;
-	QString currentCompletionWord () const;
 
 	void highlightLine (int linenum);
 public slots:
 /** update Tab caption according to the current url. Display the filename-component of the URL, or - if not available - a more elaborate description of the url. Also appends a "[modified]" if appropriate */
 	void updateCaption ();
-/** called whenever it might be appropriate to show a code completion box. The box is not shown immediately, but only after a timeout (if at all) */
-	void tryCompletionProxy (KTextEditor::Document*);
-/** show a code completion box if appropriate. Use tryCompletionProxy () instead, which will call this function after a timeout */
-	void tryCompletion ();
 	void setPopupMenu ();
 	void focusIn (KTextEditor::View *);
 /** run the currently selected command(s) or line */
@@ -240,13 +281,8 @@ private:
 	KTextEditor::Cursor saved_scroll_position;
 	KTextEditor::Document *m_doc;
 	KTextEditor::View *m_view;
-	KTextEditor::CodeCompletionInterface *cc_iface;
 	KTextEditor::MovingInterface *smart_iface;
 	RKFunctionArgHinter *hinter;
-	RKCodeCompletionModel *completion_model;
-	KTextEditor::CodeCompletionModel* kate_keyword_completion_model;
-
-	QTimer *completion_timer;
 
 	void initializeActions (KActionCollection* ac);
 



More information about the rkward-tracker mailing list