fix: 修复损坏的代码填充提示;修复控制台能被清空代码的问题;
This commit is contained in:
parent
5efff1b4c0
commit
6b020b7f70
|
@ -222,7 +222,7 @@ void QConsoleWidget::keyPressEvent(QKeyEvent *e) {
|
|||
cut();
|
||||
else {
|
||||
// cursor must be in edit zone
|
||||
if (textCursor <= inpos_)
|
||||
if (textCursor.selectionStart() <= inpos_)
|
||||
QApplication::beep();
|
||||
else
|
||||
QEditor::keyPressEvent(e);
|
||||
|
@ -264,7 +264,7 @@ bool QConsoleWidget::isSelectionInEditZone() const {
|
|||
auto selectionStart = textCursor.selectionStart();
|
||||
auto selectionEnd = textCursor.selectionEnd();
|
||||
|
||||
return selectionStart > inpos_ && selectionEnd >= inpos_;
|
||||
return selectionStart >= inpos_ && selectionEnd > inpos_;
|
||||
}
|
||||
|
||||
bool QConsoleWidget::isCursorInEditZone() const { return cursor() >= inpos_; }
|
||||
|
|
|
@ -29,8 +29,6 @@ set(DOCUMENT_SRC
|
|||
lib/document/qdocument.cpp)
|
||||
|
||||
set(WIDGETS_SRC
|
||||
lib/widgets/qcalltip.cpp
|
||||
lib/widgets/qcalltip.h
|
||||
lib/widgets/qfoldpanel.cpp
|
||||
lib/widgets/qfoldpanel.h
|
||||
lib/widgets/qlinechangepanel.cpp
|
||||
|
|
|
@ -123,8 +123,8 @@ void QCodeCompletionEngine::run() {
|
|||
/*!
|
||||
\brief Forced completion trigger
|
||||
*/
|
||||
void QCodeCompletionEngine::complete() {
|
||||
complete(editor()->cursor(), QString());
|
||||
void QCodeCompletionEngine::complete(const QString &trigger) {
|
||||
complete(editor()->cursor(), trigger);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -68,15 +68,16 @@ signals:
|
|||
void completionTriggered(const QString &s);
|
||||
|
||||
public slots:
|
||||
void complete();
|
||||
void complete(const QString &trigger = {});
|
||||
// make it public
|
||||
virtual void complete(const QDocumentCursor &c, const QString &trigger);
|
||||
|
||||
void textEdited(QKeyEvent *e);
|
||||
|
||||
protected:
|
||||
virtual void run();
|
||||
virtual bool eventFilter(QObject *o, QEvent *e);
|
||||
|
||||
virtual void complete(const QDocumentCursor &c, const QString &trigger);
|
||||
|
||||
private:
|
||||
void triggerWordLenComplete();
|
||||
|
||||
|
|
|
@ -1938,6 +1938,11 @@ void QEditor::timerEvent(QTimerEvent *e) {
|
|||
// startDrag();
|
||||
} else if (id == m_click.timerId()) {
|
||||
m_click.stop();
|
||||
} else if (id == m_inputto.timerId()) {
|
||||
m_inputto.stop();
|
||||
if (!m_blink.isActive()) {
|
||||
emit inputTimeOuted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2850,7 +2855,7 @@ void QEditor::focusInEvent(QFocusEvent *e) {
|
|||
void QEditor::focusOutEvent(QFocusEvent *e) {
|
||||
setFlag(CursorOn, false);
|
||||
m_blink.stop();
|
||||
|
||||
m_inputto.start(3000, this);
|
||||
QAbstractScrollArea::focusOutEvent(e);
|
||||
}
|
||||
|
||||
|
@ -3439,7 +3444,8 @@ bool QEditor::processCursor(QDocumentCursor &c, QKeyEvent *e, bool &b) {
|
|||
}
|
||||
} else {
|
||||
if (isPairedCloseChar(text)) {
|
||||
if (c.previousChar() == getPairedBeginChar(text)) {
|
||||
if (c.previousChar() == getPairedBeginChar(text) &&
|
||||
c.nextChar() == text) {
|
||||
c.movePosition(1);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -307,6 +307,8 @@ signals:
|
|||
|
||||
void zoomed();
|
||||
|
||||
void inputTimeOuted();
|
||||
|
||||
public slots:
|
||||
void checkClipboard();
|
||||
void reconnectWatcher();
|
||||
|
@ -443,7 +445,7 @@ protected:
|
|||
bool m_selection;
|
||||
QRect m_crect, m_margins;
|
||||
QPoint m_clickPoint, m_dragPoint;
|
||||
QBasicTimer m_blink, m_scroll, m_click, m_drag;
|
||||
QBasicTimer m_blink, m_scroll, m_click, m_drag, m_inputto;
|
||||
|
||||
QFont _docfont;
|
||||
qreal _scaleRate = 1.0;
|
||||
|
|
|
@ -1,280 +0,0 @@
|
|||
/*==============================================================================
|
||||
** Copyright (C) 2024-2027 WingSummer
|
||||
**
|
||||
** This program is free software: you can redistribute it and/or modify it under
|
||||
** the terms of the GNU Affero General Public License as published by the Free
|
||||
** Software Foundation, version 3.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
** FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
** details.
|
||||
**
|
||||
** You should have received a copy of the GNU Affero General Public License
|
||||
** along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
** =============================================================================
|
||||
*/
|
||||
|
||||
#include "qcalltip.h"
|
||||
|
||||
/*!
|
||||
\file qcalltip.cpp
|
||||
\brief Implementation of the QCallTip class
|
||||
*/
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
|
||||
#include <QToolTip>
|
||||
|
||||
/*!
|
||||
\class QCallTip
|
||||
\brief A widget dedicated to calltips display
|
||||
*/
|
||||
|
||||
QCallTip::QCallTip(QWidget *p) : QWidget(p), m_index(0) {
|
||||
setCursor(Qt::ArrowCursor);
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
auto palette = QToolTip::palette();
|
||||
_background = palette.color(QPalette::ToolTipBase);
|
||||
_foreground = palette.color(QPalette::ToolTipText);
|
||||
_borderColor = palette.color(QPalette::Shadow);
|
||||
}
|
||||
|
||||
QCallTip::~QCallTip() {}
|
||||
|
||||
QStringList QCallTip::tips() const { return m_tips; }
|
||||
|
||||
void QCallTip::setTips(const QStringList &l) {
|
||||
m_tips = l;
|
||||
m_index = 0;
|
||||
}
|
||||
|
||||
static int arrowWidth = 14;
|
||||
|
||||
void QCallTip::paintEvent(QPaintEvent *e) {
|
||||
Q_UNUSED(e)
|
||||
|
||||
QPainter p(this);
|
||||
p.setOpacity(_opacity);
|
||||
|
||||
QFontMetrics fm = fontMetrics();
|
||||
|
||||
m_up = m_down = QRect();
|
||||
|
||||
bool bPrev = m_index, bNext = (m_index + 1) < m_tips.count();
|
||||
int offset = 3, whalf = arrowWidth / 2 - 3; //, hhalf = height() / 2;
|
||||
|
||||
QRect bg(0, 0, fm.horizontalAdvance(m_tips.at(m_index)) + 6, fm.height());
|
||||
|
||||
if (bPrev) {
|
||||
bg.setWidth(bg.width() + arrowWidth);
|
||||
}
|
||||
|
||||
if (bNext) {
|
||||
bg.setWidth(bg.width() + arrowWidth);
|
||||
}
|
||||
|
||||
p.fillRect(bg, _background);
|
||||
// p.drawRect(bg);
|
||||
|
||||
p.save();
|
||||
|
||||
p.setPen(_borderColor);
|
||||
p.drawLine(0, height() - 1, bg.width() - 1, height() - 1);
|
||||
p.drawLine(bg.width() - 1, height() - 1, bg.width() - 1, 0);
|
||||
|
||||
p.drawLine(0, height() - 1, 0, 0);
|
||||
p.drawLine(0, 0, bg.width() - 1, 0);
|
||||
|
||||
int top = height() / 3, bottom = height() - height() / 3;
|
||||
|
||||
if (bPrev) {
|
||||
int x = offset + arrowWidth / 2 - 1;
|
||||
|
||||
QPoint pts[] = {QPoint(x - whalf, bottom), QPoint(x + whalf, bottom),
|
||||
QPoint(x, top)};
|
||||
|
||||
p.drawPolygon(pts, sizeof(pts) / sizeof(QPoint), Qt::WindingFill);
|
||||
|
||||
m_up = QRect(offset, 0, offset + arrowWidth, height());
|
||||
offset += arrowWidth;
|
||||
}
|
||||
|
||||
if (bNext) {
|
||||
int x = offset + arrowWidth / 2 - 1;
|
||||
|
||||
QPoint pts[] = {QPoint(x - whalf, top), QPoint(x + whalf, top),
|
||||
QPoint(x, bottom)};
|
||||
|
||||
p.drawPolygon(pts, sizeof(pts) / sizeof(QPoint), Qt::WindingFill);
|
||||
|
||||
m_down = QRect(offset, 0, offset + arrowWidth, height());
|
||||
offset += arrowWidth;
|
||||
}
|
||||
|
||||
p.setPen(_foreground);
|
||||
p.drawText(offset, fm.ascent() + 2, m_tips.at(m_index));
|
||||
|
||||
p.restore();
|
||||
|
||||
setFixedSize(bg.size() + QSize(1, 1));
|
||||
}
|
||||
|
||||
void QCallTip::keyPressEvent(QKeyEvent *e) {
|
||||
if (e->modifiers() &
|
||||
(Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier)) {
|
||||
close();
|
||||
|
||||
if (parentWidget())
|
||||
parentWidget()->setFocus();
|
||||
|
||||
e->ignore();
|
||||
return;
|
||||
}
|
||||
|
||||
QString text = e->text();
|
||||
|
||||
switch (e->key()) {
|
||||
case Qt::Key_Escape:
|
||||
close();
|
||||
|
||||
if (parentWidget())
|
||||
parentWidget()->setFocus();
|
||||
|
||||
e->accept();
|
||||
break;
|
||||
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Tab:
|
||||
// hide();
|
||||
|
||||
close();
|
||||
|
||||
if (parentWidget())
|
||||
parentWidget()->setFocus();
|
||||
|
||||
e->ignore();
|
||||
|
||||
break;
|
||||
|
||||
case Qt::Key_Up:
|
||||
|
||||
if (m_index)
|
||||
--m_index;
|
||||
|
||||
e->accept();
|
||||
update();
|
||||
break;
|
||||
|
||||
case Qt::Key_Down:
|
||||
|
||||
if ((m_index + 1) < m_tips.count())
|
||||
++m_index;
|
||||
|
||||
e->accept();
|
||||
update();
|
||||
break;
|
||||
|
||||
case Qt::Key_Backspace:
|
||||
|
||||
close();
|
||||
|
||||
if (parentWidget())
|
||||
parentWidget()->setFocus();
|
||||
|
||||
e->ignore();
|
||||
break;
|
||||
|
||||
case Qt::Key_Shift:
|
||||
case Qt::Key_Alt:
|
||||
case Qt::Key_Control:
|
||||
e->ignore();
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
if (text.size() && text.at(0).isPrint()) {
|
||||
|
||||
} else {
|
||||
close();
|
||||
|
||||
if (parentWidget())
|
||||
parentWidget()->setFocus();
|
||||
}
|
||||
|
||||
e->ignore();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void QCallTip::focusInEvent(QFocusEvent *e) { QWidget::focusInEvent(e); }
|
||||
|
||||
void QCallTip::focusOutEvent(QFocusEvent *e) {
|
||||
QWidget::focusOutEvent(e);
|
||||
|
||||
close();
|
||||
|
||||
if (parentWidget())
|
||||
parentWidget()->setFocus();
|
||||
}
|
||||
|
||||
void QCallTip::mousePressEvent(QMouseEvent *e) {
|
||||
if (m_index && m_up.isValid() && m_up.contains(e->pos())) {
|
||||
--m_index;
|
||||
} else if (((m_index + 1) < m_tips.count()) && m_down.isValid() &&
|
||||
m_down.contains(e->pos())) {
|
||||
++m_index;
|
||||
} else {
|
||||
close();
|
||||
|
||||
if (parentWidget())
|
||||
parentWidget()->setFocus();
|
||||
}
|
||||
|
||||
e->accept();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void QCallTip::mouseReleaseEvent(QMouseEvent *e) { e->accept(); }
|
||||
|
||||
qreal QCallTip::opacity() const { return _opacity; }
|
||||
|
||||
void QCallTip::setOpacity(qreal newOpacity) {
|
||||
if (qFuzzyCompare(_opacity, newOpacity))
|
||||
return;
|
||||
_opacity = newOpacity;
|
||||
emit opacityChanged();
|
||||
}
|
||||
|
||||
QColor QCallTip::borderColor() const { return _borderColor; }
|
||||
|
||||
void QCallTip::setBorderColor(const QColor &newBorderColor) {
|
||||
if (_borderColor == newBorderColor)
|
||||
return;
|
||||
_borderColor = newBorderColor;
|
||||
emit borderColorChanged();
|
||||
}
|
||||
|
||||
QColor QCallTip::foreground() const { return _foreground; }
|
||||
|
||||
void QCallTip::setForeground(const QColor &newForeground) {
|
||||
if (_foreground == newForeground)
|
||||
return;
|
||||
_foreground = newForeground;
|
||||
emit foregroundChanged();
|
||||
}
|
||||
|
||||
QColor QCallTip::background() const { return _background; }
|
||||
|
||||
void QCallTip::setBackground(const QColor &newBackground) {
|
||||
if (_background == newBackground)
|
||||
return;
|
||||
_background = newBackground;
|
||||
emit backgroundChanged();
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/*==============================================================================
|
||||
** Copyright (C) 2024-2027 WingSummer
|
||||
**
|
||||
** This program is free software: you can redistribute it and/or modify it under
|
||||
** the terms of the GNU Affero General Public License as published by the Free
|
||||
** Software Foundation, version 3.
|
||||
**
|
||||
** This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
** FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
** details.
|
||||
**
|
||||
** You should have received a copy of the GNU Affero General Public License
|
||||
** along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
** =============================================================================
|
||||
*/
|
||||
|
||||
#ifndef _QCALL_TIP_H_
|
||||
#define _QCALL_TIP_H_
|
||||
|
||||
#include "qce-config.h"
|
||||
|
||||
/*!
|
||||
\file qcalltip.h
|
||||
\brief Definition of the QCallTip class
|
||||
*/
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QCE_EXPORT QCallTip : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QColor background READ background WRITE setBackground NOTIFY
|
||||
backgroundChanged FINAL)
|
||||
Q_PROPERTY(QColor foreground READ foreground WRITE setForeground NOTIFY
|
||||
foregroundChanged FINAL)
|
||||
Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor NOTIFY
|
||||
borderColorChanged FINAL)
|
||||
Q_PROPERTY(
|
||||
qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged FINAL)
|
||||
|
||||
public:
|
||||
QCallTip(QWidget *p = nullptr);
|
||||
virtual ~QCallTip();
|
||||
|
||||
QStringList tips() const;
|
||||
void setTips(const QStringList &l);
|
||||
|
||||
QColor background() const;
|
||||
void setBackground(const QColor &newBackground);
|
||||
|
||||
QColor foreground() const;
|
||||
void setForeground(const QColor &newForeground);
|
||||
|
||||
QColor borderColor() const;
|
||||
void setBorderColor(const QColor &newBorderColor);
|
||||
|
||||
qreal opacity() const;
|
||||
void setOpacity(qreal newOpacity);
|
||||
|
||||
signals:
|
||||
void backgroundChanged();
|
||||
void foregroundChanged();
|
||||
void borderColorChanged();
|
||||
|
||||
void opacityChanged();
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent *e) override;
|
||||
virtual void keyPressEvent(QKeyEvent *e) override;
|
||||
virtual void focusInEvent(QFocusEvent *e) override;
|
||||
virtual void focusOutEvent(QFocusEvent *e) override;
|
||||
virtual void mousePressEvent(QMouseEvent *e) override;
|
||||
virtual void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
|
||||
private:
|
||||
int m_index;
|
||||
QStringList m_tips;
|
||||
QRect m_up, m_down;
|
||||
|
||||
QColor _background;
|
||||
QColor _foreground;
|
||||
QColor _borderColor;
|
||||
|
||||
qreal _opacity = 1;
|
||||
};
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -18,7 +18,6 @@
|
|||
#include "ascompletion.h"
|
||||
|
||||
#include "qasparser.h"
|
||||
#include "qcalltip.h"
|
||||
#include "qdocumentcursor.h"
|
||||
#include "qdocumentline.h"
|
||||
|
||||
|
@ -33,8 +32,9 @@
|
|||
#include "control/qcodecompletionwidget.h"
|
||||
|
||||
Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, DOT_TRIGGER, ("."))
|
||||
Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, SEMI_COLON_TRIGGER, ("::"))
|
||||
Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, DBL_COLON_TRIGGER, ("::"))
|
||||
Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, LEFT_PARE_TRIGGER, ("("))
|
||||
Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, SEMI_COLON_TRIGGER, (";"))
|
||||
|
||||
AsCompletion::AsCompletion(asIScriptEngine *engine, QObject *p)
|
||||
: QCodeCompletionEngine(p), parser(engine), _engine(engine),
|
||||
|
@ -42,10 +42,12 @@ AsCompletion::AsCompletion(asIScriptEngine *engine, QObject *p)
|
|||
Q_ASSERT(engine);
|
||||
|
||||
addTrigger(*DOT_TRIGGER);
|
||||
addTrigger(*SEMI_COLON_TRIGGER);
|
||||
addTrigger(*DBL_COLON_TRIGGER);
|
||||
|
||||
// unleash the power of call tips
|
||||
addTrigger(*LEFT_PARE_TRIGGER);
|
||||
// clear the tips
|
||||
addTrigger(*SEMI_COLON_TRIGGER);
|
||||
|
||||
setTrigWordLen(3);
|
||||
}
|
||||
|
@ -58,6 +60,9 @@ QCodeCompletionEngine *AsCompletion::clone() {
|
|||
for (auto &t : triggers())
|
||||
e->addTrigger(t);
|
||||
|
||||
connect(e, &AsCompletion::onFunctionTip, this,
|
||||
&AsCompletion::onFunctionTip);
|
||||
|
||||
emit cloned(e);
|
||||
|
||||
return e;
|
||||
|
@ -77,6 +82,12 @@ QStringList AsCompletion::extensions() const {
|
|||
void AsCompletion::complete(const QDocumentCursor &c, const QString &trigger) {
|
||||
auto txt = c.line().text();
|
||||
if (txt.isEmpty()) {
|
||||
emit onFunctionTip(this, {});
|
||||
return;
|
||||
}
|
||||
|
||||
if (trigger == *SEMI_COLON_TRIGGER) {
|
||||
emit onFunctionTip(this, {});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -97,8 +108,14 @@ void AsCompletion::complete(const QDocumentCursor &c, const QString &trigger) {
|
|||
|
||||
auto code = txt.mid(off, c.columnNumber() - off).toUtf8();
|
||||
|
||||
auto p = code.data();
|
||||
auto len = code.length();
|
||||
if (len < trigWordLen()) {
|
||||
emit onFunctionTip(this, {});
|
||||
pPopup->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
auto p = code.data();
|
||||
auto end = p + len;
|
||||
|
||||
struct Token {
|
||||
|
@ -113,6 +130,11 @@ void AsCompletion::complete(const QDocumentCursor &c, const QString &trigger) {
|
|||
for (; p < end;) {
|
||||
asUINT tokenLen = 0;
|
||||
auto tt = _engine->ParseToken(p, len, &tokenLen);
|
||||
if (tt == asTC_WHITESPACE) {
|
||||
p += tokenLen;
|
||||
pos += tokenLen;
|
||||
continue;
|
||||
}
|
||||
Token token;
|
||||
token.pos = pos;
|
||||
token.type = tt;
|
||||
|
@ -122,143 +144,121 @@ void AsCompletion::complete(const QDocumentCursor &c, const QString &trigger) {
|
|||
pos += tokenLen;
|
||||
}
|
||||
|
||||
auto r =
|
||||
std::find_if(tokens.rbegin(), tokens.rend(), [](const Token &token) {
|
||||
return token.type != asTC_WHITESPACE;
|
||||
});
|
||||
if (r == tokens.rend()) {
|
||||
return;
|
||||
}
|
||||
auto getNamespace = [](const QVector<Token> &tokens) -> QByteArrayList {
|
||||
auto rbegin = tokens.rbegin();
|
||||
auto rend = tokens.rend();
|
||||
|
||||
QByteArrayList nss;
|
||||
bool semiFlag = true;
|
||||
auto p = rbegin;
|
||||
for (; p != rend; ++p) {
|
||||
if (semiFlag) {
|
||||
if (p->type != asTC_IDENTIFIER) {
|
||||
break;
|
||||
} else {
|
||||
nss.prepend(p->content);
|
||||
semiFlag = false;
|
||||
}
|
||||
} else {
|
||||
if (p->content != *DBL_COLON_TRIGGER) {
|
||||
break;
|
||||
}
|
||||
semiFlag = true;
|
||||
}
|
||||
}
|
||||
return nss;
|
||||
};
|
||||
|
||||
QByteArray fn;
|
||||
QList<QCodeNode *> nodes;
|
||||
|
||||
QCodeCompletionWidget::Filter filter =
|
||||
QCodeCompletionWidget::FilterFlag::KeepAll;
|
||||
auto &_headerNodes = parser.headerNodes();
|
||||
QFlags<QCodeCompletionWidget::FilterFlag> filter(
|
||||
QCodeCompletionWidget::FilterFlag::Public |
|
||||
QCodeCompletionWidget::FilterFlag::KeepAllFunctions |
|
||||
QCodeCompletionWidget::FilterFlag::KeepAllSub);
|
||||
|
||||
if (trigger.isEmpty() && trigWordLen() <= r->content.length()) {
|
||||
auto eb = tokens.back();
|
||||
if (eb.type == asTC_KEYWORD) {
|
||||
if (tokens.isEmpty()) {
|
||||
pPopup->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
QString prefix;
|
||||
auto etoken = tokens.back();
|
||||
|
||||
// if trigger is empty, it's making editing
|
||||
if (trigger.isEmpty()) {
|
||||
// it can not be any trigger, so take the last as prefix
|
||||
prefix = etoken.content;
|
||||
tokens.removeLast();
|
||||
if (tokens.isEmpty()) {
|
||||
applyEmptyNsNode(nodes);
|
||||
} else {
|
||||
etoken = tokens.back(); // checking later
|
||||
}
|
||||
}
|
||||
|
||||
if (nodes.isEmpty()) {
|
||||
if (etoken.type == asTC_KEYWORD) {
|
||||
// only support these
|
||||
if (eb.content == *SEMI_COLON_TRIGGER) {
|
||||
complete(c, *SEMI_COLON_TRIGGER);
|
||||
} else if (eb.content == *DOT_TRIGGER) {
|
||||
complete(c, *DOT_TRIGGER);
|
||||
auto cur = c;
|
||||
cur.setColumnNumber(off + etoken.pos);
|
||||
if (etoken.content == *DBL_COLON_TRIGGER) {
|
||||
complete(cur, *DBL_COLON_TRIGGER);
|
||||
pPopup->setCursor(c);
|
||||
pPopup->setPrefix(prefix);
|
||||
return;
|
||||
} else if (etoken.content == *DOT_TRIGGER) {
|
||||
complete(cur, *DOT_TRIGGER);
|
||||
pPopup->setCursor(c);
|
||||
pPopup->setPrefix(prefix);
|
||||
return;
|
||||
} else {
|
||||
pPopup->hide();
|
||||
}
|
||||
return;
|
||||
} else if (eb.type == asTC_IDENTIFIER) {
|
||||
fn = eb.content;
|
||||
|
||||
// completion for a.b.c or a::b.c or a::b::c.d or ::a::b.c
|
||||
// first validate
|
||||
auto rbegin = tokens.rbegin();
|
||||
auto rend = tokens.rend();
|
||||
|
||||
rbegin++;
|
||||
if (rbegin == rend) {
|
||||
applyEmptyNsNode(nodes);
|
||||
} else {
|
||||
if (rbegin->content == SEMI_COLON_TRIGGER) {
|
||||
QByteArrayList nss;
|
||||
bool semiFlag = true;
|
||||
auto p = rbegin;
|
||||
for (; p != rend; ++p) {
|
||||
if (semiFlag) {
|
||||
if (p->type != asTC_IDENTIFIER) {
|
||||
break;
|
||||
} else {
|
||||
nss.prepend(p->content);
|
||||
semiFlag = false;
|
||||
}
|
||||
} else {
|
||||
if (p->content != SEMI_COLON_TRIGGER) {
|
||||
break;
|
||||
}
|
||||
semiFlag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (etoken.type != asTC_IDENTIFIER) {
|
||||
pPopup->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
nodes = lookupNamespace(nss);
|
||||
if (nodes.isEmpty()) {
|
||||
auto &_headerNodes = parser.headerNodes();
|
||||
|
||||
if (etoken.content.length() >= trigWordLen()) {
|
||||
// completion for a.b.c or a::b.c or a::b::c.d or ::a::b.c
|
||||
if (trigger == *DBL_COLON_TRIGGER) {
|
||||
auto ns = getNamespace(tokens);
|
||||
auto idx = tokens.length() - ns.length() * 2;
|
||||
if (idx >= 0) {
|
||||
if (tokens.at(idx).content == *DOT_TRIGGER) {
|
||||
pPopup->hide();
|
||||
return;
|
||||
}
|
||||
} else if (rbegin->content == DOT_TRIGGER) {
|
||||
// TODO only PR
|
||||
} else {
|
||||
}
|
||||
nodes = lookupNamespace(ns);
|
||||
if (nodes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
} else if (trigger == *LEFT_PARE_TRIGGER) {
|
||||
// the first is function name, an identifier
|
||||
fn = etoken.content;
|
||||
tokens.removeLast();
|
||||
if (!tokens.isEmpty()) {
|
||||
if (tokens.back().content == *DBL_COLON_TRIGGER) {
|
||||
tokens.removeLast();
|
||||
}
|
||||
}
|
||||
auto ns = getNamespace(tokens);
|
||||
auto idx = tokens.length() - ns.length() * 2 + 1;
|
||||
if (idx >= 0 && idx < tokens.length()) {
|
||||
if (tokens.at(idx).content == *DOT_TRIGGER) {
|
||||
pPopup->hide();
|
||||
return;
|
||||
}
|
||||
}
|
||||
nodes = lookupNamespace(ns);
|
||||
if (nodes.isEmpty()) {
|
||||
applyEmptyNsNode(nodes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// pPopup->setTemporaryNodes(temp);
|
||||
pPopup->setPrefix(fn);
|
||||
pPopup->setFilter(filter);
|
||||
pPopup->setCompletions(nodes);
|
||||
pPopup->popup();
|
||||
} else {
|
||||
if (trigger == *SEMI_COLON_TRIGGER) {
|
||||
auto rbegin = tokens.rbegin();
|
||||
auto rend = tokens.rend();
|
||||
|
||||
QByteArrayList nss;
|
||||
bool semiFlag = true;
|
||||
auto p = rbegin;
|
||||
for (; p != rend; ++p) {
|
||||
if (semiFlag) {
|
||||
if (p->type != asTC_IDENTIFIER) {
|
||||
break;
|
||||
} else {
|
||||
nss.prepend(p->content);
|
||||
semiFlag = false;
|
||||
}
|
||||
} else {
|
||||
if (p->content != *SEMI_COLON_TRIGGER) {
|
||||
break;
|
||||
}
|
||||
semiFlag = true;
|
||||
}
|
||||
}
|
||||
|
||||
nodes = lookupNamespace(nss);
|
||||
if (nodes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
} else if (trigger == *LEFT_PARE_TRIGGER) {
|
||||
if (r != tokens.rend()) {
|
||||
auto pr = std::next(r);
|
||||
if (pr != tokens.rend()) {
|
||||
if (pr->content == *SEMI_COLON_TRIGGER) {
|
||||
if (pr != tokens.rend()) {
|
||||
auto prr = std::next(pr);
|
||||
auto ns = prr->content;
|
||||
if (prr->type == asTC_IDENTIFIER) {
|
||||
for (auto &n : _headerNodes) {
|
||||
auto name = n->qualifiedName();
|
||||
if (name == ns) {
|
||||
nodes << n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nodes.count()) {
|
||||
if (trigger == *LEFT_PARE_TRIGGER) {
|
||||
QStringList tips;
|
||||
|
||||
// qDebug("fn %s", fn.constData());
|
||||
|
||||
QString tip;
|
||||
for (auto &n : nodes) {
|
||||
for (auto &f : n->children()) {
|
||||
if (f->type() != QCodeNode::Function ||
|
||||
|
@ -266,55 +266,34 @@ void AsCompletion::complete(const QDocumentCursor &c, const QString &trigger) {
|
|||
continue;
|
||||
}
|
||||
|
||||
auto tip =
|
||||
QString::fromUtf8(f->role(QCodeNode::Arguments))
|
||||
.prepend('(')
|
||||
.append(')');
|
||||
|
||||
if (!tips.contains(tip))
|
||||
tips << tip;
|
||||
tip = QString::fromUtf8(f->role(QCodeNode::Return)) +
|
||||
QLatin1Char(' ') +
|
||||
QString::fromUtf8(f->role(QCodeNode::Name)) +
|
||||
QString::fromUtf8(f->role(QCodeNode::Arguments))
|
||||
.prepend('(')
|
||||
.append(')');
|
||||
break;
|
||||
}
|
||||
if (!tip.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!tips.isEmpty()) {
|
||||
QRect r = editor()->cursorRect();
|
||||
QDocumentCursor cursor = editor()->cursor();
|
||||
QDocumentLine line = cursor.line();
|
||||
|
||||
int hx = editor()->horizontalOffset(),
|
||||
cx = line.cursorToX(cursor.columnNumber());
|
||||
|
||||
auto ct = new QCallTip(editor()->viewport());
|
||||
ct->move(cx - hx, r.y() + r.height());
|
||||
ct->setTips(tips);
|
||||
ct->show();
|
||||
ct->setFocus();
|
||||
|
||||
#ifdef TRACE_COMPLETION
|
||||
qDebug("parsing + scoping + search + pre-display : elapsed "
|
||||
"%i ms",
|
||||
time.elapsed());
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
// pPopup->setTemporaryNodes(temp);
|
||||
pPopup->setPrefix({});
|
||||
pPopup->setFilter(QCodeCompletionWidget::Filter(filter));
|
||||
pPopup->setCompletions(nodes);
|
||||
|
||||
#ifdef TRACE_COMPLETION
|
||||
qDebug(
|
||||
"parsing + scoping + search + pre-display : elapsed %i ms",
|
||||
time.elapsed());
|
||||
#endif
|
||||
|
||||
pPopup->popup();
|
||||
emit onFunctionTip(this, tip);
|
||||
}
|
||||
} else {
|
||||
// qDeleteAll(temp);
|
||||
// qDebug("completion failed");
|
||||
}
|
||||
}
|
||||
|
||||
auto cur = c;
|
||||
cur.movePosition(trigger.length());
|
||||
pPopup->setCursor(cur);
|
||||
pPopup->setPrefix(prefix);
|
||||
pPopup->setFilter(filter);
|
||||
pPopup->setCompletions(nodes);
|
||||
|
||||
if (!pPopup->isCompleting()) {
|
||||
pPopup->popup();
|
||||
}
|
||||
}
|
||||
|
||||
void AsCompletion::applyEmptyNsNode(QList<QCodeNode *> &nodes) {
|
||||
|
|
|
@ -40,6 +40,9 @@ public:
|
|||
|
||||
QCodeCompletionWidget *codeCompletionWidget() const;
|
||||
|
||||
signals:
|
||||
void onFunctionTip(AsCompletion *cp, const QString &content);
|
||||
|
||||
protected:
|
||||
virtual void complete(const QDocumentCursor &c,
|
||||
const QString &trigger) override;
|
||||
|
|
|
@ -13,7 +13,7 @@ LangService &LangService::instance() {
|
|||
return ins;
|
||||
}
|
||||
|
||||
void LangService::init(asIScriptEngine *engine) {
|
||||
void LangService::init(ScriptingConsole *console) {
|
||||
QFormatScheme *format = nullptr;
|
||||
|
||||
switch (SkinManager::instance().currentTheme()) {
|
||||
|
@ -34,7 +34,25 @@ void LangService::init(asIScriptEngine *engine) {
|
|||
m_language = new QLanguageFactory(format, this);
|
||||
m_language->addDefinitionPath(QStringLiteral(":/qcodeedit"));
|
||||
|
||||
auto engine = console->machine()->engine();
|
||||
|
||||
_completion = new AsCompletion(engine, this);
|
||||
connect(_completion, &AsCompletion::onFunctionTip, this,
|
||||
[this, console](AsCompletion *cp, const QString &fn) {
|
||||
QString header;
|
||||
if (!fn.isEmpty()) {
|
||||
header = QStringLiteral("<font color=\"gold\">") +
|
||||
tr("AutoComplete:") + QStringLiteral("</font>");
|
||||
}
|
||||
auto editor = cp->editor();
|
||||
if (editor == console) {
|
||||
emit onConsoleTip(header + QStringLiteral("<b>") + fn +
|
||||
QStringLiteral("</b>"));
|
||||
} else {
|
||||
emit onScriptEditorTip(header + QStringLiteral("<b>") + fn +
|
||||
QStringLiteral("</b>"));
|
||||
}
|
||||
});
|
||||
m_language->addCompletionEngine(_completion);
|
||||
|
||||
initAdditionalFormatScheme();
|
||||
|
|
|
@ -2,19 +2,18 @@
|
|||
#define LANGSERVICE_H
|
||||
|
||||
#include "class/ascompletion.h"
|
||||
#include "control/scriptingconsole.h"
|
||||
#include "qlanguagefactory.h"
|
||||
|
||||
#include "angelscript.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class LangService : QObject {
|
||||
class LangService : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static LangService &instance();
|
||||
|
||||
void init(asIScriptEngine *engine);
|
||||
void init(ScriptingConsole *console);
|
||||
|
||||
QLanguageFactory *languageFactory() const;
|
||||
|
||||
|
@ -26,6 +25,10 @@ public:
|
|||
|
||||
const QString defaultSchemeName() const;
|
||||
|
||||
signals:
|
||||
void onConsoleTip(const QString &tip);
|
||||
void onScriptEditorTip(const QString &tip);
|
||||
|
||||
private:
|
||||
LangService();
|
||||
~LangService();
|
||||
|
|
|
@ -92,7 +92,6 @@ private:
|
|||
|
||||
private:
|
||||
asIScriptEngine *_engine;
|
||||
// QScopedPointer<asCScriptCode> m_code;
|
||||
QList<QCodeNode *> _nodes;
|
||||
|
||||
QHash<QString, QCodeNode *> _buffer;
|
||||
|
|
|
@ -31,8 +31,10 @@
|
|||
#include <QRegularExpression>
|
||||
#include <QScrollBar>
|
||||
|
||||
constexpr auto PADDING = 3;
|
||||
|
||||
QCodeCompletionWidget::QCodeCompletionWidget(QEditor *p)
|
||||
: QListView(nullptr), offset(0) {
|
||||
: QListView(nullptr), offset(PADDING) {
|
||||
// setWindowFlags(Qt::FramelessWindowHint | Qt::SubWindow);
|
||||
setBatchSize(10);
|
||||
setMovement(Static);
|
||||
|
@ -111,7 +113,13 @@ void QCodeCompletionWidget::adjustGeometry() {
|
|||
setGeometry(x, y + lrect.height() - h, w, h);
|
||||
}
|
||||
|
||||
offset = 0;
|
||||
offset = PADDING;
|
||||
}
|
||||
|
||||
QDocumentCursor QCodeCompletionWidget::cursor() const { return _cur; }
|
||||
|
||||
void QCodeCompletionWidget::setCursor(const QDocumentCursor &newCur) {
|
||||
_cur = newCur;
|
||||
}
|
||||
|
||||
QEditor *QCodeCompletionWidget::editor() const {
|
||||
|
@ -123,7 +131,6 @@ void QCodeCompletionWidget::setEditor(QEditor *e) {
|
|||
if (e) {
|
||||
setParent(e->viewport());
|
||||
}
|
||||
// QObject::setParent(pEditor);
|
||||
}
|
||||
|
||||
bool QCodeCompletionWidget::hasEntries() const { return pModel->rowCount(); }
|
||||
|
@ -219,8 +226,15 @@ void QCodeCompletionWidget::complete(const QModelIndex &index) {
|
|||
static QRegularExpression re("(\\bconst\\s*)?(=\\s*0)?$");
|
||||
txt.remove(re);
|
||||
|
||||
if (prefix.length() && txt.startsWith(prefix))
|
||||
txt.remove(0, prefix.length());
|
||||
if (prefix.length() &&
|
||||
prefix.compare(txt.left(prefix.length()), Qt::CaseInsensitive) == 0) {
|
||||
if (_cur.isValid()) {
|
||||
_cur.movePosition(prefix.length(),
|
||||
QDocumentCursor::PreviousCharacter,
|
||||
QDocumentCursor::KeepAnchor);
|
||||
_cur.removeSelectedText();
|
||||
}
|
||||
}
|
||||
|
||||
e->write(txt);
|
||||
|
||||
|
@ -228,6 +242,9 @@ void QCodeCompletionWidget::complete(const QModelIndex &index) {
|
|||
auto c = e->cursor();
|
||||
c.movePosition(1, QDocumentCursor::PreviousCharacter);
|
||||
e->setCursor(c);
|
||||
auto cc = c;
|
||||
cc.movePosition(1, QDocumentCursor::PreviousCharacter);
|
||||
e->completionEngine()->complete(cc, QStringLiteral("("));
|
||||
}
|
||||
|
||||
e->setFocus();
|
||||
|
@ -330,7 +347,7 @@ void QCodeCompletionWidget::keyPressEvent(QKeyEvent *e) {
|
|||
if (prefix.length()) {
|
||||
prefix.chop(1);
|
||||
pModel->setPrefix(prefix);
|
||||
offset = -1;
|
||||
offset = -1 + PADDING;
|
||||
changed();
|
||||
} else {
|
||||
hide();
|
||||
|
@ -353,7 +370,7 @@ void QCodeCompletionWidget::keyPressEvent(QKeyEvent *e) {
|
|||
|
||||
if (text.length() && text.at(0).isPrint() && pModel->rowCount()) {
|
||||
pModel->setPrefix(prefix + text);
|
||||
offset = text.length();
|
||||
offset = text.length() + PADDING;
|
||||
changed();
|
||||
} else {
|
||||
hide();
|
||||
|
@ -384,11 +401,11 @@ void QCodeCompletionModel::clear() {
|
|||
}
|
||||
|
||||
QString QCodeCompletionModel::prefix() const {
|
||||
return QString::fromLocal8Bit(m_prefix);
|
||||
return QString::fromUtf8(m_prefix);
|
||||
}
|
||||
|
||||
void QCodeCompletionModel::setPrefix(const QString &prefix) {
|
||||
m_prefix = prefix.toLocal8Bit();
|
||||
m_prefix = prefix.toUtf8();
|
||||
emit prefixChanged(prefix);
|
||||
update();
|
||||
}
|
||||
|
@ -486,9 +503,9 @@ bool QCodeCompletionModel::match(QCodeNode *n,
|
|||
QByteArray bcxt = n->parent()->qualifiedName(),
|
||||
bnn = n->role(QCodeNode::Name);
|
||||
|
||||
if (!n || (prefix.length() && (!bnn.startsWith(prefix)))) {
|
||||
// qDebug("prefix mismatch : %s not found in %s", qPrintable(prefix),
|
||||
// bnn.constData());
|
||||
if (!n ||
|
||||
(!prefix.isEmpty() && (prefix.compare(bnn.left(prefix.length()),
|
||||
Qt::CaseInsensitive) != 0))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -504,17 +521,6 @@ bool QCodeCompletionModel::match(QCodeNode *n,
|
|||
|
||||
int cxt_off = qMax(0, bcxt.lastIndexOf("::"));
|
||||
|
||||
/*
|
||||
qDebug("filtering %s (in %s) according to %s (v:%i, s:%i, q:%i)",
|
||||
name,
|
||||
ctxt,
|
||||
qPrintable(QString::number(filter, 2)),
|
||||
visibility,
|
||||
specifiers,
|
||||
qualifiers
|
||||
);
|
||||
*/
|
||||
|
||||
if ((((type == QCodeNode::Class) || (type == QCodeNode::Typedef)) &&
|
||||
!(filter & QCodeCompletionWidget::KeepSubTypes)) ||
|
||||
((type == QCodeNode::Enum) &&
|
||||
|
@ -546,12 +552,8 @@ bool QCodeCompletionModel::match(QCodeNode *n,
|
|||
!(filter & QCodeCompletionWidget::KeepCtor)) ||
|
||||
((*name == '~') && !qstrcmp(name + 1, ctxt) &&
|
||||
!(filter & QCodeCompletionWidget::KeepDtor))))) {
|
||||
// qDebug("node %s mismatched display conditions [Ox%x]", name,
|
||||
// int(filter));
|
||||
return false;
|
||||
}
|
||||
|
||||
// qDebug("node %s matched display conditions", name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -80,6 +80,9 @@ public:
|
|||
|
||||
bool isCompleting() const;
|
||||
|
||||
QDocumentCursor cursor() const;
|
||||
void setCursor(const QDocumentCursor &newCur);
|
||||
|
||||
public slots:
|
||||
void popup();
|
||||
void clear();
|
||||
|
@ -104,6 +107,7 @@ private:
|
|||
QCodeCompletionModel *pModel;
|
||||
QPointer<QObject> pEditor;
|
||||
QList<QCodeNode *> m_temps;
|
||||
QDocumentCursor _cur;
|
||||
bool _completing = false;
|
||||
};
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include "class/winginputdialog.h"
|
||||
#include "class/wingmessagebox.h"
|
||||
#include "class/wingupdater.h"
|
||||
#include "control/qcodecompletionwidget.h"
|
||||
#include "control/toast.h"
|
||||
#include "driverselectordialog.h"
|
||||
#include "encodingdialog.h"
|
||||
|
@ -231,8 +232,10 @@ MainWindow::MainWindow(SplashDialog *splash) : FramelessMainWindow() {
|
|||
if (splash)
|
||||
splash->setInfoText(tr("SetupScriptService"));
|
||||
auto &langins = LangService::instance();
|
||||
langins.init(m_scriptConsole->machine()->engine());
|
||||
langins.init(m_scriptConsole);
|
||||
langins.applyLanguageSerivce(m_scriptConsole);
|
||||
connect(&langins, &LangService::onConsoleTip, this,
|
||||
&MainWindow::showStatus);
|
||||
|
||||
m_scriptConsole->initOutput();
|
||||
m_scriptConsole->machine()->setInsteadFoundDisabled(true);
|
||||
|
@ -932,6 +935,17 @@ MainWindow::buildUpScriptConsoleDock(ads::CDockManager *dock,
|
|||
ads::CDockAreaWidget *areaw) {
|
||||
m_scriptConsole = new ScriptingConsole(this);
|
||||
|
||||
connect(m_scriptConsole, &ScriptingConsole::consoleCommand, this,
|
||||
[this] { showStatus({}); });
|
||||
connect(m_scriptConsole, &ScriptingConsole::inputTimeOuted, this, [this] {
|
||||
auto e =
|
||||
qobject_cast<AsCompletion *>(m_scriptConsole->completionEngine());
|
||||
if (e && e->codeCompletionWidget()->isVisible()) {
|
||||
return;
|
||||
}
|
||||
showStatus({});
|
||||
});
|
||||
|
||||
auto dw = buildDockWidget(dock, QStringLiteral("ScriptConsole"),
|
||||
tr("ScriptConsole"), m_scriptConsole);
|
||||
return dock->addDockWidget(area, dw, areaw);
|
||||
|
|
|
@ -101,6 +101,9 @@ ScriptingDialog::ScriptingDialog(QWidget *parent)
|
|||
m_dock->restoreState(set.scriptDockLayout());
|
||||
_savedLayout = set.scriptDockLayout();
|
||||
|
||||
connect(&LangService::instance(), &LangService::onScriptEditorTip, m_status,
|
||||
[this](const QString &message) { m_status->showMessage(message); });
|
||||
|
||||
this->setUpdatesEnabled(true);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue