[education/rkward] /: Improve handling of partial completions
Thomas Friedrichsmeier
null at kde.org
Thu Oct 6 13:17:28 BST 2022
Git commit a15704610819d2d7caa9ae7bc9306966c5efb0d0 by Thomas Friedrichsmeier.
Committed on 06/10/2022 at 12:14.
Pushed by tfry into branch 'master'.
Improve handling of partial completions
M +1 -0 ChangeLog
M +94 -67 rkward/windows/rkcodecompletion.cpp
M +3 -3 rkward/windows/rkcodecompletion.h
https://invent.kde.org/education/rkward/commit/a15704610819d2d7caa9ae7bc9306966c5efb0d0
diff --git a/ChangeLog b/ChangeLog
index 568637a5..ffb67962 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -15,6 +15,7 @@
- Fixed: Fix zooming help/output pages with Ctrl+scroll wheel, when compiled with QWebEngine
- Fixed: Fix problem handling rkward:// links from dialogs on some sytems
- Fixed: Fix object name completion for (irregular) names starting with numbers or underscores
+- Added: Partial completions (Tab-key) consider completion candidates from all visible completion groups
- Added: R's dynamic completions (importantly for ":::", "?", and "@") are merged into the already provided completions
- Added: Provide tooltips on symbols in scripts and R console
- Added: Many new basic and advanced R, R Markdown and LaTeX snippets
diff --git a/rkward/windows/rkcodecompletion.cpp b/rkward/windows/rkcodecompletion.cpp
index 622212c9..830c34ad 100644
--- a/rkward/windows/rkcodecompletion.cpp
+++ b/rkward/windows/rkcodecompletion.cpp
@@ -291,9 +291,9 @@ void RKCompletionManager::startModel( KTextEditor::CodeCompletionModel *model, b
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());
}
+ auto ci = dynamic_cast<KTextEditor::CodeCompletionModelControllerInterface*>(model);
+ model_filters.insert(model, ci ? ci->filterString(view(), range, view()->cursorPosition()) : QString());
} else {
started_models.removeAll (model);
}
@@ -369,11 +369,79 @@ KTextEditor::Range RKCompletionManager::currentCallRange () const {
return KTextEditor::Range (call_opening, _view->cursorPosition ());
}
+bool modelHasGroups(KTextEditor::CodeCompletionModel *model) {
+ return (model->hasGroups() || (model->rowCount() && model->rowCount(model->index(0,0)))); // kate_keyword_completion_model claims to have no groups, but is still grouped
+}
+
+QStringList genPartialCompletions(KTextEditor::CodeCompletionModel *model, const QString &lead, const QModelIndex &root) {
+ QStringList candidates;
+ for (int i = 0; i < model->rowCount(root); ++i) {
+ QString candidate = model->index(i, KTextEditor::CodeCompletionModel::Name, root).data().toString();
+ if (candidate.startsWith(lead)) candidates.append(candidate.mid(lead.size()));
+ }
+ return candidates;
+}
+
+QStringList genPartialCompletions(KTextEditor::CodeCompletionModel *model, const QString &lead) {
+ QStringList candidates;
+ if (modelHasGroups(model)) {
+ for (int i = 0; i < model->rowCount(); ++i) {
+ candidates += genPartialCompletions(model, lead, model->index(i, 0));
+ }
+ } else {
+ candidates = genPartialCompletions(model, lead, QModelIndex());
+ }
+ return candidates;
+}
+
+QStringList genPartialCompletions(const QStringList &matches, const QString &lead) {
+ QStringList ret;
+ ret.reserve(matches.size());
+ for (int i = 0; i < matches.size(); ++i) {
+ const QString &m = matches[i];
+ if (!m.startsWith(lead)) continue;
+ ret.append(m.mid(lead.length()));
+ }
+ return ret;
+}
+
+QString findCommonCompletion (const QStringList &candidates) {
+ RK_TRACE (COMMANDEDITOR);
+ RK_DEBUG(COMMANDEDITOR, DL_DEBUG, "Looking for common completion among set of %d", candidates.size ());
+
+ QString ret;
+ bool first = true;
+ for (int i = candidates.size() - 1; i >= 0; --i) {
+ const QString &candidate = candidates.at(i);
+
+ if (first) {
+ ret = candidate;
+ first = false;
+ } else {
+ if (ret.length() > candidate.length()) {
+ ret = ret.left(candidate.length());
+ }
+
+ for (int c = 0; c < ret.length(); ++c) {
+ if (ret[c] != candidate[c]) {
+ if (!c) return QString();
+
+ ret = ret.left (c);
+ break;
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
bool isModelEmpty(KTextEditor::CodeCompletionModel* model, const QString &filter) {
int rootcount = model->rowCount();
+ bool groups = modelHasGroups(model);
for (int i = 0; i < rootcount; ++i) {
auto pindex = model->index(i, 0, QModelIndex());
- if (model->hasGroups()) {
+ if (groups) {
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;
@@ -436,29 +504,26 @@ bool RKCompletionManager::eventFilter (QObject*, QEvent* event) {
if (k->key () == Qt::Key_Tab && (!k->modifiers ())) {
// Try to do partial completion. Unfortunately, the base implementation in ktexteditor is totally broken (inserts the whole partial completion, without removing the start).
- // 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 (implementation-wise), and complete from the topmost-model, only
- // TODO: Handle the ktexteditor builtin models, too.
- bool exact = false;
- QString comp;
- bool handled = false;
- if (started_models.contains(arghint_model) && arghint_model->partialCompletion(&comp, &exact)) {
- handled = true;
- } else if (started_models.contains(completion_model) && completion_model->partialCompletion(&comp, &exact)) {
- handled = true;
- } else if (started_models.contains(file_completion_model) && file_completion_model->partialCompletion(&comp, &exact)) {
- handled = true;
+ // This is terribly inefficient, but fortunately, it only needs to be called, when Tab is actually pressed.
+ QStringList candidate_completions;
+ for (int i = 0; i < started_models.size(); ++i) {
+ auto model = started_models[i];
+ if (model == callhint_model) continue;
+ if (model == arghint_model) candidate_completions += arghint_model->rawPartialCompletions();
+ else if (model == completion_model) candidate_completions += completion_model->rawPartialCompletions();
+ else if (model == file_completion_model) candidate_completions += file_completion_model->rawPartialCompletions();
+ else candidate_completions += genPartialCompletions(model, model_filters[model]);
}
+ QString comp = findCommonCompletion(candidate_completions);
- if (handled) {
- RK_DEBUG(COMMANDEDITOR, DL_WARNING, "Tab completion: %s, %d", qPrintable (comp), k->type());
+ if (!comp.isEmpty()) {
+ RK_DEBUG(COMMANDEDITOR, DL_WARNING, "Tab completion: %s", qPrintable(comp));
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 entirely good: Match may have been exact, but a further (longer) match could still exist
+ if (candidate_completions.size() == 1) {
// Ouch, how messy. We want to make sure completion stops (except for any call hints), and is not re-triggered by the insertion, itself
bool callhint_active = started_models.contains(callhint_model);
cc_iface->abortCompletion ();
@@ -466,11 +531,10 @@ bool RKCompletionManager::eventFilter (QObject*, QEvent* event) {
completion_timer->stop();
if (callhint_active) startModel(callhint_model, true, currentCallRange());
}
- else if (comp.isEmpty ()) {
- QApplication::beep (); // TODO: unfortunately, we catch *two* tab events, so this is not good, yet
- }
- return true;
+ } else {
+ QApplication::beep ();
}
+ return true;
} else if ((k->key () == Qt::Key_Up || k->key () == Qt::Key_Down) && cc_iface->isCompletionActive ()) {
bool navigate = (settings->cursorNavigatesCompletions() && k->modifiers() == Qt::NoModifier) || (!settings->cursorNavigatesCompletions() && k->modifiers() == Qt::AltModifier);
@@ -654,55 +718,18 @@ QVariant RKCodeCompletionModel::data (const QModelIndex& index, int role) const
return QVariant ();
}
-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));
-
- *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 true; // it was still a match, even if we cannot complete anything
-
- ret = ret.left (c);
- break;
- }
- }
- }
- }
-
- *comp = ret;
- return !first; // at least one matching candidate was found
-}
-
-bool RKCodeCompletionModel::partialCompletion(QString *comp, bool* exact_match) {
+QStringList RKCodeCompletionModel::rawPartialCompletions() const {
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 (false);
+ if (objectpath.isEmpty() || objectpath[0].isEmpty()) return (QStringList());
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(comp, shortnames, lead, exact_match);
+ return genPartialCompletions(shortnames, lead);
}
@@ -886,10 +913,10 @@ KTextEditor::Range RKArgumentHintModel::completionRange (KTextEditor::View*, con
return manager->currentArgnameRange ();
}
-bool RKArgumentHintModel::partialCompletion(QString *comp, bool* exact) {
+QStringList RKArgumentHintModel::rawPartialCompletions() const {
RK_TRACE (COMMANDEDITOR);
- return findCommonCompletion(comp, args, fragment, exact);
+ return genPartialCompletions(args, fragment);
}
//////////////////////// RKFileCompletionModel ////////////////////
@@ -993,10 +1020,10 @@ QVariant RKFileCompletionModel::data (const QModelIndex& index, int role) const
return QVariant ();
}
-bool RKFileCompletionModel::partialCompletion(QString *comp, bool* exact) {
+QStringList RKFileCompletionModel::rawPartialCompletions() const {
RK_TRACE (COMMANDEDITOR);
- return findCommonCompletion(comp, names, current_fragment, exact);
+ return genPartialCompletions(names, current_fragment);
}
diff --git a/rkward/windows/rkcodecompletion.h b/rkward/windows/rkcodecompletion.h
index 3a8e9b2c..89d73d59 100644
--- a/rkward/windows/rkcodecompletion.h
+++ b/rkward/windows/rkcodecompletion.h
@@ -121,7 +121,7 @@ public:
void updateCompletionList(const QString& symbol, bool is_help);
QVariant data (const QModelIndex& index, int role=Qt::DisplayRole) const override;
- bool partialCompletion(QString *comp, bool* exact_match);
+ QStringList rawPartialCompletions() const;
private:
QList<QIcon> icons;
QStringList names;
@@ -159,7 +159,7 @@ public:
QVariant data (const QModelIndex& index, int role=Qt::DisplayRole) const override;
KTextEditor::Range completionRange (KTextEditor::View *view, const KTextEditor::Cursor &position) override;
- bool partialCompletion(QString *comp, bool *exact);
+ QStringList rawPartialCompletions() const;
private:
RObject *function;
QStringList args;
@@ -192,7 +192,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;
- bool partialCompletion(QString *comp, bool *exact);
+ QStringList rawPartialCompletions() const;
private slots:
void completionsReady (const QString &string, const QStringList &exes, const QStringList &files);
private:
More information about the rkward-tracker
mailing list