diff --git a/src/plugins/lua/bindings/texteditor.cpp b/src/plugins/lua/bindings/texteditor.cpp index 047df08cc3a..23d8ca8d092 100644 --- a/src/plugins/lua/bindings/texteditor.cpp +++ b/src/plugins/lua/bindings/texteditor.cpp @@ -3,13 +3,138 @@ #include "../luaengine.h" -#include - #include +#include #include +#include #include "sol/sol.hpp" +namespace { + +class Suggestion +{ +public: + Suggestion( + Utils::Text::Position start, + Utils::Text::Position end, + Utils::Text::Position position, + const QString &text) + : m_start(start) + , m_end(end) + , m_position(position) + , m_text(text) + {} + + Suggestion(const Suggestion &other) + : m_start(other.m_start) + , m_end(other.m_end) + , m_position(other.m_position) + , m_text(other.m_text) + {} + + Utils::Text::Position start() const { return m_start; } + Utils::Text::Position end() const { return m_end; } + Utils::Text::Position position() const { return m_position; } + QString text() const { return m_text; } + +private: + Utils::Text::Position m_start; + Utils::Text::Position m_end; + Utils::Text::Position m_position; + QString m_text; +}; + +QTextCursor toTextCursor(QTextDocument *doc, const Utils::Text::Position &position) +{ + QTextCursor cursor(doc); + cursor.setPosition(position.toPositionInDocument(doc)); + return cursor; +} + +QTextCursor toSelection( + QTextDocument *doc, const Utils::Text::Position &start, const Utils::Text::Position &end) +{ + QTC_ASSERT(doc, return {}); + QTextCursor cursor = toTextCursor(doc, start); + cursor.setPosition(end.toPositionInDocument(doc), QTextCursor::KeepAnchor); + + return cursor; +} + +class CyclicSuggestion : public TextEditor::TextSuggestion +{ +public: + CyclicSuggestion( + const QList &suggestions, QTextDocument *origin, int current_suggestion = 0) + : m_current_suggestion(current_suggestion) + , m_suggestions(suggestions) + { + QTC_ASSERT(current_suggestion < suggestions.size(), return); + const auto &suggestion = m_suggestions.at(m_current_suggestion); + const auto start = suggestion.start(); + const auto end = suggestion.end(); + + QString text = toTextCursor(origin, start).block().text(); + int length = text.length() - start.column; + if (start.line == end.line) + length = end.column - start.column; + + text.replace(start.column, length, suggestion.text()); + document()->setPlainText(text); + + m_start = toTextCursor(origin, suggestion.position()); + m_start.setKeepPositionOnInsert(true); + setCurrentPosition(m_start.position()); + } + + virtual bool apply() override + { + QTC_ASSERT(m_current_suggestion < m_suggestions.size(), return false); + reset(); + const auto &suggestion = m_suggestions.at(m_current_suggestion); + QTextCursor cursor = toSelection(m_start.document(), suggestion.start(), suggestion.end()); + cursor.insertText(suggestion.text()); + return true; + } + + // Returns true if the suggestion was applied completely, false if it was only partially applied. + virtual bool applyWord(TextEditor::TextEditorWidget *widget) override + { + QTC_ASSERT(m_current_suggestion < m_suggestions.size(), return false); + const auto &suggestion = m_suggestions.at(m_current_suggestion); + QTextCursor cursor = toSelection(m_start.document(), suggestion.start(), suggestion.end()); + QTextCursor currentCursor = widget->textCursor(); + const QString text = suggestion.text(); + const int startPos = currentCursor.positionInBlock() - cursor.positionInBlock() + + (cursor.selectionEnd() - cursor.selectionStart()); + const int next = Utils::endOfNextWord(text, startPos); + + if (next == -1) + return apply(); + + // TODO: Allow adding more than one line + QString subText = text.mid(startPos, next - startPos); + subText = subText.left(subText.indexOf('\n')); + if (subText.isEmpty()) + return false; + + currentCursor.insertText(subText); + return false; + } + + virtual void reset() override { m_start.removeSelectedText(); } + + virtual int position() override { return m_start.selectionEnd(); } + +private: + int m_current_suggestion; + QTextCursor m_start; + QList m_suggestions; +}; + +} // namespace + namespace Lua::Internal { class TextEditorRegistry : public QObject @@ -132,6 +257,20 @@ void addTextEditorModule() return textEditor->editorWidget()->multiTextCursor(); }); + result.new_usertype( + "Suggestion", + "create", + [](int start_line, + int start_character, + int end_line, + int end_character, + const QString &text) -> Suggestion { + auto one_based = [](int zero_based) { return zero_based + 1; }; + Utils::Text::Position start_pos = {one_based(start_line), start_character}; + Utils::Text::Position end_pos = {one_based(end_line), end_character}; + return {start_pos, end_pos, start_pos, text}; + }); + result.new_usertype( "TextDocument", sol::no_constructor, @@ -149,7 +288,26 @@ void addTextEditorModule() return std::make_pair(block.blockNumber() + 1, column + 1); }, "blockCount", - [](TextEditor::TextDocument *document) { return document->document()->blockCount(); }); + [](TextEditor::TextDocument *document) { return document->document()->blockCount(); }, + + "setSuggestions", + [](TextEditor::TextDocument *document, QList suggestions) { + if (suggestions.isEmpty()) + return; + + const auto textEditor = TextEditor::BaseTextEditor::currentTextEditor(); + if (!textEditor || textEditor->document() != document) + return; + + auto *widget = textEditor->editorWidget(); + if (widget->isReadOnly() || widget->multiTextCursor().hasMultipleCursors()) + return; + + widget->insertSuggestion( + std::make_unique(suggestions, document->document())); + } + + ); return result; }); diff --git a/src/plugins/lua/meta/texteditor.lua b/src/plugins/lua/meta/texteditor.lua index d6e9b764c01..d74b2b0131a 100644 --- a/src/plugins/lua/meta/texteditor.lua +++ b/src/plugins/lua/meta/texteditor.lua @@ -29,6 +29,17 @@ function TextDocument:blockAndColumn(position) end ---@return integer blockCount The number of blocks in the document. function TextDocument:blockCount() end +---@class Suggestion +local Suggestion = {} + +---@param startLine integer Start position line where to apply the suggestion. +---@param startCharacter integer Start position character where to apply the suggestion. +---@param endLine integer End position line where to apply the suggestion. +---@param endCharacter integer End position character where to apply the suggestion. +---@param text string Suggestions text. +---@return Suggestion suggestion The created suggestion. +function Suggestion:create(startLine, startCharacter, endLine, endCharacter, text) end + ---@class TextEditor local TextEditor = {} @@ -40,6 +51,9 @@ function TextEditor:document() end ---@return MultiTextCursor cursor The cursor of the editor. function TextEditor:cursor() end +---@param suggestions Suggestion[] A list of possible suggestions to display +function TextEditor:setSuggestions(suggestions) end + ---Returns the current editor or nil. ---@return TextEditor|nil editor The currently active editor or nil if there is none. function textEditor.currentEditor() end