[education/labplot] src/frontend/script: [scripting] removed custom completion popup, KTextEditor's built-in logic is enough, and added a customer action to bring up the completion again.

Alexander Semke <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit e155a7f3ef47d9cd6d8444d420316afc4075ccde by Alexander Semke.
Committed on 01/08/2026 at 20:22.
Pushed by asemke into branch 'master'.

[scripting] removed custom completion popup,  KTextEditor's built-in logic is enough, and added a customer action to bring up the completion again.

M  +0    -5    src/frontend/script/ScriptCompletionModel.cpp
M  +0    -4    src/frontend/script/ScriptCompletionModel.h
M  +18   -153  src/frontend/script/ScriptEditor.cpp
M  +1    -7    src/frontend/script/ScriptEditor.h

https://invent.kde.org/education/labplot/-/commit/e155a7f3ef47d9cd6d8444d420316afc4075ccde

diff --git a/src/frontend/script/ScriptCompletionModel.cpp b/src/frontend/script/ScriptCompletionModel.cpp
index 0ae5528a54..78cbd28a9b 100644
--- a/src/frontend/script/ScriptCompletionModel.cpp
+++ b/src/frontend/script/ScriptCompletionModel.cpp
@@ -226,11 +226,6 @@ void ScriptCompletionModel::startCompletionRequest() {
 
 	setRowCount(m_matches.size());
 	endResetModel();
-
-	// Emit signal for custom popup
-	Q_EMIT modelIsReady(m_matches);
-
-	// DEBUG(Q_FUNC_INFO << ", Showing " << m_matches.size() << " completions for prefix '" << prefix << "'")
 }
 
 QVariant ScriptCompletionModel::data(const QModelIndex& index, int role) const {
diff --git a/src/frontend/script/ScriptCompletionModel.h b/src/frontend/script/ScriptCompletionModel.h
index 8089b18835..9ea9407c62 100644
--- a/src/frontend/script/ScriptCompletionModel.h
+++ b/src/frontend/script/ScriptCompletionModel.h
@@ -46,10 +46,6 @@ public:
 	void abortCompletion();
 
 	bool initPylabplotSymbols();
-	const QList<CompletionItem>& matches() const { return m_matches; }
-
-Q_SIGNALS:
-	void modelIsReady(const QList<CompletionItem>&);
 
 private Q_SLOTS:
 	void startCompletionRequest();
diff --git a/src/frontend/script/ScriptEditor.cpp b/src/frontend/script/ScriptEditor.cpp
index 6afe551cc8..691561472a 100644
--- a/src/frontend/script/ScriptEditor.cpp
+++ b/src/frontend/script/ScriptEditor.cpp
@@ -35,8 +35,6 @@
 #include <QTextCharFormat>
 #include <QClipboard>
 #include <QApplication>
-#include <QListWidget>
-#include <QKeyEvent>
 
 ScriptEditor::ScriptEditor(Script* script, QWidget* parent)
 	: QWidget(parent), m_script(script) {
@@ -77,8 +75,24 @@ ScriptEditor::ScriptEditor(Script* script, QWidget* parent)
 	m_kTextEditorView->registerCompletionModel(m_completionModel);
 	m_kTextEditorView->setAutomaticInvocationEnabled(true);
 
-	// Connect to show custom popup (no parameter needed - we'll query the model)
-	connect(m_completionModel, &ScriptCompletionModel::modelIsReady, this, &ScriptEditor::showCustomCompleter);
+	// Setup manual code completion shortcut
+	// Use Ctrl+Shift+Space on macOS (Ctrl+Space conflicts with keyboard language switching)
+	// Use Ctrl+Space on other platforms (standard KTextEditor shortcut)
+	m_codeCompletionAction = new QAction(QStringLiteral("Code Completion"), this);
+#ifdef Q_OS_MACOS
+	m_codeCompletionAction->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Space);
+#else
+	m_codeCompletionAction->setShortcut(Qt::CTRL | Qt::Key_Space);
+#endif
+	m_codeCompletionAction->setWhatsThis(QStringLiteral("Manually trigger code completion"));
+	connect(m_codeCompletionAction, &QAction::triggered, [this]() {
+		if (m_completionModel && m_kTextEditorView) {
+			auto cursor = m_kTextEditorView->cursorPosition();
+			auto wordRange = m_kTextEditorView->document()->wordRangeAt(cursor);
+			m_kTextEditorView->startCompletion(wordRange, m_completionModel);
+		}
+	});
+	m_kTextEditorView->addAction(m_codeCompletionAction);
 }
 
 ScriptEditor::~ScriptEditor() {
@@ -354,152 +368,3 @@ void ScriptEditor::showOutputContextMenu(const QPoint& pos) {
 
 	menu.exec(ui.output->mapToGlobal(pos));
 }
-
-void ScriptEditor::showCustomCompleter() {
-	if (!m_completionModel)
-		return;
-
-	// Get matches from the completion model
-	const auto& matches = m_completionModel->matches();
-
-	// Hide completer if no matches
-	if (matches.isEmpty()) {
-		if (m_customCompleter && m_customCompleter->isVisible())
-			m_customCompleter->hide();
-		return;
-	}
-
-	// Create completer widget on first use
-	if (!m_customCompleter) {
-		m_customCompleter = new QListWidget();
-		m_customCompleter->setWindowFlags(Qt::Popup);
-		m_customCompleter->setFocusPolicy(Qt::NoFocus);
-		m_customCompleter->setFocusProxy(m_kTextEditorView);
-		m_customCompleter->installEventFilter(this);
-
-		connect(m_customCompleter, &QListWidget::itemActivated, this, &ScriptEditor::onCompleterItemSelected);
-		connect(m_customCompleter, &QListWidget::itemClicked, this, &ScriptEditor::onCompleterItemSelected);
-	}
-
-	// Populate with matches
-	m_customCompleter->clear();
-	for (const auto& item : matches) {
-		QString displayText = item.name;
-		// Add icon or indicator for type
-		if (item.isClass)
-			displayText += QStringLiteral("  [class]");
-		else if (item.isFunction)
-			displayText += QStringLiteral("  [function]");
-		else if (item.isVariable)
-			displayText += QStringLiteral("  [variable]");
-
-		auto* listItem = new QListWidgetItem(displayText, m_customCompleter);
-		listItem->setData(Qt::UserRole, item.name); // Store clean name for insertion
-
-		// Set tooltip with signature and docstring if available
-		QString tooltip;
-		if (!item.signature.isEmpty())
-			tooltip = item.signature;
-		else if (item.isFunction || item.isClass)
-			tooltip = item.name + QStringLiteral("()");
-
-		if (!item.docstring.isEmpty()) {
-			if (!tooltip.isEmpty())
-				tooltip += QStringLiteral("\n\n");
-			tooltip += item.docstring;
-		}
-
-		if (!tooltip.isEmpty())
-			listItem->setToolTip(tooltip);
-	}
-	m_customCompleter->setCurrentRow(0);
-
-	// Position below cursor
-	KTextEditor::Cursor cursor = m_kTextEditorView->cursorPosition();
-	QPoint cursorPos = m_kTextEditorView->cursorPositionCoordinates();
-	QPoint globalPos = m_kTextEditorView->mapToGlobal(cursorPos);
-
-	// Move below cursor line
-	globalPos.setY(globalPos.y() + m_kTextEditorView->fontMetrics().height());
-
-	m_customCompleter->move(globalPos);
-	m_customCompleter->setMinimumWidth(200);
-	m_customCompleter->setMaximumHeight(200);
-	m_customCompleter->show();
-}
-
-void ScriptEditor::onCompleterItemSelected() {
-	if (!m_customCompleter || !m_kTextEditorView)
-		return;
-
-	QListWidgetItem* item = m_customCompleter->currentItem();
-	if (!item)
-		return;
-
-	// Get clean name from UserRole data
-	QString textToInsert = item->data(Qt::UserRole).toString();
-
-	// Check if it's a function or class by looking at the display text
-	QString displayText = item->text();
-	bool isFunction = displayText.contains(QStringLiteral("[function]"));
-	bool isClass = displayText.contains(QStringLiteral("[class]"));
-
-	// Add parentheses for functions and classes
-	if (isFunction || isClass)
-		textToInsert += QStringLiteral("()");
-
-	// Replace current word with completion
-	KTextEditor::Cursor currentPos = m_kTextEditorView->cursorPosition();
-	KTextEditor::Range wordRange = m_kTextEditorView->document()->wordRangeAt(currentPos);
-	m_kTextEditorView->document()->replaceText(wordRange, textToInsert);
-
-	// Position cursor inside parentheses for functions/classes
-	if (isFunction || isClass) {
-		KTextEditor::Cursor newCursorPos = wordRange.start();
-		newCursorPos.setColumn(newCursorPos.column() + textToInsert.length() - 1); // -1 to be inside ()
-		m_kTextEditorView->setCursorPosition(newCursorPos);
-	}
-
-	// Hide completer
-	m_customCompleter->hide();
-
-	// Return focus to editor
-	m_kTextEditorView->setFocus();
-}
-
-bool ScriptEditor::eventFilter(QObject* obj, QEvent* event) {
-	if (obj == m_customCompleter && m_customCompleter->isVisible()) {
-		if (event->type() == QEvent::KeyPress) {
-			auto* keyEvent = static_cast<QKeyEvent*>(event);
-			switch (keyEvent->key()) {
-			case Qt::Key_Return:
-			case Qt::Key_Enter:
-				// Accept current selection
-				onCompleterItemSelected();
-				return true;
-
-			case Qt::Key_Escape:
-				// Hide completer
-				m_customCompleter->hide();
-				m_kTextEditorView->setFocus();
-				return true;
-
-			case Qt::Key_Up:
-			case Qt::Key_Down:
-			case Qt::Key_PageUp:
-			case Qt::Key_PageDown:
-				// Let the list widget handle navigation
-				return false;
-
-			default:
-				// Pass other keys to editor and hide completer
-				m_customCompleter->hide();
-				m_kTextEditorView->setFocus();
-				QApplication::sendEvent(m_kTextEditorView, event);
-				return true;
-			}
-		}
-	}
-
-	return QWidget::eventFilter(obj, event);
-}
diff --git a/src/frontend/script/ScriptEditor.h b/src/frontend/script/ScriptEditor.h
index eb5f19ae0d..aa2f8f892a 100644
--- a/src/frontend/script/ScriptEditor.h
+++ b/src/frontend/script/ScriptEditor.h
@@ -19,7 +19,6 @@ class QMenu;
 class Script;
 class QToolBar;
 class QToolButton;
-class QListWidget;
 class ScriptCompletionModel;
 
 namespace KTextEditor{
@@ -41,14 +40,9 @@ public:
 	void registerShortcuts();
 	void unregisterShortcuts();
 
-protected:
-	bool eventFilter(QObject* obj, QEvent* event) override;
-
 private Q_SLOTS:
 	void handleAnchorClicked(const QUrl&);
 	void showOutputContextMenu(const QPoint&);
-	void showCustomCompleter();
-	void onCompleterItemSelected();
 
 public Q_SLOTS:
 	void createContextMenu(QMenu*);
@@ -63,8 +57,8 @@ private:
 	QAction* m_clearOutputAction{nullptr};
 	QAction* m_copySelectedAction{nullptr};
 	QAction* m_copyAllOutputAction{nullptr};
+	QAction* m_codeCompletionAction{nullptr};
 	ScriptCompletionModel* m_completionModel{nullptr};
-	QListWidget* m_customCompleter{nullptr};
 
 	void initActions();
 	void initMenus();
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.