[rkward] rkward/windows: Split code completion code into a separate file

Thomas Friedrichsmeier null at kde.org
Thu Feb 21 09:50:19 GMT 2019


Git commit e42e107a3ab801b1bc500a7621183ed1631bdf7c by Thomas Friedrichsmeier.
Committed on 21/02/2019 at 09:49.
Pushed by tfry into branch 'master'.

Split code completion code into a separate file

M  +1    -0    rkward/windows/CMakeLists.txt
A  +776  -0    rkward/windows/rkcodecompletion.cpp     [License: GPL (v2+)]
A  +189  -0    rkward/windows/rkcodecompletion.h     [License: GPL (v2+)]
M  +1    -740  rkward/windows/rkcommandeditorwindow.cpp
M  +1    -157  rkward/windows/rkcommandeditorwindow.h

https://commits.kde.org/rkward/e42e107a3ab801b1bc500a7621183ed1631bdf7c

diff --git a/rkward/windows/CMakeLists.txt b/rkward/windows/CMakeLists.txt
index 75030df7..830a17b5 100644
--- a/rkward/windows/CMakeLists.txt
+++ b/rkward/windows/CMakeLists.txt
@@ -21,6 +21,7 @@ SET(windows_STAT_SRCS
 	rktoolwindowlist.cpp
 	robjectbrowser.cpp
 	rkdebugmessagewindow.cpp
+	rkcodecompletion.cpp
 )
 
 ADD_LIBRARY(windows STATIC ${windows_STAT_SRCS})
