KTextEditor

completionreplayer.cpp
1/*
2 SPDX-FileCopyrightText: KDE Developers
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "completionreplayer.h"
8#include "completionrecorder.h"
9#include "katedocument.h"
10#include "katepartdebug.h"
11#include "kateview.h"
12#include "lastchangerecorder.h"
13#include "macrorecorder.h"
14#include <vimode/inputmodemanager.h>
15
16#include <QKeyEvent>
17#include <QRegularExpression>
18
19using namespace KateVi;
20
21CompletionReplayer::CompletionReplayer(InputModeManager *viInputModeManager)
22 : m_viInputModeManager(viInputModeManager)
23{
24}
25
26CompletionReplayer::~CompletionReplayer() = default;
27
28void CompletionReplayer::start(const CompletionList &completions)
29{
30 m_nextCompletionIndex.push(0);
31 m_CompletionsToReplay.push(completions);
32}
33
34void CompletionReplayer::stop()
35{
36 m_CompletionsToReplay.pop();
37 m_nextCompletionIndex.pop();
38}
39
40void CompletionReplayer::replay()
41{
42 const Completion completion = nextCompletion();
43 KTextEditor::ViewPrivate *m_view = m_viInputModeManager->view();
44 KTextEditor::DocumentPrivate *doc = m_view->doc();
45
46 // Find beginning of the word.
47 KTextEditor::Cursor cursorPos = m_view->cursorPosition();
49 if (!doc->characterAt(cursorPos).isLetterOrNumber() && doc->characterAt(cursorPos) != QLatin1Char('_')) {
50 cursorPos.setColumn(cursorPos.column() - 1);
51 }
52 while (cursorPos.column() >= 0 && (doc->characterAt(cursorPos).isLetterOrNumber() || doc->characterAt(cursorPos) == QLatin1Char('_'))) {
53 wordStart = cursorPos;
54 cursorPos.setColumn(cursorPos.column() - 1);
55 }
56 // Find end of current word.
57 cursorPos = m_view->cursorPosition();
58 KTextEditor::Cursor wordEnd = KTextEditor::Cursor(cursorPos.line(), cursorPos.column() - 1);
59 while (cursorPos.column() < doc->lineLength(cursorPos.line())
60 && (doc->characterAt(cursorPos).isLetterOrNumber() || doc->characterAt(cursorPos) == QLatin1Char('_'))) {
61 wordEnd = cursorPos;
62 cursorPos.setColumn(cursorPos.column() + 1);
63 }
64 QString completionText = completion.completedText();
65 const KTextEditor::Range currentWord = KTextEditor::Range(wordStart, KTextEditor::Cursor(wordEnd.line(), wordEnd.column() + 1));
66 // Should we merge opening brackets? Yes, if completion is a function with arguments and after the cursor
67 // there is (optional whitespace) followed by an open bracket.
68 int offsetFinalCursorPosBy = 0;
69 if (completion.completionType() == Completion::FunctionWithArgs) {
70 const int nextMergableBracketAfterCursorPos = findNextMergeableBracketPos(currentWord.end());
71 if (nextMergableBracketAfterCursorPos != -1) {
72 if (completionText.endsWith(QLatin1String("()"))) {
73 // Strip "()".
74 completionText.chop(2);
75 } else if (completionText.endsWith(QLatin1String("();"))) {
76 // Strip "();".
77 completionText.chop(3);
78 }
79 // Ensure cursor ends up after the merged open bracket.
80 offsetFinalCursorPosBy = nextMergableBracketAfterCursorPos + 1;
81 } else {
82 if (!completionText.endsWith(QLatin1String("()")) && !completionText.endsWith(QLatin1String("();"))) {
83 // Original completion merged with an opening bracket; we'll have to add our own brackets.
84 completionText.append(QLatin1String("()"));
85 }
86 // Position cursor correctly i.e. we'll have added "functionname()" or "functionname();"; need to step back by
87 // one or two to be after the opening bracket.
88 offsetFinalCursorPosBy = completionText.endsWith(QLatin1Char(';')) ? -2 : -1;
89 }
90 }
91 KTextEditor::Cursor deleteEnd =
92 completion.removeTail() ? currentWord.end() : KTextEditor::Cursor(m_view->cursorPosition().line(), m_view->cursorPosition().column() + 0);
93
94 if (currentWord.isValid()) {
95 doc->removeText(KTextEditor::Range(currentWord.start(), deleteEnd));
96 doc->insertText(currentWord.start(), completionText);
97 } else {
98 doc->insertText(m_view->cursorPosition(), completionText);
99 }
100
101 if (offsetFinalCursorPosBy != 0) {
102 m_view->setCursorPosition(KTextEditor::Cursor(m_view->cursorPosition().line(), m_view->cursorPosition().column() + offsetFinalCursorPosBy));
103 }
104
105 if (!m_viInputModeManager->lastChangeRecorder()->isReplaying()) {
106 Q_ASSERT(m_viInputModeManager->macroRecorder()->isReplaying());
107 // Post the completion back: it needs to be added to the "last change" list ...
108 m_viInputModeManager->completionRecorder()->logCompletionEvent(completion);
109 // ... but don't log the ctrl-space that led to this call to replayCompletion(), as
110 // a synthetic ctrl-space was just added to the last change keypresses by logCompletionEvent(), and we don't
111 // want to duplicate them!
112 m_viInputModeManager->doNotLogCurrentKeypress();
113 }
114}
115
116Completion CompletionReplayer::nextCompletion()
117{
118 Q_ASSERT(m_viInputModeManager->lastChangeRecorder()->isReplaying() || m_viInputModeManager->macroRecorder()->isReplaying());
119
120 if (m_nextCompletionIndex.top() >= m_CompletionsToReplay.top().length()) {
121 qCDebug(LOG_KTE) << "Something wrong here: requesting more completions for macro than we actually have. Returning dummy.";
122 return Completion(QString(), false, Completion::PlainText);
123 }
124
125 return m_CompletionsToReplay.top()[m_nextCompletionIndex.top()++];
126}
127
128int CompletionReplayer::findNextMergeableBracketPos(const KTextEditor::Cursor startPos) const
129{
130 KTextEditor::DocumentPrivate *doc = m_viInputModeManager->view()->doc();
131 const QString lineAfterCursor = doc->text(KTextEditor::Range(startPos, KTextEditor::Cursor(startPos.line(), doc->lineLength(startPos.line()))));
132 static const QRegularExpression whitespaceThenOpeningBracket(QStringLiteral("^\\s*(\\()"), QRegularExpression::UseUnicodePropertiesOption);
133 QRegularExpressionMatch match = whitespaceThenOpeningBracket.match(lineAfterCursor);
134 int nextMergableBracketAfterCursorPos = -1;
135 if (match.hasMatch()) {
136 nextMergableBracketAfterCursorPos = match.capturedStart(1);
137 }
138 return nextMergableBracketAfterCursorPos;
139}
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int column() const noexcept
Retrieve the column on which this cursor is situated.
Definition cursor.h:192
void setColumn(int column) noexcept
Set the cursor column to column.
Definition cursor.h:201
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
static constexpr Cursor invalid() noexcept
Returns an invalid cursor.
Definition cursor.h:112
Backend of KTextEditor::Document related public KTextEditor interfaces.
QString text(KTextEditor::Range range, bool blockwise=false) const override
Get the document content within the given range.
QChar characterAt(KTextEditor::Cursor position) const override
Get the character at text position cursor.
int lineLength(int line) const override
Get the length of a given line in characters.
An object representing a section of text, from one Cursor to another.
constexpr Cursor end() const noexcept
Get the end position of this range.
constexpr Cursor start() const noexcept
Get the start position of this range.
constexpr bool isValid() const noexcept
Validity check.
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
const QList< QKeySequence > & completion()
bool isLetterOrNumber(char32_t ucs4)
void push(const T &t)
T & top()
QString & append(QChar ch)
void chop(qsizetype n)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:11:27 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.