#include "qcodeedit.h" #include "ecombobox.h" #include "iconedlabel.h" #include "qad_types.h" #include "qcodeedit_completer_p.h" #include "ui_qcodeedit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) # include #endif Q_DECLARE_METATYPE(QTextCursor) Q_DECLARE_METATYPE(QCodeEdit::ACEntry) class _QCE_Viewport: public QWidget { public: _QCE_Viewport(QWidget * p = nullptr): QWidget(p) { setObjectName("__qcodeedit_viewport__"); setMouseTracking(true); } void paintEvent(QPaintEvent *) override { if (!isEnabled()) return; ce->drawCursor(); } QCodeEdit * ce; }; QCodeEdit::QCodeEdit(QWidget * parent): QWidget(parent), ui(new Ui::QCodeEdit) { overlay = nullptr; prev_lc = auto_comp_pl = cur_search_ind = pos_press = pos_el_press = -1; timer_parse = 0; _ignore_focus_out = _destructor = _replacing = cursor_state = block_sel_state = false; _first = true; comment_text = "//"; qRegisterMetaType(); qRegisterMetaType(); ui->setupUi(this); detectTheme(); overlay = new _QCE_Viewport(ui->textCode->viewport()); overlay->ce = this; overlay->show(); ui->widgetSearch->hide(); ui->comboSearch->installEventFilter(this); ui->comboReplace->installEventFilter(this); es_line.format.setBackground(QColor(240, 245, 240)); es_line.format.setProperty(QTextFormat::FullWidthSelection, true); es_cursor.format.setBackground(QColor(220, 255, 200)); es_bracket.format.setBackground(QColor(180, 238, 180)); es_bracket.format.setForeground(Qt::red); es_search.format.setBackground(QColor(255, 240, 10)); es_range.format.setBackground(QColor(230, 246, 255)); es_range.format.setProperty(QTextFormat::FullWidthSelection, true); es_link.format.setForeground(Qt::blue); es_link.format.setFontUnderline(true); widget_help = new QFrame(); widget_help->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); widget_help->setFocusPolicy(Qt::NoFocus); widget_help->setFrameShadow(QFrame::Sunken); widget_help->setFrameShape(QFrame::StyledPanel); widget_help->setLayout(new QBoxLayout(QBoxLayout::TopToBottom)); widget_help->layout()->setContentsMargins(0, 0, 0, 0); for (int i = 0; i < lh_last; ++i) { lbl_help[i] = new IconedLabel(); lbl_help[i]->setFrameShadow(QFrame::Plain); lbl_help[i]->setFrameShape(QFrame::NoFrame); lbl_help[i]->setDirection(IconedLabel::RightToLeft); widget_help->layout()->addWidget(lbl_help[i]); } lbl_help[lhF1]->setIcon(QIcon(":/icons/f1.png")); lbl_help[lhF1]->setText(tr("Press F1 for details")); help_visible = true; completer = new QCodeEditCompleter(); cursor_width = qMax(qRound(fontHeight(this) / 10.), 1); ui->textCode->setCursorWidth(0); ui->textCode->viewport()->setMouseTracking(true); ui->textLines->viewport()->setAutoFillBackground(false); ui->textLines->viewport()->setCursor(Qt::ArrowCursor); #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) ui->textLines->setFixedWidth(ui->textLines->fontMetrics().horizontalAdvance(" ")); #else ui->textLines->setFixedWidth(ui->textLines->fontMetrics().width(" ")); #endif QAction * a = new QAction(this); ui->textCode->addAction(a); a->setShortcut(QKeySequence("Shift+Tab")); a->setShortcutContext(Qt::WidgetShortcut); connect(a, SIGNAL(triggered()), this, SLOT(deindent())); a = new QAction(this); ui->textCode->addAction(a); a->setShortcut(QKeySequence("Ctrl+D")); a->setShortcutContext(Qt::WidgetShortcut); connect(a, SIGNAL(triggered()), this, SLOT(deleteLine())); a = new QAction(this); ui->textCode->addAction(a); a->setShortcut(QKeySequence("Ctrl+Return")); a->setShortcutContext(Qt::WidgetShortcut); connect(a, SIGNAL(triggered()), this, SLOT(newLine())); a = new QAction(this); ui->textCode->addAction(a); a->setShortcut(QKeySequence("Ctrl+Up")); a->setShortcutContext(Qt::WidgetShortcut); connect(a, SIGNAL(triggered()), this, SLOT(scrollUp())); a = new QAction(this); ui->textCode->addAction(a); a->setShortcut(QKeySequence("Ctrl+Down")); a->setShortcutContext(Qt::WidgetShortcut); connect(a, SIGNAL(triggered()), this, SLOT(scrollDown())); a = new QAction(this); ui->textCode->addAction(a); a->setShortcut(QKeySequence("Ctrl+Shift+Return")); a->setShortcutContext(Qt::WidgetShortcut); connect(a, SIGNAL(triggered()), this, SLOT(newLineBefore())); ui->frame->setFocusProxy(ui->textCode); QTextOption to = ui->textLines->document()->defaultTextOption(); to.setAlignment(Qt::AlignTop | Qt::AlignRight); ui->textLines->document()->setDefaultTextOption(to); setShowSpaces(true); a = new QAction(this); a->setShortcut(QKeySequence("Ctrl+F")); a->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(a, SIGNAL(triggered(bool)), this, SLOT(search_triggered())); addAction(a); connect(completer, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(commitCompletition())); connect(completer, SIGNAL(commit()), this, SLOT(commitCompletition())); connect(completer, SIGNAL(gotoHRef(QCodeEdit::ACEntry)), this, SLOT(gotoHelpHRef(QCodeEdit::ACEntry))); connect(ui->textCode->verticalScrollBar(), SIGNAL(valueChanged(int)), ui->textLines->verticalScrollBar(), SLOT(setValue(int))); connect(ui->textCode->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(hideHelp())); connect(ui->textCode->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(resizeOverlay())); connect(ui->textCode->horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(resizeOverlay())); connect(ui->textCode, SIGNAL(textChanged()), this, SLOT(textEdit_textChanged())); connect(ui->textCode, SIGNAL(textChanged()), this, SIGNAL(textChanged())); connect(ui->textCode, SIGNAL(cursorPositionChanged()), this, SLOT(textEdit_cursorPositionChanged())); connect(ui->textCode, SIGNAL(selectionChanged()), this, SLOT(textEdit_selectionChanged())); connect(ui->textCode, SIGNAL(redoAvailable(bool)), this, SLOT(textEdit_redoAvailable(bool))); connect(ui->comboSearch->lineEdit(), SIGNAL(returnPressed()), this, SLOT(searchNext())); connect(ui->comboReplace->lineEdit(), SIGNAL(returnPressed()), this, SLOT(on_buttonReplaceSearch_clicked())); #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) connect(qApp, &QGuiApplication::focusWindowChanged, this, [this](QWindow * w) { if (w == nullptr) { hideHelp(); completer->hide(); } }); #endif updateLines(); timer_blink = startTimer(QApplication::cursorFlashTime() / 2); registerAutoCompletitionClass(-1, QCodeEdit::Keyword, "Words", QIcon(":/icons/code-word.png")); } QCodeEdit::~QCodeEdit() { _destructor = true; delete completer; delete widget_help; delete ui; } QTextCursor QCodeEdit::textCursor() const { return ui->textCode->textCursor(); } QTextDocument * QCodeEdit::document() const { return ui->textCode->document(); } void QCodeEdit::setDocument(QTextDocument * doc) { cancelBlockSelection(); if (document()) { document()->setProperty("_cursor", QVariant::fromValue(textCursor())); document()->setProperty("_vpos", textEdit()->verticalScrollBar()->value()); } ui->textCode->setEnabled(doc); ui->textLines->setEnabled(doc); documentUnset(); if (!doc) { ui->textCode->setDocument(nullptr); documentChanged(nullptr); return; } if (!qobject_cast(doc->documentLayout())) doc->setDocumentLayout(new QPlainTextDocumentLayout(doc)); ui->textCode->setDocument(doc); cursor_width = qMax(qRound(fontHeight(this) / 10.), 1); ui->textCode->setCursorWidth(0); setShowSpaces(spaces_); if (doc->property("_cursor").isValid()) { setTextCursor(doc->property("_cursor").value()); textEdit()->verticalScrollBar()->setValue(doc->property("_vpos").toInt()); } documentChanged(doc); doc->setDefaultFont(editorFont()); updateLines(); } void QCodeEdit::setTextCursor(const QTextCursor & c) { ui->textCode->setTextCursor(c); } void QCodeEdit::centerCursor() { ui->textCode->centerCursor(); updateLines(); } void QCodeEdit::insertText(const QString & text) { ui->textCode->insertPlainText(text); updateLines(); } void QCodeEdit::appendText(const QString & text) { ui->textCode->appendPlainText(text); updateLines(); } void QCodeEdit::setCustomExtraSelection(const QList & es) { es_custom = es; applyExtraSelection(); } QRect QCodeEdit::cursorRect() const { return ui->textCode->cursorRect(); } QRect QCodeEdit::cursorRect(const QTextCursor & cursor) const { return ui->textCode->cursorRect(cursor); } QString QCodeEdit::text() const { return ui->textCode->toPlainText(); } QStringList QCodeEdit::cursorScope() const { return cursor_scope; } bool QCodeEdit::showLineNumbers() const { return ui->textLines->isVisible(); } void QCodeEdit::setHelpHintVisible(bool on) { help_visible = on; } bool QCodeEdit::isHelpHintVisible() const { return help_visible; } QString QCodeEdit::commentText() const { return comment_text; } void QCodeEdit::setCommentText(QString t) { comment_text = t; } void QCodeEdit::setEditorFont(QFont f) { ui->textCode->setFont(f); ui->textLines->setFont(f); } QFont QCodeEdit::editorFont() const { return ui->textCode->font(); } QPlainTextEdit * QCodeEdit::textEdit() const { return ui->textCode; } void QCodeEdit::registerAutoCompletitionClass(int id, QCodeEdit::ACClassType ac_class, const QString & name, const QIcon & icon) { ac_classes[id] = ACClass(id, ac_class, name, icon); } int QCodeEdit::skipRange(const QString & s, int pos, QChar oc, QChar cc, QChar sc) { int cnt = 0; bool skip = false; for (int i = pos - 1; i >= 0; --i) { QChar c = s[i]; if (skip) { skip = false; continue; } if (c == sc) { skip = true; continue; } if (c == cc) { cnt++; continue; } if (c == oc) { cnt--; if (cnt == 0) return i; } } return -1; } int QCodeEdit::skipCWord(const QString & s, int pos) { QChar pc(0), c(0); for (int i = pos - 1; i >= 0; --i) { pc = c; c = s[i]; if (c.isLetterOrNumber() || (c.toLatin1() == '_')) continue; if (pc.isLetter() || (pc.toLatin1() == '_')) return i + 1; return -1; } return 0; } bool QCodeEdit::matchWritten(QString s, QString w) { if (s.isEmpty() || w.isEmpty()) return true; if (s.startsWith(w, Qt::CaseInsensitive)) return true; int sp(0); for (int i = 0; i < w.size(); ++i, ++sp) { if (sp >= s.size()) return false; QChar wc(w[i].toLower()); bool ns = false, bl = true; while (sp < s.size()) { if (ns || s[sp].toLatin1() == '_') { if (s[sp].toLatin1() == '_') { sp++; bl = false; continue; } if (s[sp].isLower() && bl) { sp++; continue; } if (s[sp].toLower() != wc) return false; } if (s[sp].toLower() == wc) break; ns = true; sp++; } if (sp >= s.size()) return false; } return true; } QChar QCodeEdit::pairChar(QChar c) { switch (c.toLatin1()) { case '\"': return '\"'; case '(': return ')'; case ')': return '('; case '[': return ']'; case ']': return '['; default: break; } return QChar(); } bool QCodeEdit::eventFilter(QObject * o, QEvent * e) { if (_destructor) return QWidget::eventFilter(o, e); if (e->type() == QEvent::Destroy) { completer->removeEventFilter(this); ui->textCode->removeEventFilter(this); ui->textCode->viewport()->removeEventFilter(this); ui->textLines->viewport()->removeEventFilter(this); return QWidget::eventFilter(o, e); } if (ui->textLines) { if (o == ui->textLines->viewport()) { QTextCursor tc; int tcpos = 0; switch (e->type()) { case QEvent::MouseButtonPress: if (!isEnabled()) { break; } tc = ui->textCode->cursorForPosition(static_cast(e)->pos()); tc.movePosition(QTextCursor::EndOfLine); pos_el_press = tc.anchor(); tc.movePosition(QTextCursor::StartOfLine); pos_press = tc.anchor(); if (!tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor)) { tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); } ui->textCode->setFocus(); ui->textCode->setTextCursor(tc); return true; case QEvent::MouseMove: if (!isEnabled()) break; tc = ui->textCode->cursorForPosition(static_cast(e)->pos()); tc.movePosition(QTextCursor::StartOfLine); if (pos_press == tc.anchor()) { if (!tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor)) { tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); } ui->textCode->setTextCursor(tc); return true; } if (pos_press < tc.anchor()) { if (!tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor)) { tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); } } tcpos = tc.position(); tc.setPosition(pos_press < tc.anchor() ? pos_press : pos_el_press); tc.setPosition(tcpos, QTextCursor::KeepAnchor); ui->textCode->setTextCursor(tc); return true; case QEvent::Wheel: if (!isEnabled()) break; QApplication::sendEvent(ui->textCode->viewport(), e); return true; default: break; } } } if (o == completer) { // qDebug() << o << e; if (e->type() == QEvent::WindowActivate) _ignore_focus_out = true; // qDebug() << e; return QWidget::eventFilter(o, e); } if (o == ui->comboSearch || o == ui->comboReplace) { // qDebug() << o << e; if (e->type() == QEvent::KeyPress) { if (static_cast(e)->key() == Qt::Key_Escape) { hideHelp(); if (completer->isVisible()) completer->hide(); else hideSearch(); } } // qDebug() << e; return QWidget::eventFilter(o, e); } if (ui->textCode) { if (o == ui->textCode->viewport()) { switch (e->type()) { case QEvent::MouseButtonPress: { cancelBlockSelection(); completer->hide(); hideHelp(); auto me = static_cast(e); if (me->modifiers().testFlag(Qt::ControlModifier) && (me->button() == Qt::LeftButton)) if (!hasBlockSelection() && !ui->textCode->textCursor().hasSelection()) gotoLink(); } break; case QEvent::MouseMove: { if (!completer->isHidden()) break; auto me = static_cast(e); if (me->buttons() != 0) switchBlockSelection(); if (me->modifiers().testFlag(Qt::ControlModifier)) if (!hasBlockSelection() && !ui->textCode->textCursor().hasSelection()) showLink(); } break; case QEvent::Paint: resizeOverlay(); break; case QEvent::DragMove: if (!isEnabled()) break; drag_cursor = ui->textCode->cursorForPosition( #if QT_VERSION_MAJOR <= 5 static_cast(e)->pos() #else static_cast(e)->position().toPoint() #endif ); repaintCursor(); break; case QEvent::MouseButtonRelease: case QEvent::DragLeave: case QEvent::Drop: cancelDragCursor(); break; default: break; } return QWidget::eventFilter(o, e); } if (o == ui->textCode) { // qDebug() << e; QMetaObject::invokeMethod(this, "syncScrolls", Qt::QueuedConnection); switch (e->type()) { case QEvent::ToolTip: if (completer->isHidden()) { QTextCursor tc = ui->textCode->cursorForPosition(static_cast(e)->pos()); tc.select(QTextCursor::WordUnderCursor); raiseHelp(tc); } break; case QEvent::KeyPress: // qDebug() << "key" << ke; if (codeKeyEvent(static_cast(e))) return true; break; case QEvent::KeyRelease: hideLink(); break; case QEvent::FocusOut: if (_ignore_focus_out) { _ignore_focus_out = false; break; } case QEvent::FocusIn: createBlockSelection(); break; case QEvent::Hide: case QEvent::HideToParent: hideLink(); case QEvent::MouseButtonPress: // qDebug() << e; completer->hide(); hideHelp(); default: break; } } } return QWidget::eventFilter(o, e); } void QCodeEdit::showEvent(QShowEvent *) { detectTheme(); if (!_first) return; _first = false; completer->installEventFilter(this); ui->textCode->installEventFilter(this); ui->textCode->viewport()->installEventFilter(this); ui->textLines->viewport()->installEventFilter(this); } void QCodeEdit::timerEvent(QTimerEvent * e) { if (e->timerId() == timer_parse) { parse(); emit parseRequest(); killTimer(timer_parse); timer_parse = 0; } if (e->timerId() == timer_blink) { cursor_state = !cursor_state; repaintCursor(); } } void QCodeEdit::leaveEvent(QEvent * e) { hideHelp(); QWidget::leaveEvent(e); } void QCodeEdit::changeEvent(QEvent * e) { QWidget::changeEvent(e); switch (e->type()) { case QEvent::LanguageChange: ui->retranslateUi(this); lbl_help[lhF1]->setText(tr("Press F1 for details")); break; case QEvent::PaletteChange: detectTheme(); break; default: break; } } bool QCodeEdit::codeKeyEvent(QKeyEvent * ke) { QChar kc; switch (ke->key()) { case Qt::Key_Space: if (ke->modifiers().testFlag(Qt::ControlModifier)) { invokeAutoCompletition(true); return true; } break; case Qt::Key_Slash: if (ke->modifiers().testFlag(Qt::ControlModifier)) { toggleComment(); return true; } break; case Qt::Key_Escape: hideHelp(); if (completer->isVisible()) { completer->hide(); return true; } if (hasBlockSelection()) { cancelBlockSelection(); return true; } if (ui->widgetSearch->isVisible()) { hideSearch(); return true; } break; case Qt::Key_Up: switchBlockSelection(ke); if (completer->isVisible()) { completer->previousCompletition(); return true; } completer->hide(); hideHelp(); if (ke->modifiers().testFlag(Qt::AltModifier)) { if (ke->modifiers().testFlag(Qt::ShiftModifier)) { return false; } else { copyLineUp(); } return true; } if (ke->modifiers().testFlag(Qt::ControlModifier) && ke->modifiers().testFlag(Qt::ShiftModifier)) { moveLineUp(); return true; } break; case Qt::Key_Down: switchBlockSelection(ke); if (completer->isVisible()) { completer->nextCompletition(); return true; } completer->hide(); hideHelp(); if (ke->modifiers().testFlag(Qt::AltModifier)) { if (ke->modifiers().testFlag(Qt::ShiftModifier)) { return false; } else { copyLineDown(); } return true; } if (ke->modifiers().testFlag(Qt::ControlModifier) && ke->modifiers().testFlag(Qt::ShiftModifier)) { moveLineDown(); return true; } break; case Qt::Key_Home: case Qt::Key_End: case Qt::Key_PageUp: case Qt::Key_PageDown: switchBlockSelection(ke); if (completer->isVisible()) { qApp->sendEvent(completer, new QKeyEvent(ke->type(), ke->key(), ke->modifiers())); return true; } break; case Qt::Key_Left: case Qt::Key_Right: switchBlockSelection(ke); if (hasBlockSelection()) { break; } case Qt::Key_Backspace: case Qt::Key_Delete: if (completer->isVisible()) { QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false)); } if (removeBlockSelection(ke->key() == Qt::Key_Delete)) { return true; } break; case Qt::Key_Return: if (hasBlockSelection()) { cancelBlockSelection(); return true; } if (completer->isVisible()) { commitCompletition(); completer->hide(); return true; } if (ui->textCode->textCursor().selectedText().isEmpty()) { QMetaObject::invokeMethod(this, "autoIndent", Qt::QueuedConnection); } break; case Qt::Key_Tab: if (!ui->textCode->textCursor().selectedText().isEmpty()) { if (ke->modifiers().testFlag(Qt::ShiftModifier)) { deindent(); } else { indent(); } return true; } break; case Qt::Key_D: if (ke->modifiers().testFlag(Qt::ControlModifier)) { completer->hide(); return true; } break; case Qt::Key_Control: if (!hasBlockSelection() && !ui->textCode->textCursor().hasSelection()) { showLink(); } break; case Qt::Key_F1: if (widget_help->isVisible()) { gotoHelpHRef(help_entry); } break; default: break; } if (!ke->text().isEmpty()) { if (hasBlockSelection() && (ke->modifiers() == 0 || ke->modifiers() == Qt::ShiftModifier || ke->modifiers() == Qt::KeypadModifier)) { insertBlockSelection(ke->text()); return true; } kc = ke->text()[0]; } if (kc == '.') { completer->hide(); QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false)); } else { if ((kc.isLetterOrNumber() || kc.toLatin1() == '_') && completer->isVisible()) { QMetaObject::invokeMethod(this, "invokeAutoCompletition", Qt::QueuedConnection, Q_ARG(bool, false)); } } return false; } void QCodeEdit::toggleComment() { QTextCursor tc = ui->textCode->textCursor(); tc.beginEditBlock(); int ss = tc.selectionStart(); int ss_ = ss; int se = tc.selectionEnd(); QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { st_.chop(1); se--; } int se_ = se; tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position(); tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position(); tc.setPosition(ss); bool need_comment = false; QMap comms; while (tc.position() <= se_) { int line = tc.blockNumber(); tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, comment_text.size()); if (line == tc.blockNumber()) { if (tc.selectedText() == comment_text) { comms[line] = true; } else { need_comment = true; comms[line] = false; } } else { comms[line] = false; } tc.movePosition(QTextCursor::StartOfLine); if (!tc.movePosition(QTextCursor::Down)) { break; } } tc.setPosition(ss); bool first = true; while (tc.position() <= se_) { int line = tc.blockNumber(); if (need_comment) { if (!comms[line]) { tc.insertText(comment_text); se_ += comment_text.size(); if (first) ss_ += comment_text.size(); } } else { if (comms[line]) { tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, comment_text.size()); tc.removeSelectedText(); se_ -= comment_text.size(); if (first) ss_ -= comment_text.size(); } } first = false; tc.movePosition(QTextCursor::StartOfLine); if (!tc.movePosition(QTextCursor::Down)) { break; } } tc.setPosition(ss_); tc.setPosition(se_, QTextCursor::KeepAnchor); ui->textCode->setTextCursor(tc); tc.endEditBlock(); } char antiBracket(char c) { switch (c) { case '(': return ')'; case '[': return ']'; case '{': return '}'; case '<': return '>'; case ')': return '('; case ']': return '['; case '}': return '{'; case '>': return '<'; } return 0; } void QCodeEdit::highlightBrackets() { es_brackets.clear(); QTextCursor stc = ui->textCode->textCursor(), tc; QTextEdit::ExtraSelection es; stc.setPosition(stc.position()); QTextCursor::MoveOperation mop[2] = {QTextCursor::Left, QTextCursor::Right}; QString mbr[2] = {")]}>", "([{<"}; for (int d = 0; d < 2; ++d) { tc = stc; tc.movePosition(mop[d], QTextCursor::KeepAnchor); if (!tc.selectedText().isEmpty()) { char ch = tc.selectedText()[0].toLatin1(); if (mbr[d].contains(ch)) { es = es_bracket; es.cursor = tc; es_brackets << es; QTextCursor ftc = tc; int bcnt = 1; char fch = antiBracket(ch); while (bcnt > 0) { ftc.setPosition(ftc.position()); if (!ftc.movePosition(mop[d], QTextCursor::KeepAnchor)) break; // qDebug() << tc.selectedText(); if (ftc.selectedText().isEmpty()) break; if (ftc.selectedText()[0].toLatin1() == ch) ++bcnt; if (ftc.selectedText()[0].toLatin1() == fch) --bcnt; } if (bcnt == 0) { es.cursor = ftc; es_brackets << es; es.format = es_range.format; es.cursor.setPosition(tc.position(), QTextCursor::KeepAnchor); if (!es.cursor.selection().isEmpty()) { es_brackets << es; } } } } } } QColor inverseColorValue(const QColor & c) { int hsv[4]; c.getHsl(&hsv[0], &hsv[1], &hsv[2], &hsv[3]); return QColor::fromHsl(hsv[0], hsv[1], 255 - hsv[2], hsv[3]); } QBrush QCodeEdit::invertedBrush(const QBrush & b) { auto ret = b; ret.setColor(inverseColorValue(b.color())); return ret; } QTextCharFormat QCodeEdit::invertedFormat(const QTextCharFormat & f) { auto ret = f; ret.setForeground(invertedBrush(ret.foreground())); ret.setBackground(invertedBrush(ret.background())); return ret; } void QCodeEdit::applyExtraSelection() { QList esl; esl << es_line << es_selected << es_custom << es_brackets << es_search_list << es_cursor << es_link; if (is_dark_theme) { for (auto & e: esl) { e.format = invertedFormat(e.format); } } esl << es_blockselection; ui->textCode->setExtraSelections(esl); } void QCodeEdit::clearSearch() { es_search_list.clear(); applyExtraSelection(); } void QCodeEdit::moveToSearch() { if (es_search_list.isEmpty()) { return; } if (cur_search_ind < 0) { cur_search_ind += es_search_list.size(); } if (cur_search_ind >= es_search_list.size()) { cur_search_ind = 0; } if (cur_search_ind < 0 || cur_search_ind >= es_search_list.size()) { return; } ui->textCode->setTextCursor(es_search_list[cur_search_ind].cursor); } int QCodeEdit::searchIndFromCursor() { if (es_search_list.isEmpty()) return -1; int ci = ui->textCode->textCursor().anchor(); for (int i = 0; i < es_search_list.size(); ++i) { if (es_search_list[i].cursor.anchor() > ci) { return i - 1; } } return -1; } QRect QCodeEdit::cursorRect() { QRect r = ui->textCode->cursorRect(textCursor()), lr = r; if (hasBlockSelection()) { r |= ui->textCode->cursorRect(block_start_cursor); lr.setY(r.y()); lr.setHeight(r.height()); } lr.setWidth(cursor_width); return lr; } QRect QCodeEdit::blockSelectionRect() { QTextCursor tc = ui->textCode->textCursor(); QPoint ps(block_start_cursor.positionInBlock(), block_start_cursor.blockNumber()); QPoint pe(tc.positionInBlock(), tc.blockNumber()); return (ui->textCode->cursorRect(tc) | ui->textCode->cursorRect(block_start_cursor)) .translated(ui->textCode->horizontalScrollBar()->value(), 0); } QVector QCodeEdit::blockSelectionCursors(QRect bsr) { QVector ret; // qDebug() << bsr; int sline = ui->textCode->cursorForPosition(bsr.topLeft()).blockNumber(); int eline = ui->textCode->cursorForPosition(bsr.bottomRight()).blockNumber(); for (int l = sline; l <= eline; ++l) { QTextCursor stc(ui->textCode->document()->findBlockByNumber(l)), etc; QRect stc_rect = ui->textCode->cursorRect(stc); stc = ui->textCode->cursorForPosition(stc_rect.center() + QPoint(bsr.left(), 0)); etc = ui->textCode->cursorForPosition(stc_rect.center() + QPoint(bsr.right(), 0)); stc.setPosition(etc.position(), QTextCursor::KeepAnchor); ret << stc; } return ret; } void QCodeEdit::repaintCursor() { overlay->update(cursorRect()); } void QCodeEdit::drawCursor() { if (!isEnabled() || !ui->textCode->hasFocus()) return; QPainter p(overlay); QTextCursor tc = textCursor(); // qDebug() << block_start_cursor.position() << tc.position(); QRect line = cursorRect(); if (cursor_state && ui->textCode->hasFocus()) { line.adjust(0, 1, 0, -1); p.setCompositionMode(QPainter::CompositionMode_Difference); p.fillRect(line, Qt::white); } if (!drag_cursor.isNull()) { line = ui->textCode->cursorRect(drag_cursor); line.setWidth(cursor_width); p.setCompositionMode(QPainter::CompositionMode_Difference); p.fillRect(line, Qt::white); } } bool QCodeEdit::hasBlockSelection() const { return !block_start_cursor.isNull(); } void QCodeEdit::startBlockSelection() { if (!hasBlockSelection()) { QTextCursor tc = textCursor(); block_start_cursor = tc; block_start_cursor.setPosition(tc.selectionStart()); } } void QCodeEdit::cancelBlockSelection() { block_start_cursor = QTextCursor(); es_blockselection.clear(); applyExtraSelection(); } void QCodeEdit::switchBlockSelection(QKeyEvent * ke) { bool alt = QApplication::keyboardModifiers().testFlag(Qt::AltModifier); if (ke) { alt = ke->modifiers().testFlag(Qt::AltModifier) && ke->modifiers().testFlag(Qt::ShiftModifier); } if (alt) { startBlockSelection(); QTextCursor tc = ui->textCode->textCursor(); QTextCursor::MoveOperation op = QTextCursor::NoMove; if (!ke) { return; } switch (ke->key()) { case Qt::Key_Left: op = QTextCursor::Left; break; case Qt::Key_Right: op = QTextCursor::Right; break; case Qt::Key_Up: op = QTextCursor::Up; break; case Qt::Key_Down: op = QTextCursor::Down; break; default: break; } if (op != QTextCursor::NoMove) { tc.movePosition(op); ui->textCode->setTextCursor(tc); } repaintCursor(); } else { cancelBlockSelection(); } } bool QCodeEdit::removeBlockSelection(bool is_del) { if (!hasBlockSelection()) { return false; } QRect bsr = blockSelectionRect(); bool del = false; bool back = false; if (bsr.width() <= 1) { if (is_del) { del = true; } else { back = true; } } QVector clist = blockSelectionCursors(bsr); QTextCursor tc = ui->textCode->textCursor(); tc.beginEditBlock(); int bspx = ui->textCode->cursorRect(block_start_cursor).center().x(); int nullw = ui->textCode->fontMetrics(). #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) horizontalAdvance #else width #endif (" "); int min_dist = nullw / 2; int new_pos = -1; for (QTextCursor & c: clist) { if (del || back) { if (qAbs(bspx - ui->textCode->cursorRect(c).center().x()) > min_dist) { continue; } int line = c.blockNumber(); c.movePosition(del ? QTextCursor::Right : QTextCursor::Left, QTextCursor::KeepAnchor); if (line != c.blockNumber()) { continue; } // qDebug() << qAbs(bspx - ui->textCode->cursorRect(c).center().x()) << min_dist; } c.removeSelectedText(); if (c.blockNumber() == tc.blockNumber()) { new_pos = c.position(); } } tc.setPosition(new_pos); ui->textCode->setTextCursor(tc); tc.endEditBlock(); return true; } void QCodeEdit::insertBlockSelection(QString text) { if (!hasBlockSelection()) { return; } QTextCursor tc = ui->textCode->textCursor(); QRect bsr = blockSelectionRect(); // qDebug() << "___" << bsr; int scrl = ui->textCode->horizontalScrollBar()->value(); if (bsr.width() > 1) { removeBlockSelection(false); bsr = blockSelectionRect(); // qDebug() << "del" << bsr; } tc.beginEditBlock(); int nullw = ui->textCode->fontMetrics(). #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) horizontalAdvance #else width #endif (" "); QVector clist = blockSelectionCursors(bsr); for (QTextCursor & c: clist) { c.removeSelectedText(); int spcnt = (bsr.left() - ui->textCode->cursorRect(c).center().x() - scrl) / nullw; if (spcnt > 0) { c.insertText(QString(spcnt, QChar(' '))); } c.insertText(text); } tc.endEditBlock(); } void QCodeEdit::createBlockSelection() { if (!hasBlockSelection()) { return; } es_blockselection.clear(); QTextEdit::ExtraSelection es; es.format.setForeground(palette().brush(QPalette::HighlightedText)); es.format.setBackground(palette().brush(QPalette::Highlight)); QVector clist = blockSelectionCursors(blockSelectionRect()); for (QTextCursor & c: clist) { es.cursor = c; es_blockselection << es; } applyExtraSelection(); } void QCodeEdit::cancelDragCursor() { drag_cursor = QTextCursor(); overlay->update(); } void QCodeEdit::searchAll() { QString st = ui->comboSearch->currentText(); es_search_list.clear(); if (!st.isEmpty() && !ui->widgetSearch->isHidden()) { QTextDocument::FindFlags ff = QTextDocument::FindFlags(); if (ui->buttonSearchCase->isChecked()) ff |= QTextDocument::FindCaseSensitively; if (ui->buttonSearchWord->isChecked()) ff |= QTextDocument::FindWholeWords; QTextCursor tc(ui->textCode->document()->begin()); QTextEdit::ExtraSelection es = es_search; while (true) { tc = ui->textCode->document()->find(st, tc, ff); if (tc.isNull()) { break; } es.cursor = tc; es_search_list << es; } } applyExtraSelection(); QString ss; if (es_search_list.isEmpty()) { ss = "color: rgb(180, 0, 0);"; } ui->comboSearch->lineEdit()->setStyleSheet(ss); } void QCodeEdit::search_triggered() { QTextCursor tc = ui->textCode->textCursor(); QString st = tc.selectedText(); if (st.isEmpty()) { tc.select(QTextCursor::WordUnderCursor); st = tc.selectedText(); } search(st); if (ui->comboSearch->findText(st) < 0) { ui->comboSearch->insertItem(0, st); } } void QCodeEdit::syncScrolls() { ui->textLines->verticalScrollBar()->setValue(ui->textCode->verticalScrollBar()->value()); ui->textLines->setHorizontalScrollBarPolicy(ui->textCode->horizontalScrollBar()->isVisible() ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff); } void QCodeEdit::scrollUp() { ui->textCode->verticalScrollBar()->setValue(ui->textCode->verticalScrollBar()->value() - 1); } void QCodeEdit::scrollDown() { ui->textCode->verticalScrollBar()->setValue(ui->textCode->verticalScrollBar()->value() + 1); } void QCodeEdit::deleteLine() { QTextCursor tc = ui->textCode->textCursor(); tc.beginEditBlock(); tc.movePosition(QTextCursor::EndOfLine); tc.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); bool md = true; if (!tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor)) { tc.movePosition(QTextCursor::StartOfLine); tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); md = false; } tc.removeSelectedText(); tc.movePosition(QTextCursor::StartOfLine); if (md) tc.movePosition(QTextCursor::Down); ui->textCode->setTextCursor(tc); tc.endEditBlock(); } void QCodeEdit::copyLineUp() { QTextCursor tc = ui->textCode->textCursor(); tc.beginEditBlock(); int ss = tc.selectionStart(); int ss_ = ss; int se = tc.selectionEnd(); QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { st_.chop(1); se--; } int se_ = se; tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position(); tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position(); tc.setPosition(ss); tc.setPosition(se, QTextCursor::KeepAnchor); bool ins_nl = false; if (!tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor)) { ins_nl = true; } QString l = tc.selectedText(); tc.beginEditBlock(); tc.setPosition(ss); if (ins_nl) { l.append("\n"); } tc.insertText(l); tc.setPosition(ss_); tc.setPosition(se_, QTextCursor::KeepAnchor); tc.endEditBlock(); ui->textCode->setTextCursor(tc); tc.endEditBlock(); } void QCodeEdit::copyLineDown() { QTextCursor tc = ui->textCode->textCursor(); tc.beginEditBlock(); int ss = tc.selectionStart(); int ss_ = ss; int se = tc.selectionEnd(); QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { st_.chop(1); se--; } int se_ = se; tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position(); tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position(); tc.setPosition(ss); tc.setPosition(se, QTextCursor::KeepAnchor); bool ins_nl = false; if (!tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor)) { ins_nl = true; } QString l = tc.selectedText(); tc.beginEditBlock(); tc.setPosition(ss); ss_ += l.size(); se_ += l.size(); if (ins_nl) { l.append("\n"); ss_++; se_++; } tc.insertText(l); tc.setPosition(ss_); tc.setPosition(se_, QTextCursor::KeepAnchor); tc.endEditBlock(); ui->textCode->setTextCursor(tc); tc.endEditBlock(); } void QCodeEdit::moveLineUp() { QTextCursor tc = ui->textCode->textCursor(); int ss = tc.selectionStart(); int ss_ = ss; int se = tc.selectionEnd(); QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { st_.chop(1); se--; } int se_ = se; tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position(); tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position(); tc.setPosition(ss); if (!tc.movePosition(QTextCursor::Up)) { return; } tc.beginEditBlock(); tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor); QString l = tc.selectedText(); ss -= l.size(); se -= l.size(); ss_ -= l.size(); se_ -= l.size(); tc.beginEditBlock(); tc.removeSelectedText(); tc.setPosition(se); bool de = false; if (!tc.movePosition(QTextCursor::Right)) { l.prepend("\n"); de = true; } tc.insertText(l); if (de) { tc.movePosition(QTextCursor::End); tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor); tc.removeSelectedText(); } tc.setPosition(ss_); tc.setPosition(se_, QTextCursor::KeepAnchor); tc.endEditBlock(); ui->textCode->setTextCursor(tc); tc.endEditBlock(); } void QCodeEdit::moveLineDown() { QTextCursor tc = ui->textCode->textCursor(); int ss = tc.selectionStart(); int ss_ = ss; int se = tc.selectionEnd(); QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { st_.chop(1); se--; } int se_ = se; tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position(); tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position(); tc.setPosition(se); if (!tc.movePosition(QTextCursor::Right)) { return; } tc.beginEditBlock(); bool de = false; if (!tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor)) { tc.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); de = true; } QString l = tc.selectedText(); tc.beginEditBlock(); tc.removeSelectedText(); tc.setPosition(ss); if (de) { l += "\n"; } tc.insertText(l); if (de) { tc.movePosition(QTextCursor::End); tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor); tc.removeSelectedText(); } ss += l.size(); se += l.size(); ss_ += l.size(); se_ += l.size(); tc.setPosition(ss_); tc.setPosition(se_, QTextCursor::KeepAnchor); tc.endEditBlock(); ui->textCode->setTextCursor(tc); tc.endEditBlock(); } void QCodeEdit::indent() { QTextCursor tc = ui->textCode->textCursor(); tc.beginEditBlock(); int ss = tc.selectionStart(); int ss_ = ss; int se = tc.selectionEnd(); QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { st_.chop(1); se--; } int se_ = se; tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position(); tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position(); tc.setPosition(ss); while (tc.position() < se_) { tc.insertText("\t"); se_++; tc.movePosition(QTextCursor::StartOfLine); if (!tc.movePosition(QTextCursor::Down)) { break; } } tc.setPosition(ss_ + 1); tc.setPosition(se_, QTextCursor::KeepAnchor); ui->textCode->setTextCursor(tc); tc.endEditBlock(); } void QCodeEdit::deindent() { QTextCursor tc = ui->textCode->textCursor(); tc.beginEditBlock(); int ss = tc.selectionStart(); int ss_ = ss; int se = tc.selectionEnd(); QString st_ = tc.selection().toPlainText(); if (st_.endsWith("\n")) { st_.chop(1); se--; } int se_ = se; tc.setPosition(ss); tc.movePosition(QTextCursor::StartOfLine); ss = tc.position(); tc.setPosition(se); tc.movePosition(QTextCursor::EndOfLine); se = tc.position(); tc.setPosition(ss); bool first = true; while (tc.position() < se_) { tc.movePosition(QTextCursor::StartOfLine); tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); int rs = 0; if (tc.selectedText() == "\t") { tc.removeSelectedText(); rs = 1; } else { for (int i = 0; i < 4; ++i) { tc.movePosition(QTextCursor::StartOfLine); tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); if (tc.selectedText() == " ") { tc.removeSelectedText(); rs++; } } } if (first) { first = false; ss_ -= rs; } se_ -= rs; tc.movePosition(QTextCursor::StartOfLine); if (!tc.movePosition(QTextCursor::Down)) break; } tc.setPosition(ss_); tc.setPosition(se_, QTextCursor::KeepAnchor); ui->textCode->setTextCursor(tc); tc.endEditBlock(); } void QCodeEdit::autoIndent() { QTextCursor tc = ui->textCode->textCursor(); QTextCursor stc = tc; tc.movePosition(QTextCursor::StartOfLine); if (!tc.movePosition(QTextCursor::Up)) { return; } tc.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor); QString line = tc.selectedText(); QString tabs; int i = 0; for (; i < line.size(); ++i) if (!line[i].isSpace()) { tabs = line.left(i); break; } if (i >= line.size()) { tabs = line.left(line.size() - 1); } int nt = qMax(0, line.count(QChar('{')) - line.count(QChar('}'))); tabs.append(QString("\t").repeated(nt)); if (tabs.isEmpty()) { return; } stc.beginEditBlock(); stc.insertText(tabs); ui->textCode->setTextCursor(stc); stc.endEditBlock(); } void QCodeEdit::showLink() { QTextCursor tc = ui->textCode->cursorForPosition(ui->textCode->mapFromGlobal(QCursor::pos())); tc.select(QTextCursor::WordUnderCursor); link_entry = findEntryOnCursor(tc); if (link_entry.isNull()) { hideLink(); return; } if (!linkValidate(link_entry)) { hideLink(); return; } es_link.cursor = tc; overlay->setCursor(tc.isNull() ? Qt::IBeamCursor : Qt::PointingHandCursor); applyExtraSelection(); // qDebug() << "showLink" << tc.selectedText() << link_entry.type << link_entry.name; } void QCodeEdit::hideLink() { es_link.cursor = QTextCursor(); overlay->setCursor(Qt::IBeamCursor); link_entry = ACEntry(); applyExtraSelection(); // qDebug() << "hideLink"; } void QCodeEdit::gotoLink() { if (link_entry.isNull()) return; QMetaObject::invokeMethod(this, "_activateLink", Qt::QueuedConnection, Q_ARG(QCodeEdit::ACEntry, link_entry)); hideLink(); } void QCodeEdit::scrollToTop() { prev_lc = -1; updateLines(); ui->textCode->verticalScrollBar()->setValue(0); ui->textLines->verticalScrollBar()->setValue(0); } void QCodeEdit::newLine() { QTextCursor tc = ui->textCode->textCursor(); tc.movePosition(QTextCursor::EndOfLine); tc.insertText("\n"); ui->textCode->setTextCursor(tc); QMetaObject::invokeMethod(this, "autoIndent", Qt::QueuedConnection); } void QCodeEdit::newLineBefore() { QTextCursor tc = ui->textCode->textCursor(); tc.movePosition(QTextCursor::StartOfLine); tc.insertText("\n"); tc.movePosition(QTextCursor::Up); ui->textCode->setTextCursor(tc); QMetaObject::invokeMethod(this, "autoIndent", Qt::QueuedConnection); } void QCodeEdit::setFocus() { ui->textCode->setFocus(); } void QCodeEdit::setText(const QString & t) { ui->textCode->setPlainText(t); } void QCodeEdit::updateLines() { if (timer_parse > 0) { killTimer(timer_parse); } timer_parse = startTimer(500); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) # if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) ui->textCode->setTabStopDistance(ui->textCode->fontMetrics().horizontalAdvance(" ")); # else ui->textCode->setTabStopDistance(ui->textCode->fontMetrics().width(" ")); # endif #else ui->textCode->setTabStopWidth(ui->textCode->fontMetrics().width(" ")); #endif int lc = ui->textCode->document()->lineCount(); if (prev_lc == lc) { return; } prev_lc = lc; #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) ui->textLines->setFixedWidth(ui->textLines->fontMetrics().horizontalAdvance(QString(" %1").arg(lc))); #else ui->textLines->setFixedWidth(ui->textLines->fontMetrics().width(QString(" %1").arg(lc))); #endif ui->textLines->clear(); for (int i = 1; i <= lc; ++i) { ui->textLines->appendPlainText(QString("%1").arg(i)); } ui->textLines->verticalScrollBar()->setValue(ui->textCode->verticalScrollBar()->value()); } QString QCodeEdit::selectArg(QString s, int arg) { if (!s.contains('(') || arg < 0) { return s; } QString ss = s.left(s.indexOf('(')); s.remove(0, ss.size()); if (s.startsWith('(')) { s.remove(0, 1); } if (s.endsWith(')')) { s.chop(1); } QStringList al = s.split(","); QString ret = ss + "("; for (int i = 0; i < al.size(); ++i) { if (i > 0) { ret += ", "; } if (i == arg) { ret += ""; } ret += al[i].trimmed(); if (i == arg) { ret += ""; } } ret += ")"; return ret; } QCodeEdit::ACEntry QCodeEdit::findEntryOnCursor(QTextCursor tc, int arg, ACClass * acc, QPair * scope) { bool ok = false; QPair sc = getScope(tc, &ok); if (scope) { *scope = sc; } QString st = tc.selectedText(); if (arg >= 0) { st = sc.second; } if (!ok || st.isEmpty()) { return ACEntry(); } ok = false; ACList acl(autoCompletitionList(sc.first, sc.second)); for (const ACSection & i: acl) { for (const ACEntry & s: i.second) { QString ts = s.name; // qDebug() << ts << st; if (ts != st) { if (ts.startsWith(st)) { ts.remove(0, st.size()); ts = ts.trimmed(); if (!ts.isEmpty()) { if (ts[0] != '(') { continue; } } } else { continue; } } // qDebug() << s.second << st; if (acc) { *acc = ac_classes.value(i.first); } return s; } } return ACEntry(); } void QCodeEdit::detectTheme() { is_dark_theme = palette().color(QPalette::Window).valueF() < 0.5; } void QCodeEdit::raiseHelp(QTextCursor tc, int arg) { ACClass acc; QPair scope; ACEntry e = findEntryOnCursor(tc, arg, &acc, &scope); if (e.isNull()) { hideHelp(); return; } lbl_help[lhMain]->setText(QString("[%1] %2 %3").arg(acc.name, e.type, selectArg(e.name, arg))); lbl_help[lhMain]->setIcon(acc.icon); QString hint; if (!e.hint.isEmpty()) { hint = QString("%1").arg(e.hint); } lbl_help[lhHint]->setText(hint); lbl_help[lhHint]->setHidden(hint.isEmpty()); // qDebug() << "help found" << tc.selectionStart() << tc.selectionEnd(); es_cursor.cursor = tc; applyExtraSelection(); // tc.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor); lbl_help[lhMain]->setFont(font()); lbl_help[lhF1]->setVisible(!e.help_href.isEmpty() && help_visible); qApp->processEvents(); widget_help->adjustSize(); widget_help->resize(widget_help->sizeHint()); qApp->processEvents(); QRect whr = ui->textCode->cursorRect(tc); #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) whr.setWidth(ui->textCode->fontMetrics().horizontalAdvance(tc.selectedText())); #else whr.setWidth(ui->textCode->fontMetrics().width(tc.selectedText())); #endif QPoint whp; whp.setX(whr.left() - whr.width() - (widget_help->width() - whr.width()) / 2); whp.setY(whr.top() - widget_help->height() - (fontHeight(this) / 3)); // qDebug() << whr << whp << widget_help->width() << ", " << st; widget_help->move(ui->textCode->viewport()->mapToGlobal(whp)); widget_help->show(); widget_help->raise(); help_entry = e; cursor_scope = scope.first; cursor_scope << scope.second; // qDebug() << "tooltip" << st; } void QCodeEdit::gotoHelpHRef(QCodeEdit::ACEntry e) { if (e.help_href.isEmpty()) { return; } QDesktopServices::openUrl(e.help_href); } void QCodeEdit::resizeOverlay() { if (overlay) { overlay->setGeometry(ui->textCode->viewport()->geometry()); } } void QCodeEdit::hideHelp() { help_entry = ACEntry(); widget_help->hide(); es_cursor.cursor = QTextCursor(); cursor_scope.clear(); applyExtraSelection(); } QTextCursor QCodeEdit::functionStart(QTextCursor tc, int * arg) { QString doc = ui->textCode->toPlainText(); int bcnt = 0, a = 0, i = -1; for (i = tc.position() - 1; i >= 0; --i) { if (doc[i] == ')') bcnt++; if (doc[i] == '(') { if (bcnt == 0) { break; } else { bcnt--; } } if (doc[i] == ',' && bcnt == 0) { a++; } } if (i < 0) { return QTextCursor(); } if (arg) { *arg = a; } QTextCursor ret(ui->textCode->document()); ret.setPosition(i); // qDebug() << "found" << i << a; return ret; } QCodeEdit::ACList QCodeEdit::wordsCompletitionList(const QString & written) const { QCodeEdit::ACList ret; if (!written.isEmpty()) { QTextCursor tc = QTextCursor(ui->textCode->document()->begin()); QTextCursor stc; QStringList acwl; tc = QTextCursor(ui->textCode->document()->begin()); while (true) { tc = ui->textCode->document()->find(written, tc); if (tc.isNull()) { break; } stc = tc; stc.movePosition(QTextCursor::Left); stc.select(QTextCursor::WordUnderCursor); if (!stc.selectedText().isEmpty() && stc.selectedText().trimmed() != written) { acwl << stc.selectedText(); } } acwl.removeDuplicates(); ACSection acl; acl.first = -1; for (const QString & s: acwl) { acl.second << ACEntry("", s); } ret << acl; } return ret; } QPair QCodeEdit::getScope(QTextCursor tc, bool * ok) { QPair ret; QTextCursor stc = tc; if (tc.isNull()) { completer->hide(); if (ok) { *ok = false; } return ret; } int line = tc.block().firstLineNumber(); if (completer->isVisible()) { if (auto_comp_pl != line) { completer->hide(); auto_comp_pl = line; if (ok) { *ok = false; } return ret; } } QString doc = ui->textCode->toPlainText(); auto_comp_pl = line; completer->clear(); int spos = tc.position(), cpos = spos; tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor); QStringList scope; QString written = tc.selectedText().trimmed(); // qDebug() << "\n*** invokeAutoCompletition ***"; if (written != "_" && !written.leftJustified(1)[0].isLetterOrNumber()) { written.clear(); } else { cpos = skipCWord(doc, spos); if (cpos >= 0) { written = doc.mid(cpos, spos - cpos).trimmed(); } } while (cpos >= 0) { cpos--; // qDebug() << "char =" << doc.mid(cpos, 1); if (doc.mid(cpos, 1) != ".") break; QChar c = doc.mid(cpos - 1, 1).leftJustified(1)[0]; int ppos = cpos; if (c == '\"' || c == ')' || c == ']') { cpos = skipRange(doc, cpos, pairChar(c), c, '\\'); // qDebug() << "range" << cpos; if (cpos < 0) break; } int npos = skipCWord(doc, cpos); if (npos < 0) break; scope.push_front(doc.mid(npos, ppos - npos)); cpos = npos; } ret.first = scope; ret.second = written; if (ok) *ok = true; return ret; } void QCodeEdit::invokeAutoCompletition(bool force) { int arg = -1; QTextCursor htc = functionStart(ui->textCode->textCursor(), &arg); if (!htc.isNull()) { // qDebug() << "raise"; raiseHelp(htc, arg); } bool ok; QPair scope = getScope(ui->textCode->textCursor(), &ok); if (!ok) return; ACList acl(autoCompletitionList(scope.first, scope.second)); // qDebug() << written << scope << acl.size(); if (scope.first.isEmpty() && scope.second.isEmpty() && !force) { completer->hide(); hideHelp(); return; } if (word_complete) { acl << wordsCompletitionList(scope.second); } QFont bf(font()); bf.setBold(true); foreach(const ACSection & ac, acl) { if (ac.second.isEmpty()) { continue; } ACClass acc = ac_classes.value(ac.first); completer->addItems(bf, acc, ac); } completer->invoke(ui->textCode->mapToGlobal(ui->textCode->cursorRect().bottomRight())); } void QCodeEdit::commitCompletition() { if (completer->currentItem() == nullptr) return; if (!completer->currentItem()->flags().testFlag(Qt::ItemIsSelectable)) return; QString ins = completer->currentValue(), ret = completer->currentReturn(); QTextCursor tc = ui->textCode->textCursor(), stc = tc; tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); bool ins_br = true, shifted = false; if (!tc.selectedText().isEmpty()) { if (!tc.selectedText()[0].isLetterOrNumber() && !tc.selectedText()[0].isSpace() && !(tc.selectedText()[0] == '_')) { stc.movePosition(QTextCursor::Left); shifted = true; } else { tc.movePosition(QTextCursor::Left); tc.movePosition(QTextCursor::EndOfWord); tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); } if (!tc.selectedText().isEmpty()) { if (tc.selectedText()[0].toLatin1() == '(') { ins_br = false; } } } if (ins.contains("(")) { ins = ins.left(ins.indexOf("(")) + "()"; } if (!ins_br && ins.endsWith("()")) { ins.chop(2); } tc = stc; tc.select(QTextCursor::WordUnderCursor); if (!tc.selectedText().leftJustified(1)[0].isLetterOrNumber() && !(tc.selectedText().leftJustified(1)[0] == '_')) { tc = stc; if (shifted) { tc.movePosition(QTextCursor::Right); } } ui->textCode->setTextCursor(tc); ui->textCode->textCursor().insertText(ins); tc = ui->textCode->textCursor(); if (ins_br) { if (ret == "void") { tc.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); if (tc.selectedText() != ";") { ui->textCode->textCursor().insertText(";"); tc.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 2); } } if (ins.endsWith(")") && !completer->currentValue().endsWith("()")) { tc.movePosition(QTextCursor::Left); ui->textCode->setTextCursor(tc); } } else { if (completer->currentValue().endsWith(")")) { tc.movePosition(QTextCursor::Right); ui->textCode->setTextCursor(tc); } if (completer->currentValue().endsWith("()")) { tc.movePosition(QTextCursor::Right); ui->textCode->setTextCursor(tc); } } completer->hide(); } void QCodeEdit::textEdit_cursorPositionChanged() { es_line.cursor = ui->textCode->textCursor(); // qDebug() << "cursorPositionChanged" << es_line.cursor.position(); es_line.cursor.select(QTextCursor::LineUnderCursor); es_line.cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); highlightBrackets(); applyExtraSelection(); if (timer_blink) { killTimer(timer_blink); } timer_blink = startTimer(QApplication::cursorFlashTime() / 2); cursor_state = true; createBlockSelection(); } void QCodeEdit::textEdit_textChanged() { if (_replacing) { return; } searchAll(); updateLines(); } void QCodeEdit::textEdit_selectionChanged() { if (hasBlockSelection()) { QTextCursor tc = ui->textCode->textCursor(); // qDebug() << block_start_cursor.selectionStart() << tc.selectionEnd(); bool bs = ui->textCode->blockSignals(true); tc.clearSelection(); ui->textCode->setTextCursor(tc); ui->textCode->blockSignals(bs); applyExtraSelection(); return; } block_start_cursor = QTextCursor(); es_selected.clear(); QString sf = ui->textCode->textCursor().selectedText(); if (sf.trimmed().isEmpty() || sf.contains("\n")) { applyExtraSelection(); return; } QTextCursor tc(ui->textCode->document()->begin()); QTextEdit::ExtraSelection es; es.format.setBackground(QColor(251, 250, 150)); while (true) { tc = ui->textCode->document()->find(sf, tc, QTextDocument::FindCaseSensitively | QTextDocument::FindWholeWords); if (tc.isNull()) break; es.cursor = tc; es_selected << es; } applyExtraSelection(); } void QCodeEdit::textEdit_redoAvailable(bool available) { if (available) { cancelBlockSelection(); } } void QCodeEdit::setShowSpaces(bool yes) { spaces_ = yes; QTextOption to = ui->textCode->document()->defaultTextOption(); QTextOption::Flags tof = to.flags(); if (yes) { tof |= QTextOption::ShowTabsAndSpaces; } else { tof &= ~QTextOption::ShowTabsAndSpaces; } to.setFlags(tof); ui->textCode->document()->setDefaultTextOption(to); } void QCodeEdit::setShowLineNumbers(bool yes) { ui->textLines->setVisible(yes); } void QCodeEdit::search(const QString & t) { ui->widgetSearch->show(); ui->comboSearch->setEditText(QString()); ui->comboSearch->setEditText(t); ui->comboSearch->setFocus(); searchNext(false); } void QCodeEdit::searchNext(bool next) { if (es_search_list.isEmpty()) { return; } cur_search_ind = searchIndFromCursor() + (next ? 1 : 0); moveToSearch(); } void QCodeEdit::searchPrevious() { if (es_search_list.isEmpty()) { return; } cur_search_ind = searchIndFromCursor() - 1; moveToSearch(); } void QCodeEdit::hideSearch() { ui->widgetSearch->hide(); searchAll(); } void QCodeEdit::on_comboSearch_currentTextChanged(const QString & t) { searchAll(); searchNext(false); } void QCodeEdit::on_buttonReplace_clicked() { if (es_search_list.isEmpty() || cur_search_ind < 0 || cur_search_ind >= es_search_list.size()) { return; } if (ui->textCode->textCursor() != es_search_list[cur_search_ind].cursor) { return; } if (ui->textCode->textCursor().selectedText().size() != es_search_list[cur_search_ind].cursor.selectedText().size()) { return; } ui->textCode->textCursor().insertText(ui->comboReplace->currentText()); } void QCodeEdit::on_buttonReplaceSearch_clicked() { on_buttonReplace_clicked(); searchNext(); } void QCodeEdit::on_buttonReplaceAll_clicked() { _replacing = true; QString rt = ui->comboReplace->currentText(); textCursor().beginEditBlock(); for (int i = es_search_list.size() - 1; i >= 0; --i) { es_search_list[i].cursor.insertText(rt); } _replacing = false; textCursor().endEditBlock(); textEdit_textChanged(); } QString QCodeEdit::ACEntry::declaration() const { if (declaration_pos < 0 || declaration_file.isEmpty()) { return QString(); } return (declaration_file + ": %1").arg(declaration_pos); }