diff --git a/rkward/windows/rkcodecompletion.cpp b/rkward/windows/rkcodecompletion.cpp
new file mode 100644
index 00000000..e9329bb4
--- /dev/null
+++ b/rkward/windows/rkcodecompletion.cpp
@@ -0,0 +1,776 @@
+/***************************************************************************
+                          rkcodecompletion  -  description
+                             -------------------
+    begin                : Thu Feb 21 2019
+    copyright            : (C) 2004-2019 by Thomas Friedrichsmeier
+    email                : thomas.friedrichsmeier at kdemail.net
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+#include "rkcodecompletion.h"
+
+#include <ktexteditor/document.h>
+#include <ktexteditor/editor.h>
+#include <KLocalizedString>
+#include <KActionCollection>
+
+#include <QApplication>
+#include <QKeyEvent>
+#include <QIcon>
+#include <QDir>
+
+#include "../misc/rkcommonfunctions.h"
+#include "../misc/rkstandardicons.h"
+#include "../core/rfunctionobject.h"
+#include "../core/robjectlist.h"
+#include "../settings/rksettingsmodulecommandeditor.h"
+
+#include "../debug.h"
+
+
+//////////////////////// RKCompletionManager //////////////////////
+
+RKCompletionManager::RKCompletionManager (KTextEditor::View* view) : QObject (view) {
+	RK_TRACE (COMMANDEDITOR);
+
+	_view = view;
+	active = false;
+	update_call = true;
+	cached_position = KTextEditor::Cursor (-1, -1);
+
+	cc_iface = qobject_cast<KTextEditor::CodeCompletionInterface*> (view);
+	if (cc_iface) {
+		cc_iface->setAutomaticInvocationEnabled (false);
+		completion_model = new RKCodeCompletionModel (this);
+		file_completion_model = new RKFileCompletionModel (this);
+		callhint_model = new RKCallHintModel (this);
+		arghint_model = new RKArgumentHintModel (this);
+		cc_iface->registerCompletionModel (callhint_model);
+		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);
+		const QObjectList children = _view->children ();
+		for (QObjectList::const_iterator it = children.constBegin(); it != children.constEnd (); ++it) {
+			(*it)->installEventFilter (this);  // to handle Tab-key; installing on the view, alone, is not enough.
+		}
+
+		// 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 (active) {
+			// Handle this in the next event cycle, as more than one event may trigger
+			completion_timer->start (0);
+			// TODO: Actually, in this case we should try to _update_ the existing completion, i.e. try to save some CPU cycles.
+			tryCompletion ();
+		}
+		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) {
+		// NOTE: This should not be possible, because the connections  have not been set up in the constructor, in this case.
+		RK_ASSERT (cc_iface);
+		return;
+	}
+
+	KTextEditor::Document *doc = _view->document ();
+	KTextEditor::Cursor c = _view->cursorPosition();
+	cached_position = c;
+	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-1, false, &start, &end);
+	if (end > cursor_pos) {
+		symbol_range = KTextEditor::Range (-1, -1, -1, -1);   // Only hint when at the end of a word/symbol: https://mail.kde.org/pipermail/rkward-devel/2015-April/004122.html
+	} else 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 ()) {
+		QString filename;
+		// as a very simple heuristic: If the current symbol starts with a quote, we should probably attempt file name completion, instead of symbol name completion
+		if (word.startsWith ('\"') || word.startsWith ('\'') || word.startsWith ('`')) {
+			symbol_range.setStart (KTextEditor::Cursor (symbol_range.start ().line (), symbol_range.start ().column () + 1));   // confine range to inside the quotes.
+			filename = word.mid (1);
+			word.clear ();
+		}
+
+		completion_model->updateCompletionList (word);
+		file_completion_model->updateCompletionList (filename);
+	} else {
+		completion_model->updateCompletionList (QString ());
+		file_completion_model->updateCompletionList (QString ());
+	}
+
+	updateCallHint ();
+
+	// update ArgHint.
+	argname_range = KTextEditor::Range (-1, -1, -1, -1);
+	// Named arguments are just like regular symbols, *but* we must require that they are preceeded by either a ',', or the opening '(', immediately.
+	// Otherwise, they are an argument value expression, for sure.
+	if (callhint_model->currentFunction ()) {
+		for (int i = symbol_range.start ().column () - 1; i >= 0; --i) {
+			QChar c = current_line.at (i);
+			if (c == ',' || c == '(') {
+				argname_range = symbol_range;
+				break;
+			} else if (!c.isSpace ()) {
+				break;
+			}
+		}
+	}
+	arghint_model->updateCompletionList (callhint_model->currentFunction (), argname_range.isValid () ? doc->text (argname_range) : QString ());
+
+	updateVisibility ();
+}
+
+void RKCompletionManager::updateCallHint () {
+	RK_TRACE (COMMANDEDITOR);
+
+	if (!update_call) return;
+	update_call = false;
+
+	int line = cached_position.line () + 1;
+	QString full_context;
+	int potential_symbol_end = -2;
+	KTextEditor::Document *doc = _view->document ();
+	while (potential_symbol_end < -1 && line >= 0) {
+		--line;
+		QString context_line = doc->line (line);
+		if (context_line.startsWith ('>')) continue; // For console: TODO limit to console
+		full_context.prepend (context_line);
+
+		int pos = context_line.length () - 1;
+		if (line == cached_position.line ()) pos = cached_position.column () - 1;
+		for (int i = pos; i >= 0; --i) {
+			QChar c = context_line.at (i);
+			if (c == '(') {
+				KTextEditor::DefaultStyle style = doc->defaultStyleAt (KTextEditor::Cursor (line, i));
+				if (style != KTextEditor::dsComment && style != KTextEditor::dsString && style != KTextEditor::dsChar) {
+					potential_symbol_end = i - 1;
+					break;
+				}
+			}
+		}
+	}
+
+	// now find out where the symbol to the left of the opening brace ends
+	// there cannot be a line-break between the opening brace, and the symbol name (or can there?), so no need to fetch further context
+	while ((potential_symbol_end >= 0) && full_context.at (potential_symbol_end).isSpace ()) {
+		--potential_symbol_end;
+	}
+
+	// now identify the symbol and object (if any)
+	RObject *object = 0;
+	call_opening = KTextEditor::Cursor (-1, -1);
+	if (potential_symbol_end > 0) {
+		QString effective_symbol = RKCommonFunctions::getCurrentSymbol (full_context, potential_symbol_end);
+		if (!effective_symbol.isEmpty ()) {
+			object = RObjectList::getObjectList ()->findObject (effective_symbol);
+			call_opening = KTextEditor::Cursor (line, potential_symbol_end+1);
+		}
+	}
+
+	callhint_model->setFunction (object);
+}
+
+void startModel (KTextEditor::CodeCompletionInterface* iface, KTextEditor::CodeCompletionModel *model, bool start, const KTextEditor::Range &range, QList<KTextEditor::CodeCompletionModel*> *active_models) {
+	if (start && model->rowCount () > 0) {
+		if (!active_models->contains (model)) {
+			iface->startCompletion (range, model);
+			active_models->append (model);
+		}
+	} else {
+		active_models->removeAll (model);
+	}
+}
+
+void RKCompletionManager::updateVisibility () {
+	RK_TRACE (COMMANDEDITOR);
+
+	if (!cc_iface->isCompletionActive ()) {
+		active_models.clear ();
+	}
+
+	bool min_len = (currentCompletionWord ().length () >= RKSettingsModuleCommandEditor::completionMinChars ());
+	startModel (cc_iface, completion_model, min_len, symbol_range, &active_models);
+	startModel (cc_iface, file_completion_model, min_len, symbol_range, &active_models);
+	if (kate_keyword_completion_model) {
+		// 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);
+	}
+// 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, true, currentCallRange (), &active_models);
+	startModel (cc_iface, arghint_model, true, argname_range, &active_models);
+
+	if (!active_models.isEmpty ()) {
+		active = true;
+	} else {
+		cc_iface->abortCompletion ();
+		active = false;
+	}
+
+	if (!active) update_call = true;
+}
+
+void RKCompletionManager::textInserted (KTextEditor::Document*, const KTextEditor::Cursor& position, const QString& text) {
+	if (active) {
+		if (position < call_opening) update_call = true;
+		else if (text.contains (QChar ('(')) || text.contains (QChar (')'))) update_call = true;
+	}
+	tryCompletionProxy();
+}
+
+void RKCompletionManager::textRemoved (KTextEditor::Document*, const KTextEditor::Range& range, const QString& text) {
+	if (active) {
+		if (range.start () < call_opening) update_call = true;
+		else if (text.contains (QChar ('(')) || text.contains (QChar (')'))) update_call = true;
+	}
+	tryCompletionProxy();
+}
+
+void RKCompletionManager::lineWrapped (KTextEditor::Document* , const KTextEditor::Cursor& ) {
+// should already have been handled by textInserted()
+	tryCompletionProxy ();
+}
+
+void RKCompletionManager::lineUnwrapped (KTextEditor::Document* , int ) {
+// should already have been handled by textRemoved()
+	tryCompletionProxy ();
+}
+
+void RKCompletionManager::cursorPositionChanged (KTextEditor::View* view, const KTextEditor::Cursor& newPosition) {
+	if (active) {
+		if (newPosition < call_opening) update_call = true;
+		else {
+			QString text = view->document ()->text (KTextEditor::Range (newPosition, cached_position));
+			if (text.contains (QChar ('(')) || text.contains (QChar (')'))) update_call = true;
+		}
+	}
+	tryCompletionProxy ();
+}
+
+KTextEditor::Range RKCompletionManager::currentCallRange () const {
+	return KTextEditor::Range (call_opening, _view->cursorPosition ());
+}
+
+bool RKCompletionManager::eventFilter (QObject* watched, QEvent* event) {
+	if (event->type () == QEvent::KeyPress || event->type () == QEvent::ShortcutOverride) {
+		RK_TRACE (COMMANDEDITOR);	// avoid loads of empty traces, putting this here
+		QKeyEvent *k = static_cast<QKeyEvent *> (event);
+
+		if (k->key () == Qt::Key_Tab && (!k->modifiers ())) {
+			// TODO: It is not quite clear, what behavior is desirable, in case more than one completion model is active at a time.
+			//       For now, we use the simplest solution (implemenation-wise), and complete from the topmost-model, only
+			// TODO: Handle the ktexteditor builtin models, too.
+			bool exact = false;
+			QString comp;
+			bool handled = false;
+			if (active_models.contains (arghint_model)) {
+				comp = arghint_model->partialCompletion (&exact);
+				handled = true;
+			} else if (active_models.contains (completion_model)) {
+				comp = completion_model->partialCompletion (&exact);
+				handled = true;
+			} else if (active_models.contains (file_completion_model)) {
+				comp = file_completion_model->partialCompletion (&exact);
+				handled = true;
+			}
+
+			if (handled) {
+				RK_DEBUG(COMMANDEDITOR, DL_DEBUG, "Tab completion: %s", qPrintable (comp));
+				view ()->document ()->insertText (view ()->cursorPosition (), comp);
+				if (exact) cc_iface->abortCompletion ();
+				else if (comp.isEmpty ()) {
+					QApplication::beep (); // TODO: unfortunately, we catch *two* tab events, so this is not good, yet
+				}
+				return true;
+			}
+		} else if ((k->key () == Qt::Key_Up || k->key () == Qt::Key_Down) && cc_iface->isCompletionActive ()) {
+			// Make up / down-keys (without alt) navigate in the document (aborting the completion)
+			// Meke alt+up / alt+down naviate in the completion list
+			if (k->modifiers () & Qt::AltModifier) {
+				if (k->type() != QKeyEvent::KeyPress) return true;  // eat the release event
+
+				// No, we cannot just send a fake key event, easily...
+				KActionCollection *kate_edit_actions = view ()->findChild<KActionCollection*> ("edit_actions");
+				QAction *action = kate_edit_actions ? (kate_edit_actions->action (k->key () == Qt::Key_Up ? "move_line_up" : "move_line_down")) : 0;
+				if (!action) {
+					kate_edit_actions = view ()->actionCollection ();
+					action = kate_edit_actions ? (kate_edit_actions->action (k->key () == Qt::Key_Up ? "move_line_up" : "move_line_down")) : 0;
+				}
+				if (action) action->trigger ();
+				else RK_ASSERT (action);
+				return true;
+			} else {
+				cc_iface->abortCompletion ();
+				active = false;
+				return false;
+			}
+		}
+	}
+
+	return false;
+}
+
+//////////////////////// 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 (RKCompletionManager *manager) : RKCompletionModelBase (manager) {
+	RK_TRACE (COMMANDEDITOR);
+
+	setHasGroups (true);
+}
+
+RKCodeCompletionModel::~RKCodeCompletionModel () {
+	RK_TRACE (COMMANDEDITOR);
+}
+
+void RKCodeCompletionModel::updateCompletionList (const QString& symbol) {
+	RK_TRACE (COMMANDEDITOR);
+
+	if (current_symbol == symbol) return;	// already up to date
+	beginResetModel ();
+
+	RObject::ObjectList matches;
+	QStringList objectpath = RObject::parseObjectPath (symbol);
+	if (!objectpath.isEmpty () && !objectpath[0].isEmpty ()) {  // Skip completion, if the current symbol is '""' (or another empty quote), for instance
+		matches = RObjectList::getObjectList ()->findObjectsMatching (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.
+	n_completions = matches.size ();
+	icons.clear ();
+	icons.reserve (n_completions);
+	names = RObject::getFullNames (matches, RKSettingsModuleCommandEditor::completionOptions());
+	for (int i = 0; i < n_completions; ++i) {
+		icons.append (RKStandardIcons::iconForObject (matches[i]));
+	}
+
+	current_symbol = symbol;
+
+	endResetModel ();
+}
+
+KTextEditor::Range RKCodeCompletionModel::completionRange (KTextEditor::View *, const KTextEditor::Cursor&c) {
+	return manager->currentSymbolRange ();
+}
+
+QVariant RKCodeCompletionModel::data (const QModelIndex& index, int role) const {
+	if (isHeaderItem (index)) {
+		if (role == Qt::DisplayRole) return i18n ("Objects on search path");
+		if (role == KTextEditor::CodeCompletionModel::GroupRole) return Qt::DisplayRole;
+		if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) return 1;  // sort below arghint model
+		return QVariant ();
+	}
+
+	int col = index.column ();
+	int row = index.row ();
+
+	if ((role == Qt::DisplayRole) || (role == KTextEditor::CodeCompletionModel::CompletionRole)) {
+		if (col == KTextEditor::CodeCompletionModel::Name) {
+			return (names.value (row));
+		}
+	} else if (role == Qt::DecorationRole) {
+		if (col == KTextEditor::CodeCompletionModel::Icon) {
+			return (icons.value (row));
+		}
+	} else if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) {
+		return (row);  // disable sorting
+	} else if (role == KTextEditor::CodeCompletionModel::MatchQuality) {
+		return (10);
+	}
+
+	return QVariant ();
+}
+
+QString findCommonCompletion (const QStringList &list, const QString &lead, bool *exact_match) {
+	RK_TRACE (COMMANDEDITOR);
+	RK_DEBUG(COMMANDEDITOR, DL_DEBUG, "Looking for commong completion among set of %d, starting with %s", list.size (), qPrintable (lead));
+
+	*exact_match = true;
+	QString ret;
+	bool first = true;
+	int lead_size = lead.count ();
+	for (int i = list.size () - 1; i >= 0; --i) {
+		if (!list[i].startsWith (lead)) continue;
+
+		QString candidate = list[i].mid (lead_size);
+		if (first) {
+			ret = candidate;
+			first = false;
+		} else {
+			if (ret.length () > candidate.length ()) {
+				ret = ret.left (candidate.length ());
+				*exact_match = false;
+			}
+
+			for (int c = 0; c < ret.length(); ++c) {
+				if (ret[c] != candidate[c]) {
+					*exact_match = false;
+					if (!c) return QString ();
+
+					ret = ret.left (c);
+					break;
+				}
+			}
+		}
+	}
+
+	return ret;
+}
+
+QString RKCodeCompletionModel::partialCompletion (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 ());
+	RObject::ObjectList matches = RObjectList::getObjectList ()->findObjectsMatching (current_symbol);
+
+	QStringList shortnames;
+	for (int i = 0; i < matches.count (); ++i) {
+		shortnames.append (matches[i]->getShortName ());
+	}
+	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));
+}
+
+//////////////////////// RKCallHintModel //////////////////////////
+RKCallHintModel::RKCallHintModel (RKCompletionManager* manager) : RKCompletionModelBase (manager) {
+	RK_TRACE (COMMANDEDITOR);
+	function = 0;
+}
+
+#include <QDesktopWidget>
+// TODO: There could be more than one function by a certain name, and we could support this!
+void RKCallHintModel::setFunction(RObject* _function) {
+	RK_TRACE (COMMANDEDITOR);
+
+	if (function == _function) return;
+	function = _function;
+
+	beginResetModel ();
+	if (function && function->isType (RObject::Function)) {
+		// initialize hint
+		RFunctionObject *fo = static_cast<RFunctionObject*> (function);
+		QStringList args = fo->argumentNames ();
+		QStringList defs = fo->argumentDefaults ();
+
+		name = function->getFullName ();
+
+		formals = '(';
+		formatting.clear ();
+		KTextEditor::Attribute format;
+//		format.setFontBold (); // NOTE: Not good. makes size mis-calculation worse.
+		format.setForeground (QBrush (Qt::green));  // But turns out purple?!
+
+		// NOTE: Unfortunately, adding new-lines within (long) formals does not work. If this issue turns out to be relevant, we'll have to resort to breaking the formals into
+		// several (dummy) items.
+		int pos = 1;
+		for (int i = 0; i < args.size (); ++i) {
+			QString pair = args[i];
+			if (!defs.value(i).isEmpty ()) pair.append ('=' + defs[i]);
+			formatting.append ({ pos + args[i].length (), pair.length ()-args[i].length (), format });
+
+			if (i < (args.size () - 1)) pair.append (", ");
+			formals.append (pair);
+
+			pos = pos + pair.length ();
+		}
+		formals.append (')');
+		n_completions = 1;
+	} else {
+		n_completions = 0;
+	}
+	endResetModel ();
+}
+
+QVariant RKCallHintModel::data (const QModelIndex& index, int role) const {
+	if (isHeaderItem (index)) {
+		if (role == Qt::DisplayRole) return i18n ("Function calls");  // NOTE: Header is not currently used as of KF5 5.44.0
+		if (role == KTextEditor::CodeCompletionModel::GroupRole) return Qt::DisplayRole;
+		return QVariant ();
+	}
+
+	int col = index.column ();
+	if (role == Qt::DisplayRole) {
+		if (col == KTextEditor::CodeCompletionModel::Prefix) return (name);
+		if (col == KTextEditor::CodeCompletionModel::Arguments) return (formals);
+		if (col == KTextEditor::CodeCompletionModel::Postfix) return ("        "); // Size is of a bit for KF5 5.44.0. Provide some padding to work around cut-off parts.
+	} else if (role == KTextEditor::CodeCompletionModel::ArgumentHintDepth) {
+		return 1;
+	} else if (role == KTextEditor::CodeCompletionModel::CompletionRole) {
+		return KTextEditor::CodeCompletionModel::Function;
+	} else if (role == KTextEditor::CodeCompletionModel::HighlightingMethod) {
+		if (col == KTextEditor::CodeCompletionModel::Arguments) return KTextEditor::CodeCompletionModel::CustomHighlighting;
+	} else if (role == KTextEditor::CodeCompletionModel::CustomHighlight) {
+		if (col == KTextEditor::CodeCompletionModel::Arguments)  return formatting;
+	} else if (role == KTextEditor::CodeCompletionModel::MatchQuality) {
+		return (10);
+	}
+
+	return QVariant ();
+}
+
+KTextEditor::Range RKCallHintModel::completionRange (KTextEditor::View *, const KTextEditor::Cursor&) {
+	return manager->currentCallRange ();
+}
+
+//////////////////////// RKArgumentHintModel //////////////////////
+RKArgumentHintModel::RKArgumentHintModel (RKCompletionManager* manager) : RKCompletionModelBase (manager) {
+	RK_TRACE (COMMANDEDITOR);
+
+	function = 0;
+}
+
+void RKArgumentHintModel::updateCompletionList (RObject* _function, const QString &argument) {
+	RK_TRACE (COMMANDEDITOR);
+
+	bool changed = false;
+	if (function != _function) {
+		beginResetModel ();
+		function = _function;
+		if (function && function->isType (RObject::Function)) {
+			// initialize hint
+			RFunctionObject *fo = static_cast<RFunctionObject*> (function);
+			args = fo->argumentNames ();
+			defs = fo->argumentDefaults ();
+		} else {
+			args.clear ();
+			defs.clear ();
+		}
+	}
+
+	if (changed || (argument != fragment)) {
+		if (!changed) {
+			changed = true;
+			beginResetModel ();
+		}
+		fragment = argument;
+		matches.clear ();
+		for (int i = 0; i < args.size (); ++i) {
+			if (args[i].startsWith (fragment)) matches.append (i);
+		}
+	}
+
+	if (changed) {
+		n_completions = matches.size ();
+RK_DEBUG(COMMANDEDITOR, DL_ERROR, "%d matches", n_completions);
+		endResetModel ();
+	}
+}
+
+QVariant RKArgumentHintModel::data (const QModelIndex& index, int role) const {
+	if (isHeaderItem (index)) {
+		if (role == Qt::DisplayRole) return i18n ("Function arguments");
+		if (role == KTextEditor::CodeCompletionModel::GroupRole) return Qt::DisplayRole;
+		if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) return 0; // Sort above other models (except calltip)
+		return QVariant ();
+	}
+
+	int col = index.column ();
+	int row = index.row ();
+	if (role == Qt::DisplayRole) {
+		if (col == KTextEditor::CodeCompletionModel::Name) return (args.value (matches.value (row)));
+		if (col == KTextEditor::CodeCompletionModel::Postfix) {
+			QString def = defs.value (matches.value (row));
+			if (!def.isEmpty ()) return (QString ('=' + def));
+		}
+	} else if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) {
+		return row;  // disable sorting
+	} else if (role == KTextEditor::CodeCompletionModel::CompletionRole) {
+		return KTextEditor::CodeCompletionModel::Function;
+	} else if (role == KTextEditor::CodeCompletionModel::MatchQuality) {
+		return (20);
+	}
+
+	return QVariant ();
+}
+
+KTextEditor::Range RKArgumentHintModel::completionRange (KTextEditor::View*, const KTextEditor::Cursor&) {
+	return manager->currentArgnameRange ();
+}
+
+QString RKArgumentHintModel::partialCompletion (bool* exact) {
+	RK_TRACE (COMMANDEDITOR);
+
+	return (findCommonCompletion (args, fragment, exact));
+}
+
+//////////////////////// RKFileCompletionModel ////////////////////
+#include <KUrlCompletion>
+RKFileCompletionModelWorker::RKFileCompletionModelWorker (const QString &_string) : QThread () {
+	RK_TRACE (COMMANDEDITOR);
+	string = _string;
+	connect (this, &QThread::finished, this, &QObject::deleteLater);
+}
+
+void RKFileCompletionModelWorker::run () {
+	RK_TRACE (COMMANDEDITOR);
+
+	KUrlCompletion comp;
+	comp.setDir (QUrl::fromLocalFile (QDir::currentPath ()));
+	comp.makeCompletion (string);
+	QStringList files = comp.allMatches ();
+
+	QStringList exes;
+	if (!QDir (string).isAbsolute ()) {  // NOTE: KUrlCompletion does not handle this well, returns, e.g. "/home/me/firefox" when given "/home/me/f"; KF5 5.44.0
+		comp.setMode (KUrlCompletion::ExeCompletion);
+		comp.makeCompletion (string);
+		exes = comp.allMatches ();
+	}
+
+	emit (completionsReady (string, exes, files));
+}
+
+RKFileCompletionModel::RKFileCompletionModel (RKCompletionManager* manager) : RKCompletionModelBase (manager) {
+	RK_TRACE (COMMANDEDITOR);
+	worker = 0;
+}
+
+RKFileCompletionModel::~RKFileCompletionModel () {
+	RK_TRACE (COMMANDEDITOR);
+}
+
+void RKFileCompletionModel::updateCompletionList(const QString& fragment) {
+	RK_TRACE (COMMANDEDITOR);
+
+	if (current_fragment == fragment) return;
+
+	current_fragment = fragment;
+	launchThread ();
+}
+
+void RKFileCompletionModel::launchThread () {
+	RK_TRACE (COMMANDEDITOR);
+
+	if (current_fragment.isEmpty ()) {
+		completionsReady (QString (), QStringList (), QStringList ());
+	} else if (!worker) {
+		RK_DEBUG (COMMANDEDITOR, DL_DEBUG, "Launching filename completion thread for '%s'", qPrintable (current_fragment));
+		worker = new RKFileCompletionModelWorker (current_fragment);
+		connect (worker, &RKFileCompletionModelWorker::completionsReady, this, &RKFileCompletionModel::completionsReady);
+		worker->start ();
+	}
+}
+
+void RKFileCompletionModel::completionsReady (const QString& string, const QStringList& exes, const QStringList& files) {
+	RK_TRACE (COMMANDEDITOR);
+
+	RK_DEBUG (COMMANDEDITOR, DL_DEBUG, "Filename completion finished for '%s': %d matches", qPrintable (string), files.size () + exes.size ());
+	worker = 0;
+	if (current_fragment == string) {
+		beginResetModel ();
+		names = files + exes; // TODO: This could be prettier
+		n_completions = names.size ();
+		endResetModel ();
+	} else {
+		launchThread ();
+	}
+}
+
+QVariant RKFileCompletionModel::data (const QModelIndex& index, int role) const {
+	if (isHeaderItem (index)) {
+		if (role == Qt::DisplayRole) return i18n ("Local file names");
+		if (role == KTextEditor::CodeCompletionModel::GroupRole) return Qt::DisplayRole;
+		if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) return 1; // Sort below arghint model
+		return QVariant ();
+	}
+
+	int col = index.column ();
+	int row = index.row ();
+	if ((role == Qt::DisplayRole) || (role == KTextEditor::CodeCompletionModel::CompletionRole)) {
+		if (col == KTextEditor::CodeCompletionModel::Name) {
+			return (names.value (row));
+		}
+	} else if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) {
+		return (row);  // disable sorting
+	} else if (role == KTextEditor::CodeCompletionModel::MatchQuality) {
+		return (10);
+	}
+	return QVariant ();
+}
+
+QString RKFileCompletionModel::partialCompletion (bool* exact) {
+	RK_TRACE (COMMANDEDITOR);
+
+	return (findCommonCompletion (names, current_fragment, exact));
+}
diff --git a/rkward/windows/rkcodecompletion.h b/rkward/windows/rkcodecompletion.h
new file mode 100644
index 00000000..fd778ad1
--- /dev/null
+++ b/rkward/windows/rkcodecompletion.h
@@ -0,0 +1,189 @@
+/***************************************************************************
+                          rkcodecompletion  -  description
+                             -------------------
+    begin                : Thu Feb 21 2019
+    copyright            : (C) 2004-2019 by Thomas Friedrichsmeier
+    email                : thomas.friedrichsmeier at kdemail.net
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+#ifndef RKCODECOMPLETION_H
+#define RKCODECOMPLETION_H
+
+#include <QWidget>
+#include <QTimer>
+#include <QString>
+
+#include <ktexteditor/view.h>
+
+#include <ktexteditor/codecompletionmodel.h>
+#include <ktexteditor/codecompletioninterface.h>
+#include <ktexteditor/codecompletionmodelcontrollerinterface.h>
+
+class QEvent;
+class RKCodeCompletionModel;
+class RKFileCompletionModel;
+class RKCallHintModel;
+class RKArgumentHintModel;
+class RKCompletionManager : public QObject {
+	Q_OBJECT
+public:
+	RKCompletionManager (KTextEditor::View *view);
+	~RKCompletionManager ();
+
+	QString currentCompletionWord () const;
+	KTextEditor::Range currentSymbolRange () const { return symbol_range; };
+	KTextEditor::Range currentArgnameRange () const { return argname_range; };
+	KTextEditor::Range currentCallRange () const;
+	KTextEditor::View* view () const { return (_view); };
+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:
+	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;
+	RKFileCompletionModel *file_completion_model;
+	RKCallHintModel *callhint_model;
+	RKArgumentHintModel *arghint_model;
+	KTextEditor::CodeCompletionModel* kate_keyword_completion_model;
+	QTimer *completion_timer;
+
+	KTextEditor::View *_view;
+	KTextEditor::Cursor cached_position;
+
+	KTextEditor::Range symbol_range;
+	KTextEditor::Cursor call_opening;
+	KTextEditor::Range argname_range;
+
+	bool update_call;
+	bool active;
+
+	QList<KTextEditor::CodeCompletionModel*> active_models;
+};
+
+/** 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 ();
+
+	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; };
+
+	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 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);
+	QVariant data (const QModelIndex& index, int role=Qt::DisplayRole) const override;
+	QString partialCompletion (bool* exact_match);
+private:
+	QList<QIcon> icons;
+	QStringList names;
+	QString current_symbol;
+};
+
+class RObject;
+class RKCallHintModel : public RKCompletionModelBase {
+	Q_OBJECT
+public:
+	explicit RKCallHintModel (RKCompletionManager *manager);
+	void setFunction (RObject *function);
+
+	QVariant data (const QModelIndex& index, int role=Qt::DisplayRole) const override;
+	KTextEditor::Range completionRange (KTextEditor::View *view, const KTextEditor::Cursor &position) override;
+	RObject *currentFunction () const { return function; };
+private:
+	RObject *function;
+	QString name;
+	QString formals;
+	QVariantList formatting;
+};
+
+class RKArgumentHintModel : public RKCompletionModelBase {
+	Q_OBJECT
+public:
+	explicit RKArgumentHintModel (RKCompletionManager *manager);
+	void updateCompletionList (RObject *function, const QString& argument);
+
+	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);
+private:
+	RObject *function;
+	QStringList args;
+	QStringList defs;
+	QString fragment;
+	QList<int> matches;
+};
+
+#include <QThread>
+class RKFileCompletionModelWorker : public QThread {
+	Q_OBJECT
+public:
+	explicit RKFileCompletionModelWorker (const QString &string);
+signals:
+	void completionsReady (const QString &string, const QStringList &exes, const QStringList &files);
+private:
+	void run () override;
+	QString string;
+};
+
+class RKFileCompletionModel : public RKCompletionModelBase {
+	Q_OBJECT
+public:
+	explicit RKFileCompletionModel (RKCompletionManager *manager);
+	~RKFileCompletionModel ();
+
+	void updateCompletionList (const QString& fragment);
+	QVariant data (const QModelIndex& index, int role=Qt::DisplayRole) const override;
+	QString partialCompletion (bool *exact);
+private slots:
+	void completionsReady (const QString &string, const QStringList &exes, const QStringList &files);
+private:
+	void launchThread ();
+	QStringList names;
+	QString current_fragment;
+	RKFileCompletionModelWorker *worker;
+};
+
+#endif
diff --git a/rkward/windows/rkcommandeditorwindow.cpp b/rkward/windows/rkcommandeditorwindow.cpp
index 85a103ef..74d63a49 100644
--- a/rkward/windows/rkcommandeditorwindow.cpp
+++ b/rkward/windows/rkcommandeditorwindow.cpp
@@ -65,6 +65,7 @@
 #include "rkhelpsearchwindow.h"
 #include "rkhtmlwindow.h"
 #include "rkworkplace.h"
+#include "rkcodecompletion.h"
 
 #include "../debug.h"
 
@@ -1181,746 +1182,6 @@ bool RKFunctionArgHinter::eventFilter (QObject *, QEvent *e) {
 	return false;
 }
 
-//////////////////////// RKCompletionManager //////////////////////
-
-RKCompletionManager::RKCompletionManager (KTextEditor::View* view) : QObject (view) {
-	RK_TRACE (COMMANDEDITOR);
-
-	_view = view;
-	active = false;
-	update_call = true;
-	cached_position = KTextEditor::Cursor (-1, -1);
-
-	cc_iface = qobject_cast<KTextEditor::CodeCompletionInterface*> (view);
-	if (cc_iface) {
-		cc_iface->setAutomaticInvocationEnabled (false);
-		completion_model = new RKCodeCompletionModel (this);
-		file_completion_model = new RKFileCompletionModel (this);
-		callhint_model = new RKCallHintModel (this);
-		arghint_model = new RKArgumentHintModel (this);
-		cc_iface->registerCompletionModel (callhint_model);
-		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);
-		const QObjectList children = _view->children ();
-		for (QObjectList::const_iterator it = children.constBegin(); it != children.constEnd (); ++it) {
-			(*it)->installEventFilter (this);  // to handle Tab-key; installing on the view, alone, is not enough.
-		}
-
-		// 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 (active) {
-			// Handle this in the next event cycle, as more than one event may trigger
-			completion_timer->start (0);
-			// TODO: Actually, in this case we should try to _update_ the existing completion, i.e. try to save some CPU cycles.
-			tryCompletion ();
-		}
-		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) {
-		// NOTE: This should not be possible, because the connections  have not been set up in the constructor, in this case.
-		RK_ASSERT (cc_iface);
-		return;
-	}
-
-	KTextEditor::Document *doc = _view->document ();
-	KTextEditor::Cursor c = _view->cursorPosition();
-	cached_position = c;
-	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-1, false, &start, &end);
-	if (end > cursor_pos) {
-		symbol_range = KTextEditor::Range (-1, -1, -1, -1);   // Only hint when at the end of a word/symbol: https://mail.kde.org/pipermail/rkward-devel/2015-April/004122.html
-	} else 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 ()) {
-		QString filename;
-		// as a very simple heuristic: If the current symbol starts with a quote, we should probably attempt file name completion, instead of symbol name completion
-		if (word.startsWith ('\"') || word.startsWith ('\'') || word.startsWith ('`')) {
-			symbol_range.setStart (KTextEditor::Cursor (symbol_range.start ().line (), symbol_range.start ().column () + 1));   // confine range to inside the quotes.
-			filename = word.mid (1);
-			word.clear ();
-		}
-
-		completion_model->updateCompletionList (word);
-		file_completion_model->updateCompletionList (filename);
-	} else {
-		completion_model->updateCompletionList (QString ());
-		file_completion_model->updateCompletionList (QString ());
-	}
-
-	updateCallHint ();
-
-	// update ArgHint.
-	argname_range = KTextEditor::Range (-1, -1, -1, -1);
-	// Named arguments are just like regular symbols, *but* we must require that they are preceeded by either a ',', or the opening '(', immediately.
-	// Otherwise, they are an argument value expression, for sure.
-	if (callhint_model->currentFunction ()) {
-		for (int i = symbol_range.start ().column () - 1; i >= 0; --i) {
-			QChar c = current_line.at (i);
-			if (c == ',' || c == '(') {
-				argname_range = symbol_range;
-				break;
-			} else if (!c.isSpace ()) {
-				break;
-			}
-		}
-	}
-	arghint_model->updateCompletionList (callhint_model->currentFunction (), argname_range.isValid () ? doc->text (argname_range) : QString ());
-
-	updateVisibility ();
-}
-
-void RKCompletionManager::updateCallHint () {
-	RK_TRACE (COMMANDEDITOR);
-
-	if (!update_call) return;
-	update_call = false;
-
-	int line = cached_position.line () + 1;
-	QString full_context;
-	int potential_symbol_end = -2;
-	KTextEditor::Document *doc = _view->document ();
-	while (potential_symbol_end < -1 && line >= 0) {
-		--line;
-		QString context_line = doc->line (line);
-		if (context_line.startsWith ('>')) continue; // For console: TODO limit to console
-		full_context.prepend (context_line);
-
-		int pos = context_line.length () - 1;
-		if (line == cached_position.line ()) pos = cached_position.column () - 1;
-		for (int i = pos; i >= 0; --i) {
-			QChar c = context_line.at (i);
-			if (c == '(') {
-				KTextEditor::DefaultStyle style = doc->defaultStyleAt (KTextEditor::Cursor (line, i));
-				if (style != KTextEditor::dsComment && style != KTextEditor::dsString && style != KTextEditor::dsChar) {
-					potential_symbol_end = i - 1;
-					break;
-				}
-			}
-		}
-	}
-
-	// now find out where the symbol to the left of the opening brace ends
-	// there cannot be a line-break between the opening brace, and the symbol name (or can there?), so no need to fetch further context
-	while ((potential_symbol_end >= 0) && full_context.at (potential_symbol_end).isSpace ()) {
-		--potential_symbol_end;
-	}
-
-	// now identify the symbol and object (if any)
-	RObject *object = 0;
-	call_opening = KTextEditor::Cursor (-1, -1);
-	if (potential_symbol_end > 0) {
-		QString effective_symbol = RKCommonFunctions::getCurrentSymbol (full_context, potential_symbol_end);
-		if (!effective_symbol.isEmpty ()) {
-			object = RObjectList::getObjectList ()->findObject (effective_symbol);
-			call_opening = KTextEditor::Cursor (line, potential_symbol_end+1);
-		}
-	}
-
-	callhint_model->setFunction (object);
-}
-
-void startModel (KTextEditor::CodeCompletionInterface* iface, KTextEditor::CodeCompletionModel *model, bool start, const KTextEditor::Range &range, QList<KTextEditor::CodeCompletionModel*> *active_models) {
-	if (start && model->rowCount () > 0) {
-		if (!active_models->contains (model)) {
-			iface->startCompletion (range, model);
-			active_models->append (model);
-		}
-	} else {
-		active_models->removeAll (model);
-	}
-}
-
-void RKCompletionManager::updateVisibility () {
-	RK_TRACE (COMMANDEDITOR);
-
-	if (!cc_iface->isCompletionActive ()) {
-		active_models.clear ();
-	}
-
-	bool min_len = (currentCompletionWord ().length () >= RKSettingsModuleCommandEditor::completionMinChars ());
-	startModel (cc_iface, completion_model, min_len, symbol_range, &active_models);
-	startModel (cc_iface, file_completion_model, min_len, symbol_range, &active_models);
-	if (kate_keyword_completion_model) {
-		// 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);
-	}
-// 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, true, currentCallRange (), &active_models);
-	startModel (cc_iface, arghint_model, true, argname_range, &active_models);
-
-	if (!active_models.isEmpty ()) {
-		active = true;
-	} else {
-		cc_iface->abortCompletion ();
-		active = false;
-	}
-
-	if (!active) update_call = true;
-}
-
-void RKCompletionManager::textInserted (KTextEditor::Document*, const KTextEditor::Cursor& position, const QString& text) {
-	if (active) {
-		if (position < call_opening) update_call = true;
-		else if (text.contains (QChar ('(')) || text.contains (QChar (')'))) update_call = true;
-	}
-	tryCompletionProxy();
-}
-
-void RKCompletionManager::textRemoved (KTextEditor::Document*, const KTextEditor::Range& range, const QString& text) {
-	if (active) {
-		if (range.start () < call_opening) update_call = true;
-		else if (text.contains (QChar ('(')) || text.contains (QChar (')'))) update_call = true;
-	}
-	tryCompletionProxy();
-}
-
-void RKCompletionManager::lineWrapped (KTextEditor::Document* , const KTextEditor::Cursor& ) {
-// should already have been handled by textInserted()
-	tryCompletionProxy ();
-}
-
-void RKCompletionManager::lineUnwrapped (KTextEditor::Document* , int ) {
-// should already have been handled by textRemoved()
-	tryCompletionProxy ();
-}
-
-void RKCompletionManager::cursorPositionChanged (KTextEditor::View* view, const KTextEditor::Cursor& newPosition) {
-	if (active) {
-		if (newPosition < call_opening) update_call = true;
-		else {
-			QString text = view->document ()->text (KTextEditor::Range (newPosition, cached_position));
-			if (text.contains (QChar ('(')) || text.contains (QChar (')'))) update_call = true;
-		}
-	}
-	tryCompletionProxy ();
-}
-
-KTextEditor::Range RKCompletionManager::currentCallRange () const {
-	return KTextEditor::Range (call_opening, _view->cursorPosition ());
-}
-
-bool RKCompletionManager::eventFilter (QObject* watched, QEvent* event) {
-	if (event->type () == QEvent::KeyPress || event->type () == QEvent::ShortcutOverride) {
-		RK_TRACE (COMMANDEDITOR);	// avoid loads of empty traces, putting this here
-		QKeyEvent *k = static_cast<QKeyEvent *> (event);
-
-		if (k->key () == Qt::Key_Tab && (!k->modifiers ())) {
-			// TODO: It is not quite clear, what behavior is desirable, in case more than one completion model is active at a time.
-			//       For now, we use the simplest solution (implemenation-wise), and complete from the topmost-model, only
-			// TODO: Handle the ktexteditor builtin models, too.
-			bool exact = false;
-			QString comp;
-			bool handled = false;
-			if (active_models.contains (arghint_model)) {
-				comp = arghint_model->partialCompletion (&exact);
-				handled = true;
-			} else if (active_models.contains (completion_model)) {
-				comp = completion_model->partialCompletion (&exact);
-				handled = true;
-			} else if (active_models.contains (file_completion_model)) {
-				comp = file_completion_model->partialCompletion (&exact);
-				handled = true;
-			}
-
-			if (handled) {
-				RK_DEBUG(COMMANDEDITOR, DL_DEBUG, "Tab completion: %s", qPrintable (comp));
-				view ()->document ()->insertText (view ()->cursorPosition (), comp);
-				if (exact) cc_iface->abortCompletion ();
-				else if (comp.isEmpty ()) {
-					QApplication::beep (); // TODO: unfortunately, we catch *two* tab events, so this is not good, yet
-				}
-				return true;
-			}
-		} else if ((k->key () == Qt::Key_Up || k->key () == Qt::Key_Down) && cc_iface->isCompletionActive ()) {
-			// Make up / down-keys (without alt) navigate in the document (aborting the completion)
-			// Meke alt+up / alt+down naviate in the completion list
-			if (k->modifiers () & Qt::AltModifier) {
-				if (k->type() != QKeyEvent::KeyPress) return true;  // eat the release event
-
-				// No, we cannot just send a fake key event, easily...
-				KActionCollection *kate_edit_actions = view ()->findChild<KActionCollection*> ("edit_actions");
-				QAction *action = kate_edit_actions ? (kate_edit_actions->action (k->key () == Qt::Key_Up ? "move_line_up" : "move_line_down")) : 0;
-				if (!action) {
-					kate_edit_actions = view ()->actionCollection ();
-					action = kate_edit_actions ? (kate_edit_actions->action (k->key () == Qt::Key_Up ? "move_line_up" : "move_line_down")) : 0;
-				}
-				if (action) action->trigger ();
-				else RK_ASSERT (action);
-				return true;
-			} else {
-				cc_iface->abortCompletion ();
-				active = false;
-				return false;
-			}
-		}
-	}
-
-	return false;
-}
-
-//////////////////////// 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 (RKCompletionManager *manager) : RKCompletionModelBase (manager) {
-	RK_TRACE (COMMANDEDITOR);
-
-	setHasGroups (true);
-}
-
-RKCodeCompletionModel::~RKCodeCompletionModel () {
-	RK_TRACE (COMMANDEDITOR);
-}
-
-void RKCodeCompletionModel::updateCompletionList (const QString& symbol) {
-	RK_TRACE (COMMANDEDITOR);
-
-	if (current_symbol == symbol) return;	// already up to date
-	beginResetModel ();
-
-	RObject::ObjectList matches;
-	QStringList objectpath = RObject::parseObjectPath (symbol);
-	if (!objectpath.isEmpty () && !objectpath[0].isEmpty ()) {  // Skip completion, if the current symbol is '""' (or another empty quote), for instance
-		matches = RObjectList::getObjectList ()->findObjectsMatching (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.
-	n_completions = matches.size ();
-	icons.clear ();
-	icons.reserve (n_completions);
-	names = RObject::getFullNames (matches, RKSettingsModuleCommandEditor::completionOptions());
-	for (int i = 0; i < n_completions; ++i) {
-		icons.append (RKStandardIcons::iconForObject (matches[i]));
-	}
-
-	current_symbol = symbol;
-
-	endResetModel ();
-}
-
-KTextEditor::Range RKCodeCompletionModel::completionRange (KTextEditor::View *, const KTextEditor::Cursor&c) {
-	return manager->currentSymbolRange ();
-}
-
-QVariant RKCodeCompletionModel::data (const QModelIndex& index, int role) const {
-	if (isHeaderItem (index)) {
-		if (role == Qt::DisplayRole) return i18n ("Objects on search path");
-		if (role == KTextEditor::CodeCompletionModel::GroupRole) return Qt::DisplayRole;
-		if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) return 1;  // sort below arghint model
-		return QVariant ();
-	}
-
-	int col = index.column ();
-	int row = index.row ();
-
-	if ((role == Qt::DisplayRole) || (role == KTextEditor::CodeCompletionModel::CompletionRole)) {
-		if (col == KTextEditor::CodeCompletionModel::Name) {
-			return (names.value (row));
-		}
-	} else if (role == Qt::DecorationRole) {
-		if (col == KTextEditor::CodeCompletionModel::Icon) {
-			return (icons.value (row));
-		}
-	} else if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) {
-		return (row);  // disable sorting
-	} else if (role == KTextEditor::CodeCompletionModel::MatchQuality) {
-		return (10);
-	}
-
-	return QVariant ();
-}
-
-QString findCommonCompletion (const QStringList &list, const QString &lead, bool *exact_match) {
-	RK_TRACE (COMMANDEDITOR);
-	RK_DEBUG(COMMANDEDITOR, DL_DEBUG, "Looking for commong completion among set of %d, starting with %s", list.size (), qPrintable (lead));
-
-	*exact_match = true;
-	QString ret;
-	bool first = true;
-	int lead_size = lead.count ();
-	for (int i = list.size () - 1; i >= 0; --i) {
-		if (!list[i].startsWith (lead)) continue;
-
-		QString candidate = list[i].mid (lead_size);
-		if (first) {
-			ret = candidate;
-			first = false;
-		} else {
-			if (ret.length () > candidate.length ()) {
-				ret = ret.left (candidate.length ());
-				*exact_match = false;
-			}
-
-			for (int c = 0; c < ret.length(); ++c) {
-				if (ret[c] != candidate[c]) {
-					*exact_match = false;
-					if (!c) return QString ();
-
-					ret = ret.left (c);
-					break;
-				}
-			}
-		}
-	}
-
-	return ret;
-}
-
-QString RKCodeCompletionModel::partialCompletion (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 ());
-	RObject::ObjectList matches = RObjectList::getObjectList ()->findObjectsMatching (current_symbol);
-
-	QStringList shortnames;
-	for (int i = 0; i < matches.count (); ++i) {
-		shortnames.append (matches[i]->getShortName ());
-	}
-	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));
-}
-
-//////////////////////// RKCallHintModel //////////////////////////
-RKCallHintModel::RKCallHintModel (RKCompletionManager* manager) : RKCompletionModelBase (manager) {
-	RK_TRACE (COMMANDEDITOR);
-	function = 0;
-}
-
-#include <QDesktopWidget>
-// TODO: There could be more than one function by a certain name, and we could support this!
-void RKCallHintModel::setFunction(RObject* _function) {
-	RK_TRACE (COMMANDEDITOR);
-
-	if (function == _function) return;
-	function = _function;
-
-	beginResetModel ();
-	if (function && function->isType (RObject::Function)) {
-		// initialize hint
-		RFunctionObject *fo = static_cast<RFunctionObject*> (function);
-		QStringList args = fo->argumentNames ();
-		QStringList defs = fo->argumentDefaults ();
-
-		name = function->getFullName ();
-
-		formals = '(';
-		formatting.clear ();
-		KTextEditor::Attribute format;
-//		format.setFontBold (); // NOTE: Not good. makes size mis-calculation worse.
-		format.setForeground (QBrush (Qt::green));  // But turns out purple?!
-
-		// NOTE: Unfortunately, adding new-lines within (long) formals does not work. If this issue turns out to be relevant, we'll have to resort to breaking the formals into
-		// several (dummy) items.
-		int pos = 1;
-		for (int i = 0; i < args.size (); ++i) {
-			QString pair = args[i];
-			if (!defs.value(i).isEmpty ()) pair.append ('=' + defs[i]);
-			formatting.append ({ pos + args[i].length (), pair.length ()-args[i].length (), format });
-
-			if (i < (args.size () - 1)) pair.append (", ");
-			formals.append (pair);
-
-			pos = pos + pair.length ();
-		}
-		formals.append (')');
-		n_completions = 1;
-	} else {
-		n_completions = 0;
-	}
-	endResetModel ();
-}
-
-QVariant RKCallHintModel::data (const QModelIndex& index, int role) const {
-	if (isHeaderItem (index)) {
-		if (role == Qt::DisplayRole) return i18n ("Function calls");  // NOTE: Header is not currently used as of KF5 5.44.0
-		if (role == KTextEditor::CodeCompletionModel::GroupRole) return Qt::DisplayRole;
-		return QVariant ();
-	}
-
-	int col = index.column ();
-	if (role == Qt::DisplayRole) {
-		if (col == KTextEditor::CodeCompletionModel::Prefix) return (name);
-		if (col == KTextEditor::CodeCompletionModel::Arguments) return (formals);
-		if (col == KTextEditor::CodeCompletionModel::Postfix) return ("        "); // Size is of a bit for KF5 5.44.0. Provide some padding to work around cut-off parts.
-	} else if (role == KTextEditor::CodeCompletionModel::ArgumentHintDepth) {
-		return 1;
-	} else if (role == KTextEditor::CodeCompletionModel::CompletionRole) {
-		return KTextEditor::CodeCompletionModel::Function;
-	} else if (role == KTextEditor::CodeCompletionModel::HighlightingMethod) {
-		if (col == KTextEditor::CodeCompletionModel::Arguments) return KTextEditor::CodeCompletionModel::CustomHighlighting;
-	} else if (role == KTextEditor::CodeCompletionModel::CustomHighlight) {
-		if (col == KTextEditor::CodeCompletionModel::Arguments)  return formatting;
-	} else if (role == KTextEditor::CodeCompletionModel::MatchQuality) {
-		return (10);
-	}
-
-	return QVariant ();
-}
-
-KTextEditor::Range RKCallHintModel::completionRange (KTextEditor::View *, const KTextEditor::Cursor&) {
-	return manager->currentCallRange ();
-}
-
-//////////////////////// RKArgumentHintModel //////////////////////
-RKArgumentHintModel::RKArgumentHintModel (RKCompletionManager* manager) : RKCompletionModelBase (manager) {
-	RK_TRACE (COMMANDEDITOR);
-
-	function = 0;
-}
-
-void RKArgumentHintModel::updateCompletionList (RObject* _function, const QString &argument) {
-	RK_TRACE (COMMANDEDITOR);
-
-	bool changed = false;
-	if (function != _function) {
-		beginResetModel ();
-		function = _function;
-		if (function && function->isType (RObject::Function)) {
-			// initialize hint
-			RFunctionObject *fo = static_cast<RFunctionObject*> (function);
-			args = fo->argumentNames ();
-			defs = fo->argumentDefaults ();
-		} else {
-			args.clear ();
-			defs.clear ();
-		}
-	}
-
-	if (changed || (argument != fragment)) {
-		if (!changed) {
-			changed = true;
-			beginResetModel ();
-		}
-		fragment = argument;
-		matches.clear ();
-		for (int i = 0; i < args.size (); ++i) {
-			if (args[i].startsWith (fragment)) matches.append (i);
-		}
-	}
-
-	if (changed) {
-		n_completions = matches.size ();
-		endResetModel ();
-	}
-}
-
-QVariant RKArgumentHintModel::data (const QModelIndex& index, int role) const {
-	if (isHeaderItem (index)) {
-		if (role == Qt::DisplayRole) return i18n ("Function arguments");
-		if (role == KTextEditor::CodeCompletionModel::GroupRole) return Qt::DisplayRole;
-		if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) return 0; // Sort above other models (except calltip)
-		return QVariant ();
-	}
-
-	int col = index.column ();
-	int row = index.row ();
-	if (role == Qt::DisplayRole) {
-		if (col == KTextEditor::CodeCompletionModel::Name) return (args.value (matches.value (row)));
-		if (col == KTextEditor::CodeCompletionModel::Postfix) {
-			QString def = defs.value (matches.value (row));
-			if (!def.isEmpty ()) return (QString ('=' + def));
-		}
-	} else if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) {
-		return row;  // disable sorting
-	} else if (role == KTextEditor::CodeCompletionModel::CompletionRole) {
-		return KTextEditor::CodeCompletionModel::Function;
-	} else if (role == KTextEditor::CodeCompletionModel::MatchQuality) {
-		return (20);
-	}
-
-	return QVariant ();
-}
-
-KTextEditor::Range RKArgumentHintModel::completionRange (KTextEditor::View*, const KTextEditor::Cursor&) {
-	return manager->currentArgnameRange ();
-}
-
-QString RKArgumentHintModel::partialCompletion (bool* exact) {
-	RK_TRACE (COMMANDEDITOR);
-
-	return (findCommonCompletion (args, fragment, exact));
-}
-
-//////////////////////// RKFileCompletionModel ////////////////////
-#include <KUrlCompletion>
-RKFileCompletionModelWorker::RKFileCompletionModelWorker (const QString &_string) : QThread () {
-	RK_TRACE (COMMANDEDITOR);
-	string = _string;
-	connect (this, &QThread::finished, this, &QObject::deleteLater);
-}
-
-void RKFileCompletionModelWorker::run () {
-	RK_TRACE (COMMANDEDITOR);
-
-	KUrlCompletion comp;
-	comp.setDir (QUrl::fromLocalFile (QDir::currentPath ()));
-	comp.makeCompletion (string);
-	QStringList files = comp.allMatches ();
-
-	QStringList exes;
-	if (!QDir (string).isAbsolute ()) {  // NOTE: KUrlCompletion does not handle this well, returns, e.g. "/home/me/firefox" when given "/home/me/f"; KF5 5.44.0
-		comp.setMode (KUrlCompletion::ExeCompletion);
-		comp.makeCompletion (string);
-		exes = comp.allMatches ();
-	}
-
-	emit (completionsReady (string, exes, files));
-}
-
-RKFileCompletionModel::RKFileCompletionModel (RKCompletionManager* manager) : RKCompletionModelBase (manager) {
-	RK_TRACE (COMMANDEDITOR);
-	worker = 0;
-}
-
-RKFileCompletionModel::~RKFileCompletionModel () {
-	RK_TRACE (COMMANDEDITOR);
-}
-
-void RKFileCompletionModel::updateCompletionList(const QString& fragment) {
-	RK_TRACE (COMMANDEDITOR);
-
-	if (current_fragment == fragment) return;
-
-	current_fragment = fragment;
-	launchThread ();
-}
-
-void RKFileCompletionModel::launchThread () {
-	RK_TRACE (COMMANDEDITOR);
-
-	if (current_fragment.isEmpty ()) {
-		completionsReady (QString (), QStringList (), QStringList ());
-	} else if (!worker) {
-		RK_DEBUG (COMMANDEDITOR, DL_DEBUG, "Launching filename completion thread for '%s'", qPrintable (current_fragment));
-		worker = new RKFileCompletionModelWorker (current_fragment);
-		connect (worker, &RKFileCompletionModelWorker::completionsReady, this, &RKFileCompletionModel::completionsReady);
-		worker->start ();
-	}
-}
-
-void RKFileCompletionModel::completionsReady (const QString& string, const QStringList& exes, const QStringList& files) {
-	RK_TRACE (COMMANDEDITOR);
-
-	RK_DEBUG (COMMANDEDITOR, DL_DEBUG, "Filename completion finished for '%s': %d matches", qPrintable (string), files.size () + exes.size ());
-	worker = 0;
-	if (current_fragment == string) {
-		beginResetModel ();
-		names = files + exes; // TODO: This could be prettier
-		n_completions = names.size ();
-		endResetModel ();
-	} else {
-		launchThread ();
-	}
-}
-
-QVariant RKFileCompletionModel::data (const QModelIndex& index, int role) const {
-	if (isHeaderItem (index)) {
-		if (role == Qt::DisplayRole) return i18n ("Local file names");
-		if (role == KTextEditor::CodeCompletionModel::GroupRole) return Qt::DisplayRole;
-		if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) return 1; // Sort below arghint model
-		return QVariant ();
-	}
-
-	int col = index.column ();
-	int row = index.row ();
-	if ((role == Qt::DisplayRole) || (role == KTextEditor::CodeCompletionModel::CompletionRole)) {
-		if (col == KTextEditor::CodeCompletionModel::Name) {
-			return (names.value (row));
-		}
-	} else if (role == KTextEditor::CodeCompletionModel::InheritanceDepth) {
-		return (row);  // disable sorting
-	} else if (role == KTextEditor::CodeCompletionModel::MatchQuality) {
-		return (10);
-	}
-	return QVariant ();
-}
-
-QString RKFileCompletionModel::partialCompletion (bool* exact) {
-	RK_TRACE (COMMANDEDITOR);
-
-	return (findCommonCompletion (names, current_fragment, exact));
-}
-
-
 // static
 KTextEditor::Document* RKCommandHighlighter::_doc = 0;
 KTextEditor::View* RKCommandHighlighter::_view = 0;
diff --git a/rkward/windows/rkcommandeditorwindow.h b/rkward/windows/rkcommandeditorwindow.h
index 18b9f400..3273263b 100644
--- a/rkward/windows/rkcommandeditorwindow.h
+++ b/rkward/windows/rkcommandeditorwindow.h
@@ -104,166 +104,10 @@ private:
 	QLabel *arghints_popup;
 };
 
-class RKCodeCompletionModel;
-class RKFileCompletionModel;
-class RKCallHintModel;
-class RKArgumentHintModel;
-class RKCompletionManager : public QObject {
-	Q_OBJECT
-public:
-	RKCompletionManager (KTextEditor::View *view);
-	~RKCompletionManager ();
-
-	QString currentCompletionWord () const;
-	KTextEditor::Range currentSymbolRange () const { return symbol_range; };
-	KTextEditor::Range currentArgnameRange () const { return argname_range; };
-	KTextEditor::Range currentCallRange () const;
-	KTextEditor::View* view () const { return (_view); };
-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:
-	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;
-	RKFileCompletionModel *file_completion_model;
-	RKCallHintModel *callhint_model;
-	RKArgumentHintModel *arghint_model;
-	KTextEditor::CodeCompletionModel* kate_keyword_completion_model;
-	QTimer *completion_timer;
-
-	KTextEditor::View *_view;
-	KTextEditor::Cursor cached_position;
-
-	KTextEditor::Range symbol_range;
-	KTextEditor::Cursor call_opening;
-	KTextEditor::Range argname_range;
-
-	bool update_call;
-	bool active;
-
-	QList<KTextEditor::CodeCompletionModel*> active_models;
-};
-
-/** 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 ();
-
-	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; };
-
-	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 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);
-	QVariant data (const QModelIndex& index, int role=Qt::DisplayRole) const override;
-	QString partialCompletion (bool* exact_match);
-private:
-	QList<QIcon> icons;
-	QStringList names;
-	QString current_symbol;
-};
-
-class RKCallHintModel : public RKCompletionModelBase {
-	Q_OBJECT
-public:
-	explicit RKCallHintModel (RKCompletionManager *manager);
-	void setFunction (RObject *function);
-
-	QVariant data (const QModelIndex& index, int role=Qt::DisplayRole) const override;
-	KTextEditor::Range completionRange (KTextEditor::View *view, const KTextEditor::Cursor &position) override;
-	RObject *currentFunction () const { return function; };
-private:
-	RObject *function;
-	QString name;
-	QString formals;
-	QVariantList formatting;
-};
-
-class RKArgumentHintModel : public RKCompletionModelBase {
-	Q_OBJECT
-public:
-	explicit RKArgumentHintModel (RKCompletionManager *manager);
-	void updateCompletionList (RObject *function, const QString& argument);
-
-	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);
-private:
-	RObject *function;
-	QStringList args;
-	QStringList defs;
-	QString fragment;
-	QList<int> matches;
-};
-
-#include <QThread>
-class RKFileCompletionModelWorker : public QThread {
-	Q_OBJECT
-public:
-	explicit RKFileCompletionModelWorker (const QString &string);
-signals:
-	void completionsReady (const QString &string, const QStringList &exes, const QStringList &files);
-private:
-	void run () override;
-	QString string;
-};
-
-class RKFileCompletionModel : public RKCompletionModelBase {
-	Q_OBJECT
-public:
-	explicit RKFileCompletionModel (RKCompletionManager *manager);
-	~RKFileCompletionModel ();
-
-	void updateCompletionList (const QString& fragment);
-	QVariant data (const QModelIndex& index, int role=Qt::DisplayRole) const override;
-	QString partialCompletion (bool *exact);
-private slots:
-	void completionsReady (const QString &string, const QStringList &exes, const QStringList &files);
-private:
-	void launchThread ();
-	QStringList names;
-	QString current_fragment;
-	RKFileCompletionModelWorker *worker;
-};
-
 class RKJobSequence;
 class RKXMLGUIPreviewArea;
 class RKPreviewManager;
+class RKCompletionManager;
 
 /**
 	\brief Provides an editor window for R-commands, as well as a text-editor window in general.



More information about the rkward-tracker mailing list