diff --git a/language/duchain/navigation/abstractnavigationcontext.cpp b/language/duchain/navigation/abstractnavigationcontext.cpp index 5747958cef..5b74d741fc 100644 --- a/language/duchain/navigation/abstractnavigationcontext.cpp +++ b/language/duchain/navigation/abstractnavigationcontext.cpp @@ -1,507 +1,509 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstractnavigationcontext.h" #include #include +#include + #include "abstractdeclarationnavigationcontext.h" #include "abstractnavigationwidget.h" #include "usesnavigationcontext.h" #include "../../../interfaces/icore.h" #include "../../../interfaces/idocumentcontroller.h" #include "../functiondeclaration.h" #include "../namespacealiasdeclaration.h" #include "../types/functiontype.h" #include "../types/structuretype.h" #include "util/debug.h" #include #include #include namespace KDevelop { void AbstractNavigationContext::setTopContext(KDevelop::TopDUContextPointer context) { m_topContext = context; } KDevelop::TopDUContextPointer AbstractNavigationContext::topContext() const { return m_topContext; } AbstractNavigationContext::AbstractNavigationContext( KDevelop::TopDUContextPointer topContext, AbstractNavigationContext* previousContext) : m_selectedLink(0), m_shorten(false), m_linkCount(-1), m_currentPositionLine(0), m_previousContext(previousContext), m_topContext(topContext) { } void AbstractNavigationContext::addExternalHtml( const QString& text ) { int lastPos = 0; int pos = 0; QString fileMark = QStringLiteral("KDEV_FILE_LINK{"); while( pos < text.length() && (pos = text.indexOf( fileMark, pos)) != -1 ) { modifyHtml() += text.mid(lastPos, pos-lastPos); pos += fileMark.length(); if( pos != text.length() ) { int fileEnd = text.indexOf('}', pos); if( fileEnd != -1 ) { QString file = text.mid( pos, fileEnd - pos ); pos = fileEnd + 1; const QUrl url = QUrl::fromUserInput(file); makeLink( url.fileName(), file, NavigationAction( url, KTextEditor::Cursor() ) ); } } lastPos = pos; } modifyHtml() += text.mid(lastPos, text.length()-lastPos); } void AbstractNavigationContext::makeLink( const QString& name, DeclarationPointer declaration, NavigationAction::Type actionType ) { NavigationAction action( declaration, actionType ); makeLink(name, QString(), action); } QString AbstractNavigationContext::createLink(const QString& name, QString, const NavigationAction& action) { if(m_shorten) { //Do not create links in shortened mode, it's only for viewing return typeHighlight(name.toHtmlEscaped()); } // NOTE: Since the by definition in the HTML standard some uri components // are case-insensitive, we define a new lowercase link-id for each // link. Otherwise Qt 5 seems to mess up the casing and the link // cannot be matched when it's executed. QString hrefId = QString("link_%1").arg(m_links.count()); m_links[ hrefId ] = action; m_intLinks[ m_linkCount ] = action; m_linkLines[ m_linkCount ] = m_currentLine; if(m_currentPositionLine == m_currentLine) { m_currentPositionLine = -1; m_selectedLink = m_linkCount; } QString str = name.toHtmlEscaped(); if( m_linkCount == m_selectedLink ) str = "" + str + ""; QString ret = "" + str + ""; if( m_selectedLink == m_linkCount ) m_selectedLinkAction = action; ++m_linkCount; return ret; } void AbstractNavigationContext::makeLink( const QString& name, QString targetId, const NavigationAction& action) { modifyHtml() += createLink(name, targetId, action); } void AbstractNavigationContext::clear() { m_linkCount = 0; m_currentLine = 0; m_currentText.clear(); m_links.clear(); m_intLinks.clear(); m_linkLines.clear(); } NavigationContextPointer AbstractNavigationContext::executeLink (QString link) { if(!m_links.contains(link)) return NavigationContextPointer(this); return execute(m_links[link]); } NavigationContextPointer AbstractNavigationContext::executeKeyAction(QString key) { Q_UNUSED(key); return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::execute(const NavigationAction& action) { if(action.targetContext) return NavigationContextPointer(action.targetContext); if(action.type == NavigationAction::ExecuteKey) return executeKeyAction(action.key); if( !action.decl && (action.type != NavigationAction::JumpToSource || action.document.isEmpty()) ) { qCDebug(LANGUAGE) << "Navigation-action has invalid declaration" << endl; return NavigationContextPointer(this); } qRegisterMetaType("KTextEditor::Cursor"); switch( action.type ) { case NavigationAction::ExecuteKey: break; case NavigationAction::None: qCDebug(LANGUAGE) << "Tried to execute an invalid action in navigation-widget" << endl; break; case NavigationAction::NavigateDeclaration: { AbstractDeclarationNavigationContext* ctx = dynamic_cast(m_previousContext); if( ctx && ctx->declaration() == action.decl ) return NavigationContextPointer( m_previousContext ); return AbstractNavigationContext::registerChild(action.decl); } break; case NavigationAction::NavigateUses: { IContextBrowser* browser = ICore::self()->pluginController()->extensionForPlugin(); if (browser) { browser->showUses(action.decl); return NavigationContextPointer(this); } - // fall-through + Q_FALLTHROUGH(); } case NavigationAction::ShowUses: return registerChild(new UsesNavigationContext(action.decl.data(), this)); case NavigationAction::JumpToSource: { QUrl doc = action.document; KTextEditor::Cursor cursor = action.cursor; { DUChainReadLocker lock(DUChain::lock()); if(action.decl) { if(doc.isEmpty()) { doc = action.decl->url().toUrl(); /* if(action.decl->internalContext()) cursor = action.decl->internalContext()->range().start() + KTextEditor::Cursor(0, 1); else*/ cursor = action.decl->rangeInCurrentRevision().start(); } action.decl->activateSpecialization(); } } //This is used to execute the slot delayed in the event-loop, so crashes are avoided QMetaObject::invokeMethod( ICore::self()->documentController(), "openDocument", Qt::QueuedConnection, Q_ARG(QUrl, doc), Q_ARG(KTextEditor::Cursor, cursor) ); break; } case NavigationAction::ShowDocumentation: { auto doc = ICore::self()->documentationController()->documentationForDeclaration(action.decl.data()); ICore::self()->documentationController()->showDocumentation(doc); } break; } return NavigationContextPointer( this ); } void AbstractNavigationContext::setPreviousContext(KDevelop::AbstractNavigationContext* previous) { m_previousContext = previous; } NavigationContextPointer AbstractNavigationContext::registerChild( AbstractNavigationContext* context ) { m_children << NavigationContextPointer(context); return m_children.last(); } NavigationContextPointer AbstractNavigationContext::registerChild(DeclarationPointer declaration) { //We create a navigation-widget here, and steal its context.. evil ;) QScopedPointer navigationWidget(declaration->context()->createNavigationWidget(declaration.data())); if (AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget.data()) ) { NavigationContextPointer ret = abstractNavigationWidget->context(); ret->setPreviousContext(this); m_children << ret; return ret; } else { return NavigationContextPointer(this); } } const int lineJump = 3; void AbstractNavigationContext::down() { //Make sure link-count is valid if( m_linkCount == -1 ) { DUChainReadLocker lock; html(); } int fromLine = m_currentPositionLine; if(m_selectedLink >= 0 && m_selectedLink < m_linkCount) { if(fromLine == -1) fromLine = m_linkLines[m_selectedLink]; for(int newSelectedLink = m_selectedLink+1; newSelectedLink < m_linkCount; ++newSelectedLink) { if(m_linkLines[newSelectedLink] > fromLine && m_linkLines[newSelectedLink] - fromLine <= lineJump) { m_selectedLink = newSelectedLink; m_currentPositionLine = -1; return; } } } if(fromLine == -1) fromLine = 0; m_currentPositionLine = fromLine + lineJump; if(m_currentPositionLine > m_currentLine) m_currentPositionLine = m_currentLine; } void AbstractNavigationContext::up() { //Make sure link-count is valid if( m_linkCount == -1 ) { DUChainReadLocker lock; html(); } int fromLine = m_currentPositionLine; if(m_selectedLink >= 0 && m_selectedLink < m_linkCount) { if(fromLine == -1) fromLine = m_linkLines[m_selectedLink]; for(int newSelectedLink = m_selectedLink-1; newSelectedLink >= 0; --newSelectedLink) { if(m_linkLines[newSelectedLink] < fromLine && fromLine - m_linkLines[newSelectedLink] <= lineJump) { m_selectedLink = newSelectedLink; m_currentPositionLine = -1; return; } } } if(fromLine == -1) fromLine = m_currentLine; m_currentPositionLine = fromLine - lineJump; if(m_currentPositionLine < 0) m_currentPositionLine = 0; } void AbstractNavigationContext::nextLink() { //Make sure link-count is valid if( m_linkCount == -1 ) { DUChainReadLocker lock; html(); } m_currentPositionLine = -1; if( m_linkCount > 0 ) m_selectedLink = (m_selectedLink+1) % m_linkCount; } void AbstractNavigationContext::previousLink() { //Make sure link-count is valid if( m_linkCount == -1 ) { DUChainReadLocker lock; html(); } m_currentPositionLine = -1; if( m_linkCount > 0 ) { --m_selectedLink; if( m_selectedLink < 0 ) m_selectedLink += m_linkCount; } Q_ASSERT(m_selectedLink >= 0); } int AbstractNavigationContext::linkCount() const { return m_linkCount; } void AbstractNavigationContext::setPrefixSuffix( const QString& prefix, const QString& suffix ) { m_prefix = prefix; m_suffix = suffix; } NavigationContextPointer AbstractNavigationContext::back() { if(m_previousContext) return NavigationContextPointer(m_previousContext); else return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept() { if( m_selectedLink >= 0 && m_selectedLink < m_linkCount ) { NavigationAction action = m_intLinks[m_selectedLink]; return execute(action); } return NavigationContextPointer(this); } NavigationContextPointer AbstractNavigationContext::accept(IndexedDeclaration decl) { if(decl.data()) { NavigationAction action(DeclarationPointer(decl.data()), NavigationAction::NavigateDeclaration); return execute(action); }else{ return NavigationContextPointer(this); } } NavigationContextPointer AbstractNavigationContext::acceptLink(const QString& link) { if( !m_links.contains(link) ) { qCDebug(LANGUAGE) << "Executed unregistered link " << link << endl; return NavigationContextPointer(this); } return execute(m_links[link]); } NavigationAction AbstractNavigationContext::currentAction() const { return m_selectedLinkAction; } QString AbstractNavigationContext::declarationKind(DeclarationPointer decl) { const AbstractFunctionDeclaration* function = dynamic_cast(decl.data()); QString kind; if( decl->isTypeAlias() ) kind = i18n("Typedef"); else if( decl->kind() == Declaration::Type ) { if( decl->type() ) kind = i18n("Class"); } else if( decl->kind() == Declaration::Instance ) { kind = i18n("Variable"); } else if ( decl->kind() == Declaration::Namespace ) { kind = i18n("Namespace"); } if( NamespaceAliasDeclaration* alias = dynamic_cast(decl.data()) ) { if( alias->identifier().isEmpty() ) kind = i18n("Namespace import"); else kind = i18n("Namespace alias"); } if(function) kind = i18n("Function"); if( decl->isForwardDeclaration() ) kind = i18n("Forward Declaration"); return kind; } QString AbstractNavigationContext::html(bool shorten) { m_shorten = shorten; return QString(); } bool AbstractNavigationContext::alreadyComputed() const { return !m_currentText.isEmpty(); } bool AbstractNavigationContext::isWidgetMaximized() const { return true; } QWidget* AbstractNavigationContext::widget() const { return nullptr; } ///Splits the string by the given regular expression, but keeps the split-matches at the end of each line static QStringList splitAndKeep(QString str, QRegExp regExp) { QStringList ret; int place = regExp.indexIn(str); while(place != -1) { ret << str.left(place + regExp.matchedLength()); str = str.mid(place + regExp.matchedLength()); place = regExp.indexIn(str); } ret << str; return ret; } void AbstractNavigationContext::addHtml(QString html) { QRegExp newLineRegExp("
|
"); foreach(const QString& line, splitAndKeep(html, newLineRegExp)) { m_currentText += line; if(line.indexOf(newLineRegExp) != -1) { ++m_currentLine; if(m_currentLine == m_currentPositionLine) { m_currentText += QStringLiteral(" <-> "); // ><-> is <-> } } } } QString AbstractNavigationContext::currentHtml() const { return m_currentText; } QString AbstractNavigationContext::fontSizePrefix(bool /*shorten*/) const { return QString(); } QString AbstractNavigationContext::fontSizeSuffix(bool /*shorten*/) const { return QString(); } QString Colorizer::operator() ( const QString& str ) const { QString ret = "" + str + ""; if( m_formatting & Fixed ) ret = ""+ret+""; if ( m_formatting & Bold ) ret = ""+ret+""; if ( m_formatting & Italic ) ret = ""+ret+""; return ret; } const Colorizer AbstractNavigationContext::typeHighlight(QStringLiteral("006000")); const Colorizer AbstractNavigationContext::errorHighlight(QStringLiteral("990000")); const Colorizer AbstractNavigationContext::labelHighlight(QStringLiteral("000000")); const Colorizer AbstractNavigationContext::codeHighlight(QStringLiteral("005000")); const Colorizer AbstractNavigationContext::propertyHighlight(QStringLiteral("009900")); const Colorizer AbstractNavigationContext::navigationHighlight(QStringLiteral("000099")); const Colorizer AbstractNavigationContext::importantHighlight(QStringLiteral("000000"), Colorizer::Bold | Colorizer::Italic); const Colorizer AbstractNavigationContext::commentHighlight(QStringLiteral("303030")); const Colorizer AbstractNavigationContext::nameHighlight(QStringLiteral("000000"), Colorizer::Bold); } diff --git a/language/duchain/stringhelpers.cpp b/language/duchain/stringhelpers.cpp index 0b43a44719..6967faa598 100644 --- a/language/duchain/stringhelpers.cpp +++ b/language/duchain/stringhelpers.cpp @@ -1,591 +1,595 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "stringhelpers.h" #include "safetycounter.h" #include "util/debug.h" +#include + #include #include namespace { template int strip_impl(const T& str, T& from) { if( str.isEmpty() ) return 0; int i = 0; int ip = 0; int s = from.length(); for( int a = 0; a < s; a++ ) { if( QChar(from[a]).isSpace() ) { continue; } else { if( from[a] == str[i] ) { i++; ip = a+1; if( i == (int)str.length() ) break; } else { break; } } } if( ip ) { from.remove(0, ip); } return s - from.length(); } template int rStrip_impl(const T& str, T& from) { if( str.isEmpty() ) return 0; int i = 0; int ip = from.length(); int s = from.length(); for( int a = s-1; a >= 0; a-- ) { if( QChar( from[a] ).isSpace() ) { ///@todo Check whether this can cause problems in utf-8, as only one real character is treated! continue; } else { if( from[a] == str[i] ) { i++; ip = a; if( i == (int)str.length() ) break; } else { break; } } } if( ip != (int)from.length() ) { from = from.left( ip ); } return s - from.length(); } template T formatComment_impl(const T& comment) { T ret; QList lines = comment.split( '\n' ); if ( !lines.isEmpty() ) { auto it = lines.begin(); auto eit = lines.end(); // remove common leading chars from the beginning of lines for( ; it != eit; ++it ) { // don't trigger repeated temporary allocations here static const T tripleSlash("///"); static const T doubleSlash("//"); static const T doubleStar("**"); static const T slashDoubleStar("/**"); strip_impl( tripleSlash, *it ); strip_impl( doubleSlash, *it ); strip_impl( doubleStar, *it ); rStrip_impl( slashDoubleStar, *it ); } foreach(const T& line, lines) { if(!ret.isEmpty()) ret += '\n'; ret += line; } } return ret.trimmed(); } } namespace KDevelop { class ParamIteratorPrivate { public: QString m_prefix; QString m_source; QString m_parens; int m_cur; int m_curEnd; int m_end; int next() const { return findCommaOrEnd( m_source, m_cur, m_parens[ 1 ] ); } }; bool parenFits( QChar c1, QChar c2 ) { if( c1 == '<' && c2 == '>' ) return true; else if( c1 == '(' && c2 == ')' ) return true; else if( c1 == '[' && c2 == ']' ) return true; else if( c1 == '{' && c2 == '}' ) return true; else return false; } int findClose( const QString& str , int pos ) { int depth = 0; QList st; QChar last = ' '; for( int a = pos; a < (int)str.length(); a++) { switch(str[a].unicode()) { case '<': case '(': case '[': case '{': st.push_front( str[a] ); depth++; break; case '>': if( last == '-' ) break; + Q_FALLTHROUGH(); case ')': case ']': case '}': if( !st.isEmpty() && parenFits(st.front(), str[a]) ) { depth--; st.pop_front(); } break; case '"': last = str[a]; a++; while( a < (int)str.length() && (str[a] != '"' || last == '\\')) { last = str[a]; a++; } continue; break; case '\'': last = str[a]; a++; while( a < (int)str.length() && (str[a] != '\'' || last == '\\')) { last = str[a]; a++; } continue; break; } last = str[a]; if( depth == 0 ) { return a; } } return -1; } int findCommaOrEnd( const QString& str , int pos, QChar validEnd) { for( int a = pos; a < (int)str.length(); a++) { switch(str[a].unicode()) { case '"': case '(': case '[': case '{': case '<': a = findClose( str, a ); if( a == -1 ) return str.length(); break; case ')': case ']': case '}': case '>': if( validEnd != ' ' && validEnd != str[a] ) continue; + Q_FALLTHROUGH(); case ',': return a; } } return str.length(); } QString reverse( const QString& str ) { QString ret; int len = str.length(); for( int a = len-1; a >= 0; --a ) { switch(str[a].unicode()) { case '(': ret += ')'; continue; case '[': ret += ']'; continue; case '{': ret += '}'; continue; case '<': ret += '>'; continue; case ')': ret += '('; continue; case ']': ret += '['; continue; case '}': ret += '{'; continue; case '>': ret += '<'; continue; default: ret += str[a]; continue; } } return ret; } ///@todo this hackery sucks QString escapeForBracketMatching(QString str) { str.replace(QStringLiteral("<<"), QStringLiteral("$&")); str.replace(QStringLiteral(">>"), QStringLiteral("$$")); str.replace(QStringLiteral("\\\""), QStringLiteral("$!")); str.replace(QStringLiteral("->"), QStringLiteral("$?")); return str; } QString escapeFromBracketMatching(QString str) { str.replace(QStringLiteral("$&"), QStringLiteral("<<")); str.replace(QStringLiteral("$$"), QStringLiteral(">>")); str.replace(QStringLiteral("$!"), QStringLiteral("\\\"")); str.replace(QStringLiteral("$?"), QStringLiteral("->")); return str; } void skipFunctionArguments(QString str, QStringList& skippedArguments, int& argumentsStart ) { QString withStrings = escapeForBracketMatching(str); str = escapeForBracketMatching(clearStrings(str)); //Blank out everything that can confuse the bracket-matching algorithm QString reversed = reverse( str.left(argumentsStart) ); QString withStringsReversed = reverse( withStrings.left(argumentsStart) ); //Now we should decrease argumentStart at the end by the count of steps we go right until we find the beginning of the function SafetyCounter s( 1000 ); int pos = 0; int len = reversed.length(); //we are searching for an opening-brace, but the reversion has also reversed the brace while( pos < len && s ) { int lastPos = pos; pos = KDevelop::findCommaOrEnd( reversed, pos ) ; if( pos > lastPos ) { QString arg = reverse( withStringsReversed.mid(lastPos, pos-lastPos) ).trimmed(); if( !arg.isEmpty() ) skippedArguments.push_front( escapeFromBracketMatching(arg) ); //We are processing the reversed reverseding, so push to front } if( reversed[pos] == ')' || reversed[pos] == '>' ) break; else ++pos; } if( !s ) { qCDebug(LANGUAGE) << "skipFunctionArguments: Safety-counter triggered"; } argumentsStart -= pos; } QString reduceWhiteSpace(QString str) { str = str.trimmed(); QString ret; QChar spaceChar = ' '; bool hadSpace = false; for( int a = 0; a < str.length(); a++ ) { if( str[a].isSpace() ) { hadSpace = true; } else { if( hadSpace ) { hadSpace = false; ret += spaceChar; } ret += str[a]; } } return ret; } void fillString( QString& str, int start, int end, QChar replacement ) { for( int a = start; a < end; a++) str[a] = replacement; } QString stripFinalWhitespace(QString str) { for( int a = str.length() - 1; a >= 0; --a ) { if( !str[a].isSpace() ) return str.left( a+1 ); } return QString(); } QString clearComments( QString str, QChar replacement ) { QString withoutStrings = clearStrings(str, '$'); int pos = -1, newlinePos = -1, endCommentPos = -1, nextPos = -1, dest = -1; while ( (pos = str.indexOf('/', pos + 1)) != -1 ) { newlinePos = withoutStrings.indexOf('\n', pos); if (withoutStrings[pos + 1] == '/') { //C style comment dest = newlinePos == -1 ? str.length() : newlinePos; fillString(str, pos, dest, replacement); pos = dest; } else if (withoutStrings[pos + 1] == '*') { //CPP style comment endCommentPos = withoutStrings.indexOf(QStringLiteral("*/"), pos + 2); if (endCommentPos != -1) endCommentPos += 2; dest = endCommentPos == -1 ? str.length() : endCommentPos; while (pos < dest) { nextPos = (dest > newlinePos && newlinePos != -1) ? newlinePos : dest; fillString(str, pos, nextPos, replacement); pos = nextPos; if (pos == newlinePos) { ++pos; //Keep newlines intact, skip them newlinePos = withoutStrings.indexOf('\n', pos + 1); } } } } return str; } QString clearStrings( QString str, QChar replacement ) { bool inString = false; for(int pos = 0; pos < str.length(); ++pos) { //Skip cpp comments if(!inString && pos + 1 < str.length() && str[pos] == '/' && str[pos+1] == '*') { pos += 2; while(pos + 1 < str.length()) { if (str[pos] == '*' && str[pos + 1] == '/') { ++pos; break; } ++pos; } } //Skip cstyle comments if(!inString && pos + 1 < str.length() && str[pos] == '/' && str[pos+1] == '/') { pos += 2; while(pos < str.length() && str[pos] != '\n') { ++pos; } } //Skip a character a la 'b' if(!inString && str[pos] == '\'' && pos + 3 <= str.length()) { //skip the opening ' str[pos] = replacement; ++pos; if(str[pos] == '\\') { //Skip an escape character str[pos] = replacement; ++pos; } //Skip the actual character str[pos] = replacement; ++pos; //Skip the closing ' if(pos < str.length() && str[pos] == '\'') { str[pos] = replacement; } continue; } bool intoString = false; if(str[pos] == '"' && !inString) intoString = true; if(inString || intoString) { if(inString) { if(str[pos] == '"') inString = false; }else{ inString = true; } bool skip = false; if(str[pos] == '\\') skip = true; str[pos] = replacement; if(skip) { ++pos; if(pos < str.length()) str[pos] = replacement; } } } return str; } int strip(const QByteArray& str, QByteArray& from) { return strip_impl(str, from); } int rStrip(const QByteArray& str, QByteArray& from) { return rStrip_impl(str, from); } QByteArray formatComment(const QByteArray& comment) { return formatComment_impl(comment); } QString formatComment(const QString& comment) { return formatComment_impl(comment); } ParamIterator::~ParamIterator() { delete d; } ParamIterator::ParamIterator( QString parens, QString source, int offset ) : d(new ParamIteratorPrivate) { d->m_source = source; d->m_parens = parens; d->m_cur = offset; d->m_curEnd = offset; d->m_end = d->m_source.length(); ///The whole search should be stopped when: A) The end-sign is found on the top-level B) A closing-brace of parameters was found int parenBegin = d->m_source.indexOf( parens[ 0 ], offset ); //Search for an interrupting end-sign that comes before the found paren-begin int foundEnd = -1; if( parens.length() > 2 ) { foundEnd = d->m_source.indexOf( parens[2], offset ); if( foundEnd > parenBegin && parenBegin != -1 ) foundEnd = -1; } if( foundEnd != -1 ) { //We have to stop the search, because we found an interrupting end-sign before the opening-paren d->m_prefix = d->m_source.mid( offset, foundEnd - offset ); d->m_curEnd = d->m_end = d->m_cur = foundEnd; } else { if( parenBegin != -1 ) { //We have a valid prefix before an opening-paren. Take the prefix, and start iterating parameters. d->m_prefix = d->m_source.mid( offset, parenBegin - offset ); d->m_cur = parenBegin + 1; d->m_curEnd = d->next(); if( d->m_curEnd == d->m_source.length() ) { //The paren was not closed. It might be an identifier like "operator<", so count everything as prefix. d->m_prefix = d->m_source.mid(offset); d->m_curEnd = d->m_end = d->m_cur = d->m_source.length(); } } else { //We have neither found an ending-character, nor an opening-paren, so take the whole input and end d->m_prefix = d->m_source.mid(offset); d->m_curEnd = d->m_end = d->m_cur = d->m_source.length(); } } } ParamIterator& ParamIterator::operator ++() { if( d->m_source[d->m_curEnd] == d->m_parens[1] ) { //We have reached the end-paren. Stop iterating. d->m_cur = d->m_end = d->m_curEnd + 1; } else { //Iterate on through parameters d->m_cur = d->m_curEnd + 1; if ( d->m_cur < ( int ) d->m_source.length() ) { d->m_curEnd = d->next(); } } return *this; } QString ParamIterator::operator *() { return d->m_source.mid( d->m_cur, d->m_curEnd - d->m_cur ).trimmed(); } ParamIterator::operator bool() const { return d->m_cur < ( int ) d->m_end; } QString ParamIterator::prefix() const { return d->m_prefix; } uint ParamIterator::position() const { return (uint)d->m_cur; } } diff --git a/plugins/quickopen/quickopenwidget.cpp b/plugins/quickopen/quickopenwidget.cpp index bcc2dcb464..ec2368f077 100644 --- a/plugins/quickopen/quickopenwidget.cpp +++ b/plugins/quickopen/quickopenwidget.cpp @@ -1,515 +1,518 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * Copyright 2016 Kevin Funk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "quickopenwidget.h" #include #include "debug.h" #include "expandingtree/expandingdelegate.h" #include "quickopenmodel.h" #include #include +#include + #include #include #include #include #include using namespace KDevelop; class QuickOpenDelegate : public ExpandingDelegate { Q_OBJECT public: QuickOpenDelegate(ExpandingWidgetModel* model, QObject* parent = nullptr) : ExpandingDelegate(model, parent) { } QList createHighlighting(const QModelIndex& index, QStyleOptionViewItem& option) const override { QList highlighting = index.data(KTextEditor::CodeCompletionModel::CustomHighlight).toList(); if(!highlighting.isEmpty()) return highlightingFromVariantList(highlighting); return ExpandingDelegate::createHighlighting( index, option ); } }; QuickOpenWidget::QuickOpenWidget( QString title, QuickOpenModel* model, const QStringList& initialItems, const QStringList& initialScopes, bool listOnly, bool noSearchField ) : m_model(model) , m_expandedTemporary(false) , m_hadNoCommandSinceAlt(true) { m_filterTimer.setSingleShot(true); connect(&m_filterTimer, &QTimer::timeout, this, &QuickOpenWidget::applyFilter); Q_UNUSED( title ); ui.setupUi( this ); ui.list->header()->hide(); ui.list->setRootIsDecorated( false ); ui.list->setVerticalScrollMode( QAbstractItemView::ScrollPerItem ); connect(ui.list->verticalScrollBar(), &QScrollBar::valueChanged, m_model, &QuickOpenModel::placeExpandingWidgets); ui.searchLine->setFocus(); ui.list->setItemDelegate( new QuickOpenDelegate( m_model, ui.list ) ); if(!listOnly) { QStringList allTypes = m_model->allTypes(); QStringList allScopes = m_model->allScopes(); QMenu* itemsMenu = new QMenu(this); foreach( const QString &type, allTypes ) { QAction* action = new QAction(type, itemsMenu); action->setCheckable(true); action->setChecked(initialItems.isEmpty() || initialItems.contains( type )); connect( action, &QAction::toggled, this, &QuickOpenWidget::updateProviders, Qt::QueuedConnection ); itemsMenu->addAction(action); } ui.itemsButton->setMenu(itemsMenu); QMenu* scopesMenu = new QMenu(this); foreach( const QString &scope, allScopes ) { QAction* action = new QAction(scope, scopesMenu); action->setCheckable(true); action->setChecked(initialScopes.isEmpty() || initialScopes.contains( scope ) ); connect( action, &QAction::toggled, this, &QuickOpenWidget::updateProviders, Qt::QueuedConnection ); scopesMenu->addAction(action); } ui.scopesButton->setMenu(scopesMenu); }else{ ui.list->setFocusPolicy(Qt::StrongFocus); ui.scopesButton->hide(); ui.itemsButton->hide(); ui.label->hide(); ui.label_2->hide(); } showSearchField(!noSearchField); ui.okButton->hide(); ui.cancelButton->hide(); ui.searchLine->installEventFilter( this ); ui.list->installEventFilter( this ); ui.list->setFocusPolicy(Qt::NoFocus); ui.scopesButton->setFocusPolicy(Qt::NoFocus); ui.itemsButton->setFocusPolicy(Qt::NoFocus); connect( ui.searchLine, &QLineEdit::textChanged, this, &QuickOpenWidget::textChanged ); connect( ui.list, &ExpandingTree::doubleClicked, this, &QuickOpenWidget::doubleClicked ); connect(ui.okButton, &QPushButton::clicked, this, &QuickOpenWidget::accept); connect(ui.okButton, &QPushButton::clicked, this, &QuickOpenWidget::ready); connect(ui.cancelButton, &QPushButton::clicked, this, &QuickOpenWidget::ready); updateProviders(); updateTimerInterval(true); // no need to call this, it's done by updateProviders already // m_model->restart(); } void QuickOpenWidget::showStandardButtons(bool show) { if(show) { ui.okButton->show(); ui.cancelButton->show(); }else{ ui.okButton->hide(); ui.cancelButton->hide(); } } void QuickOpenWidget::updateTimerInterval(bool cheapFilterChange) { const int MAX_ITEMS = 10000; if ( cheapFilterChange && m_model->rowCount(QModelIndex()) < MAX_ITEMS ) { // cheap change and there are currently just a few items, // so apply filter instantly m_filterTimer.setInterval(0); } else if ( m_model->unfilteredRowCount() < MAX_ITEMS ) { // not a cheap change, but there are generally // just a few items in the list: apply filter instantly m_filterTimer.setInterval(0); } else { // otherwise use a timer to prevent sluggishness while typing m_filterTimer.setInterval(300); } } void QuickOpenWidget::showEvent(QShowEvent* e) { QWidget::showEvent(e); // The column width only has an effect _after_ the widget has been shown ui.list->setColumnWidth( 0, 20 ); } void QuickOpenWidget::setAlternativeSearchField(QLineEdit* alterantiveSearchField) { ui.searchLine = alterantiveSearchField; ui.searchLine->installEventFilter( this ); connect( ui.searchLine, &QLineEdit::textChanged, this, &QuickOpenWidget::textChanged ); } void QuickOpenWidget::showSearchField(bool b) { if(b){ ui.searchLine->show(); ui.searchLabel->show(); }else{ ui.searchLine->hide(); ui.searchLabel->hide(); } } void QuickOpenWidget::prepareShow() { ui.list->setModel( nullptr ); ui.list->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); m_model->setTreeView( ui.list ); ui.list->setModel( m_model ); m_filterTimer.stop(); m_filter = QString(); if (!m_preselectedText.isEmpty()) { ui.searchLine->setText(m_preselectedText); ui.searchLine->selectAll(); } m_model->restart(false); connect( ui.list->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &QuickOpenWidget::callRowSelected ); connect( ui.list->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QuickOpenWidget::callRowSelected ); } void QuickOpenWidgetDialog::run() { m_widget->prepareShow(); m_dialog->show(); } QuickOpenWidget::~QuickOpenWidget() { m_model->setTreeView( nullptr ); } QuickOpenWidgetDialog::QuickOpenWidgetDialog(QString title, QuickOpenModel* model, const QStringList& initialItems, const QStringList& initialScopes, bool listOnly, bool noSearchField) { m_widget = new QuickOpenWidget(title, model, initialItems, initialScopes, listOnly, noSearchField); // the QMenu might close on esc and we want to close the whole dialog then connect( m_widget, &QuickOpenWidget::aboutToHide, this, &QuickOpenWidgetDialog::deleteLater ); m_dialog = new QDialog( ICore::self()->uiController()->activeMainWindow() ); m_dialog->resize(QSize(800, 400)); m_dialog->setWindowTitle(title); QVBoxLayout* layout = new QVBoxLayout(m_dialog); layout->addWidget(m_widget); m_widget->showStandardButtons(true); connect(m_widget, &QuickOpenWidget::ready, m_dialog, &QDialog::close); connect( m_dialog, &QDialog::accepted, m_widget, &QuickOpenWidget::accept ); } QuickOpenWidgetDialog::~QuickOpenWidgetDialog() { delete m_dialog; } void QuickOpenWidget::setPreselectedText(const QString& text) { m_preselectedText = text; } void QuickOpenWidget::updateProviders() { if(QAction* action = (sender() ? qobject_cast(sender()) : nullptr)) { QMenu* menu = qobject_cast(action->parentWidget()); if(menu) { menu->show(); menu->setActiveAction(action); } } QStringList checkedItems; if(ui.itemsButton->menu()) { foreach( QObject* obj, ui.itemsButton->menu()->children() ) { QAction* box = qobject_cast( obj ); if( box ) { if( box->isChecked() ) checkedItems << box->text().remove('&'); } } ui.itemsButton->setText(checkedItems.join(QStringLiteral(", "))); } QStringList checkedScopes; if(ui.scopesButton->menu()) { foreach( QObject* obj, ui.scopesButton->menu()->children() ) { QAction* box = qobject_cast( obj ); if( box ) { if( box->isChecked() ) checkedScopes << box->text().remove('&'); } } ui.scopesButton->setText(checkedScopes.join(QStringLiteral(", "))); } emit itemsChanged( checkedItems ); emit scopesChanged( checkedScopes ); m_model->enableProviders( checkedItems, checkedScopes ); } void QuickOpenWidget::textChanged( const QString& str ) { // "cheap" when something was just appended to the current filter updateTimerInterval(str.startsWith(m_filter)); m_filter = str; m_filterTimer.start(); } void QuickOpenWidget::applyFilter() { m_model->textChanged( m_filter ); QModelIndex currentIndex = m_model->index(0, 0, QModelIndex()); ui.list->selectionModel()->setCurrentIndex( currentIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows | QItemSelectionModel::Current ); callRowSelected(); } void QuickOpenWidget::callRowSelected() { QModelIndex currentIndex = ui.list->selectionModel()->currentIndex(); if( currentIndex.isValid() ) m_model->rowSelected( currentIndex ); else qCDebug(PLUGIN_QUICKOPEN) << "current index is not valid"; } void QuickOpenWidget::accept() { QString filterText = ui.searchLine->text(); m_model->execute( ui.list->currentIndex(), filterText ); } void QuickOpenWidget::doubleClicked ( const QModelIndex & index ) { // crash guard: https://bugs.kde.org/show_bug.cgi?id=297178 ui.list->setCurrentIndex(index); QMetaObject::invokeMethod(this, "accept", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "ready", Qt::QueuedConnection); } void QuickOpenWidget::avoidMenuAltFocus() { // send an invalid key event to the main menu bar. The menu bar will // stop listening when observing another key than ALT between the press // and the release. QKeyEvent event1(QEvent::KeyPress, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event1); QKeyEvent event2(QEvent::KeyRelease, 0, Qt::NoModifier); QApplication::sendEvent(ICore::self()->uiController()->activeMainWindow()->menuBar(), &event2); } bool QuickOpenWidget::eventFilter ( QObject * watched, QEvent * event ) { QKeyEvent *keyEvent = dynamic_cast(event); if( event->type() == QEvent::KeyRelease ) { if(keyEvent->key() == Qt::Key_Alt) { if((m_expandedTemporary && m_altDownTime.msecsTo( QTime::currentTime() ) > 300) || (!m_expandedTemporary && m_altDownTime.msecsTo( QTime::currentTime() ) < 300 && m_hadNoCommandSinceAlt)) { //Unexpand the item QModelIndex row = ui.list->selectionModel()->currentIndex(); if( row.isValid() ) { row = row.sibling( row.row(), 0 ); if(m_model->isExpanded( row )) m_model->setExpanded( row, false ); } } m_expandedTemporary = false; } } if( event->type() == QEvent::KeyPress ) { m_hadNoCommandSinceAlt = false; if(keyEvent->key() == Qt::Key_Alt) { avoidMenuAltFocus(); m_hadNoCommandSinceAlt = true; //Expand QModelIndex row = ui.list->selectionModel()->currentIndex(); if( row.isValid() ) { row = row.sibling( row.row(), 0 ); m_altDownTime = QTime::currentTime(); if(!m_model->isExpanded( row )) { m_expandedTemporary = true; m_model->setExpanded( row, true ); } } } switch( keyEvent->key() ) { case Qt::Key_Tab: if ( keyEvent->modifiers() == Qt::NoModifier ) { // Tab should work just like Down QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier)); QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Down, Qt::NoModifier)); return true; } break; case Qt::Key_Backtab: if ( keyEvent->modifiers() == Qt::ShiftModifier ) { // Shift + Tab should work just like Up QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier)); QCoreApplication::sendEvent(ui.list, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Up, Qt::NoModifier)); return true; } break; case Qt::Key_Down: case Qt::Key_Up: { if( keyEvent->modifiers() == Qt::AltModifier ) { QWidget* w = m_model->expandingWidget(ui.list->selectionModel()->currentIndex()); if( KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast( w ) ){ if( keyEvent->key() == Qt::Key_Down ) interface->down(); else interface->up(); return true; } return false; } + Q_FALLTHROUGH(); } case Qt::Key_PageUp: case Qt::Key_PageDown: if(watched == ui.list ) return false; QApplication::sendEvent( ui.list, event ); //callRowSelected(); return true; case Qt::Key_Left: { //Expand/unexpand if( keyEvent->modifiers() == Qt::AltModifier ) { //Eventually Send action to the widget QWidget* w = m_model->expandingWidget(ui.list->selectionModel()->currentIndex()); if( KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast( w ) ){ interface->previous(); return true; } } else { QModelIndex row = ui.list->selectionModel()->currentIndex(); if( row.isValid() ) { row = row.sibling( row.row(), 0 ); if( m_model->isExpanded( row ) ) { m_model->setExpanded( row, false ); return true; } } } return false; } case Qt::Key_Right: { //Expand/unexpand if( keyEvent->modifiers() == Qt::AltModifier ) { //Eventually Send action to the widget QWidget* w = m_model->expandingWidget(ui.list->selectionModel()->currentIndex()); if( KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast( w ) ){ interface->next(); return true; } } else { QModelIndex row = ui.list->selectionModel()->currentIndex(); if( row.isValid() ) { row = row.sibling( row.row(), 0 ); if( !m_model->isExpanded( row ) ) { m_model->setExpanded( row, true ); return true; } } } return false; } case Qt::Key_Return: case Qt::Key_Enter: { if (m_filterTimer.isActive()) { m_filterTimer.stop(); applyFilter(); } if( keyEvent->modifiers() == Qt::AltModifier ) { //Eventually Send action to the widget QWidget* w = m_model->expandingWidget(ui.list->selectionModel()->currentIndex()); if( KDevelop::QuickOpenEmbeddedWidgetInterface* interface = dynamic_cast( w ) ){ interface->accept(); return true; } } else { QString filterText = ui.searchLine->text(); //Safety: Track whether this object is deleted. When execute() is called, a dialog may be opened, //which kills the quickopen widget. QPointer stillExists(this); if( m_model->execute( ui.list->currentIndex(), filterText ) ) { if(!stillExists) return true; if(!(keyEvent->modifiers() & Qt::ShiftModifier)) emit ready(); } else { //Maybe the filter-text was changed: if( filterText != ui.searchLine->text() ) { ui.searchLine->setText( filterText ); } } } return true; } } } return false; } #include "quickopenwidget.moc" diff --git a/qtcompat_p.h b/qtcompat_p.h index 52a2e18ee7..12024b2249 100644 --- a/qtcompat_p.h +++ b/qtcompat_p.h @@ -1,41 +1,64 @@ /* Copyright (c) 2017 Kevin Funk #if QT_VERSION < QT_VERSION_CHECK(5,7,0) namespace QtPrivate { template struct QAddConst { typedef const T Type; }; } // this adds const to non-const objects (like std::as_const) template Q_DECL_CONSTEXPR typename QtPrivate::QAddConst::Type &qAsConst(T &t) Q_DECL_NOTHROW { return t; } // prevent rvalue arguments: template void qAsConst(const T &&) Q_DECL_EQ_DELETE; #endif #endif + +// compat for Q_FALLTHROUGH +#if QT_VERSION < QT_VERSION_CHECK(5,8,0) + +#if defined(__has_cpp_attribute) +# if __has_cpp_attribute(fallthrough) +# define Q_FALLTHROUGH() [[fallthrough]] +# elif __has_cpp_attribute(clang::fallthrough) +# define Q_FALLTHROUGH() [[clang::fallthrough]] +# elif __has_cpp_attribute(gnu::fallthrough) +# define Q_FALLTHROUGH() [[gnu::fallthrough]] +# endif +#endif + +#ifndef Q_FALLTHROUGH +# if defined(__GNUC__) && !defined(__INTEL_COMPILER) && (((__GNUC__ * 100) + __GNUC_MINOR__) >= 700) +# define Q_FALLTHROUGH() __attribute__((fallthrough)) +# else +# define Q_FALLTHROUGH() (void)0 +# endif +#endif + +#endif