[education/rkward] rkward/windows: Make sure dynamic completion items (arriving late) get shown, even if no further completions had forced showing the completion widget.

Thomas Friedrichsmeier null at kde.org
Wed Oct 5 16:33:03 BST 2022


Git commit 49151839e616c3cf1a1c20313e161890ec9dbf10 by Thomas Friedrichsmeier.
Committed on 05/10/2022 at 12:08.
Pushed by tfry into branch 'master'.

Make sure dynamic completion items (arriving late) get shown, even if no further completions had forced showing the completion widget.

M  +78   -45   rkward/windows/rkcodecompletion.cpp
M  +9    -5    rkward/windows/rkcodecompletion.h

https://invent.kde.org/education/rkward/commit/49151839e616c3cf1a1c20313e161890ec9dbf10

diff --git a/rkward/windows/rkcodecompletion.cpp b/rkward/windows/rkcodecompletion.cpp
index fffbe3ab..f611ad92 100644
--- a/rkward/windows/rkcodecompletion.cpp
+++ b/rkward/windows/rkcodecompletion.cpp
@@ -283,43 +283,49 @@ void RKCompletionManager::updateCallHint () {
 	callhint_model->setFunction (object);
 }
 
-void startModel (KTextEditor::CodeCompletionInterface* iface, KTextEditor::CodeCompletionModel *model, bool start, const KTextEditor::Range &range, QList<KTextEditor::CodeCompletionModel*> *active_models) {
+void RKCompletionManager::startModel( KTextEditor::CodeCompletionModel *model, bool start, const KTextEditor::Range &range) {
 	if (start) {
-		if (model->rowCount () == 0) start = false;
-		if (!range.isValid ()) start = false;
+		if (!range.isValid()) start = false;
 	}
 	if (start) {
-		if (!active_models->contains (model)) {
-			iface->startCompletion (range, model);
-			active_models->append (model);
+		if (!started_models.contains (model)) {
+			cc_iface->startCompletion (range, model);
+			started_models.append (model);
+			auto ci = dynamic_cast<KTextEditor::CodeCompletionModelControllerInterface*>(model);
+			model_filters.insert(model, ci ? ci->filterString(view(), range, view()->cursorPosition()) : QString());
 		}
 	} else {
-		active_models->removeAll (model);
+		started_models.removeAll (model);
 	}
 }
 
 void RKCompletionManager::updateVisibility () {
 	RK_TRACE (COMMANDEDITOR);
 
-	if (user_triggered || !cc_iface->isCompletionActive ()) {
-		active_models.clear ();
+	if (user_triggered || !cc_iface->isCompletionActive()) {
+		started_models.clear ();
 	}
 
-	bool min_len = (currentCompletionWord ().length () >= settings->autoMinChars ()) || user_triggered;
-	startModel (cc_iface, completion_model, min_len && settings->isEnabled (RKCodeCompletionSettings::Object), symbol_range, &active_models);
-	startModel (cc_iface, file_completion_model, min_len && settings->isEnabled (RKCodeCompletionSettings::Filename), symbol_range, &active_models);
-	if (kate_keyword_completion_model && settings->isEnabled (RKCodeCompletionSettings::AutoWord)) {
+	bool min_len = (currentCompletionWord().length() >= settings->autoMinChars()) || user_triggered;
+	startModel(completion_model, min_len && settings->isEnabled(RKCodeCompletionSettings::Object), symbol_range);
+	startModel(file_completion_model, min_len && settings->isEnabled(RKCodeCompletionSettings::Filename), symbol_range);
+	if (kate_keyword_completion_model && settings->isEnabled(RKCodeCompletionSettings::AutoWord)) {
 		// Model needs to update, first, as we have not handled it in tryCompletion:
-		if (min_len) kate_keyword_completion_model->completionInvoked (view (), symbol_range, KTextEditor::CodeCompletionModel::ManualInvocation);
-		startModel (cc_iface, kate_keyword_completion_model, min_len, symbol_range, &active_models);
+		if (min_len) kate_keyword_completion_model->completionInvoked(view(), symbol_range, KTextEditor::CodeCompletionModel::ManualInvocation);
+		startModel(kate_keyword_completion_model, min_len, symbol_range);
 	}
 // NOTE: Freaky bug in KF 5.44.0: Call hint will not show for the first time, if logically above the primary screen. TODO: provide patch for kateargumenthinttree.cpp:166pp
-	startModel (cc_iface, callhint_model, checkSaneVersion() && settings->isEnabled (RKCodeCompletionSettings::Calltip), currentCallRange (), &active_models);
-	startModel (cc_iface, arghint_model, min_len && settings->isEnabled (RKCodeCompletionSettings::Arghint), argname_range, &active_models);
+	startModel(callhint_model, checkSaneVersion() && settings->isEnabled(RKCodeCompletionSettings::Calltip), currentCallRange());
+	startModel(arghint_model, min_len && settings->isEnabled(RKCodeCompletionSettings::Arghint), argname_range);
+}
 
-	if (active_models.isEmpty ()) {
-		cc_iface->abortCompletion ();
-	}
+// NOTE: We call this only for models that have been previously empty. Otherwise, completion widget is already shown (unless user aborted completion),
+//       and the new items would show, automatically.
+void RKCompletionManager::emptyModelGainedLateData(RKCompletionModelBase* model) {
+	RK_TRACE (COMMANDEDITOR);
+
+	RK_ASSERT(started_models.removeAll(model)); // should have been started before, thus be in the list
+	startModel(model, true, model->completionRange(view(), view()->cursorPosition()));
 }
 
 void RKCompletionManager::textInserted (KTextEditor::Document*, const KTextEditor::Cursor& position, const QString& text) {
@@ -365,6 +371,22 @@ KTextEditor::Range RKCompletionManager::currentCallRange () const {
 	return KTextEditor::Range (call_opening, _view->cursorPosition ());
 }
 
+bool isModelEmpty(KTextEditor::CodeCompletionModel* model, const QString &filter) {
+	int rootcount = model->rowCount();
+	for (int i = 0; i < rootcount; ++i) {
+		auto pindex = model->index(i, 0, QModelIndex());
+		if (model->hasGroups()) {
+			int groupcount = model->rowCount(pindex);
+			for (int j = 0; j < groupcount; ++j) {
+				if (model->index(j, KTextEditor::CodeCompletionModel::Name, pindex).data().toString().startsWith(filter)) return false;
+			}
+		} else {
+			if (model->index(i, KTextEditor::CodeCompletionModel::Name, QModelIndex()).data().toString().startsWith(filter)) return false;
+		}
+	}
+	return true;
+}
+
 bool RKCompletionManager::eventFilter (QObject*, QEvent* event) {
 	if (event->type () == QEvent::KeyPress || event->type () == QEvent::ShortcutOverride) {
 		RK_TRACE (COMMANDEDITOR);	// avoid loads of empty traces, putting this here
@@ -380,12 +402,21 @@ bool RKCompletionManager::eventFilter (QObject*, QEvent* event) {
 		}
 
 		// If only the calltip is active, make sure the tab-key and enter behave as a regular keys. There is no completion in this case.
-		if (active_models.count () == 1 && active_models[0] == callhint_model) {
-			if (((k->key() == Qt::Key_Tab) || (k->key() == Qt::Key_Return) || (k->key() == Qt::Key_Enter)) || (k->key() == Qt::Key_Backtab) || (k->key() == Qt::Key_Up) || (k->key() == Qt::Key_Down)) {
-				completion_timer->start(0);
-				cc_iface->abortCompletion(); // That's a bit lame, but the least hacky way to get the key into the document. completion_timer was started, so
-				                             // the completion window should come back up, without delay
-				return false;
+		if (started_models.value(0) == callhint_model) {
+			if ((k->key() == Qt::Key_Tab) || (k->key() == Qt::Key_Return) || (k->key() == Qt::Key_Enter) || (k->key() == Qt::Key_Backtab) || (k->key() == Qt::Key_Up) || (k->key() == Qt::Key_Down)) {
+				bool allempty = true;
+				for (int i = 1; i < started_models.size(); ++i) {
+					if (!isModelEmpty(started_models[i], model_filters[started_models[i]])) {
+						allempty = false;
+						break;
+					}
+				}
+				if (allempty) {
+					completion_timer->start(0);
+					cc_iface->abortCompletion(); // That's a bit lame, but the least hacky way to get the key into the document. completion_timer was started, so
+								// the completion window should come back up, without delay
+					return false;
+				}
 			}
 		}
 
@@ -395,8 +426,9 @@ bool RKCompletionManager::eventFilter (QObject*, QEvent* event) {
 				return true;
 			}
 			cc_iface->forceCompletion();
-// TODO: If nothing was actually modified, should return press should be sent? Configurable?
+// TODO: If nothing was actually modified, should return press be sent? Configurable?
 			if (settings->autoEnabled ()) ignore_next_trigger = true;
+// TODO: not good: Also hides the calltip. Instead, perhaps, if all a model contains is a single exact match, it should auto-hide
 			return true;
 		}
 
@@ -408,27 +440,25 @@ bool RKCompletionManager::eventFilter (QObject*, QEvent* event) {
 			bool exact = false;
 			QString comp;
 			bool handled = false;
-			if (active_models.contains (arghint_model)) {
-				comp = arghint_model->partialCompletion (&exact);
+			if (started_models.contains(arghint_model) && arghint_model->partialCompletion(&comp, &exact)) {
 				handled = true;
-			} else if (active_models.contains (completion_model)) {
-				comp = completion_model->partialCompletion (&exact);
+			} else if (started_models.contains(completion_model) && completion_model->partialCompletion(&comp, &exact)) {
 				handled = true;
-			} else if (active_models.contains (file_completion_model)) {
-				comp = file_completion_model->partialCompletion (&exact);
+			} else if (started_models.contains(file_completion_model) && file_completion_model->partialCompletion(&comp, &exact)) {
 				handled = true;
 			}
 
 			if (handled) {
-				RK_DEBUG(COMMANDEDITOR, DL_DEBUG, "Tab completion: %s", qPrintable (comp));
+				RK_DEBUG(COMMANDEDITOR, DL_WARNING, "Tab completion: %s, %d", qPrintable (comp), k->type());
 				if (k->type () == QEvent::ShortcutOverride) {
 					// Too bad for all the duplicate work, but the event will re-trigger as a keypress event, and we need to intercept that one, too.
 					return true;
 				}
 				view ()->document ()->insertText (view ()->cursorPosition (), comp);
 				if (exact) {
+// TODO: Not good. a) it also kills the callhint, b) Match may have been exact, but an further (longer) match could still exist
 					// Ouch, how messy. We want to make sure completion stops, and is not re-triggered by the insertion, itself
-					active_models.clear ();
+					started_models.clear ();
 					cc_iface->abortCompletion ();
 					if (settings->autoEnabled ()) ignore_next_trigger = true;
 				}
@@ -561,6 +591,7 @@ void RKCodeCompletionModel::updateCompletionList(const QString& symbol, bool is_
 void RKCodeCompletionModel::addRCompletions() {
 	RK_TRACE (COMMANDEDITOR);
 
+	bool was_empty = (n_completions == 0);
 	QStringList addlist = rcompletions->results();
 	if (addlist.isEmpty()) return;
 	beginInsertRows(index(0, 0), n_completions, n_completions + addlist.size());
@@ -570,6 +601,7 @@ void RKCodeCompletionModel::addRCompletions() {
 		icons.append(RKStandardIcons::getIcon(RKStandardIcons::WindowConsole));
 	}
 	endInsertRows();
+	if (was_empty) manager->emptyModelGainedLateData(this);
 }
 
 KTextEditor::Range RKCodeCompletionModel::completionRange (KTextEditor::View *, const KTextEditor::Cursor&) {
@@ -604,7 +636,7 @@ QVariant RKCodeCompletionModel::data (const QModelIndex& index, int role) const
 	return QVariant ();
 }
 
-QString findCommonCompletion (const QStringList &list, const QString &lead, bool *exact_match) {
+bool findCommonCompletion (QString *comp, const QStringList &list, const QString &lead, bool *exact_match) {
 	RK_TRACE (COMMANDEDITOR);
 	RK_DEBUG(COMMANDEDITOR, DL_DEBUG, "Looking for common completion among set of %d, starting with %s", list.size (), qPrintable (lead));
 
@@ -628,7 +660,7 @@ QString findCommonCompletion (const QStringList &list, const QString &lead, bool
 			for (int c = 0; c < ret.length(); ++c) {
 				if (ret[c] != candidate[c]) {
 					*exact_match = false;
-					if (!c) return QString ();
+					if (!c) return true; // it was still a match, even if we cannot complete anything
 
 					ret = ret.left (c);
 					break;
@@ -637,17 +669,18 @@ QString findCommonCompletion (const QStringList &list, const QString &lead, bool
 		}
 	}
 
-	return ret;
+	*comp = ret;
+	return !first;  // at least one matching candidate was found
 }
 
-QString RKCodeCompletionModel::partialCompletion (bool* exact_match) {
+bool RKCodeCompletionModel::partialCompletion(QString *comp, bool* exact_match) {
 	RK_TRACE (COMMANDEDITOR);
 
 	// Here, we need to do completion on the *last* portion of the object-path, only, so we will be able to complete "obj" to "object", even if "object" is present as
 	// both packageA::object and packageB::object.
 	// Thus as a first step, we look up the short names. We do this, lazily, as this function is only called on demand.
 	QStringList objectpath = RObject::parseObjectPath (current_symbol);
-	if (objectpath.isEmpty () || objectpath[0].isEmpty ()) return (QString ());
+	if (objectpath.isEmpty () || objectpath[0].isEmpty()) return (false);
 	RObject::ObjectList matches = RObjectList::getObjectList ()->findObjectsMatching (current_symbol);
 
 	QStringList shortnames;
@@ -657,7 +690,7 @@ QString RKCodeCompletionModel::partialCompletion (bool* exact_match) {
 	QString lead = objectpath.last ();
 	if (!shortnames.value (0).startsWith (lead)) lead.clear ();  // This could happen if the current path ends with '$', for instance
 
-	return (findCommonCompletion (shortnames, lead, exact_match));
+	return findCommonCompletion(comp, shortnames, lead, exact_match);
 }
 
 
@@ -859,10 +892,10 @@ KTextEditor::Range RKArgumentHintModel::completionRange (KTextEditor::View*, con
 	return manager->currentArgnameRange ();
 }
 
-QString RKArgumentHintModel::partialCompletion (bool* exact) {
+bool RKArgumentHintModel::partialCompletion(QString *comp, bool* exact) {
 	RK_TRACE (COMMANDEDITOR);
 
-	return (findCommonCompletion (args, fragment, exact));
+	return findCommonCompletion(comp, args, fragment, exact);
 }
 
 //////////////////////// RKFileCompletionModel ////////////////////
@@ -966,10 +999,10 @@ QVariant RKFileCompletionModel::data (const QModelIndex& index, int role) const
 	return QVariant ();
 }
 
-QString RKFileCompletionModel::partialCompletion (bool* exact) {
+bool RKFileCompletionModel::partialCompletion(QString *comp, bool* exact) {
 	RK_TRACE (COMMANDEDITOR);
 
-	return (findCommonCompletion (names, current_fragment, exact));
+	return findCommonCompletion(comp, names, current_fragment, exact);
 }
 
 
diff --git a/rkward/windows/rkcodecompletion.h b/rkward/windows/rkcodecompletion.h
index 3b62f837..7b7c0b9e 100644
--- a/rkward/windows/rkcodecompletion.h
+++ b/rkward/windows/rkcodecompletion.h
@@ -18,6 +18,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <ktexteditor/codecompletionmodelcontrollerinterface.h>
 
 class QEvent;
+class RKCompletionModelBase;
 class RKCodeCompletionSettings;
 class RKCodeCompletionModel;
 class RKFileCompletionModel;
@@ -37,6 +38,7 @@ public:
 	KTextEditor::Range currentCallRange () const;
 	KTextEditor::View* view () const { return (_view); };
 	void setLinePrefixes(const QString &_prefix, const QString &_continuation_prefix) { prefix = _prefix; continuation_prefix = _continuation_prefix; };
+	void emptyModelGainedLateData(RKCompletionModelBase *model);
 public slots:
 	void userTriggeredCompletion ();
 private slots:
@@ -48,10 +50,11 @@ private slots:
 /** show a code completion box if appropriate. Use tryCompletionProxy () instead, which will call this function after a timeout */
 	void tryCompletion ();
 private:
+	void startModel(KTextEditor::CodeCompletionModel* model, bool start, const KTextEditor::Range &range);
+	void updateVisibility();
 	bool eventFilter (QObject *watched, QEvent *event) override;
 /** 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 ();
-	void updateVisibility ();
 	void updateCallHint ();
 	KTextEditor::CodeCompletionInterface *cc_iface;
 	RKCodeCompletionModel *completion_model;
@@ -75,7 +78,8 @@ private:
 	QString prefix;
 	QString continuation_prefix;
 
-	QList<KTextEditor::CodeCompletionModel*> active_models;
+	QList<KTextEditor::CodeCompletionModel*> started_models;
+	QHash<KTextEditor::CodeCompletionModel*, QString> model_filters;
 };
 
 /** Base class for the completion models employed in script editor. Essentially it takes care of the bureaucratic overhead involved in providing a group header */
@@ -116,7 +120,7 @@ public:
 
 	void updateCompletionList(const QString& symbol, bool is_help);
 	QVariant data (const QModelIndex& index, int role=Qt::DisplayRole) const override;
-	QString partialCompletion (bool* exact_match);
+	bool partialCompletion(QString *comp, bool* exact_match);
 private:
 	QList<QIcon> icons;
 	QStringList names;
@@ -153,7 +157,7 @@ public:
 
 	QVariant data (const QModelIndex& index, int role=Qt::DisplayRole) const override;
 	KTextEditor::Range completionRange (KTextEditor::View *view, const KTextEditor::Cursor &position) override;
-	QString partialCompletion (bool *exact);
+	bool partialCompletion(QString *comp, bool *exact);
 private:
 	RObject *function;
 	QStringList args;
@@ -186,7 +190,7 @@ public:
 	KTextEditor::Range completionRange (KTextEditor::View *view, const KTextEditor::Cursor &position) override;
 	void updateCompletionList (const QString& fragment);
 	QVariant data (const QModelIndex& index, int role=Qt::DisplayRole) const override;
-	QString partialCompletion (bool *exact);
+	bool partialCompletion(QString *comp, bool *exact);
 private slots:
 	void completionsReady (const QString &string, const QStringList &exes, const QStringList &files);
 private:



More information about the rkward-tracker mailing list