/* * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "editor.h" #include #include #include #include #include #include #include "settings.h" #include "mainwindow.h" #include "systemconsts.h" #include #include #include #include #include "qsynedit/highlighter/cpp.h" #include "HighlighterManager.h" #include "qsynedit/exporter/synrtfexporter.h" #include "qsynedit/exporter/synhtmlexporter.h" #include "qsynedit/Constants.h" #include #include #include #include #include #include #include #include #include #include #include "iconsmanager.h" #include "debugger.h" #include "editorlist.h" #include #include "project.h" using namespace std; SaveException::SaveException(const QString& reason) { mReason = reason; mReasonBuffer = mReason.toLocal8Bit(); } SaveException::SaveException(const QString&& reason) { mReason = reason; mReasonBuffer = mReason.toLocal8Bit(); } const QString& SaveException::reason() const noexcept{ return mReason; } const char* SaveException::what() const noexcept { return mReasonBuffer; } Editor::Editor(QWidget *parent): Editor(parent,QObject::tr("untitled"),ENCODING_SYSTEM_DEFAULT,false,true,nullptr) { } Editor::Editor(QWidget *parent, const QString& filename, const QByteArray& encoding, bool inProject, bool isNew, QTabWidget* parentPageControl): SynEdit(parent), mEncodingOption(encoding), mFilename(QFileInfo(filename).absoluteFilePath()), mParentPageControl(parentPageControl), mInProject(inProject), mIsNew(isNew), mSyntaxIssues(), mSyntaxErrorColor(Qt::red), mSyntaxWarningColor("orange"), mLineCount(0), mActiveBreakpointLine(-1), mLastIdCharPressed(0), mCurrentWord(), mCurrentTipType(TipType::None), mOldHighlightedWord(), mCurrentHighlightedWord(), mSaving(false) { mCurrentLineModified = false; mUseCppSyntax = pSettings->editor().defaultFileCpp(); if (mFilename.isEmpty()) { mFilename = tr("untitled")+QString("%1").arg(getNewFileNumber()); } QFileInfo fileInfo(mFilename); if (mParentPageControl!=nullptr) { mParentPageControl->addTab(this,""); updateCaption(); } PSynHighlighter highlighter; if (!isNew) { loadFile(); highlighter = highlighterManager.getHighlighter(mFilename); } else { if (mEncodingOption == ENCODING_AUTO_DETECT) mFileEncoding = ENCODING_ASCII; else mFileEncoding = mEncodingOption; highlighter=highlighterManager.getCppHighlighter(); } if (highlighter) { setHighlighter(highlighter); setUseCodeFolding(true); } else { setUseCodeFolding(false); } if (inProject) { mParser = pMainWindow->project()->cppParser(); } else { initParser(); } if (pSettings->editor().readOnlySytemHeader() && (mParser->isSystemHeaderFile(mFilename) || mParser->isProjectHeaderFile(mFilename))) { this->setModified(false); setReadOnly(true); updateCaption(); } mCompletionPopup = pMainWindow->completionPopup(); mHeaderCompletionPopup = pMainWindow->headerCompletionPopup(); applySettings(); applyColorScheme(pSettings->editor().colorScheme()); //Initialize User Code Template stuff; mXOffsetSince =0; mTabStopY=-1; mTabStopBegin= -1; mTabStopEnd= -1; //mLineBeforeTabStop=""; //mLineAfterTabStop = ""; connect(this,&SynEdit::statusChanged,this,&Editor::onStatusChanged); connect(this,&SynEdit::gutterClicked,this,&Editor::onGutterClicked); onStatusChanged(SynStatusChange::scOpenFile); setAttribute(Qt::WA_Hover,true); connect(this,&SynEdit::linesDeleted, this, &Editor::onLinesDeleted); connect(this,&SynEdit::linesInserted, this, &Editor::onLinesInserted); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &QWidget::customContextMenuRequested, pMainWindow, &MainWindow::onEditorContextMenu); mCanAutoSave = false; if (isNew && parentPageControl!=nullptr) { QString fileTemplate = pMainWindow->codeSnippetManager()->newFileTemplate(); if (!fileTemplate.isEmpty()) { insertCodeSnippet(fileTemplate); mCanAutoSave = true; } } if (!isNew && parentPageControl!=nullptr) { resetBookmarks(); resetBreakpoints(); } mStatementColors = pMainWindow->statementColors(); } Editor::~Editor() { if (mParentPageControl) { pMainWindow->fileSystemWatcher()->removePath(mFilename); pMainWindow->caretList().removeEditor(this); pMainWindow->updateCaretActions(); int index = mParentPageControl->indexOf(this); mParentPageControl->removeTab(index); this->setParent(nullptr); } } void Editor::loadFile(QString filename) { if (filename.isEmpty()) { this->lines()->loadFromFile(mFilename,mEncodingOption,mFileEncoding); } else { filename = QFileInfo(filename).absoluteFilePath(); this->lines()->loadFromFile(filename,mEncodingOption,mFileEncoding); } //this->setModified(false); updateCaption(); pMainWindow->updateForEncodingInfo(); switch(getFileType(mFilename)) { case FileType::CppSource: mUseCppSyntax = true; break; case FileType::CSource: mUseCppSyntax = false; break; default: mUseCppSyntax = pSettings->editor().defaultFileCpp(); } if (highlighter() && mParser) { reparse(); if (pSettings->editor().syntaxCheckWhenLineChanged()) { checkSyntaxInBack(); } reparseTodo(); } mLastIdCharPressed = 0; } void Editor::saveFile(QString filename) { QFile file(filename); this->lines()->saveToFile(file,mEncodingOption, pSettings->editor().useUTF8ByDefault()? ENCODING_UTF8 : QTextCodec::codecForLocale()->name(), mFileEncoding); pMainWindow->updateForEncodingInfo(); } void Editor::convertToEncoding(const QByteArray &encoding) { mEncodingOption = encoding; setModified(true); save(); } bool Editor::save(bool force, bool doReparse) { if (this->mIsNew && !force) { return saveAs(); } //is this file writable; pMainWindow->fileSystemWatcher()->removePath(mFilename); try { // QFileInfo info(mFilename); // if (!force && !info.isWritable()) { // QMessageBox::critical(pMainWindow,tr("Error"), // tr("File %1 is not writable!").arg(mFilename)); // return false; // } saveFile(mFilename); pMainWindow->fileSystemWatcher()->addPath(mFilename); setModified(false); mIsNew = false; this->updateCaption(); } catch (SaveException& exception) { if (!force) { QMessageBox::critical(pMainWindow,tr("Error"), exception.reason()); } pMainWindow->fileSystemWatcher()->addPath(mFilename); return false; } if (doReparse && mParser) { reparse(); } if (doReparse && pSettings->editor().syntaxCheckWhenSave()) checkSyntaxInBack(); reparseTodo(); return true; } bool Editor::saveAs(const QString &name, bool fromProject){ QString newName = name; QString oldName = mFilename; bool firstSave = isNew(); if (name.isEmpty()) { QString selectedFileFilter; QString defaultExt; if (pSettings->editor().defaultFileCpp()) { selectedFileFilter = pSystemConsts->defaultCPPFileFilter(); defaultExt = "cpp"; } else { selectedFileFilter = pSystemConsts->defaultCFileFilter(); defaultExt = "c"; } QFileDialog dialog(this,tr("Save As"),extractFilePath(mFilename), pSystemConsts->defaultFileFilters().join(";;")); dialog.selectNameFilter(selectedFileFilter); dialog.setDefaultSuffix(defaultExt); dialog.selectFile(mFilename); dialog.setFileMode(QFileDialog::AnyFile); dialog.setAcceptMode(QFileDialog::AcceptSave); connect(&dialog, &QFileDialog::filterSelected, [&dialog](const QString &filter){ int pos = filter.indexOf("*."); if (pos>=0) { QString suffix; pos+=2; while (posproject() && !fromProject) { int unitIndex = pMainWindow->project()->indexInUnits(mFilename); if (unitIndex>=0) { pMainWindow->project()->saveUnitAs(unitIndex,newName,false); } } clearSyntaxIssues(); pMainWindow->fileSystemWatcher()->removePath(mFilename); if (pSettings->codeCompletion().enabled() && mParser) mParser->invalidateFile(mFilename); try { mFilename = newName; saveFile(mFilename); mIsNew = false; setModified(false); this->updateCaption(); } catch (SaveException& exception) { QMessageBox::critical(pMainWindow,tr("Error"), exception.reason()); return false; } pMainWindow->fileSystemWatcher()->addPath(mFilename); switch(getFileType(mFilename)) { case FileType::CppSource: mUseCppSyntax = true; break; case FileType::CSource: mUseCppSyntax = false; break; default: mUseCppSyntax = pSettings->editor().defaultFileCpp(); } //update (reassign highlighter) PSynHighlighter newHighlighter = HighlighterManager().getHighlighter(mFilename); if (newHighlighter) { setUseCodeFolding(true); } else { setUseCodeFolding(false); } setHighlighter(newHighlighter); if (!newHighlighter || newHighlighter->getName() != SYN_HIGHLIGHTER_CPP) { mSyntaxIssues.clear(); } applyColorScheme(pSettings->editor().colorScheme()); reparse(); if (pSettings->editor().syntaxCheckWhenSave()) pMainWindow->checkSyntaxInBack(this); reparseTodo(); if (pSettings->editor().readOnlySytemHeader() && (!mParser->isSystemHeaderFile(mFilename) && !mParser->isProjectHeaderFile(mFilename))) { setReadOnly(false); updateCaption(); } emit renamed(oldName, newName , firstSave); return true; } void Editor::activate() { if (mParentPageControl!=nullptr) mParentPageControl->setCurrentWidget(this); setFocus(); } const QByteArray& Editor::encodingOption() const noexcept{ return mEncodingOption; } void Editor::setEncodingOption(const QByteArray& encoding) noexcept{ mEncodingOption = encoding; if (!isNew()) loadFile(); else pMainWindow->updateForEncodingInfo(); if (mInProject) { std::shared_ptr project = pMainWindow->project(); if (project) { int index = project->indexInUnits(this); if (index>=0) { PProjectUnit unit = project->units()[index]; unit->setEncoding(mEncodingOption); } } } } const QByteArray& Editor::fileEncoding() const noexcept{ return mFileEncoding; } const QString& Editor::filename() const noexcept{ return mFilename; } bool Editor::inProject() const noexcept{ return mInProject; } bool Editor::isNew() const noexcept { return mIsNew; } QTabWidget* Editor::pageControl() noexcept{ return mParentPageControl; } static int findWidgetInPageControl(QTabWidget* pageControl, QWidget* child) { for (int i=0;icount();i++) { if (pageControl->widget(i)==child) return i; } return -1; } void Editor::setPageControl(QTabWidget *newPageControl) { if (mParentPageControl==newPageControl) return; if (mParentPageControl!=nullptr) { int index = findWidgetInPageControl(mParentPageControl,this); if (index>=0) mParentPageControl->removeTab(index); } mParentPageControl= newPageControl; if (newPageControl!=nullptr) { mParentPageControl->addTab(this,""); updateCaption(); } } void Editor::undoSymbolCompletion(int pos) { PSynHighlighterAttribute Attr; QString Token; bool tokenFinished; SynHighlighterTokenType tokenType; if (!highlighter()) return; if (!pSettings->editor().removeSymbolPairs()) return; if (!getHighlighterAttriAtRowCol(caretXY(), Token, tokenFinished, tokenType, Attr)) return; if ((tokenType == SynHighlighterTokenType::Comment) && (!tokenFinished)) return ; //convert caret x to string index; pos--; if (pos<0 || pos+1>=lineText().length()) return; QChar DeletedChar = lineText().at(pos); QChar NextChar = lineText().at(pos+1); if ((tokenType == SynHighlighterTokenType::Character) && (DeletedChar != '\'')) return; if (tokenType == SynHighlighterTokenType::StringEscapeSequence) return; if (tokenType == SynHighlighterTokenType::String) { if ((DeletedChar!='"') && (DeletedChar!='(')) return; if ((DeletedChar=='"') && (Token!="\"\"")) return; if ((DeletedChar=='(') && (!Token.startsWith("R\""))) return; } if ((DeletedChar == '\'') && (tokenType == SynHighlighterTokenType::Number)) return; if ((DeletedChar == '<') && ((tokenType != SynHighlighterTokenType::PreprocessDirective) || !lineText().startsWith("#include"))) return; if ( (pSettings->editor().completeBracket() && (DeletedChar == '[') && (NextChar == ']')) || (pSettings->editor().completeParenthese() && (DeletedChar == '(') && (NextChar == ')')) || (pSettings->editor().completeGlobalInclude() && (DeletedChar == '<') && (NextChar == '>')) || (pSettings->editor().completeBrace() && (DeletedChar == '{') && (NextChar == '}')) || (pSettings->editor().completeSingleQuote() && (DeletedChar == '\'') && (NextChar == '\'')) || (pSettings->editor().completeDoubleQuote() && (DeletedChar == '\"') && (NextChar == '\"'))) { commandProcessor(SynEditorCommand::ecDeleteChar); } } void Editor::wheelEvent(QWheelEvent *event) { if ( (event->modifiers() & Qt::ControlModifier)!=0) { int size = pSettings->editor().fontSize(); if (event->angleDelta().y()>0) { size = std::min(99,size+1); pSettings->editor().setFontSize(size); pMainWindow->updateEditorSettings(); event->accept(); return; } else if (event->angleDelta().y()<0) { size = std::max(2,size-1); pSettings->editor().setFontSize(size); pMainWindow->updateEditorSettings(); event->accept(); return; } } SynEdit::wheelEvent(event); } void Editor::focusInEvent(QFocusEvent *event) { SynEdit::focusInEvent(event); if (mParser) { connect(mParser.get(), &CppParser::onEndParsing, this, &SynEdit::invalidate); } pMainWindow->updateAppTitle(); pMainWindow->updateEditorActions(); pMainWindow->updateStatusbarForLineCol(); pMainWindow->updateForStatusbarModeInfo(); pMainWindow->updateClassBrowserForEditor(this); } void Editor::focusOutEvent(QFocusEvent *event) { SynEdit::focusOutEvent(event); if (mParser) { disconnect(mParser.get(), &CppParser::onEndParsing, this, &SynEdit::invalidate); } //pMainWindow->updateClassBrowserForEditor(nullptr); pMainWindow->updateStatusbarForLineCol(); pMainWindow->updateForStatusbarModeInfo(); pMainWindow->functionTip()->hide(); } void Editor::keyPressEvent(QKeyEvent *event) { bool handled = false; auto action = finally([&,this]{ if (!handled) { SynEdit::keyPressEvent(event); } else { event->accept(); } }); if (readOnly()) return; switch (event->key()) { case Qt::Key_Return: case Qt::Key_Enter: mLastIdCharPressed = 0; if (mTabStopBegin>=0) { // editing user code template handled = true; mTabStopBegin = -1; invalidateLine(caretY()); clearUserCodeInTabStops(); } else { QString s = lineText().mid(0,caretX()-1).trimmed(); if (s=="/**") { //javadoc style docstring s = lineText().mid(caretX()-1).trimmed(); if (s=="*/") { BufferCoord p = caretXY(); setBlockBegin(p); p.Char = lineText().length()+1; setBlockEnd(p); setSelText(""); } handled = true; QStringList insertString; insertString.append(""); PStatement function; if (mParser) function = mParser->findFunctionAt(mFilename,caretY()+1); if (function) { QStringList params; QString funcName = function->command; bool isVoid = (function->type == "void"); foreach (const PStatement& child, function->children) { if (child->kind == StatementKind::skParameter) params.append(child->command); } insertString.append(QString(" * @brief ")+USER_CODE_IN_INSERT_POS); if (!params.isEmpty()) insertString.append(" * "); foreach (const QString& param, params) { insertString.append(QString(" * @param %1 %2") .arg(param, USER_CODE_IN_INSERT_POS)); } if (!isVoid) { insertString.append(" * "); insertString.append(QString(" * @return ")+USER_CODE_IN_INSERT_POS); } insertString.append(" */"); // } else if (caretY()==1) { /* file header */ // insertString.append(QString(" * @file %1%2") // .arg(USER_CODE_IN_REPL_POS_BEGIN) // .arg(USER_CODE_IN_REPL_POS_END)); // insertString.append(QString(" * @brief: ")+ USER_CODE_IN_INSERT_POS); // insertString.append(QString(" * @version: ")+ USER_CODE_IN_INSERT_POS); // insertString.append(QString(" * @copyright: ")+ USER_CODE_IN_INSERT_POS); // insertString.append(QString(" * @author: ")+ USER_CODE_IN_INSERT_POS); // insertString.append(" * @date: "); // insertString.append(" * "); // insertString.append(" **/"); } else { insertString.append(QString(" * ")+USER_CODE_IN_INSERT_POS); insertString.append(" */"); } insertCodeSnippet(linesToText(insertString)); } else if (highlighter() && caretY()>=2 && highlighter()->isLastLineCommentNotFinished( lines()->ranges(caretY()-2).state)) { s=trimLeft(lineText()); if (s.startsWith("* ")) { handled = true; int right = lines()->getString(caretY()-1).length()-caretX(); s=lineBreak()+"* "; insertString(s,false); BufferCoord p = caretXY(); p.Line++; p.Char = lines()->getString(p.Line-1).length()+1; if (right>0) { p.Char -=right+1; } setCaretXY(p); } } } return; case Qt::Key_Escape: // Update function tip mLastIdCharPressed = 0; if (mTabStopBegin>=0) { mTabStopBegin = -1; invalidateLine(caretY()); clearUserCodeInTabStops(); } pMainWindow->functionTip()->hide(); return; case Qt::Key_Tab: tab(); return; case Qt::Key_Up: if (pMainWindow->functionTip()->isVisible()) { handled = true; pMainWindow->functionTip()->previousTip(); } else { mLastIdCharPressed = 0; clearUserCodeInTabStops(); } return; case Qt::Key_Down: if (pMainWindow->functionTip()->isVisible()) { handled = true; pMainWindow->functionTip()->nextTip(); } else { mLastIdCharPressed = 0; clearUserCodeInTabStops(); } return; case Qt::Key_Delete: // remove completed character mLastIdCharPressed = 0; if (!selAvail()) { undoSymbolCompletion(caretX()); } return; case Qt::Key_Backspace: // remove completed character mLastIdCharPressed = 0; if (!selAvail()) { undoSymbolCompletion(caretX()-1); } return; } QString t = event->text(); if (t.isEmpty()) return; QChar ch = t[0]; if (isIdentChar(ch)) { mLastIdCharPressed++; if (pSettings->codeCompletion().enabled() && pSettings->codeCompletion().showCompletionWhileInput() ) { if (mLastIdCharPressed==1) { if (mParser && mParser->isIncludeLine(lineText())) { // is a #include line setSelText(ch); showHeaderCompletion(false); handled=true; return; } else { QString lastWord = getPreviousWordAtPositionForSuggestion(caretXY()); if (!lastWord.isEmpty()) { if (CppTypeKeywords.contains(lastWord)) { if (lastWord == "long" || lastWord == "short" || lastWord == "signed" || lastWord == "unsigned" ) { setSelText(ch); showCompletion(lastWord,false); handled=true; return; } //last word is a type keyword, this is a var or param define, and dont show suggestion // if devEditor.UseTabnine then // ShowTabnineCompletion; return; } PStatement statement = mParser->findStatementOf( mFilename, lastWord, caretY()); StatementKind kind = mParser->getKindOfStatement(statement); if (kind == StatementKind::skClass || kind == StatementKind::skEnumClassType || kind == StatementKind::skEnumType || kind == StatementKind::skTypedef) { //last word is a typedef/class/struct, this is a var or param define, and dont show suggestion // if devEditor.UseTabnine then // ShowTabnineCompletion; return; } } setSelText(ch); showCompletion("",false); handled=true; return; } } } } else { //preprocessor ? if ((mLastIdCharPressed=0) && (ch=='#') && lineText().isEmpty()) { if (pSettings->codeCompletion().enabled() && pSettings->codeCompletion().showCompletionWhileInput() ) { mLastIdCharPressed++; setSelText(ch); showCompletion("",false); handled=true; return; } } //javadoc directive? if ((mLastIdCharPressed=0) && (ch=='#') && lineText().trimmed().startsWith('*')) { if (pSettings->codeCompletion().enabled() && pSettings->codeCompletion().showCompletionWhileInput() ) { mLastIdCharPressed++; setSelText(ch); showCompletion("",false); handled=true; return; } } mLastIdCharPressed = 0; switch (ch.unicode()) { case '"': case '\'': case '(': case ')': case '{': case '}': case '[': case ']': case '<': case '*': handled = handleSymbolCompletion(ch); return; case '>': if ((caretX() <= 1) || lineText().isEmpty() || lineText()[caretX() - 2] != '-') { handled = handleSymbolCompletion(ch); return; } break; } } // Spawn code completion window if we are allowed to if (pSettings->codeCompletion().enabled()) handled = handleCodeCompletion(ch); } void Editor::onGutterPaint(QPainter &painter, int aLine, int X, int Y) { IconsManager::PPixmap icon; if (mActiveBreakpointLine == aLine) { icon = pIconsManager->getPixmap(IconsManager::GUTTER_ACTIVEBREAKPOINT); } else if (hasBreakpoint(aLine)) { icon = pIconsManager->getPixmap(IconsManager::GUTTER_BREAKPOINT); } else { PSyntaxIssueList lst = getSyntaxIssuesAtLine(aLine); if (lst) { bool hasError=false; for (const PSyntaxIssue& issue : *lst) { if (issue->issueType == CompileIssueType::Error) { hasError = true; break;; } } if (hasError) { icon = pIconsManager->getPixmap(IconsManager::GUTTER_SYNTAX_ERROR); } else { icon = pIconsManager->getPixmap(IconsManager::GUTTER_SYNTAX_WARNING); } } else if (hasBookmark(aLine)) { icon = pIconsManager->getPixmap(IconsManager::GUTTER_BOOKMARK); } } if (icon) { X = 5; Y += (this->textHeight() - icon->height()) / 2; painter.drawPixmap(X,Y,*icon); } } void Editor::onGetEditingAreas(int Line, SynEditingAreaList &areaList) { areaList.clear(); if (mTabStopBegin>=0 && mTabStopY == Line) { PSynEditingArea p = make_shared(); p->type = SynEditingAreaType::eatRectangleBorder; // int spaceCount = leftSpaces(mLineBeforeTabStop); // int spaceBefore = mLineBeforeTabStop.length()-TrimLeft(mLineBeforeTabStop).length(); p->beginX = mTabStopBegin; p->endX = mTabStopEnd; p->color = highlighter()->stringAttribute()->foreground(); areaList.append(p); } PSyntaxIssueList lst = getSyntaxIssuesAtLine(Line); if (lst) { for (const PSyntaxIssue& issue: *lst) { PSynEditingArea p=std::make_shared(); p->beginX = issue->col; p->endX = issue->endCol; if (issue->issueType == CompileIssueType::Error) { p->color = mSyntaxErrorColor; } else { p->color = mSyntaxWarningColor; } p->type = SynEditingAreaType::eatWaveUnderLine; areaList.append(p); } } } bool Editor::onGetSpecialLineColors(int Line, QColor &foreground, QColor &backgroundColor) { if (Line == mActiveBreakpointLine && mActiveBreakpointBackgroundColor.isValid()) { if (mActiveBreakpointForegroundColor.isValid()) foreground = mActiveBreakpointForegroundColor; backgroundColor = mActiveBreakpointBackgroundColor; return true; } else if (hasBreakpoint(Line) && mBreakpointBackgroundColor.isValid()) { if (mBreakpointForegroundColor.isValid()) foreground = mBreakpointForegroundColor; backgroundColor = mBreakpointBackgroundColor; return true; } // end else if Line = fErrorLine then begin // StrToThemeColor(tc, devEditor.Syntax.Values[cErr]); // BG := tc.Background; // FG := tc.Foreground; // if (BG <> clNone) or (FG<>clNone) then // Special := TRUE; // end; return false; } void Editor::onPreparePaintHighlightToken(int line, int aChar, const QString &token, PSynHighlighterAttribute attr, SynFontStyles &style, QColor &foreground, QColor &background) { if (token.isEmpty()) return; if (mParser && highlighter() && (attr == highlighter()->identifierAttribute())) { BufferCoord p{aChar,line}; BufferCoord pBeginPos,pEndPos; QString s= getWordAtPosition(this,p, pBeginPos,pEndPos, WordPurpose::wpInformation); // qDebug()<findStatementOf(mFilename, s , p.Line); StatementKind kind = mParser->getKindOfStatement(statement); if (kind == StatementKind::skUnknown) { if ((pEndPos.Line>=1) && (pEndPos.Char>=0) && (pEndPos.Char < lines()->getString(pEndPos.Line-1).length()) && (lines()->getString(pEndPos.Line-1)[pEndPos.Char] == '(')) { kind = StatementKind::skFunction; } else { kind = StatementKind::skVariable; } } PColorSchemeItem item = mStatementColors->value(kind,PColorSchemeItem()); if (item) { foreground = item->foreground(); //background = item->background(); style.setFlag(SynFontStyle::fsBold,item->bold()); style.setFlag(SynFontStyle::fsItalic,item->italic()); style.setFlag(SynFontStyle::fsUnderline,item->underlined()); style.setFlag(SynFontStyle::fsStrikeOut,item->strikeout()); } else { foreground = highlighter()->identifierAttribute()->foreground(); } } //selection if (highlighter() && attr) { if (((attr == highlighter()->identifierAttribute()) || (attr == highlighter()->keywordAttribute()) || (attr->name() == SYNS_AttrPreprocessor) ) && (token == mCurrentHighlightedWord)) { if (mCurrentHighlighWordForeground.isValid()) foreground = mCurrentHighlighWordForeground; if (mCurrentHighlighWordBackground.isValid()) background = mCurrentHighlighWordBackground; } else if (!selAvail() && attr->name() == SYNS_AttrSymbol && pSettings->editor().highlightMathingBraces()) { // qDebug()<type() == QEvent::HoverEnter || event->type() == QEvent::HoverMove) && pSettings->editor().enableTooltips() ) { QHoverEvent *helpEvent = static_cast(event); BufferCoord p; TipType reason = getTipType(helpEvent->pos(),p); PSyntaxIssue pError; int line ; if (reason == TipType::Error) { pError = getSyntaxIssueAtPosition(p); } else if (pointToLine(helpEvent->pos(),line)) { //issue tips is prefered PSyntaxIssueList issues = getSyntaxIssuesAtLine(line); if (issues && !issues->isEmpty()) { reason = TipType::Error; pError = issues->front(); } } // Get subject bool isIncludeLine = false; BufferCoord pBeginPos,pEndPos; QString s; QStringList expression; switch (reason) { case TipType::Preprocessor: // When hovering above a preprocessor line, determine if we want to show an include or a identifier hint s = lines()->getString(p.Line - 1); isIncludeLine = mParser->isIncludeLine(s); if (!isIncludeLine) s = wordAtRowCol(p); break; case TipType::Identifier: if (pMainWindow->debugger()->executing() && !pMainWindow->debugger()->inferiorRunning()) s = getWordAtPosition(this,p, pBeginPos,pEndPos, WordPurpose::wpEvaluation); // debugging else if (//devEditor.ParserHints and !mCompletionPopup->isVisible() && !mHeaderCompletionPopup->isVisible()) { expression = getExpressionAtPosition(p); s = expression.join(""); // information during coding } break; case TipType::Selection: s = selText(); // when a selection is available, always only use that break; case TipType::Error: s = pError->token; break; case TipType::None: cancelHint(); event->ignore(); return true; } s = s.trimmed(); if ((s == mCurrentWord) && (mCurrentTipType == reason)) { if (helpEvent->modifiers() == Qt::ControlModifier) { setCursor(Qt::PointingHandCursor); } else { updateMouseCursor(); } event->ignore(); return true; // do NOT remove hint when subject stays the same } // Remove hint cancelHint(); mCurrentWord = s; mCurrentTipType = reason; // Determine what to do with subject QString hint = ""; switch (reason) { case TipType::Preprocessor: if (isIncludeLine) { if (pSettings->editor().enableHeaderToolTips()) hint = getFileHint(s); } else if (//devEditor.ParserHints and !mCompletionPopup->isVisible() && !mHeaderCompletionPopup->isVisible()) { if (pSettings->editor().enableIdentifierToolTips()) hint = getParserHint(QStringList(),s,p.Line); } break; case TipType::Identifier: case TipType::Selection: if (!mCompletionPopup->isVisible() && !mHeaderCompletionPopup->isVisible()) { if (pMainWindow->debugger()->executing() && (pSettings->editor().enableDebugTooltips())) { showDebugHint(s,p.Line); } else if (pSettings->editor().enableIdentifierToolTips()) { //if devEditor.ParserHints { hint = getParserHint(expression, s, p.Line); } } break; case TipType::Error: if (pSettings->editor().enableIssueToolTips()) hint = getErrorHint(pError); break; default: break; } // qDebug()<<"hint:"<(QApplication::instance()); // if (app->keyboardModifiers().testFlag(Qt::ControlModifier)) { if (helpEvent->modifiers() == Qt::ControlModifier) { setCursor(Qt::PointingHandCursor); } else if (cursor() == Qt::PointingHandCursor) { updateMouseCursor(); } QToolTip::showText(mapToGlobal(helpEvent->pos()),hint); event->ignore(); } else { updateMouseCursor(); event->ignore(); } return true; } else if (event->type() == QEvent::HoverLeave) { cancelHint(); return true; } else if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease ) { if (!mCurrentWord.isEmpty()) { QKeyEvent* keyEvent = dynamic_cast(event); if (keyEvent->key() == Qt::Key_Control) { QApplication* app = dynamic_cast(QApplication::instance()); QHoverEvent* hoverEvent=new QHoverEvent(QEvent::HoverMove, mapFromGlobal(QCursor::pos()), mapFromGlobal(QCursor::pos()), Qt::ControlModifier ); app->postEvent(this,hoverEvent); } } } return SynEdit::event(event); } void Editor::mouseReleaseEvent(QMouseEvent *event) { if (event->button() | Qt::LeftButton) { mLastIdCharPressed = 0; } // if ctrl+clicked if ((event->modifiers() == Qt::ControlModifier) && (event->button() == Qt::LeftButton)) { BufferCoord p; if (pointToCharLine(event->pos(),p)) { QString s = lines()->getString(p.Line - 1); if (mParser->isIncludeLine(s)) { QString filename = mParser->getHeaderFileName(mFilename,s); Editor * e = pMainWindow->editorList()->getEditorByFilename(filename); if (e) { e->setCaretPositionAndActivate(1,1); return; } } else { gotoDeclaration(p); return; } } } SynEdit::mouseReleaseEvent(event); } void Editor::inputMethodEvent(QInputMethodEvent *event) { SynEdit::inputMethodEvent(event); QString s = event->commitString(); if (s.isEmpty()) return; if (pMainWindow->completionPopup()->isVisible()) { onCompletionInputMethod(event); return; } else { mLastIdCharPressed+=s.length(); if (pSettings->codeCompletion().enabled() && pSettings->codeCompletion().showCompletionWhileInput() ) { if (mLastIdCharPressed>=1) { QString lastWord = getPreviousWordAtPositionForSuggestion(caretXY()); if (!lastWord.isEmpty()) { if (CppTypeKeywords.contains(lastWord)) { return; } PStatement statement = mParser->findStatementOf( mFilename, lastWord, caretY()); StatementKind kind = mParser->getKindOfStatement(statement); if (kind == StatementKind::skClass || kind == StatementKind::skEnumClassType || kind == StatementKind::skEnumType || kind == StatementKind::skTypedef) { //last word is a typedef/class/struct, this is a var or param define, and dont show suggestion // if devEditor.UseTabnine then // ShowTabnineCompletion; return; } } showCompletion("",false); return; } } } } void Editor::closeEvent(QCloseEvent *) { if (mHeaderCompletionPopup) mHeaderCompletionPopup->hide(); if (mCompletionPopup) mCompletionPopup->hide(); if (pMainWindow->functionTip()) pMainWindow->functionTip()->hide(); } void Editor::copyToClipboard() { if (pSettings->editor().copySizeLimit()) { int startLine = blockBegin().Line; int endLine = blockEnd().Line; if ((endLine-startLine+1) > pSettings->editor().copyLineLimits()) { QMessageBox::critical(pMainWindow,tr("Error"), tr("The text to be copied exceeds count limit!")); return; } if ((selText().length()) > pSettings->editor().copyCharLimits() * 1000) { QMessageBox::critical(pMainWindow,tr("Error"), tr("The text to be copied exceeds character limit!")); return; } } switch(pSettings->editor().copyWithFormatAs()) { case 1: //HTML copyAsHTML(); break;; default: SynEdit::copyToClipboard(); } } void Editor::cutToClipboard() { if (pSettings->editor().copySizeLimit()) { if (lines()->count() > pSettings->editor().copyLineLimits()) { QMessageBox::critical(pMainWindow,tr("Error"), tr("The text to be cut exceeds count limit!")); return; } if (lines()->getTextLength() > pSettings->editor().copyCharLimits() * 1000) { QMessageBox::critical(pMainWindow,tr("Error"), tr("The text to be cut exceeds character limit!")); return; } } SynEdit::cutToClipboard(); } void Editor::copyAsHTML() { if (!selAvail()) return; SynHTMLExporter exporter(tabWidth()); exporter.setTitle(QFileInfo(mFilename).fileName()); exporter.setExportAsText(false); exporter.setUseBackground(pSettings->editor().copyHTMLUseBackground()); exporter.setFont(font()); PSynHighlighter hl = highlighter(); if (!pSettings->editor().copyHTMLUseEditorColor()) { hl = highlighterManager.copyHighlighter(highlighter()); highlighterManager.applyColorScheme(hl,pSettings->editor().copyHTMLColorScheme()); } exporter.setHighlighter(hl); exporter.setOnFormatToken(std::bind(&Editor::onExportedFormatToken, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5 )); exporter.setCreateHTMLFragment(true); exporter.ExportRange(lines(),blockBegin(),blockEnd()); QMimeData * mimeData = new QMimeData; //sethtml will convert buffer to QString , which will cause encoding trouble mimeData->setData(exporter.clipboardFormat(),exporter.buffer()); mimeData->setText(selText()); QGuiApplication::clipboard()->clear(); QGuiApplication::clipboard()->setMimeData(mimeData); } void Editor::setCaretPosition(int line, int aChar) { this->uncollapseAroundLine(line); this->setCaretXYCentered(BufferCoord{aChar,line}); } void Editor::setCaretPositionAndActivate(int line, int aChar) { this->uncollapseAroundLine(line); if (!this->hasFocus()) this->activate(); this->setCaretXYCentered(BufferCoord{aChar,line}); } void Editor::addSyntaxIssues(int line, int startChar, int endChar, CompileIssueType errorType, const QString &hint) { PSyntaxIssue pError; BufferCoord p; QString token; SynHighlighterTokenType tokenType; int tokenKind,start; PSynHighlighterAttribute attr; PSyntaxIssueList lst; if ((line<1) || (line>lines()->count())) return; pError = std::make_shared(); p.Char = startChar; p.Line = line; if (startChar >= lines()->getString(line-1).length()) { start = 1; token = lines()->getString(line-1); } else if (endChar < 1) { if (!getHighlighterAttriAtRowColEx(p,token,tokenType,tokenKind,start,attr)) return; } else { start = startChar; token = lines()->getString(line-1).mid(start-1,endChar-startChar); } pError->startChar = start; pError->endChar = start + token.length(); // pError->col = charToColumn(line,pError->startChar); // pError->endCol = charToColumn(line,pError->endChar); pError->col = pError->startChar; pError->endCol = pError->endChar; pError->hint = hint; pError->token = token; pError->issueType = errorType; if (mSyntaxIssues.contains(line)) { lst = mSyntaxIssues[line]; } else { lst = std::make_shared(); mSyntaxIssues[line] = lst; } lst->append(pError); } void Editor::clearSyntaxIssues() { mSyntaxIssues.clear(); } void Editor::gotoNextSyntaxIssue() { auto iter = mSyntaxIssues.find(caretY()); if (iter==mSyntaxIssues.end()) return; iter++; if (iter==mSyntaxIssues.end()) return; BufferCoord p; p.Char = (*iter)->at(0)->startChar; p.Line = iter.key(); setCaretXY(p); } void Editor::gotoPrevSyntaxIssue() { auto iter = mSyntaxIssues.find(caretY()); if (iter==mSyntaxIssues.end()) return; if (iter==mSyntaxIssues.begin()) return; iter--; BufferCoord p; p.Char = (*iter)->at(0)->startChar; p.Line = iter.key(); setCaretXY(p); } bool Editor::hasNextSyntaxIssue() const { auto iter = mSyntaxIssues.find(caretY()); if (iter==mSyntaxIssues.end()) return false; iter++; if (iter==mSyntaxIssues.end()) return false; return true; } bool Editor::hasPrevSyntaxIssue() const { auto iter = mSyntaxIssues.find(caretY()); if (iter==mSyntaxIssues.end()) return true; if (iter==mSyntaxIssues.begin()) return true; return false; } Editor::PSyntaxIssueList Editor::getSyntaxIssuesAtLine(int line) { if (mSyntaxIssues.contains(line)) return mSyntaxIssues[line]; return PSyntaxIssueList(); } Editor::PSyntaxIssue Editor::getSyntaxIssueAtPosition(const BufferCoord &pos) { PSyntaxIssueList lst = getSyntaxIssuesAtLine(pos.Line); if (!lst) return PSyntaxIssue(); foreach (const PSyntaxIssue& issue, *lst) { if (issue->startChar<=pos.Char && pos.Char<=issue->endChar) return issue; } return PSyntaxIssue(); } void Editor::onStatusChanged(SynStatusChanges changes) { if ((!changes.testFlag(SynStatusChange::scReadOnly) && !changes.testFlag(SynStatusChange::scInsertMode) && (lines()->count()!=mLineCount) && (lines()->count()!=0) && ((mLineCount>0) || (lines()->count()>1))) || (mCurrentLineModified && !changes.testFlag(SynStatusChange::scReadOnly) && changes.testFlag(SynStatusChange::scCaretY))) { mCurrentLineModified = false; if (pSettings->codeCompletion().clearWhenEditorHidden() && changes.testFlag(SynStatusChange::scOpenFile)) { } else{ reparse(); } if (pSettings->editor().syntaxCheckWhenLineChanged()) checkSyntaxInBack(); reparseTodo(); } mLineCount = lines()->count(); if (changes.testFlag(scModifyChanged)) { updateCaption(); } if (changes.testFlag(scModified)) { mCurrentLineModified = true; if (mParentPageControl!=nullptr) mCanAutoSave = true; } if (changes.testFlag(SynStatusChange::scCaretX) || changes.testFlag(SynStatusChange::scCaretY)) { if (mTabStopBegin >=0) { if (mTabStopY==caretY()) { if (mLineAfterTabStop.isEmpty()) { if (lineText().startsWith(mLineBeforeTabStop)) mTabStopBegin = mLineBeforeTabStop.length()+1; mTabStopEnd = lineText().length()+1; } else { if (lineText().startsWith(mLineBeforeTabStop) && lineText().endsWith(mLineAfterTabStop)) mTabStopBegin = mLineBeforeTabStop.length()+1; mTabStopEnd = lineText().length() - mLineAfterTabStop.length()+1; } mXOffsetSince = mTabStopEnd - caretX(); if (caretX() < mTabStopBegin || caretX() > (mTabStopEnd+1)) { mTabStopBegin = -1; } } else { if (mTabStopBegin>=0) { invalidateLine(mTabStopY); mTabStopBegin = -1; clearUserCodeInTabStops(); } } } else if (!selAvail() && highlighter() && pSettings->editor().highlightMathingBraces()){ invalidateLine(mHighlightCharPos1.Line); invalidateLine(mHighlightCharPos2.Line); mHighlightCharPos1 = BufferCoord{0,0}; mHighlightCharPos2 = BufferCoord{0,0}; // Is there a bracket char before us? int lineLength = lineText().length(); int ch = caretX() - 2; BufferCoord coord; if (ch>=0 && ch=0 && chsymbolAttribute()) { BufferCoord complementCharPos = getMatchingBracketEx(coord); if (!foldHidesLine(coord.Line) && !foldHidesLine(complementCharPos.Line)) { mHighlightCharPos1 = coord; mHighlightCharPos2 = complementCharPos; invalidateLine(mHighlightCharPos1.Line); invalidateLine(mHighlightCharPos2.Line); } } } } // scSelection includes anything caret related if (changes.testFlag(SynStatusChange::scSelection)) { if (!selAvail() && pSettings->editor().highlightCurrentWord()) { mCurrentHighlightedWord = wordAtCursor(); } else if (selAvail() && blockBegin() == wordStart() && blockEnd() == wordEnd()){ mCurrentHighlightedWord = selText(); } else { mCurrentHighlightedWord = ""; } if (mOldHighlightedWord != mCurrentHighlightedWord) { invalidate(); mOldHighlightedWord = mCurrentHighlightedWord; } pMainWindow->updateStatusbarForLineCol(); // Update the function tip if (pSettings->editor().showFunctionTips()) { updateFunctionTip(); } } if (changes.testFlag(scInsertMode) | changes.testFlag(scReadOnly)) pMainWindow->updateForStatusbarModeInfo(); pMainWindow->updateEditorActions(); if (changes.testFlag(SynStatusChange::scCaretY) && mParentPageControl) { pMainWindow->caretList().addCaret(this,caretY(),caretX()); pMainWindow->updateCaretActions(); } } void Editor::onGutterClicked(Qt::MouseButton button, int , int , int line) { if (button == Qt::LeftButton) { toggleBreakpoint(line); } mGutterClickedLine = line; } void Editor::onTipEvalValueReady(const QString& value) { if (mCurrentWord == mCurrentDebugTipWord) { QString newValue; if (value.length()>100) { newValue = value.left(100) + "..."; } else { newValue = value; } QToolTip::showText(QCursor::pos(), mCurrentDebugTipWord + " = " + newValue); } disconnect(pMainWindow->debugger(), &Debugger::evalValueReady, this, &Editor::onTipEvalValueReady); } void Editor::onLinesDeleted(int first, int count) { pMainWindow->caretList().linesDeleted(this,first,count); pMainWindow->debugger()->breakpointModel()->onFileDeleteLines(mFilename,first,count); pMainWindow->bookmarkModel()->onFileDeleteLines(mFilename,first,count); resetBreakpoints(); resetBookmarks(); if (!pSettings->editor().syntaxCheckWhenLineChanged()) { //todo: update syntax issues } } void Editor::onLinesInserted(int first, int count) { pMainWindow->caretList().linesInserted(this,first,count); pMainWindow->debugger()->breakpointModel()->onFileInsertLines(mFilename,first,count); pMainWindow->bookmarkModel()->onFileInsertLines(mFilename,first,count); resetBreakpoints(); resetBookmarks(); if (!pSettings->editor().syntaxCheckWhenLineChanged()) { //todo: update syntax issues } } bool Editor::isBraceChar(QChar ch) { switch( ch.unicode()) { case '{': case '}': case '[': case ']': case '(': case ')': return true; default: return false; } } void Editor::resetBookmarks() { mBookmarkLines=pMainWindow->bookmarkModel()->bookmarksInFile(mFilename); invalidate(); } void Editor::resetBreakpoints() { mBreakpointLines.clear(); foreach (const PBreakpoint& breakpoint, pMainWindow->debugger()->breakpointModel()->breakpoints()) { if (breakpoint->filename == mFilename) { mBreakpointLines.insert(breakpoint->line); } } invalidate(); } bool Editor::notParsed() { if (!mParser) return true; return mParser->findFileIncludes(mFilename)==nullptr; } void Editor::insertLine() { ExecuteCommand(SynEditorCommand::ecInsertLine,QChar(),nullptr); } void Editor::deleteWord() { ExecuteCommand(SynEditorCommand::ecDeleteWord,QChar(),nullptr); } void Editor::deleteLine() { ExecuteCommand(SynEditorCommand::ecDeleteLine,QChar(),nullptr); } void Editor::duplicateLine() { ExecuteCommand(SynEditorCommand::ecDuplicateLine,QChar(),nullptr); } void Editor::deleteToEOL() { ExecuteCommand(SynEditorCommand::ecDeleteEOL,QChar(),nullptr); } void Editor::deleteToBOL() { ExecuteCommand(SynEditorCommand::ecDeleteBOL,QChar(),nullptr); } QStringList Editor::getOwnerExpressionAndMemberAtPositionForCompletion( const BufferCoord &pos, QString &memberOperator, QStringList &memberExpression) { QStringList expression = getExpressionAtPosition(pos); return getOwnerExpressionAndMember(expression,memberOperator,memberExpression); } QStringList Editor::getExpressionAtPosition( const BufferCoord &pos) { QStringList result; if (!highlighter()) return result; int line = pos.Line-1; int ch = pos.Char-1; int symbolMatchingLevel = 0; LastSymbolType lastSymbolType=LastSymbolType::None; PSynHighlighter highlighter = highlighterManager.getHighlighter(mFilename); if (!highlighter) return result; while (true) { if (line>=lines()->count() || line<0) break; QStringList tokens; if (line==0) { highlighter->resetState(); } else { highlighter->setState(lines()->ranges(line-1)); } QString sLine = lines()->getString(line); highlighter->setLine(sLine,line-1); while (!highlighter->eol()) { int start = highlighter->getTokenPos(); QString token = highlighter->getToken(); int endPos = start + token.length()-1; if (start>ch) { break; } PSynHighlighterAttribute attr = highlighter->getTokenAttribute(); if ( (line == pos.Line-1) && (start<=ch) && (ch<=endPos)) { if (attr==highlighter->commentAttribute() || attr == highlighter->stringAttribute()) { return result; } } if (attr!=highlighter->commentAttribute() && attr!=highlighter->whitespaceAttribute()){ tokens.append(token); } highlighter->next(); } for (int i=tokens.count()-1;i>=0;i--) { QString token = tokens[i]; switch(lastSymbolType) { case LastSymbolType::ScopeResolutionOperator: //before '::' if (token==">") { lastSymbolType=LastSymbolType::MatchingAngleQuotation; symbolMatchingLevel=0; } else if (isIdentChar(token.front())) { lastSymbolType=LastSymbolType::Identifier; } else return result; break; case LastSymbolType::ObjectMemberOperator: //before '.' case LastSymbolType::PointerMemberOperator: //before '->' case LastSymbolType::PointerToMemberOfObjectOperator: //before '.*' case LastSymbolType::PointerToMemberOfPointerOperator: //before '->*' if (token == ")" ) { lastSymbolType=LastSymbolType::MatchingParenthesis; symbolMatchingLevel = 0; } else if (token == "]") { lastSymbolType=LastSymbolType::MatchingBracket; symbolMatchingLevel = 0; } else if (isIdentChar(token.front())) { lastSymbolType=LastSymbolType::Identifier; } else return result; break; case LastSymbolType::AsteriskSign: // before '*': if (token == '*') { } else return result; break; case LastSymbolType::AmpersandSign: // before '&': return result; break; case LastSymbolType::ParenthesisMatched: //before '()' // if (token == ".") { // lastSymbolType=LastSymbolType::ObjectMemberOperator; // } else if (token=="->") { // lastSymbolType = LastSymbolType::PointerMemberOperator; // } else if (token == ".*") { // lastSymbolType = LastSymbolType::PointerToMemberOfObjectOperator; // } else if (token == "->*"){ // lastSymbolType = LastSymbolType::PointerToMemberOfPointerOperator; // } else if (token==">") { // lastSymbolType=LastSymbolType::MatchingAngleQuotation; // symbolMatchingLevel=0; // } else if (token == ")" ) { lastSymbolType=LastSymbolType::MatchingParenthesis; symbolMatchingLevel = 0; } else if (token == "]") { lastSymbolType=LastSymbolType::MatchingBracket; symbolMatchingLevel = 0; } else if (token == "*") { lastSymbolType=LastSymbolType::AsteriskSign; } else if (token == "&") { lastSymbolType=LastSymbolType::AmpersandSign; } else if (isIdentChar(token.front())) { lastSymbolType=LastSymbolType::Identifier; } else return result; break; case LastSymbolType::BracketMatched: //before '[]' if (token == ")" ) { lastSymbolType=LastSymbolType::MatchingParenthesis; symbolMatchingLevel = 0; } else if (token == "]") { lastSymbolType=LastSymbolType::MatchingBracket; symbolMatchingLevel = 0; } else if (isIdentChar(token.front())) { lastSymbolType=LastSymbolType::Identifier; } else return result; break; case LastSymbolType::AngleQuotationMatched: //before '<>' if (isIdentChar(token.front())) { lastSymbolType=LastSymbolType::Identifier; } else return result; break; case LastSymbolType::None: if (token =="::") { lastSymbolType=LastSymbolType::ScopeResolutionOperator; } else if (token == ".") { lastSymbolType=LastSymbolType::ObjectMemberOperator; } else if (token=="->") { lastSymbolType = LastSymbolType::PointerMemberOperator; } else if (token == ".*") { lastSymbolType = LastSymbolType::PointerToMemberOfObjectOperator; } else if (token == "->*"){ lastSymbolType = LastSymbolType::PointerToMemberOfPointerOperator; } else if (token == ")" ) { lastSymbolType=LastSymbolType::MatchingParenthesis; symbolMatchingLevel = 0; } else if (token == "]") { lastSymbolType=LastSymbolType::MatchingBracket; symbolMatchingLevel = 0; } else if (isIdentChar(token.front())) { lastSymbolType=LastSymbolType::Identifier; } else return result; break; case LastSymbolType::TildeSign: if (token =="::") { lastSymbolType=LastSymbolType::ScopeResolutionOperator; } else { // "~" must appear after "::" result.pop_front(); return result; } break;; case LastSymbolType::Identifier: if (token =="::") { lastSymbolType=LastSymbolType::ScopeResolutionOperator; } else if (token == ".") { lastSymbolType=LastSymbolType::ObjectMemberOperator; } else if (token=="->") { lastSymbolType = LastSymbolType::PointerMemberOperator; } else if (token == ".*") { lastSymbolType = LastSymbolType::PointerToMemberOfObjectOperator; } else if (token == "->*"){ lastSymbolType = LastSymbolType::PointerToMemberOfPointerOperator; } else if (token == "~") { lastSymbolType=LastSymbolType::TildeSign; } else if (token == "*") { lastSymbolType=LastSymbolType::AsteriskSign; } else if (token == "&") { lastSymbolType=LastSymbolType::AmpersandSign; } else return result; // stop matching; break; case LastSymbolType::MatchingParenthesis: if (token=="(") { if (symbolMatchingLevel==0) { lastSymbolType=LastSymbolType::ParenthesisMatched; } else { symbolMatchingLevel--; } } else if (token==")") { symbolMatchingLevel++; } break; case LastSymbolType::MatchingBracket: if (token=="[") { if (symbolMatchingLevel==0) { lastSymbolType=LastSymbolType::BracketMatched; } else { symbolMatchingLevel--; } } else if (token=="]") { symbolMatchingLevel++; } break; case LastSymbolType::MatchingAngleQuotation: if (token=="<") { if (symbolMatchingLevel==0) { lastSymbolType=LastSymbolType::MatchingAngleQuotation; } else { symbolMatchingLevel--; } } else if (token==">") { symbolMatchingLevel++; } break; } result.push_front(token); } line--; if (line>=0) ch = lines()->getString(line).length()+1; } return result; } QString Editor::getWordForCompletionSearch(const BufferCoord &pos,bool permitTilde) { QString result = ""; QString s; s = lines()->getString(pos.Line - 1); int len = s.length(); int wordBegin = pos.Char - 1 - 1; //BufferCoord::Char starts with 1 int wordEnd = pos.Char - 1 - 1; while ((wordBegin >= 0) && (wordBegineditor().completeSymbols() || selAvail()) return false; if (!insertMode()) return false; //todo: better methods to detect current caret type if (caretX() <= 1) { if (caretY()>1) { if (highlighter()->isLastLineCommentNotFinished(lines()->ranges(caretY() - 2).state)) return false; if (highlighter()->isLastLineStringNotFinished(lines()->ranges(caretY() - 2).state) && (key!='\"') && (key!='\'')) return false; } } else { BufferCoord HighlightPos = BufferCoord{caretX()-1, caretY()}; // Check if that line is highlighted as comment PSynHighlighterAttribute Attr; QString Token; bool tokenFinished; SynHighlighterTokenType tokenType; if (getHighlighterAttriAtRowCol(HighlightPos, Token, tokenFinished, tokenType,Attr)) { if ((tokenType == SynHighlighterTokenType::Comment) && (!tokenFinished)) return false; if ((tokenType == SynHighlighterTokenType::String) && (!tokenFinished) && (key!='\'') && (key!='\"') && (key!='(') && (key!=')')) return false; if (( key=='<' || key =='>') && (mParser && !mParser->isIncludeLine(lineText()))) return false; if ((key == '\'') && (Attr->name() == "SYNS_AttrNumber")) return false; } } // Check if that line is highlighted as string or character or comment // if (Attr = fText.Highlighter.StringAttribute) or (Attr = fText.Highlighter.CommentAttribute) or SameStr(Attr.Name, // 'Character') then // Exit; QuoteStatus status; switch(key.unicode()) { case '(': if (pSettings->editor().completeParenthese()) { return handleParentheseCompletion(); } return false; case ')': if (pSettings->editor().completeParenthese() && pSettings->editor().overwriteSymbols()) { return handleParentheseSkip(); } return false; case '[': if (pSettings->editor().completeBracket()) { return handleBracketCompletion(); } return false; case ']': if (pSettings->editor().completeBracket() && pSettings->editor().overwriteSymbols()) { return handleBracketSkip(); } return false; case '*': status = getQuoteStatus(); if (pSettings->editor().completeComment() && (status == QuoteStatus::NotQuote)) { return handleMultilineCommentCompletion(); } return false; case '{': if (pSettings->editor().completeBrace()) { return handleBraceCompletion(); } return false; case '}': if (pSettings->editor().completeBrace() && pSettings->editor().overwriteSymbols()) { return handleBraceSkip(); } return false; case '\'': if (pSettings->editor().completeSingleQuote()) { return handleSingleQuoteCompletion(); } return false; case '\"': if (pSettings->editor().completeDoubleQuote()) { return handleDoubleQuoteCompletion(); } return false; case '<': if (pSettings->editor().completeGlobalInclude()) { // #include <> return handleGlobalIncludeCompletion(); } return false; case '>': if (pSettings->editor().completeGlobalInclude() && pSettings->editor().overwriteSymbols()) { // #include <> return handleGlobalIncludeSkip(); } return false; } return false; } bool Editor::handleParentheseCompletion() { QuoteStatus status = getQuoteStatus(); if (status == QuoteStatus::RawString || status == QuoteStatus::NotQuote) { beginUpdate(); commandProcessor(SynEditorCommand::ecChar,'('); BufferCoord oldCaret = caretXY(); commandProcessor(SynEditorCommand::ecChar,')'); setCaretXY(oldCaret); endUpdate(); return true; } // if (status == QuoteStatus::NotQuote) && FunctionTipAllowed then // fFunctionTip.Activated := true; return false; } bool Editor::handleParentheseSkip() { if (getCurrentChar() != ')') return false; QuoteStatus status = getQuoteStatus(); if (status == QuoteStatus::RawStringNoEscape) { setCaretXY( BufferCoord{caretX() + 1, caretY()}); // skip over return true; } if (status != QuoteStatus::NotQuote) return false; BufferCoord pos = getMatchingBracket(); if (pos.Line != 0) { setCaretXY( BufferCoord{caretX() + 1, caretY()}); // skip over return true; } // if FunctionTipAllowed then // fFunctionTip.Activated := false; return false; } bool Editor::handleBracketCompletion() { // QuoteStatus status = getQuoteStatus(); // if (status == QuoteStatus::RawString || status == QuoteStatus::NotQuote) { beginUpdate(); commandProcessor(SynEditorCommand::ecChar,'['); BufferCoord oldCaret = caretXY(); commandProcessor(SynEditorCommand::ecChar,']'); setCaretXY(oldCaret); endUpdate(); return true; // } } bool Editor::handleBracketSkip() { if (getCurrentChar() != ']') return false; BufferCoord pos = getMatchingBracket(); if (pos.Line != 0) { setCaretXY( BufferCoord{caretX() + 1, caretY()}); // skip over return true; } return false; } bool Editor::handleMultilineCommentCompletion() { if ((caretX()-2 < lineText().length()) && (lineText()[caretX() - 2] == '/')) { beginUpdate(); commandProcessor(SynEditorCommand::ecChar,'*'); BufferCoord oldCaret = caretXY(); commandProcessor(SynEditorCommand::ecChar,'*'); commandProcessor(SynEditorCommand::ecChar,'/'); setCaretXY(oldCaret); endUpdate(); return true; } return false; } bool Editor::handleBraceCompletion() { QString s = lineText().trimmed(); int i= caretY()-2; while ((s.isEmpty()) && (i>=0)) { s=lines()->getString(i); i--; } beginUpdate(); commandProcessor(SynEditorCommand::ecChar,'{'); BufferCoord oldCaret = caretXY(); commandProcessor(SynEditorCommand::ecChar,'}'); if ( ( (s.startsWith("struct") || s.startsWith("class") || s.startsWith("union") || s.startsWith("typedef") || s.startsWith("public") || s.startsWith("private") || s.startsWith("enum") ) && !s.contains(';') ) || s.endsWith('=')) { commandProcessor(SynEditorCommand::ecChar,';'); } setCaretXY(oldCaret); endUpdate(); return true; } bool Editor::handleBraceSkip() { if (getCurrentChar() != '}') return false; BufferCoord pos = getMatchingBracket(); if (pos.Line != 0) { bool oldInsertMode = insertMode(); setInsertMode(false); //set mode to overwrite commandProcessor(SynEditorCommand::ecChar,'}'); setInsertMode(oldInsertMode); // setCaretXY( BufferCoord{caretX() + 1, caretY()}); // skip over return true; } return false; } bool Editor::handleSingleQuoteCompletion() { QuoteStatus status = getQuoteStatus(); QChar ch = getCurrentChar(); if (ch == '\'') { if (status == QuoteStatus::SingleQuote) { setCaretXY( BufferCoord{caretX() + 1, caretY()}); // skip over return true; } } else { if (status == QuoteStatus::NotQuote) { if (ch == 0 || highlighter()->isWordBreakChar(ch) || highlighter()->isSpaceChar(ch)) { // insert '' beginUpdate(); commandProcessor(SynEditorCommand::ecChar,'\''); BufferCoord oldCaret = caretXY(); commandProcessor(SynEditorCommand::ecChar,'\''); setCaretXY(oldCaret); endUpdate(); return true; } } } return false; } bool Editor::handleDoubleQuoteCompletion() { QuoteStatus status = getQuoteStatus(); QChar ch = getCurrentChar(); if (ch == '"') { if (status == QuoteStatus::DoubleQuote || status == QuoteStatus::RawString) { setCaretXY( BufferCoord{caretX() + 1, caretY()}); // skip over return true; } } else { if (status == QuoteStatus::NotQuote) { if ((ch == 0) || highlighter()->isWordBreakChar(ch) || highlighter()->isSpaceChar(ch)) { // insert "" beginUpdate(); commandProcessor(SynEditorCommand::ecChar,'"'); BufferCoord oldCaret = caretXY(); commandProcessor(SynEditorCommand::ecChar,'"'); setCaretXY(oldCaret); endUpdate(); return true; } } } return false; } bool Editor::handleGlobalIncludeCompletion() { if (!lineText().startsWith('#')) return false; QString s= lineText().mid(1).trimmed(); if (!s.startsWith("include")) //it's not #include return false; beginUpdate(); commandProcessor(SynEditorCommand::ecChar,'<'); BufferCoord oldCaret = caretXY(); commandProcessor(SynEditorCommand::ecChar,'>'); setCaretXY(oldCaret); endUpdate(); return true; } bool Editor::handleGlobalIncludeSkip() { if (getCurrentChar()!='>') return false; QString s= lineText().mid(1).trimmed(); if (!s.startsWith("include")) //it's not #include return false; BufferCoord pos = getMatchingBracket(); if (pos.Line != 0) { setCaretXY( BufferCoord{caretX() + 1, caretY()}); // skip over return true; } return false; } bool Editor::handleCodeCompletion(QChar key) { if (!mCompletionPopup->isEnabled()) return false; switch(key.unicode()) { case '.': setSelText(key); showCompletion("",false); return true; case '>': setSelText(key); if ((caretX() > 2) && (lineText().length() >= 2) && (lineText()[caretX() - 3] == '-')) showCompletion("",false); return true; case ':': ExecuteCommand(SynEditorCommand::ecChar,':',nullptr); //setSelText(key); if ((caretX() > 2) && (lineText().length() >= 2) && (lineText()[caretX() - 3] == ':')) showCompletion("",false); return true; case '/': case '\\': setSelText(key); if (mParser->isIncludeLine(lineText())) { showHeaderCompletion(false); } return true; default: return false; } } void Editor::initParser() { mParser = std::make_shared(); mParser->setOnGetFileStream( std::bind( &EditorList::getContentFromOpenedEditor,pMainWindow->editorList(), std::placeholders::_1, std::placeholders::_2)); resetCppParser(mParser); mParser->setEnabled((highlighter() && highlighter()->getClass() == SynHighlighterClass::CppHighlighter)); } Editor::QuoteStatus Editor::getQuoteStatus() { QuoteStatus Result = QuoteStatus::NotQuote; if ((caretY()>1) && highlighter()->isLastLineStringNotFinished(lines()->ranges(caretY() - 2).state)) Result = QuoteStatus::DoubleQuote; QString Line = lines()->getString(caretY()-1); int posX = caretX()-1; if (posX >= Line.length()) { posX = Line.length()-1; } for (int i=0; itodoParser()->parseFile(mFilename); } void Editor::insertString(const QString &value, bool moveCursor) { beginUpdate(); auto action = finally([this]{ endUpdate(); }); BufferCoord oldCursorPos = caretXY(); setSelText(value); if (!moveCursor) { setCaretXY(oldCursorPos); } } void Editor::insertCodeSnippet(const QString &code) { clearUserCodeInTabStops(); mXOffsetSince = 0; mTabStopBegin = -1; mTabStopEnd = -1; mTabStopY =0; mLineBeforeTabStop = ""; mLineAfterTabStop = ""; // prevent lots of repaints beginUpdate(); auto action = finally([this]{ endUpdate(); }); QStringList sl = textToLines(parseMacros(code)); int lastI=0; int spaceCount = GetLeftSpacing( leftSpaces(lineText()),true).length(); QStringList newSl; for (int i=0;i0) lastPos = -spaceCount; while (true) { int insertPos = s.indexOf(USER_CODE_IN_INSERT_POS); if (insertPos < 0) // no %INSERT% macro in this line now break; PTabStop p = std::make_shared(); s.remove(insertPos, QString(USER_CODE_IN_INSERT_POS).length()); //insertPos--; p->x = insertPos - lastPos; p->endX = p->x ; p->y = i - lastI; lastPos = insertPos; lastI = i; mUserCodeInTabStops.append(p); } lastPos = 0; if (i>0) lastPos = -spaceCount; while (true) { int insertPos = s.indexOf(USER_CODE_IN_REPL_POS_BEGIN); if (insertPos < 0) // no %INSERT% macro in this line now break; PTabStop p = std::make_shared(); s.remove(insertPos, QString(USER_CODE_IN_REPL_POS_BEGIN).length()); //insertPos--; p->x = insertPos - lastPos; int insertEndPos = insertPos + s.mid(insertPos).indexOf(USER_CODE_IN_REPL_POS_END); if (insertEndPos < insertPos) { p->endX = s.length(); } else { s.remove(insertEndPos, QString(USER_CODE_IN_REPL_POS_END).length()); //insertEndPos--; p->endX = insertEndPos - lastPos; } p->y=i-lastI; lastPos = insertEndPos; lastI = i; mUserCodeInTabStops.append(p); } newSl.append(s); } BufferCoord cursorPos = caretXY(); QString s = linesToText(newSl); // if EndsStr(#13#10,s) then // Delete(s,Length(s)-1,2) // else if EndsStr(#10, s) then // Delete(s,Length(s),1); setSelText(s); setCaretXY(cursorPos); //restore cursor pos before insert // fText.SelText := s; // Text.CaretXY := CursorPos; if (mUserCodeInTabStops.count()>0) { mTabStopBegin = caretX(); mTabStopEnd = caretX(); popUserCodeInTabStops(); } if (!code.isEmpty()) { mLastIdCharPressed = 0; } } void Editor::print() { QPrinter printer; QPrintDialog dialog(&printer, this); dialog.setWindowTitle(tr("Print Document")); // if (editor->selAvail()) // dialog.addEnabledOption(QAbstractPrintDialog::PrintSelection); if (dialog.exec() != QDialog::Accepted) { return; } QTextDocument doc; // if (editor->selAvail()) { // doc.setPlainText(editor->selText()); // } else { QStringList lst = contents(); for (int i=0;ieditor().copyRTFUseBackground()); exporter.setFont(font()); PSynHighlighter hl = highlighter(); if (!pSettings->editor().copyRTFUseEditorColor()) { hl = highlighterManager.copyHighlighter(highlighter()); highlighterManager.applyColorScheme(hl,pSettings->editor().copyRTFColorScheme()); } exporter.setHighlighter(hl); exporter.setOnFormatToken(std::bind(&Editor::onExportedFormatToken, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5 )); exporter.ExportAll(lines()); exporter.SaveToFile(rtfFilename); } void Editor::exportAsHTML(const QString &htmlFilename) { SynHTMLExporter exporter(tabWidth()); exporter.setTitle(extractFileName(htmlFilename)); exporter.setExportAsText(false); exporter.setUseBackground(pSettings->editor().copyHTMLUseBackground()); exporter.setFont(font()); PSynHighlighter hl = highlighter(); if (!pSettings->editor().copyHTMLUseEditorColor()) { hl = highlighterManager.copyHighlighter(highlighter()); highlighterManager.applyColorScheme(hl,pSettings->editor().copyHTMLColorScheme()); } exporter.setHighlighter(hl); exporter.setOnFormatToken(std::bind(&Editor::onExportedFormatToken, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5 )); exporter.ExportAll(lines()); exporter.SaveToFile(htmlFilename); } void Editor::showCompletion(const QString& preWord,bool autoComplete) { if (!pSettings->codeCompletion().enabled()) return; if (!mParser->enabled()) return; if (mCompletionPopup->isVisible()) // already in search, don't do it again return; QString word=""; QString s; PSynHighlighterAttribute attr; bool tokenFinished; SynHighlighterTokenType tokenType; BufferCoord pBeginPos, pEndPos; if (getHighlighterAttriAtRowCol( BufferCoord{caretX() - 1, caretY()}, s, tokenFinished,tokenType, attr)) { if (tokenType == SynHighlighterTokenType::PreprocessDirective) {//Preprocessor word = getWordAtPosition(this,caretXY(),pBeginPos,pEndPos, WordPurpose::wpDirective); if (!word.startsWith('#')) { word = ""; } } else if (tokenType == SynHighlighterTokenType::Comment) { //Comment, javadoc tag word = getWordAtPosition(this,caretXY(),pBeginPos,pEndPos, WordPurpose::wpJavadoc); if (!word.startsWith('@')) { return; } } else if ( (tokenType != SynHighlighterTokenType::Symbol) && (tokenType != SynHighlighterTokenType::Space) && (tokenType != SynHighlighterTokenType::Identifier) ) { return; } } // Position it at the top of the next line QPoint p = rowColumnToPixels(displayXY()); p+=QPoint(0,textHeight()+2); mCompletionPopup->move(mapToGlobal(p)); mCompletionPopup->setRecordUsage(pSettings->codeCompletion().recordUsage()); mCompletionPopup->setSortByScope(pSettings->codeCompletion().sortByScope()); mCompletionPopup->setShowKeywords(pSettings->codeCompletion().showKeywords()); mCompletionPopup->setShowCodeSnippets(pSettings->codeCompletion().showCodeIns()); if (pSettings->codeCompletion().showCodeIns()) { mCompletionPopup->setCodeSnippets(pMainWindow->codeSnippetManager()->snippets()); } mCompletionPopup->setIgnoreCase(pSettings->codeCompletion().ignoreCase()); mCompletionPopup->resize(pSettings->codeCompletion().width(), pSettings->codeCompletion().height()); // fCompletionBox.CodeInsList := dmMain.CodeInserts.ItemList; // fCompletionBox.SymbolUsage := dmMain.SymbolUsage; // fCompletionBox.ShowCount := devCodeCompletion.MaxCount; //Set Font size; mCompletionPopup->setFont(font()); // Redirect key presses to completion box if applicable //todo: mCompletionPopup->setKeypressedCallback([this](QKeyEvent *event)->bool{ return onCompletionKeyPressed(event); }); mCompletionPopup->setParser(mParser); mCompletionPopup->setUseCppKeyword(mUseCppSyntax); pMainWindow->functionTip()->hide(); mCompletionPopup->show(); // Scan the current function body mCompletionPopup->setCurrentStatement( mParser->findAndScanBlockAt(mFilename, caretY()) ); if (word.isEmpty()) { //word=getWordAtPosition(this,caretXY(),pBeginPos,pEndPos, WordPurpose::wpCompletion); QString memberOperator; QStringList memberExpression; BufferCoord pos = caretXY(); pos.Char--; QStringList ownerExpression = getOwnerExpressionAndMemberAtPositionForCompletion( pos, memberOperator, memberExpression); // qDebug()<prepareSearch( preWord, ownerExpression, memberOperator, memberExpression, mFilename, caretY()); } else { QStringList memberExpression; memberExpression.append(word); mCompletionPopup->prepareSearch(preWord, QStringList(), "", memberExpression, mFilename, caretY()); } // Filter the whole statement list if (mCompletionPopup->search(word, autoComplete)) { //only one suggestion and it's not input while typing completionInsert(pSettings->codeCompletion().appendFunc()); } } void Editor::showHeaderCompletion(bool autoComplete) { if (!pSettings->codeCompletion().enabled()) return; // if not devCodeCompletion.Enabled then // Exit; if (mHeaderCompletionPopup->isVisible()) // already in search, don't do it again return; // Position it at the top of the next line QPoint p = rowColumnToPixels(displayXY()); p.setY(p.y() + textHeight() + 2); mHeaderCompletionPopup->move(mapToGlobal(p)); mHeaderCompletionPopup->setIgnoreCase(pSettings->codeCompletion().ignoreCase()); mHeaderCompletionPopup->resize(pSettings->codeCompletion().width(), pSettings->codeCompletion().height()); //Set Font size; mHeaderCompletionPopup->setFont(font()); // Redirect key presses to completion box if applicable mHeaderCompletionPopup->setKeypressedCallback([this](QKeyEvent* event)->bool{ return onHeaderCompletionKeyPressed(event); }); mHeaderCompletionPopup->setParser(mParser); BufferCoord pBeginPos,pEndPos; QString word = getWordAtPosition(this,caretXY(),pBeginPos,pEndPos, WordPurpose::wpHeaderCompletionStart); if (word.isEmpty()) return; if (!word.startsWith('"') && !word.startsWith('<')) return; if (word.lastIndexOf('"')>0 || word.lastIndexOf('>')>0) return; pMainWindow->functionTip()->hide(); mHeaderCompletionPopup->show(); mHeaderCompletionPopup->setSearchLocal(word.startsWith('"')); word.remove(0,1); mHeaderCompletionPopup->prepareSearch(word, mFilename); // Filter the whole statement list if (mHeaderCompletionPopup->search(word, autoComplete)) //only one suggestion and it's not input while typing headerCompletionInsert(); // if only have one suggestion, just use it } bool Editor::testInFunc(int x, int y) { bool result = false; QString s = lines()->getString(y); int posY = y; int posX = std::min(x,s.length()-1); // x is started from 1 int bracketLevel=0; while (true) { while (posX < 0) { posY--; if (posY < 0) return false; s = lines()->getString(posY); posX = s.length()-1; } if (s[posX] == '>' || s[posX] == ']') { bracketLevel++; } else if (s[posX] == '<' || s[posX] == '[') { bracketLevel--; } else if (bracketLevel==0) { switch (s[posX].unicode()) { case '(': return true; case ';': case '{': return false; } if (!(isIdentChar(s[posX]) || s[posX] == ' ' || s[posX] == '\t' || s[posX] == '*' || s[posX] == '&')) break;; } posX--; } return result; } void Editor::completionInsert(bool appendFunc) { PStatement statement = mCompletionPopup->selectedStatement(); if (!statement) return; if (pSettings->codeCompletion().recordUsage() && statement->kind != StatementKind::skUserCodeSnippet) { statement->usageCount+=1; pMainWindow->symbolUsageManager()->updateUsage(statement->fullName, statement->usageCount); } QString funcAddOn = ""; // delete the part of the word that's already been typed ... BufferCoord p = wordEnd(); BufferCoord pStart = wordStart(); setCaretAndSelection(pStart,pStart,p); // if we are inserting a function, if (appendFunc) { if (statement->kind == StatementKind::skFunction || statement->kind == StatementKind::skConstructor || statement->kind == StatementKind::skDestructor) { if ((p.Char >= lineText().length()) // it's the last char on line || (lineText().at(p.Char-1) != '(')) { // it don't have '(' after it if (statement->fullName!="std::endl") funcAddOn = "()"; } } } // ... by replacing the selection if (statement->kind == StatementKind::skUserCodeSnippet) { // it's a user code template // insertUserCodeIn(Statement->value); //first move caret to the begin of the word to be replaced insertCodeSnippet(statement->value); } else { if ( (statement->kind == StatementKind::skKeyword || statement->kind == StatementKind::skPreprocessor) && (statement->command.startsWith('#') || statement->command.startsWith('@')) ) { setSelText(statement->command.mid(1)); } else setSelText(statement->command + funcAddOn); if (!funcAddOn.isEmpty()) mLastIdCharPressed = 0; // Move caret inside the ()'s, only when the user has something to do there... if (!funcAddOn.isEmpty() && (statement->args != "()") && (statement->args != "(void)")) { setCaretX(caretX() - funcAddOn.length()+1); //todo: function hint // immediately activate function hint // if devEditor.ShowFunctionTip and Assigned(fText.Highlighter) then begin // fText.SetFocus; // fFunctionTip.Parser := fParser; // fFunctionTip.FileName := fFileName; // fFunctionTip.Show; // end; } } mCompletionPopup->hide(); } void Editor::headerCompletionInsert() { QString headerName = mHeaderCompletionPopup->selectedFilename(); if (headerName.isEmpty()) return; // delete the part of the word that's already been typed ... BufferCoord p = caretXY(); int posBegin = p.Char-1; int posEnd = p.Char-1; QString sLine = lineText(); while ((posBegin>0) && (isIdentChar(sLine[posBegin-1]) || (sLine[posBegin-1]=='.'))) posBegin--; while ((posEnd < sLine.length()) && (isIdentChar(sLine[posEnd]) || (sLine[posEnd]=='.'))) posEnd++; p.Char = posBegin+1; setBlockBegin(p); p.Char = posEnd+1; setBlockEnd(p); setSelText(headerName); mCompletionPopup->hide(); } bool Editor::onCompletionKeyPressed(QKeyEvent *event) { bool processed = false; if (!mCompletionPopup->isEnabled()) return false; QString oldPhrase = mCompletionPopup->memberPhrase(); WordPurpose purpose = WordPurpose::wpCompletion; if (oldPhrase.startsWith('#')) { purpose = WordPurpose::wpDirective; } else if (oldPhrase.startsWith('@')) { purpose = WordPurpose::wpJavadoc; } QString phrase; BufferCoord pBeginPos,pEndPos; switch (event->key()) { case Qt::Key_Shift: case Qt::Key_Control: case Qt::Key_Meta: case Qt::Key_Alt: //ignore it return true; case Qt::Key_Backspace: ExecuteCommand( SynEditorCommand::ecDeleteLastChar, QChar(), nullptr); // Simulate backspace in editor if (purpose == WordPurpose::wpCompletion) { phrase = getWordForCompletionSearch(caretXY(), mCompletionPopup->memberOperator()=="::"); } else phrase = getWordAtPosition(this,caretXY(), pBeginPos,pEndPos, purpose); mLastIdCharPressed = phrase.length(); if (phrase.isEmpty()) { mCompletionPopup->hide(); } else { mCompletionPopup->search(phrase, false); } return true; case Qt::Key_Escape: mCompletionPopup->hide(); return true; case Qt::Key_Return: case Qt::Key_Enter: case Qt::Key_Tab: completionInsert(pSettings->codeCompletion().appendFunc()); return true; default: if (event->text().isEmpty()) { //stop completion mCompletionPopup->hide(); keyPressEvent(event); return true; } } QChar ch = event->text().front(); if (isIdentChar(ch)) { setSelText(ch); if (purpose == WordPurpose::wpCompletion) { phrase = getWordForCompletionSearch(caretXY(),mCompletionPopup->memberOperator()=="::"); } else phrase = getWordAtPosition(this,caretXY(), pBeginPos,pEndPos, purpose); mLastIdCharPressed = phrase.length(); mCompletionPopup->search(phrase, false); return true; } else { //stop completion mCompletionPopup->hide(); keyPressEvent(event); return true; } return processed; } bool Editor::onHeaderCompletionKeyPressed(QKeyEvent *event) { bool processed = false; if (!mHeaderCompletionPopup->isEnabled()) return false; QString phrase; BufferCoord pBeginPos,pEndPos; switch (event->key()) { case Qt::Key_Backspace: ExecuteCommand( SynEditorCommand::ecDeleteLastChar, QChar(), nullptr); // Simulate backspace in editor phrase = getWordAtPosition(this,caretXY(), pBeginPos,pEndPos, WordPurpose::wpHeaderCompletion); mLastIdCharPressed = phrase.length(); mHeaderCompletionPopup->search(phrase, false); return true; case Qt::Key_Escape: mHeaderCompletionPopup->hide(); return true; case Qt::Key_Return: case Qt::Key_Enter: case Qt::Key_Tab: headerCompletionInsert(); mHeaderCompletionPopup->hide(); return true; case Qt::Key_Shift: return false; default: if (event->text().isEmpty()) { //stop completion mHeaderCompletionPopup->hide(); keyPressEvent(event); return true; } } QChar ch = event->text().front(); if (isIdentChar(ch) || ch == '.' || ch =='_' || ch=='+') { setSelText(ch); phrase = getWordAtPosition(this,caretXY(), pBeginPos,pEndPos, WordPurpose::wpHeaderCompletion); mLastIdCharPressed = phrase.length(); mHeaderCompletionPopup->search(phrase, false); return true; } else { //stop completion mHeaderCompletionPopup->hide(); keyPressEvent(event); return true; } return processed; } bool Editor::onCompletionInputMethod(QInputMethodEvent *event) { bool processed = false; if (!mCompletionPopup->isVisible()) return processed; QString s=event->commitString(); if (!s.isEmpty()) { QString phrase = getWordForCompletionSearch(caretXY(),mCompletionPopup->memberOperator()=="::"); mLastIdCharPressed = phrase.length(); mCompletionPopup->search(phrase, false); return true; } return processed; } Editor::TipType Editor::getTipType(QPoint point, BufferCoord& pos) { // Only allow in the text area... if (pointToCharLine(point, pos)) { if (!pMainWindow->debugger()->executing() && getSyntaxIssueAtPosition(pos)) { return TipType::Error; } PSynHighlighterAttribute attr; QString s; // Only allow hand tips in highlighted areas if (getHighlighterAttriAtRowCol(pos,s,attr)) { // Only allow Identifiers, Preprocessor directives, and selection if (attr) { if (selAvail()) { // do not allow when dragging selection if (isPointInSelection(pos)) return TipType::Selection; } else if (mParser && mParser->isIncludeLine(lines()->getString(pos.Line-1))) { return TipType::Preprocessor; }else if (attr == highlighter()->identifierAttribute()) return TipType::Identifier; } } } return TipType::None; } void Editor::cancelHint() { //MainForm.Debugger.OnEvalReady := nil; // disable editor hint QToolTip::hideText(); mCurrentWord = ""; mCurrentTipType = TipType::None; updateMouseCursor(); } QString Editor::getFileHint(const QString &s) { QString fileName = mParser->getHeaderFileName(mFilename, s); if (fileExists(fileName)) { return fileName + " - " + tr("Ctrl+click for more info"); } return ""; } QString Editor::getParserHint(const QStringList& expression,const QString &s, int line) { // This piece of code changes the parser database, possibly making hints and code completion invalid... QString result; PStatement statement = mParser->findStatementOf( mFilename,expression, line); if (!statement) return result; if (statement->kind == StatementKind::skFunction || statement->kind == StatementKind::skConstructor || statement->kind == StatementKind::skDestructor) { PStatement parentScope = statement->parentScope.lock(); if (parentScope && parentScope->kind == StatementKind::skNamespace) { PStatementList namespaceStatementsList = mParser->findNamespace(parentScope->command); if (namespaceStatementsList) { foreach (const PStatement& namespaceStatement, *namespaceStatementsList) { QString hint = getHintForFunction(statement,namespaceStatement, mFilename,line); if (!hint.isEmpty()) { if (!result.isEmpty()) result += "
"; result += hint; } } } } else result = getHintForFunction(statement, parentScope, mFilename,line); } else if (statement->line>0) { QFileInfo fileInfo(statement->fileName); result = mParser->prettyPrintStatement(statement,mFilename, line) + " - " + QString("%1(%2) ").arg(fileInfo.fileName()).arg(line) + tr("Ctrl+click for more info"); } else { // hard defines result = mParser->prettyPrintStatement(statement, mFilename); } // Result := StringReplace(Result, '|', #5, [rfReplaceAll]); return result; } void Editor::showDebugHint(const QString &s, int line) { PStatement statement = mParser->findStatementOf(mFilename,s,line); if (statement) { if (statement->kind != StatementKind::skVariable && statement->kind != StatementKind::skGlobalVariable && statement->kind != StatementKind::skLocalVariable && statement->kind != StatementKind::skParameter) { return; } } if (pMainWindow->debugger()->commandRunning()) return; connect(pMainWindow->debugger(), &Debugger::evalValueReady, this, &Editor::onTipEvalValueReady); mCurrentDebugTipWord = s; pMainWindow->debugger()->sendCommand("-data-evaluate-expression",s); } QString Editor::getErrorHint(const PSyntaxIssue& issue) { if (issue) { return issue->hint; } else { return ""; } } QString Editor::getHintForFunction(const PStatement &statement, const PStatement &scopeStatement, const QString& filename, int line) { QString result; const StatementMap& children = mParser->statementList().childrenStatements(scopeStatement); foreach (const PStatement& childStatement, children){ if (statement->command == childStatement->command && statement->kind == childStatement->kind) { if ((line < childStatement->line) && childStatement->fileName == filename) continue; if (!result.isEmpty()) result += "
"; QFileInfo fileInfo(childStatement->fileName); result = mParser->prettyPrintStatement(childStatement,filename,line) + " - " + QString("%1(%2) ").arg(fileInfo.fileName()).arg(line) + tr("Ctrl+click for more info"); } } return result; } void Editor::updateFunctionTip() { if (pMainWindow->completionPopup()->isVisible()) { pMainWindow->functionTip()->hide(); return; } BufferCoord caretPos = caretXY(); ContentsCoord curPos = fromBufferCoord(caretPos); ContentsCoord cursorPos = curPos; int nBraces = 0; int nCommas = 0; int FMaxScanLength = 500; // Find out where the function ends... for (int i=0;ifunctionTip()->hide(); return; // Opening brace, increase count } QChar nextCh = *(curPos+1); if (ch == '(') { nBraces++; // Ending brace, decrease count or success (found ending)! } else if (ch == ')') { nBraces--; if (nBraces == -1) break; // Single line comments } else if ((ch == '/') && (nextCh == '/')) { // Walk up to an enter sequence while (ch!='\0' && ch!='\n') { curPos+=1; ch = *curPos; } // Skip linebreak; if (ch == '\n') { curPos += 1; } } else if ((ch == '/') && (nextCh == '*')) { // Walk up to "*/" while (ch!='\0' && !(ch=='*' && nextCh=='/')) { curPos += 1; ch = *curPos; nextCh = *(curPos+1); } // Step over if (ch!='\0') { curPos+=1; } } else curPos += 1; } //qDebug()<<"first pass:"<functionTip()->hide(); return; } //ContentsCoord FFunctionEnd = curPos; int paramPos = 0; bool paramPosFounded = false; // We've stopped at the ending ), start walking backwards )*here* with nBraces = -1 for (int i=0;ifunctionTip()->hide(); return; } if (prevCh == '/' && ch == '*' ) { curPos -= 1; break; } } } else if (ch == ')') { nBraces++ ; } else if (ch == '(') { nBraces--; if (nBraces == -1) // found it! break;; } else if (ch == ',') { if (nBraces == 0) { if (curPos <= cursorPos && !paramPosFounded) { paramPos = nCommas; paramPosFounded = true; } nCommas++; } } curPos -= 1; if (curPos.atStart()) break; } if (paramPosFounded) paramPos = nCommas - paramPos; //qDebug()<<"second pass:"<functionTip()->hide(); return; } //ContentsCoord FFunctionStart = curPos; // Skip blanks while (!curPos.atStart()) { QChar prevCh = *(curPos-1); if (prevCh == '\t' || prevCh == ' ' || prevCh == '\n') { curPos-=1; } else { break; } } ContentsCoord prevPos = curPos-1; if (prevPos.atStart()) { pMainWindow->functionTip()->hide(); return; } // Get the name of the function we're about to show BufferCoord FuncStartXY = prevPos.toBufferCoord(); QString token; PSynHighlighterAttribute HLAttr; if (!getHighlighterAttriAtRowCol(FuncStartXY,token,HLAttr)) { pMainWindow->functionTip()->hide(); return; } if (HLAttr != highlighter()->identifierAttribute()) { pMainWindow->functionTip()->hide(); return; } BufferCoord pWordBegin, pWordEnd; QString s = getWordAtPosition(this, FuncStartXY, pWordBegin,pWordEnd, WordPurpose::wpInformation); // qDebug()<functionTip()->functionFullName() && !mParser->parsing()) { pMainWindow->functionTip()->clearTips(); QList statements=mParser->getListOfFunctions(mFilename, s, FuncStartXY.Line); foreach (const PStatement statement, statements) { pMainWindow->functionTip()->addTip( statement->command, statement->fullName, statement->type, statement->args, statement->noNameArgs); } } // If we can't find it in our database, hide if (pMainWindow->functionTip()->tipCount()<=0) { pMainWindow->functionTip()->hide(); return; } // Position it at the top of the next line QPoint p = rowColumnToPixels(displayXY()); p+=QPoint(0,textHeight()+2); pMainWindow->functionTip()->move(mapToGlobal(p)); pMainWindow->functionTip()->setFunctioFullName(s); pMainWindow->functionTip()->guessFunction(nCommas); pMainWindow->functionTip()->setParamIndex( paramPos ); pMainWindow->functionTip()->show(); } void Editor::clearUserCodeInTabStops() { mUserCodeInTabStops.clear(); } void Editor::popUserCodeInTabStops() { if (mTabStopBegin < 0) { clearUserCodeInTabStops(); return; } BufferCoord newCursorPos; int tabStopEnd; int tabStopBegin; if (mUserCodeInTabStops.count() > 0) { PTabStop p = mUserCodeInTabStops.front(); // Update the cursor if (p->y ==0) { tabStopBegin = mTabStopEnd + p->x; tabStopEnd = mTabStopEnd + p->endX; } else { tabStopBegin = p->x+1; tabStopEnd = p->endX+1; } mTabStopY = caretY() + p->y; newCursorPos.Line = mTabStopY; newCursorPos.Char = tabStopBegin; setCaretXY(newCursorPos); setBlockBegin(newCursorPos); newCursorPos.Char = tabStopEnd; setBlockEnd(newCursorPos); mTabStopBegin = tabStopBegin; mTabStopEnd = tabStopEnd; mLineBeforeTabStop = lineText().mid(0, mTabStopBegin-1) ; mLineAfterTabStop = lineText().mid(mTabStopEnd-1) ; mXOffsetSince=0; mUserCodeInTabStops.pop_front(); } } void Editor::onExportedFormatToken(PSynHighlighter syntaxHighlighter, int Line, int column, const QString &token, PSynHighlighterAttribute& attr) { if (!syntaxHighlighter) return; if (token.isEmpty()) return; //don't do this if (mCompletionPopup->isVisible() || mHeaderCompletionPopup->isVisible()) return; if (mParser && (attr == syntaxHighlighter->identifierAttribute())) { BufferCoord p{column,Line}; BufferCoord pBeginPos,pEndPos; QString s= getWordAtPosition(this,p, pBeginPos,pEndPos, WordPurpose::wpInformation); // qDebug()<findStatementOf(mFilename, s , p.Line); StatementKind kind = mParser->getKindOfStatement(statement); if (kind == StatementKind::skUnknown) { if ((pEndPos.Line>=1) && (pEndPos.Char>=0) && (pEndPos.Char < lines()->getString(pEndPos.Line-1).length()) && (lines()->getString(pEndPos.Line-1)[pEndPos.Char] == '(')) { kind = StatementKind::skFunction; } else { kind = StatementKind::skVariable; } } SynEditCppHighlighter* cppHighlighter = dynamic_cast(syntaxHighlighter.get()); switch(kind) { case StatementKind::skFunction: case StatementKind::skConstructor: case StatementKind::skDestructor: attr = cppHighlighter->functionAttribute(); break; case StatementKind::skClass: case StatementKind::skTypedef: case StatementKind::skAlias: attr = cppHighlighter->classAttribute(); break; case StatementKind::skEnumClassType: case StatementKind::skEnumType: break; case StatementKind::skLocalVariable: case StatementKind::skParameter: attr = cppHighlighter->localVarAttribute(); break; case StatementKind::skVariable: attr = cppHighlighter->variableAttribute(); break; case StatementKind::skGlobalVariable: attr = cppHighlighter->globalVarAttribute(); break; case StatementKind::skEnum: case StatementKind::skPreprocessor: attr = cppHighlighter->preprocessorAttribute(); break; case StatementKind::skKeyword: attr = cppHighlighter->keywordAttribute(); break; case StatementKind::skNamespace: case StatementKind::skNamespaceAlias: attr = cppHighlighter->stringAttribute(); break; default: break; } } } bool Editor::canAutoSave() const { return mCanAutoSave; } void Editor::setCanAutoSave(bool newCanAutoSave) { mCanAutoSave = newCanAutoSave; } const QDateTime &Editor::hideTime() const { return mHideTime; } void Editor::setHideTime(const QDateTime &newHideTime) { mHideTime = newHideTime; } const std::shared_ptr > > &Editor::statementColors() const { return mStatementColors; } void Editor::setStatementColors(const std::shared_ptr > > &newStatementColors) { mStatementColors = newStatementColors; } bool Editor::useCppSyntax() const { return mUseCppSyntax; } void Editor::setUseCppSyntax(bool newUseCppSyntax) { mUseCppSyntax = newUseCppSyntax; } void Editor::setInProject(bool newInProject) { if (mInProject == newInProject) return; mInProject = newInProject; if (mInProject) { mParser = pMainWindow->project()->cppParser(); if (isVisible()) { if (mParser && mParser->parsing()) { connect(mParser.get(), &CppParser::onEndParsing, this, &SynEdit::invalidate); } else { invalidate(); } } } else { initParser(); } } void Editor::gotoDeclaration(const BufferCoord &pos) { // Exit early, don't bother creating a stream (which is slow) BufferCoord pBeginPos, pEndPos; QString phrase = getWordAtPosition(this,pos,pBeginPos,pEndPos, WordPurpose::wpInformation); if (phrase.isEmpty()) return; PStatement statement = mParser->findStatementOf( mFilename,phrase,pos.Line); if (!statement) { pMainWindow->updateStatusbarMessage(tr("Symbol '%1' not found!").arg(phrase)); return; } QString filename; int line; if (statement->fileName == mFilename && statement->line == pos.Line) { filename = statement->definitionFileName; line = statement->definitionLine; } else { filename = statement->fileName; line = statement->line; } Editor* e = pMainWindow->editorList()->getEditorByFilename(filename); if (e) { e->setCaretPositionAndActivate(line,1); } } void Editor::gotoDefinition(const BufferCoord &pos) { // Exit early, don't bother creating a stream (which is slow) BufferCoord pBeginPos, pEndPos; QString phrase = getWordAtPosition(this,pos,pBeginPos,pEndPos, WordPurpose::wpInformation); if (phrase.isEmpty()) return; PStatement statement = mParser->findStatementOf( mFilename,phrase,pos.Line); if (!statement) { pMainWindow->updateStatusbarMessage(tr("Symbol '%1' not found!").arg(phrase)); return; } QString filename; int line; filename = statement->definitionFileName; line = statement->definitionLine; Editor* e = pMainWindow->editorList()->getEditorByFilename(filename); if (e) { e->setCaretPositionAndActivate(line,1); } } QString getWordAtPosition(SynEdit *editor, const BufferCoord &p, BufferCoord &pWordBegin, BufferCoord &pWordEnd, Editor::WordPurpose purpose) { QString result = ""; QString s; if ((p.Line<1) || (p.Line>editor->lines()->count())) { pWordBegin = p; pWordEnd = p; return ""; } s = editor->lines()->getString(p.Line - 1); int len = s.length(); int wordBegin = p.Char - 1 - 1; //BufferCoord::Char starts with 1 int wordEnd = p.Char - 1 - 1; // Copy forward until end of word if (purpose == Editor::WordPurpose::wpEvaluation || purpose == Editor::WordPurpose::wpInformation) { while (wordEnd + 1 < len) { if ((purpose == Editor::WordPurpose::wpEvaluation) && (s[wordEnd + 1] == '[')) { if (!findComplement(s, '[', ']', wordEnd, 1)) break; } else if (editor->isIdentChar(s[wordEnd + 1])) { wordEnd++; } else break; } } // Copy backward until # if (purpose == Editor::WordPurpose::wpDirective) { while ((wordBegin >= 0) && (wordBegin < len)) { if (editor->isIdentChar(s[wordBegin])) wordBegin--; else if (s[wordBegin] == '#') { wordBegin--; break; } else break; } } // Copy backward until @ if (purpose == Editor::WordPurpose::wpJavadoc) { while ((wordBegin >= 0) && (wordBegin < len)) { if (editor->isIdentChar(s[wordBegin])) wordBegin--; else if (s[wordBegin] == '@') { wordBegin--; break; } else break; } } // Copy backward until begin of path if (purpose == Editor::WordPurpose::wpHeaderCompletion) { while ((wordBegin >= 0) && (wordBegin < len)) { if (editor->isIdentChar(s[wordBegin])) { wordBegin--; } else if (s[wordBegin] == '.' || s[wordBegin] == '+') { wordBegin--; } else if (s[wordBegin] == '/' || s[wordBegin] == '\\') { wordBegin--; break; } else break; } } if (purpose == Editor::WordPurpose::wpHeaderCompletionStart) { while ((wordBegin >= 0) && (wordBegin < len)) { if (s[wordBegin] == '"' || s[wordBegin] == '<') { wordBegin--; break; } else if (s[wordBegin] == '/' || s[wordBegin] == '\\' || s[wordBegin] == '.') { wordBegin--; } else if (editor->isIdentChar(s[wordBegin])) wordBegin--; else break; } } // && ( wordBegin < len) // Copy backward until begin of word if (purpose == Editor::WordPurpose::wpCompletion || purpose == Editor::WordPurpose::wpEvaluation || purpose == Editor::WordPurpose::wpInformation) { while ((wordBegin >= 0) && (wordBeginisIdentChar(s[wordBegin])) { wordBegin--; } else if (s[wordBegin] == '.' || s[wordBegin] == ':' || s[wordBegin] == '~') { // allow destructor signs wordBegin--; } else if ( (s[wordBegin] == '>') && (wordBegin+2', '<', wordBegin, -1)) break; else wordBegin--; // step over > } else if ((wordBegin-1 >= 0) && (s[wordBegin - 1] == '-') && (s[wordBegin] == '>')) { wordBegin-=2; } else if ((wordBegin-1 >= 0) && (s[wordBegin - 1] == ':') && (s[wordBegin] == ':')) { wordBegin-=2; } else if ((wordBegin > 0) && (s[wordBegin] == ')')) { if (!findComplement(s, ')', '(', wordBegin, -1)) break; else wordBegin--; // step over mathing ( } else break; } } // Get end result result = s.mid(wordBegin+1, wordEnd - wordBegin); pWordBegin.Line = p.Line; pWordBegin.Char = wordBegin+1; pWordEnd.Line = p.Line; pWordEnd.Char = wordEnd; // last line still have part of word if (!result.isEmpty() && ( result[0] == '.' || result[0] == '-') && (purpose == Editor::WordPurpose::wpCompletion || purpose == Editor::WordPurpose::wpEvaluation || purpose == Editor::WordPurpose::wpInformation)) { int i = wordBegin; int line=p.Line; while (line>=1) { while (i>=0) { if (s[i] == ' ' || s[i] == '\t') i--; else break; } if (i<0) { line--; if (line>=1) { s=editor->lines()->getString(line-1); i=s.length(); continue; } else break; } else { BufferCoord highlightPos; BufferCoord pDummy; highlightPos.Line = line; highlightPos.Char = i+1; result = getWordAtPosition(editor, highlightPos,pWordBegin,pDummy,purpose)+result; break; } } } // Strip function parameters int paramBegin,paramEnd; while (true) { paramBegin = result.indexOf('('); if (paramBegin > 0) { paramEnd = paramBegin; if ((paramBegin==0) && findComplement(result, '(', ')', paramEnd, 1) && (paramEnd = result.length()-1) ) { //remove the enclosing parenthese pair result = result.mid(1,result.length()-2); continue; } else { paramEnd = paramBegin; if (findComplement(result, '(', ')', paramEnd, 1)) { result.remove(paramBegin, paramEnd - paramBegin + 1); } else break; } } else break; } paramBegin = 0; while ((paramBegin < result.length()) && (result[paramBegin] == '*')) { paramBegin++; } result.remove(0,paramBegin); return result; } QString Editor::getPreviousWordAtPositionForSuggestion(const BufferCoord &p) { QString result; if ((p.Line<1) || (p.Line>lines()->count())) { return ""; } bool inFunc = testInFunc(p.Char-1,p.Line-1); QString s = lines()->getString(p.Line - 1); int wordBegin; int wordEnd = p.Char-1; if (wordEnd >= s.length()) wordEnd = s.length()-1; while (true) { int bracketLevel=0; bool skipNextWord=false; while (wordEnd > 0) { if (s[wordEnd] == '>' || s[wordEnd] == ']') { bracketLevel++; } else if (s[wordEnd] == '<' || s[wordEnd] == '[') { bracketLevel--; } else if (bracketLevel==0) { //we can't differentiate multiple definition and function parameter define here , so we don't handle ',' if (s[wordEnd] == ',') { if (inFunc) // in func, dont skip ',' break; else skipNextWord=true; } else if (s[wordEnd] != ' ' && s[wordEnd] != '\t') { break; } } wordEnd--; } if (wordEnd<0) return ""; if (bracketLevel > 0) return ""; if (!isIdentChar(s[wordEnd])) return ""; wordBegin = wordEnd; while ((wordBegin >= 0) && isIdentChar(s[wordBegin])) { wordBegin--; } wordBegin++; if (s[wordBegin]>='0' && s[wordBegin]<='9') // not valid word return ""; result = s.mid(wordBegin, wordEnd - wordBegin+1); if ((result != "const") && !skipNextWord) break; wordEnd = wordBegin-1; } return result; } void Editor::reformat() { if (readOnly()) return; #ifndef Q_OS_WIN if (!fileExists(pSettings->environment().AStylePath())) { QMessageBox::critical(this, tr("astyle not found"), tr("Can't find astyle in \"%1\".").arg(pSettings->environment().AStylePath())); return; } #endif //we must remove all breakpoints and syntax issues onLinesDeleted(1,lines()->count()); QByteArray content = text().toUtf8(); QStringList args = pSettings->codeFormatter().getArguments(); #ifdef Q_OS_WIN QByteArray newContent = runAndGetOutput("astyle.exe", pSettings->dirs().appDir(), args, content); #else QByteArray newContent = runAndGetOutput(pSettings->environment().AStylePath(), extractFileDir(pSettings->environment().AStylePath()), args, content); #endif int oldTopLine = topLine(); BufferCoord mOldCaret = caretXY(); selectAll(); SynEditorOptions oldOptions = getOptions(); SynEditorOptions newOptions = oldOptions; newOptions.setFlag(SynEditorOption::eoAutoIndent,false); setOptions(newOptions); setSelText(QString::fromUtf8(newContent)); setCaretXY(mOldCaret); setTopLine(oldTopLine); setOptions(oldOptions); reparse(); checkSyntaxInBack(); reparseTodo(); pMainWindow->updateEditorActions(); } void Editor::checkSyntaxInBack() { if (readOnly()) return; if(pSettings->editor().syntaxCheck()) pMainWindow->checkSyntaxInBack(this); } const PCppParser &Editor::parser() { return mParser; } void Editor::tab() { if (mUserCodeInTabStops.count()>0) { int oldLine = caretY(); popUserCodeInTabStops(); if (oldLine!=caretY()) { invalidateLine(oldLine); } invalidateLine(caretY()); return; } else { if (mTabStopBegin >= 0) { mTabStopBegin = -1; invalidateLine(caretY()); return; } } SynEdit::tab(); } int Editor::gutterClickedLine() const { return mGutterClickedLine; } void Editor::toggleBreakpoint(int line) { if (hasBreakpoint(line)) { mBreakpointLines.remove(line); pMainWindow->debugger()->removeBreakpoint(line,this); } else { mBreakpointLines.insert(line); pMainWindow->debugger()->addBreakpoint(line,this); } invalidateGutterLine(line); invalidateLine(line); } void Editor::clearBreakpoints() { pMainWindow->debugger()->deleteBreakpoints(this); mBreakpointLines.clear(); invalidate(); } bool Editor::hasBreakpoint(int line) { return mBreakpointLines.contains(line); } void Editor::addBookmark(int line, const QString& description) { mBookmarkLines.insert(line); pMainWindow->bookmarkModel()->addBookmark(mFilename,line,description); invalidateGutterLine(line); } void Editor::removeBookmark(int line) { mBookmarkLines.remove(line); pMainWindow->bookmarkModel()->removeBookmark(mFilename,line); invalidateGutterLine(line); } bool Editor::hasBookmark(int line) { return mBookmarkLines.contains(line); } void Editor::clearBookmarks() { mBookmarkLines.clear(); pMainWindow->bookmarkModel()->removeBookmarks(mFilename); invalidateGutter(); } void Editor::removeBreakpointFocus() { if (mActiveBreakpointLine!=-1) { int oldLine = mActiveBreakpointLine; mActiveBreakpointLine = -1; invalidateGutterLine(oldLine); invalidateLine(oldLine); } } void Editor::modifyBreakpointProperty(int line) { int index; PBreakpoint breakpoint = pMainWindow->debugger()->breakpointAt(line,this,index); if (!breakpoint) return; bool isOk; QString s=QInputDialog::getText(this, tr("Break point condition"), tr("Enter the condition of the breakpoint:"), QLineEdit::Normal, breakpoint->condition,&isOk); if (isOk) { pMainWindow->debugger()->setBreakPointCondition(index,s); } } void Editor::setActiveBreakpointFocus(int Line, bool setFocus) { if (Line != mActiveBreakpointLine) { removeBreakpointFocus(); // Put the caret at the active breakpoint mActiveBreakpointLine = Line; if (setFocus) setCaretPositionAndActivate(Line,1); else setCaretPosition(Line,1); // Invalidate new active line invalidateGutterLine(Line); invalidateLine(Line); } } void Editor::applySettings() { SynEditorOptions options = eoAltSetsColumnMode | eoDragDropEditing | eoDropFiles | eoKeepCaretX | eoTabsToSpaces | eoRightMouseMovesCursor | eoScrollByOneLess | eoTabIndent | eoHideShowScrollbars; //options options.setFlag(eoAutoIndent,pSettings->editor().autoIndent()); options.setFlag(eoTabsToSpaces,pSettings->editor().tabToSpaces()); options.setFlag(eoKeepCaretX,pSettings->editor().keepCaretX()); options.setFlag(eoEnhanceHomeKey,pSettings->editor().enhanceHomeKey()); options.setFlag(eoEnhanceEndKey,pSettings->editor().enhanceEndKey()); options.setFlag(eoHideShowScrollbars,pSettings->editor().autoHideScrollbar()); options.setFlag(eoScrollPastEol,pSettings->editor().scrollPastEol()); options.setFlag(eoScrollPastEof,pSettings->editor().scrollPastEof()); options.setFlag(eoScrollByOneLess,pSettings->editor().scrollByOneLess()); options.setFlag(eoHalfPageScroll,pSettings->editor().halfPageScroll()); options.setFlag(eoHalfPageScroll,pSettings->editor().halfPageScroll()); options.setFlag(eoShowRainbowColor, pSettings->editor().rainbowParenthesis()); setOptions(options); setTabWidth(pSettings->editor().tabWidth()); setInsertCaret(pSettings->editor().caretForInsert()); setOverwriteCaret(pSettings->editor().caretForOverwrite()); setCaretUseTextColor(pSettings->editor().caretUseTextColor()); setCaretColor(pSettings->editor().caretColor()); codeFolding().indentGuides = pSettings->editor().showIndentLines(); codeFolding().indentGuidesColor = pSettings->editor().indentLineColor(); codeFolding().fillIndents = pSettings->editor().fillIndents(); QFont f=QFont(pSettings->editor().fontName()); f.setPixelSize(pointToPixel(pSettings->editor().fontSize())); f.setStyleStrategy(QFont::PreferAntialias); setFont(f); // Set gutter properties gutter().setLeftOffset(pointToPixel(pSettings->editor().fontSize()) + pSettings->editor().gutterLeftOffset()); gutter().setRightOffset(pSettings->editor().gutterRightOffset()); gutter().setBorderStyle(SynGutterBorderStyle::None); gutter().setUseFontStyle(pSettings->editor().gutterUseCustomFont()); if (pSettings->editor().gutterUseCustomFont()) { f=QFont(pSettings->editor().gutterFontName()); f.setPixelSize(pointToPixel(pSettings->editor().gutterFontSize())); } else { f=QFont(pSettings->editor().fontName()); f.setPixelSize(pointToPixel(pSettings->editor().fontSize())); } f.setStyleStrategy(QFont::PreferAntialias); gutter().setFont(f); gutter().setDigitCount(pSettings->editor().gutterDigitsCount()); gutter().setVisible(pSettings->editor().gutterVisible()); gutter().setAutoSize(pSettings->editor().gutterAutoSize()); gutter().setShowLineNumbers(pSettings->editor().gutterShowLineNumbers()); gutter().setLeadingZeros(pSettings->editor().gutterAddLeadingZero()); if (pSettings->editor().gutterLineNumbersStartZero()) gutter().setLineNumberStart(0); else gutter().setLineNumberStart(1); //font color if (pSettings->editor().showRightEdgeLine()) { setRightEdge(pSettings->editor().rightEdgeWidth()); setRightEdgeColor(pSettings->editor().rightEdgeLineColor()); } else { setRightEdge(0); } setMouseWheelScrollSpeed(pSettings->editor().mouseWheelScrollSpeed()); } static PSynHighlighterAttribute createRainbowAttribute(const QString& attrName, const QString& schemeName, const QString& schemeItemName) { PColorSchemeItem item = pColorManager->getItem(schemeName,schemeItemName); if (item) { PSynHighlighterAttribute attr = std::make_shared(attrName); attr->setForeground(item->foreground()); attr->setBackground(item->background()); return attr; } return PSynHighlighterAttribute(); } void Editor::applyColorScheme(const QString& schemeName) { SynEditorOptions options = getOptions(); options.setFlag(SynEditorOption::eoShowRainbowColor, pSettings->editor().rainbowParenthesis()); setOptions(options); highlighterManager.applyColorScheme(highlighter(),schemeName); if (pSettings->editor().rainbowParenthesis()) { PSynHighlighterAttribute attr0 =createRainbowAttribute(SYNS_AttrSymbol, schemeName,COLOR_SCHEME_BRACE_1); PSynHighlighterAttribute attr1 =createRainbowAttribute(SYNS_AttrSymbol, schemeName,COLOR_SCHEME_BRACE_2); PSynHighlighterAttribute attr2 =createRainbowAttribute(SYNS_AttrSymbol, schemeName,COLOR_SCHEME_BRACE_3); PSynHighlighterAttribute attr3 =createRainbowAttribute(SYNS_AttrSymbol, schemeName,COLOR_SCHEME_BRACE_4); setRainbowAttrs(attr0,attr1,attr2,attr3); } PColorSchemeItem item = pColorManager->getItem(schemeName,COLOR_SCHEME_ACTIVE_LINE); if (item) { setActiveLineColor(item->background()); } item = pColorManager->getItem(schemeName,COLOR_SCHEME_GUTTER); if (item) { gutter().setTextColor(item->foreground()); gutter().setColor(item->background()); } item = pColorManager->getItem(schemeName,COLOR_SCHEME_GUTTER_ACTIVE_LINE); if (item) { gutter().setActiveLineTextColor(item->foreground()); } item = pColorManager->getItem(schemeName,COLOR_SCHEME_FOLD_LINE); if (item) { codeFolding().folderBarLinesColor = item->foreground(); } item = pColorManager->getItem(schemeName,COLOR_SCHEME_INDENT_GUIDE_LINE); if (item) { codeFolding().indentGuidesColor = item->foreground(); } item = pColorManager->getItem(schemeName,COLOR_SCHEME_ERROR); if (item) { this->mSyntaxErrorColor = item->foreground(); } item = pColorManager->getItem(schemeName,COLOR_SCHEME_WARNING); if (item) { this->mSyntaxWarningColor = item->foreground(); } item = pColorManager->getItem(schemeName,COLOR_SCHEME_SELECTION); if (item) { setSelectedForeground(item->foreground()); setSelectedBackground(item->background()); } else { this->setForegroundColor(palette().color(QPalette::HighlightedText)); this->setBackgroundColor(palette().color(QPalette::Highlight)); } item = pColorManager->getItem(schemeName,COLOR_SCHEME_ACTIVE_BREAKPOINT); if (item) { this->mActiveBreakpointForegroundColor = item->foreground(); this->mActiveBreakpointBackgroundColor = item->background(); } item = pColorManager->getItem(schemeName,COLOR_SCHEME_BREAKPOINT); if (item) { this->mBreakpointForegroundColor = item->foreground(); this->mBreakpointBackgroundColor = item->background(); } item = pColorManager->getItem(schemeName,COLOR_SCHEME_TEXT); if (item) { this->setForegroundColor(item->foreground()); this->setBackgroundColor(item->background()); } else { this->setForegroundColor(palette().color(QPalette::Text)); this->setBackgroundColor(palette().color(QPalette::Base)); } item = pColorManager->getItem(schemeName,COLOR_SCHEME_CURRENT_HIGHLIGHTED_WORD); if (item) { mCurrentHighlighWordForeground = item->foreground(); mCurrentHighlighWordBackground = item->background(); } else { mCurrentHighlighWordForeground = selectedForeground(); mCurrentHighlighWordBackground = selectedBackground(); } this->invalidate(); } void Editor::updateCaption(const QString& newCaption) { if (mParentPageControl==nullptr) { return; } int index = mParentPageControl->indexOf(this); if (index==-1) return; if (newCaption.isEmpty()) { QString caption = QFileInfo(mFilename).fileName(); if (this->modified()) { caption.append("[*]"); } if (this->readOnly()) { caption.append("["+tr("Readonly")+"]"); } mParentPageControl->setTabText(index,caption); } else { mParentPageControl->setTabText(index,newCaption); } }