diff --git a/analyzers/cppcheck/parser.cpp b/analyzers/cppcheck/parser.cpp index f20245c7a4..8ae2a72d39 100644 --- a/analyzers/cppcheck/parser.cpp +++ b/analyzers/cppcheck/parser.cpp @@ -1,307 +1,307 @@ /* This file is part of KDevelop Copyright 2013 Christoph Thielecke Copyright 2016 Anton Anikin 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 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parser.h" #include "debug.h" #include #include #include #include #include #include namespace cppcheck { /** * Convert the value of \ attribute of \ element from cppcheck's * XML-output to 'good-looking' HTML-version. This is necessary because the * displaying of the original message is performed without line breaks - such * tooltips are uncomfortable to read, and large messages will not fit into the * screen. * * This function put the original message into \ tag that automatically * provides line wrapping by builtin capabilities of Qt library. The source text * also can contain tokens '\012' (line break) - they are present in the case of * source code examples. In such cases, the entire text between the first and * last tokens (i.e. source code) is placed into \ tag. * * @param[in] input the original value of \ attribute * @return HTML version for displaying in problem's tooltip */ QString verboseMessageToHtml( const QString & input ) { QString output(QStringLiteral("%1").arg(input.toHtmlEscaped())); output.replace(QLatin1String("\\012"), QLatin1String("\n")); if (output.count('\n') >= 2) { - output.replace(output.indexOf('\n'), 1, QLatin1String("
") );
-        output.replace(output.lastIndexOf('\n'), 1, QLatin1String("

") ); + output.replace(output.indexOf('\n'), 1, QStringLiteral("
") );
+        output.replace(output.lastIndexOf('\n'), 1, QStringLiteral("

") ); } return output; } CppcheckParser::CppcheckParser() : m_errorInconclusive(false) { } CppcheckParser::~CppcheckParser() { } void CppcheckParser::clear() { m_stateStack.clear(); } bool CppcheckParser::startElement() { State newState = Unknown; qCDebug(KDEV_CPPCHECK) << "CppcheckParser::startElement: elem: " << qPrintable(name().toString()); if (name() == "results") { newState = Results; } else if (name() == "cppcheck") { newState = CppCheck; } else if (name() == "errors") { newState = Errors; } else if (name() == "location") { newState = Location; if (attributes().hasAttribute(QStringLiteral("file")) && attributes().hasAttribute(QStringLiteral("line"))) { QString errorFile = attributes().value(QStringLiteral("file")).toString(); // Usually when "file0" attribute exists it associated with source and // attribute "file" associated with header). // But sometimes cppcheck produces errors with "file" and "file0" attributes // both associated with same *source* file. In such cases attribute "file" contains // only file name, without full path. Therefore we should use "file0" instead "file". if (!QFile::exists(errorFile) && attributes().hasAttribute(QStringLiteral("file0"))) { errorFile = attributes().value(QStringLiteral("file0")).toString(); } m_errorFiles += errorFile; m_errorLines += attributes().value(QStringLiteral("line")).toString().toInt(); } } else if (name() == "error") { newState = Error; - m_errorSeverity = QLatin1String("unknown"); + m_errorSeverity = QStringLiteral("unknown"); m_errorInconclusive = false; m_errorFiles.clear(); m_errorLines.clear(); m_errorMessage.clear(); m_errorVerboseMessage.clear(); if (attributes().hasAttribute(QStringLiteral("msg"))) { m_errorMessage = attributes().value(QStringLiteral("msg")).toString(); } if (attributes().hasAttribute(QStringLiteral("verbose"))) { m_errorVerboseMessage = verboseMessageToHtml(attributes().value(QStringLiteral("verbose")).toString()); } if (attributes().hasAttribute(QStringLiteral("severity"))) { m_errorSeverity = attributes().value(QStringLiteral("severity")).toString(); } if (attributes().hasAttribute(QStringLiteral("inconclusive"))) { m_errorInconclusive = true; } } else { m_stateStack.push(m_stateStack.top()); return true; } m_stateStack.push(newState); return true; } bool CppcheckParser::endElement(QVector& problems) { qCDebug(KDEV_CPPCHECK) << "CppcheckParser::endElement: elem: " << qPrintable(name().toString()); State state = m_stateStack.pop(); switch (state) { case CppCheck: if (attributes().hasAttribute(QStringLiteral("version"))) { qCDebug(KDEV_CPPCHECK) << "Cppcheck report version: " << attributes().value(QStringLiteral("version")); } break; case Errors: // errors finished break; case Error: qCDebug(KDEV_CPPCHECK) << "CppcheckParser::endElement: new error elem: line: " << (m_errorLines.isEmpty() ? QStringLiteral("?") : QString::number(m_errorLines.first())) << " at " << (m_errorFiles.isEmpty() ? QStringLiteral("?") : m_errorFiles.first()) << ", msg: " << m_errorMessage; storeError(problems); break; case Results: // results finished break; case Location: break; default: break; } return true; } QVector CppcheckParser::parse() { QVector problems; qCDebug(KDEV_CPPCHECK) << "CppcheckParser::parse!"; while (!atEnd()) { int readNextVal = readNext(); switch (readNextVal) { case StartDocument: clear(); break; case StartElement: startElement(); break; case EndElement: endElement(problems); break; case Characters: break; default: qCDebug(KDEV_CPPCHECK) << "CppcheckParser::startElement: case: " << readNextVal; break; } } qCDebug(KDEV_CPPCHECK) << "CppcheckParser::parse: end"; if (hasError()) { switch (error()) { case CustomError: case UnexpectedElementError: case NotWellFormedError: KMessageBox::error( qApp->activeWindow(), i18n("Cppcheck XML Parsing: error at line %1, column %2: %3", lineNumber(), columnNumber(), errorString()), i18n("Cppcheck Error")); break; case NoError: case PrematureEndOfDocumentError: break; } } return problems; } void CppcheckParser::storeError(QVector& problems) { // Construct problem with using first location element KDevelop::IProblem::Ptr problem = getProblem(); // Adds other elements as diagnostics. // This allows the user to track the problem. for (int locationIdx = 1; locationIdx < m_errorFiles.size(); ++locationIdx) { problem->addDiagnostic(getProblem(locationIdx)); } problems.push_back(problem); } KDevelop::IProblem::Ptr CppcheckParser::getProblem(int locationIdx) const { KDevelop::IProblem::Ptr problem(new KDevelop::DetectedProblem(i18n("Cppcheck"))); QStringList messagePrefix; QString errorMessage(m_errorMessage); if (m_errorSeverity == QLatin1String("error")) { problem->setSeverity(KDevelop::IProblem::Error); } else if (m_errorSeverity == QLatin1String("warning")) { problem->setSeverity(KDevelop::IProblem::Warning); } else { problem->setSeverity(KDevelop::IProblem::Hint); messagePrefix.push_back(m_errorSeverity); } if (m_errorInconclusive) { messagePrefix.push_back(QStringLiteral("inconclusive")); } if (!messagePrefix.isEmpty()) { errorMessage = QStringLiteral("(%1) %2").arg(messagePrefix.join(QStringLiteral(", "))).arg(m_errorMessage); } problem->setDescription(errorMessage); problem->setExplanation(m_errorVerboseMessage); KDevelop::DocumentRange range; if (locationIdx < 0 || locationIdx >= m_errorFiles.size()) { range = KDevelop::DocumentRange::invalid(); } else { range.document = KDevelop::IndexedString(m_errorFiles.at(locationIdx)); range.setBothLines(m_errorLines.at(locationIdx) - 1); range.setBothColumns(0); } problem->setFinalLocation(range); problem->setFinalLocationMode(KDevelop::IProblem::TrimmedLine); return problem; } } diff --git a/debuggers/common/mi/micommand.cpp b/debuggers/common/mi/micommand.cpp index a372d350f3..b7611e2fef 100644 --- a/debuggers/common/mi/micommand.cpp +++ b/debuggers/common/mi/micommand.cpp @@ -1,489 +1,489 @@ /*************************************************************************** begin : Sun Aug 8 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org ***************************************************************************/ /*************************************************************************** * * * 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 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "micommand.h" #include using namespace KDevMI::MI; FunctionCommandHandler::FunctionCommandHandler(const FunctionCommandHandler::Function& callback, CommandFlags flags) : _flags(flags) , _callback(callback) { } bool FunctionCommandHandler::handlesError() { return _flags & CmdHandlesError; } void FunctionCommandHandler::handle(const ResultRecord& r) { _callback(r); } MICommand::MICommand(CommandType type, const QString& command, CommandFlags flags) : type_(type) , flags_(flags) , command_(command) , commandHandler_(nullptr) , stateReloading_(false) , m_thread(-1) , m_frame(-1) { } MICommand::~MICommand() { if (commandHandler_ && commandHandler_->autoDelete()) { delete commandHandler_; } commandHandler_ = nullptr; } QString MICommand::cmdToSend() { return initialString() + '\n'; } QString MICommand::initialString() const { QString result = QString::number(token()); if (type() == NonMI) { result += command_; } else { result += miCommand(); if (m_thread != -1) result = result + QStringLiteral(" --thread %1").arg(m_thread); if (m_frame != -1) result = result + QStringLiteral(" --frame %1").arg(m_frame); if (!command_.isEmpty()) result += ' ' + command_; } return result; } bool MICommand::isUserCommand() const { return false; } void MICommand::setHandler(MICommandHandler* handler) { if (commandHandler_ && commandHandler_->autoDelete()) delete commandHandler_; commandHandler_ = handler; if (!commandHandler_) { flags_ = flags_ & ~CmdHandlesError; } } void MICommand::setHandler(const FunctionCommandHandler::Function& callback) { setHandler(new FunctionCommandHandler(callback, flags())); } bool MICommand::invokeHandler(const ResultRecord& r) { if (commandHandler_) { //ask before calling handler as it might deleted itself in handler bool autoDelete = commandHandler_->autoDelete(); commandHandler_->handle(r); if (autoDelete) { delete commandHandler_; } commandHandler_ = nullptr; return true; } else { return false; } } void MICommand::newOutput(const QString& line) { lines.push_back(line); } const QStringList& MICommand::allStreamOutput() const { return lines; } bool MICommand::handlesError() const { return commandHandler_ ? commandHandler_->handlesError() : false; } UserCommand::UserCommand(CommandType type, const QString& s) : MICommand(type, s, CmdMaybeStartsRunning) { } bool UserCommand::isUserCommand() const { return true; } QString MICommand::miCommand() const { QString command; switch (type()) { case NonMI: command = QLatin1String(""); break; case BreakAfter: - command = QLatin1String("break-after");//"ignore" + command = QStringLiteral("break-after");//"ignore" break; case BreakCommands: - command = QLatin1String("break-commands"); + command = QStringLiteral("break-commands"); break; case BreakCondition: - command = QLatin1String("break-condition");//"cond" + command = QStringLiteral("break-condition");//"cond" break; case BreakDelete: - command = QLatin1String("break-delete");//"delete breakpoint" + command = QStringLiteral("break-delete");//"delete breakpoint" break; case BreakDisable: - command = QLatin1String("break-disable");//"disable breakpoint" + command = QStringLiteral("break-disable");//"disable breakpoint" break; case BreakEnable: - command = QLatin1String("break-enable");//"enable breakpoint" + command = QStringLiteral("break-enable");//"enable breakpoint" break; case BreakInfo: - command = QLatin1String("break-info");//"info break" + command = QStringLiteral("break-info");//"info break" break; case BreakInsert: - command = QLatin1String("break-insert -f"); + command = QStringLiteral("break-insert -f"); break; case BreakList: - command = QLatin1String("break-list");//"info break" + command = QStringLiteral("break-list");//"info break" break; case BreakWatch: - command = QLatin1String("break-watch"); + command = QStringLiteral("break-watch"); break; case DataDisassemble: - command = QLatin1String("data-disassemble"); + command = QStringLiteral("data-disassemble"); break; case DataEvaluateExpression: - command = QLatin1String("data-evaluate-expression"); + command = QStringLiteral("data-evaluate-expression"); break; case DataListChangedRegisters: - command = QLatin1String("data-list-changed-registers"); + command = QStringLiteral("data-list-changed-registers"); break; case DataListRegisterNames: - command = QLatin1String("data-list-register-names"); + command = QStringLiteral("data-list-register-names"); break; case DataListRegisterValues: - command = QLatin1String("data-list-register-values"); + command = QStringLiteral("data-list-register-values"); break; case DataReadMemory: - command = QLatin1String("data-read-memory"); + command = QStringLiteral("data-read-memory"); break; case DataWriteMemory: - command = QLatin1String("data-write-memory"); + command = QStringLiteral("data-write-memory"); break; case DataWriteRegisterVariables: - command = QLatin1String("data-write-register-values"); + command = QStringLiteral("data-write-register-values"); break; case EnablePrettyPrinting: - command = QLatin1String("enable-pretty-printing"); + command = QStringLiteral("enable-pretty-printing"); break; case EnableTimings: - command = QLatin1String("enable-timings"); + command = QStringLiteral("enable-timings"); break; case EnvironmentCd: - command = QLatin1String("environment-cd"); + command = QStringLiteral("environment-cd"); break; case EnvironmentDirectory: - command = QLatin1String("environment-directory"); + command = QStringLiteral("environment-directory"); break; case EnvironmentPath: - command = QLatin1String("environment-path"); + command = QStringLiteral("environment-path"); break; case EnvironmentPwd: - command = QLatin1String("environment-pwd"); + command = QStringLiteral("environment-pwd"); break; case ExecAbort: - command = QLatin1String("exec-abort"); + command = QStringLiteral("exec-abort"); break; case ExecArguments: - command = QLatin1String("exec-arguments");//"set args" + command = QStringLiteral("exec-arguments");//"set args" break; case ExecContinue: - command = QLatin1String("exec-continue"); + command = QStringLiteral("exec-continue"); break; case ExecFinish: - command = QLatin1String("exec-finish"); + command = QStringLiteral("exec-finish"); break; case ExecInterrupt: - command = QLatin1String("exec-interrupt"); + command = QStringLiteral("exec-interrupt"); break; case ExecNext: - command = QLatin1String("exec-next"); + command = QStringLiteral("exec-next"); break; case ExecNextInstruction: - command = QLatin1String("exec-next-instruction"); + command = QStringLiteral("exec-next-instruction"); break; case ExecRun: - command = QLatin1String("exec-run"); + command = QStringLiteral("exec-run"); break; case ExecStep: - command = QLatin1String("exec-step"); + command = QStringLiteral("exec-step"); break; case ExecStepInstruction: - command = QLatin1String("exec-step-instruction"); + command = QStringLiteral("exec-step-instruction"); break; case ExecUntil: - command = QLatin1String("exec-until"); + command = QStringLiteral("exec-until"); break; case FileExecAndSymbols: - command = QLatin1String("file-exec-and-symbols");//"file" + command = QStringLiteral("file-exec-and-symbols");//"file" break; case FileExecFile: - command = QLatin1String("file-exec-file");//"exec-file" + command = QStringLiteral("file-exec-file");//"exec-file" break; case FileListExecSourceFile: - command = QLatin1String("file-list-exec-source-file"); + command = QStringLiteral("file-list-exec-source-file"); break; case FileListExecSourceFiles: - command = QLatin1String("file-list-exec-source-files"); + command = QStringLiteral("file-list-exec-source-files"); break; case FileSymbolFile: - command = QLatin1String("file-symbol-file");//"symbol-file" + command = QStringLiteral("file-symbol-file");//"symbol-file" break; case GdbExit: - command = QLatin1String("gdb-exit"); + command = QStringLiteral("gdb-exit"); break; case GdbSet: - command = QLatin1String("gdb-set");//"set" + command = QStringLiteral("gdb-set");//"set" break; case GdbShow: - command = QLatin1String("gdb-show");//"show" + command = QStringLiteral("gdb-show");//"show" break; case GdbVersion: - command = QLatin1String("gdb-version");//"show version" + command = QStringLiteral("gdb-version");//"show version" break; case InferiorTtySet: - command = QLatin1String("inferior-tty-set"); + command = QStringLiteral("inferior-tty-set"); break; case InferiorTtyShow: - command = QLatin1String("inferior-tty-show"); + command = QStringLiteral("inferior-tty-show"); break; case InterpreterExec: - command = QLatin1String("interpreter-exec"); + command = QStringLiteral("interpreter-exec"); break; case ListFeatures: - command = QLatin1String("list-features"); + command = QStringLiteral("list-features"); break; case SignalHandle: return QStringLiteral("handle"); //command = "signal-handle"; break; case StackInfoDepth: - command = QLatin1String("stack-info-depth"); + command = QStringLiteral("stack-info-depth"); break; case StackInfoFrame: - command = QLatin1String("stack-info-frame"); + command = QStringLiteral("stack-info-frame"); break; case StackListArguments: - command = QLatin1String("stack-list-arguments"); + command = QStringLiteral("stack-list-arguments"); break; case StackListFrames: - command = QLatin1String("stack-list-frames"); + command = QStringLiteral("stack-list-frames"); break; case StackListLocals: - command = QLatin1String("stack-list-locals"); + command = QStringLiteral("stack-list-locals"); break; case StackSelectFrame: - command = QLatin1String("stack-select-frame"); + command = QStringLiteral("stack-select-frame"); break; case SymbolListLines: - command = QLatin1String("symbol-list-lines"); + command = QStringLiteral("symbol-list-lines"); break; case TargetAttach: - command = QLatin1String("target-attach"); + command = QStringLiteral("target-attach"); break; case TargetDetach: - command = QLatin1String("target-detach");//"detach" + command = QStringLiteral("target-detach");//"detach" break; case TargetDisconnect: - command = QLatin1String("target-disconnect");//"disconnect" + command = QStringLiteral("target-disconnect");//"disconnect" break; case TargetDownload: - command = QLatin1String("target-download"); + command = QStringLiteral("target-download"); break; case TargetSelect: - command = QLatin1String("target-select"); + command = QStringLiteral("target-select"); break; case ThreadInfo: - command = QLatin1String("thread-info"); + command = QStringLiteral("thread-info"); break; case ThreadListIds: - command = QLatin1String("thread-list-ids"); + command = QStringLiteral("thread-list-ids"); break; case ThreadSelect: - command = QLatin1String("thread-select"); + command = QStringLiteral("thread-select"); break; case TraceFind: - command = QLatin1String("trace-find"); + command = QStringLiteral("trace-find"); break; case TraceStart: - command = QLatin1String("trace-start"); + command = QStringLiteral("trace-start"); break; case TraceStop: - command = QLatin1String("trace-stop"); + command = QStringLiteral("trace-stop"); break; case VarAssign: - command = QLatin1String("var-assign"); + command = QStringLiteral("var-assign"); break; case VarCreate: - command = QLatin1String("var-create"); + command = QStringLiteral("var-create"); break; case VarDelete: - command = QLatin1String("var-delete"); + command = QStringLiteral("var-delete"); break; case VarEvaluateExpression: - command = QLatin1String("var-evaluate-expression"); + command = QStringLiteral("var-evaluate-expression"); break; case VarInfoPathExpression: - command = QLatin1String("var-info-path-expression"); + command = QStringLiteral("var-info-path-expression"); break; case VarInfoNumChildren: - command = QLatin1String("var-info-num-children"); + command = QStringLiteral("var-info-num-children"); break; case VarInfoType: - command = QLatin1String("var-info-type"); + command = QStringLiteral("var-info-type"); break; case VarListChildren: - command = QLatin1String("var-list-children"); + command = QStringLiteral("var-list-children"); break; case VarSetFormat: - command = QLatin1String("var-set-format"); + command = QStringLiteral("var-set-format"); break; case VarSetFrozen: - command = QLatin1String("var-set-frozen"); + command = QStringLiteral("var-set-frozen"); break; case VarShowAttributes: - command = QLatin1String("var-show-attributes"); + command = QStringLiteral("var-show-attributes"); break; case VarShowFormat: - command = QLatin1String("var-show-format"); + command = QStringLiteral("var-show-format"); break; case VarUpdate: - command = QLatin1String("var-update"); + command = QStringLiteral("var-update"); break; default: - command = QLatin1String("unknown"); + command = QStringLiteral("unknown"); break; } return '-' + command; } CommandType MICommand::type() const { return type_; } int MICommand::thread() const { return m_thread; } void MICommand::setThread(int thread) { m_thread = thread; } int MICommand::frame() const { return m_frame; } void MICommand::setFrame(int frame) { m_frame = frame; } QString MICommand::command() const { return command_; } void MICommand::setStateReloading(bool f) { stateReloading_ = f; } bool MICommand::stateReloading() const { return stateReloading_; } void MICommand::markAsEnqueued() { m_enqueueTimestamp = QDateTime::currentMSecsSinceEpoch(); } void MICommand::markAsSubmitted() { m_submitTimestamp = QDateTime::currentMSecsSinceEpoch(); } void MICommand::markAsCompleted() { m_completeTimestamp = QDateTime::currentMSecsSinceEpoch(); } qint64 MICommand::gdbProcessingTime() const { return m_completeTimestamp - m_submitTimestamp; } qint64 MICommand::queueTime() const { return m_submitTimestamp - m_enqueueTimestamp; } qint64 MICommand::totalProcessingTime() const { return m_completeTimestamp - m_enqueueTimestamp; } diff --git a/debuggers/common/mibreakpointcontroller.cpp b/debuggers/common/mibreakpointcontroller.cpp index 128493edae..0552e22f2f 100644 --- a/debuggers/common/mibreakpointcontroller.cpp +++ b/debuggers/common/mibreakpointcontroller.cpp @@ -1,759 +1,759 @@ /* This file is part of the KDE project Copyright (C) 2002 Matthias Hoelzer-Kluepfel Copyright (C) 2002 John Firebaugh Copyright (C) 2006, 2008 Vladimir Prus Copyright (C) 2007 Hamish Rodda Copyright (C) 2009 Niko Sams This library 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "mibreakpointcontroller.h" #include "debuglog.h" #include "midebugsession.h" #include "mi/micommand.h" #include "stringhelpers.h" #include #include #include using namespace KDevMI; using namespace KDevMI::MI; using namespace KDevelop; struct MIBreakpointController::Handler : public MICommandHandler { Handler(MIBreakpointController* controller, const BreakpointDataPtr& b, BreakpointModel::ColumnFlags columns) : controller(controller) , breakpoint(b) , columns(columns) { breakpoint->sent |= columns; breakpoint->dirty &= ~columns; } void handle(const ResultRecord& r) override { breakpoint->sent &= ~columns; if (r.reason == QLatin1String("error")) { breakpoint->errors |= columns; int row = controller->breakpointRow(breakpoint); if (row >= 0) { controller->updateErrorText(row, r[QStringLiteral("msg")].literal()); qWarning() << r[QStringLiteral("msg")].literal(); } } else { if (breakpoint->errors & columns) { breakpoint->errors &= ~columns; if (breakpoint->errors) { // Since at least one error column cleared, it's possible that any remaining // error bits were collateral damage; try resending the corresponding columns // to see whether errors remain. breakpoint->dirty |= (breakpoint->errors & ~breakpoint->sent); } } } } bool handlesError() override { return true; } MIBreakpointController* controller; BreakpointDataPtr breakpoint; BreakpointModel::ColumnFlags columns; }; struct MIBreakpointController::UpdateHandler : public MIBreakpointController::Handler { UpdateHandler(MIBreakpointController* c, const BreakpointDataPtr& b, BreakpointModel::ColumnFlags columns) : Handler(c, b, columns) {} void handle(const ResultRecord &r) override { Handler::handle(r); int row = controller->breakpointRow(breakpoint); if (row >= 0) { // Note: send further updates even if we got an error; who knows: perhaps // these additional updates will "unstuck" the error condition. if (breakpoint->sent == 0 && breakpoint->dirty != 0) { controller->sendUpdates(row); } controller->recalculateState(row); } } }; struct MIBreakpointController::InsertedHandler : public MIBreakpointController::Handler { InsertedHandler(MIBreakpointController* c, const BreakpointDataPtr& b, BreakpointModel::ColumnFlags columns) : Handler(c, b, columns) {} void handle(const ResultRecord &r) override { Handler::handle(r); int row = controller->breakpointRow(breakpoint); if (r.reason != QLatin1String("error")) { QString bkptKind; for (auto kind : {"bkpt", "wpt", "hw-rwpt", "hw-awpt"}) { if (r.hasField(kind)) { bkptKind = kind; break; } } if (bkptKind.isEmpty()) { qWarning() << "Gdb sent unknown breakpoint kind"; return; } const Value& miBkpt = r[bkptKind]; breakpoint->debuggerId = miBkpt[QStringLiteral("number")].toInt(); if (row >= 0) { controller->updateFromDebugger(row, miBkpt); if (breakpoint->dirty != 0) controller->sendUpdates(row); } else { // breakpoint was deleted while insertion was in flight controller->debugSession()->addCommand(BreakDelete, QString::number(breakpoint->debuggerId), CmdImmediately); } } if (row >= 0) { controller->recalculateState(row); } } }; struct MIBreakpointController::DeleteHandler : MIBreakpointController::Handler { DeleteHandler(MIBreakpointController* c, const BreakpointDataPtr& b) : Handler(c, b, nullptr) {} void handle(const ResultRecord&) override { controller->m_pendingDeleted.removeAll(breakpoint); } }; struct MIBreakpointController::IgnoreChanges { explicit IgnoreChanges(MIBreakpointController& controller) : controller(controller) { ++controller.m_ignoreChanges; } ~IgnoreChanges() { --controller.m_ignoreChanges; } MIBreakpointController& controller; }; MIBreakpointController::MIBreakpointController(MIDebugSession * parent) : IBreakpointController(parent) { Q_ASSERT(parent); connect(parent, &MIDebugSession::inferiorStopped, this, &MIBreakpointController::programStopped); int numBreakpoints = breakpointModel()->breakpoints().size(); for (int row = 0; row < numBreakpoints; ++row) breakpointAdded(row); } MIDebugSession *MIBreakpointController::debugSession() const { Q_ASSERT(QObject::parent()); return static_cast(const_cast(QObject::parent())); } int MIBreakpointController::breakpointRow(const BreakpointDataPtr& breakpoint) { return m_breakpoints.indexOf(breakpoint); } void MIBreakpointController::setDeleteDuplicateBreakpoints(bool enable) { m_deleteDuplicateBreakpoints = enable; } void MIBreakpointController::initSendBreakpoints() { for (int row = 0; row < m_breakpoints.size(); ++row) { BreakpointDataPtr breakpoint = m_breakpoints[row]; if (breakpoint->debuggerId < 0 && breakpoint->sent == 0) { createBreakpoint(row); } } } void MIBreakpointController::breakpointAdded(int row) { if (m_ignoreChanges > 0) return; auto breakpoint = BreakpointDataPtr::create(); m_breakpoints.insert(row, breakpoint); const Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row); if (!modelBreakpoint->enabled()) breakpoint->dirty |= BreakpointModel::EnableColumnFlag; if (!modelBreakpoint->condition().isEmpty()) breakpoint->dirty |= BreakpointModel::ConditionColumnFlag; if (modelBreakpoint->ignoreHits() != 0) breakpoint->dirty |= BreakpointModel::IgnoreHitsColumnFlag; if (!modelBreakpoint->address().isEmpty()) breakpoint->dirty |= BreakpointModel::LocationColumnFlag; createBreakpoint(row); } void MIBreakpointController::breakpointModelChanged(int row, BreakpointModel::ColumnFlags columns) { if (m_ignoreChanges > 0) return; BreakpointDataPtr breakpoint = m_breakpoints.at(row); breakpoint->dirty |= columns & (BreakpointModel::EnableColumnFlag | BreakpointModel::LocationColumnFlag | BreakpointModel::ConditionColumnFlag | BreakpointModel::IgnoreHitsColumnFlag); if (breakpoint->sent != 0) { // Throttle the amount of commands we send to GDB; the response handler of currently // in-flight commands will take care of sending the update. // This also prevents us from sending updates while a break-create command is in-flight. return; } if (breakpoint->debuggerId < 0) { createBreakpoint(row); } else { sendUpdates(row); } } void MIBreakpointController::breakpointAboutToBeDeleted(int row) { if (m_ignoreChanges > 0) return; BreakpointDataPtr breakpoint = m_breakpoints.at(row); m_breakpoints.removeAt(row); if (breakpoint->debuggerId < 0) { // Two possibilities: // (1) Breakpoint has never been sent to GDB, so we're done // (2) Breakpoint has been sent to GDB, but we haven't received // the response yet; the response handler will delete the // breakpoint. return; } if (debugSession()->debuggerStateIsOn(s_dbgNotStarted)) return; debugSession()->addCommand( BreakDelete, QString::number(breakpoint->debuggerId), new DeleteHandler(this, breakpoint), CmdImmediately); m_pendingDeleted << breakpoint; } // Note: despite the name, this is in fact session state changed. void MIBreakpointController::debuggerStateChanged(IDebugSession::DebuggerState state) { IgnoreChanges ignoreChanges(*this); if (state == IDebugSession::EndedState || state == IDebugSession::NotStartedState) { for (int row = 0; row < m_breakpoints.size(); ++row) { updateState(row, Breakpoint::NotStartedState); } } else if (state == IDebugSession::StartingState) { for (int row = 0; row < m_breakpoints.size(); ++row) { updateState(row, Breakpoint::DirtyState); } } } void MIBreakpointController::createBreakpoint(int row) { if (debugSession()->debuggerStateIsOn(s_dbgNotStarted)) return; BreakpointDataPtr breakpoint = m_breakpoints.at(row); Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row); Q_ASSERT(breakpoint->debuggerId < 0 && breakpoint->sent == 0); if (modelBreakpoint->location().isEmpty()) return; if (modelBreakpoint->kind() == Breakpoint::CodeBreakpoint) { QString location; if (modelBreakpoint->line() != -1) { location = QStringLiteral("%0:%1") .arg(modelBreakpoint->url().url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash)) .arg(modelBreakpoint->line() + 1); } else { location = modelBreakpoint->location(); } if (location == QLatin1String("catch throw")) { - location = QLatin1String("exception throw"); + location = QStringLiteral("exception throw"); } // Note: We rely on '-f' to be automatically added by the MICommand logic QString arguments; if (!modelBreakpoint->enabled()) arguments += QLatin1String("-d "); if (!modelBreakpoint->condition().isEmpty()) arguments += QStringLiteral("-c %0 ").arg(Utils::quoteExpression(modelBreakpoint->condition())); if (modelBreakpoint->ignoreHits() != 0) arguments += QStringLiteral("-i %0 ").arg(modelBreakpoint->ignoreHits()); arguments += Utils::quoteExpression(location); BreakpointModel::ColumnFlags sent = BreakpointModel::EnableColumnFlag | BreakpointModel::ConditionColumnFlag | BreakpointModel::IgnoreHitsColumnFlag | BreakpointModel::LocationColumnFlag; debugSession()->addCommand(BreakInsert, arguments, new InsertedHandler(this, breakpoint, sent), CmdImmediately); } else { QString opt; if (modelBreakpoint->kind() == Breakpoint::ReadBreakpoint) - opt = QLatin1String("-r "); + opt = QStringLiteral("-r "); else if (modelBreakpoint->kind() == Breakpoint::AccessBreakpoint) - opt = QLatin1String("-a "); + opt = QStringLiteral("-a "); debugSession()->addCommand(BreakWatch, opt + Utils::quoteExpression(modelBreakpoint->location()), new InsertedHandler(this, breakpoint, BreakpointModel::LocationColumnFlag), CmdImmediately); } recalculateState(row); } void MIBreakpointController::sendUpdates(int row) { if (debugSession()->debuggerStateIsOn(s_dbgNotStarted)) return; BreakpointDataPtr breakpoint = m_breakpoints.at(row); Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row); Q_ASSERT(breakpoint->debuggerId >= 0 && breakpoint->sent == 0); if (breakpoint->dirty & BreakpointModel::LocationColumnFlag) { // Gdb considers locations as fixed, so delete and re-create the breakpoint debugSession()->addCommand(BreakDelete, QString::number(breakpoint->debuggerId), CmdImmediately); breakpoint->debuggerId = -1; createBreakpoint(row); return; } if (breakpoint->dirty & BreakpointModel::EnableColumnFlag) { debugSession()->addCommand(modelBreakpoint->enabled() ? BreakEnable : BreakDisable, QString::number(breakpoint->debuggerId), new UpdateHandler(this, breakpoint, BreakpointModel::EnableColumnFlag), CmdImmediately); } if (breakpoint->dirty & BreakpointModel::IgnoreHitsColumnFlag) { debugSession()->addCommand(BreakAfter, QStringLiteral("%0 %1").arg(breakpoint->debuggerId) .arg(modelBreakpoint->ignoreHits()), new UpdateHandler(this, breakpoint, BreakpointModel::IgnoreHitsColumnFlag), CmdImmediately); } if (breakpoint->dirty & BreakpointModel::ConditionColumnFlag) { debugSession()->addCommand(BreakCondition, QStringLiteral("%0 %1").arg(breakpoint->debuggerId) .arg(modelBreakpoint->condition()), new UpdateHandler(this, breakpoint, BreakpointModel::ConditionColumnFlag), CmdImmediately); } recalculateState(row); } void MIBreakpointController::recalculateState(int row) { BreakpointDataPtr breakpoint = m_breakpoints.at(row); if (breakpoint->errors == 0) updateErrorText(row, QString()); Breakpoint::BreakpointState newState = Breakpoint::NotStartedState; if (debugSession()->state() != IDebugSession::EndedState && debugSession()->state() != IDebugSession::NotStartedState) { if (!debugSession()->debuggerStateIsOn(s_dbgNotStarted)) { if (breakpoint->dirty == 0 && breakpoint->sent == 0) { if (breakpoint->pending) { newState = Breakpoint::PendingState; } else { newState = Breakpoint::CleanState; } } else { newState = Breakpoint::DirtyState; } } } updateState(row, newState); } int MIBreakpointController::rowFromDebuggerId(int gdbId) const { for (int row = 0; row < m_breakpoints.size(); ++row) { if (gdbId == m_breakpoints[row]->debuggerId) return row; } return -1; } void MIBreakpointController::notifyBreakpointCreated(const AsyncRecord& r) { const Value& miBkpt = r[QStringLiteral("bkpt")]; // Breakpoints with multiple locations are represented by a parent breakpoint (e.g. 1) // and multiple child breakpoints (e.g. 1.1, 1.2, 1.3, ...). // We ignore the child breakpoints here in the current implementation; this can lead to dubious // results in the UI when breakpoints are marked in document views (e.g. when a breakpoint // applies to multiple overloads of a C++ function simultaneously) and in disassembly // (e.g. when a breakpoint is set in an inlined functions). if (miBkpt[QStringLiteral("number")].literal().contains('.')) return; createFromDebugger(miBkpt); } void MIBreakpointController::notifyBreakpointModified(const AsyncRecord& r) { const Value& miBkpt = r[QStringLiteral("bkpt")]; const int gdbId = miBkpt[QStringLiteral("number")].toInt(); const int row = rowFromDebuggerId(gdbId); if (row < 0) { for (const auto& breakpoint : m_pendingDeleted) { if (breakpoint->debuggerId == gdbId) { // Received a modification of a breakpoint whose deletion is currently // in-flight; simply ignore it. return; } } qCWarning(DEBUGGERCOMMON) << "Received a modification of an unknown breakpoint"; createFromDebugger(miBkpt); } else { updateFromDebugger(row, miBkpt); } } void MIBreakpointController::notifyBreakpointDeleted(const AsyncRecord& r) { const int gdbId = r[QStringLiteral("id")].toInt(); const int row = rowFromDebuggerId(gdbId); if (row < 0) { // The user may also have deleted the breakpoint via the UI simultaneously return; } IgnoreChanges ignoreChanges(*this); breakpointModel()->removeRow(row); m_breakpoints.removeAt(row); } void MIBreakpointController::createFromDebugger(const Value& miBkpt) { const QString type = miBkpt[QStringLiteral("type")].literal(); Breakpoint::BreakpointKind gdbKind; if (type == QLatin1String("breakpoint")) { gdbKind = Breakpoint::CodeBreakpoint; } else if (type == QLatin1String("watchpoint") || type == QLatin1String("hw watchpoint")) { gdbKind = Breakpoint::WriteBreakpoint; } else if (type == QLatin1String("read watchpoint")) { gdbKind = Breakpoint::ReadBreakpoint; } else if (type == QLatin1String("acc watchpoint")) { gdbKind = Breakpoint::AccessBreakpoint; } else { qCWarning(DEBUGGERCOMMON) << "Unknown breakpoint type " << type; return; } // During debugger startup, we want to avoid creating duplicate breakpoints when the same breakpoint // appears both in our model and in a init file e.g. .gdbinit BreakpointModel* model = breakpointModel(); const int numRows = model->rowCount(); for (int row = 0; row < numRows; ++row) { BreakpointDataPtr breakpoint = m_breakpoints.at(row); const bool breakpointSent = breakpoint->debuggerId >= 0 || breakpoint->sent != 0; if (breakpointSent && !m_deleteDuplicateBreakpoints) continue; Breakpoint* modelBreakpoint = model->breakpoint(row); if (modelBreakpoint->kind() != gdbKind) continue; if (gdbKind == Breakpoint::CodeBreakpoint) { bool sameLocation = false; if (miBkpt.hasField(QStringLiteral("fullname")) && miBkpt.hasField(QStringLiteral("line"))) { const QString location = Utils::unquoteExpression(miBkpt[QStringLiteral("fullname")].literal()); const int line = miBkpt[QStringLiteral("line")].toInt() - 1; if (location == modelBreakpoint->url().url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash) && line == modelBreakpoint->line()) { sameLocation = true; } } if (!sameLocation && miBkpt.hasField(QStringLiteral("original-location"))) { const QString location = miBkpt[QStringLiteral("original-location")].literal(); if (location == modelBreakpoint->location()) { sameLocation = true; } else { QRegExp rx("^(.+):(\\d+)$"); if (rx.indexIn(location) != -1 && Utils::unquoteExpression(rx.cap(1)) == modelBreakpoint->url().url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash) && rx.cap(2).toInt() - 1 == modelBreakpoint->line()) { sameLocation = true; } } } if (!sameLocation && miBkpt.hasField(QStringLiteral("what")) && miBkpt[QStringLiteral("what")].literal() == QLatin1String("exception throw")) { if (modelBreakpoint->expression() == QLatin1String("catch throw") || modelBreakpoint->expression() == QLatin1String("exception throw")) { sameLocation = true; } } if (!sameLocation) continue; } else { if (Utils::unquoteExpression(miBkpt[QStringLiteral("original-location")].literal()) != modelBreakpoint->expression()) { continue; } } QString condition; if (miBkpt.hasField(QStringLiteral("cond"))) { condition = miBkpt[QStringLiteral("cond")].literal(); } if (condition != modelBreakpoint->condition()) continue; // Breakpoint is equivalent if (!breakpointSent) { breakpoint->debuggerId = miBkpt[QStringLiteral("number")].toInt(); // Reasonable people can probably have different opinions about what the "correct" behavior // should be for the "enabled" and "ignore hits" column. // Here, we let the status in KDevelop's UI take precedence, which we suspect to be // marginally more useful. Dirty data will be sent during the initial sending of the // breakpoint list. const bool gdbEnabled = miBkpt[QStringLiteral("enabled")].literal() == QLatin1String("y"); if (gdbEnabled != modelBreakpoint->enabled()) breakpoint->dirty |= BreakpointModel::EnableColumnFlag; int gdbIgnoreHits = 0; if (miBkpt.hasField(QStringLiteral("ignore"))) gdbIgnoreHits = miBkpt[QStringLiteral("ignore")].toInt(); if (gdbIgnoreHits != modelBreakpoint->ignoreHits()) breakpoint->dirty |= BreakpointModel::IgnoreHitsColumnFlag; updateFromDebugger(row, miBkpt, BreakpointModel::EnableColumnFlag | BreakpointModel::IgnoreHitsColumnFlag); return; } // Breakpoint from the model has already been sent, but we want to delete duplicates // It is not entirely clear _which_ breakpoint ought to be deleted, and reasonable people // may have different opinions. // We suspect that it is marginally more useful to delete the existing model breakpoint; // after all, this only happens when a user command creates a breakpoint, and perhaps the // user intends to modify the breakpoint they created manually. In any case, // this situation should only happen rarely (in particular, when a user sets a breakpoint // inside the remote run script). model->removeRows(row, 1); break; // fall through to pick up the manually created breakpoint in the model } // No equivalent breakpoint found, or we have one but want to be consistent with GDB's // behavior of allowing multiple equivalent breakpoint. IgnoreChanges ignoreChanges(*this); const int row = m_breakpoints.size(); Q_ASSERT(row == model->rowCount()); switch (gdbKind) { case Breakpoint::WriteBreakpoint: model->addWatchpoint(); break; case Breakpoint::ReadBreakpoint: model->addReadWatchpoint(); break; case Breakpoint::AccessBreakpoint: model->addAccessWatchpoint(); break; case Breakpoint::CodeBreakpoint: model->addCodeBreakpoint(); break; default: Q_ASSERT(false); return; } // Since we are in ignore-changes mode, we have to add the BreakpointData manually. auto breakpoint = BreakpointDataPtr::create(); m_breakpoints << breakpoint; breakpoint->debuggerId = miBkpt[QStringLiteral("number")].toInt(); updateFromDebugger(row, miBkpt); } // This method is required for the legacy interface which will be removed void MIBreakpointController::sendMaybe(KDevelop::Breakpoint*) { Q_ASSERT(false); } void MIBreakpointController::updateFromDebugger(int row, const Value& miBkpt, BreakpointModel::ColumnFlags lockedColumns) { IgnoreChanges ignoreChanges(*this); BreakpointDataPtr breakpoint = m_breakpoints[row]; Breakpoint* modelBreakpoint = breakpointModel()->breakpoint(row); // Commands that are currently in flight will overwrite the modification we have received, // so do not update the corresponding data lockedColumns |= breakpoint->sent | breakpoint->dirty; // TODO: // Gdb has a notion of "original-location", which is the "expression" or "location" used // to set the breakpoint, and notions of the actual location of the breakpoint (function name, // address, source file and line). The breakpoint model currently does not map well to this // (though it arguably should), and does not support multi-location breakpoints at all. // We try to do the best we can until the breakpoint model gets cleaned up. if (miBkpt.hasField(QStringLiteral("fullname")) && miBkpt.hasField(QStringLiteral("line"))) { modelBreakpoint->setLocation( QUrl::fromLocalFile(Utils::unquoteExpression(miBkpt[QStringLiteral("fullname")].literal())), miBkpt[QStringLiteral("line")].toInt() - 1); } else if (miBkpt.hasField(QStringLiteral("original-location"))) { QRegExp rx("^(.+):(\\d+)$"); QString location = miBkpt[QStringLiteral("original-location")].literal(); if (rx.indexIn(location) != -1) { modelBreakpoint->setLocation(QUrl::fromLocalFile(Utils::unquoteExpression(rx.cap(1))), rx.cap(2).toInt()-1); } else { modelBreakpoint->setData(Breakpoint::LocationColumn, Utils::unquoteExpression(location)); } } else if (miBkpt.hasField(QStringLiteral("what"))) { modelBreakpoint->setExpression(miBkpt[QStringLiteral("what")].literal()); } else { qWarning() << "Breakpoint doesn't contain required location/expression data"; } if (!(lockedColumns & BreakpointModel::EnableColumnFlag)) { bool enabled = true; if (miBkpt.hasField(QStringLiteral("enabled"))) { if (miBkpt[QStringLiteral("enabled")].literal() == QLatin1String("n")) enabled = false; } modelBreakpoint->setData(Breakpoint::EnableColumn, enabled ? Qt::Checked : Qt::Unchecked); breakpoint->dirty &= ~BreakpointModel::EnableColumnFlag; } if (!(lockedColumns & BreakpointModel::ConditionColumnFlag)) { QString condition; if (miBkpt.hasField(QStringLiteral("cond"))) { condition = miBkpt[QStringLiteral("cond")].literal(); } modelBreakpoint->setCondition(condition); breakpoint->dirty &= ~BreakpointModel::ConditionColumnFlag; } if (!(lockedColumns & BreakpointModel::IgnoreHitsColumnFlag)) { int ignoreHits = 0; if (miBkpt.hasField(QStringLiteral("ignore"))) { ignoreHits = miBkpt[QStringLiteral("ignore")].toInt(); } modelBreakpoint->setIgnoreHits(ignoreHits); breakpoint->dirty &= ~BreakpointModel::IgnoreHitsColumnFlag; } breakpoint->pending = false; if (miBkpt.hasField(QStringLiteral("addr")) && miBkpt[QStringLiteral("addr")].literal() == QLatin1String("")) { breakpoint->pending = true; } int hitCount = 0; if (miBkpt.hasField(QStringLiteral("times"))) { hitCount = miBkpt[QStringLiteral("times")].toInt(); } updateHitCount(row, hitCount); recalculateState(row); } void MIBreakpointController::programStopped(const AsyncRecord& r) { if (!r.hasField(QStringLiteral("reason"))) return; const QString reason = r[QStringLiteral("reason")].literal(); int debuggerId = -1; if (reason == QLatin1String("breakpoint-hit")) { debuggerId = r[QStringLiteral("bkptno")].toInt(); } else if (reason == QLatin1String("watchpoint-trigger")) { debuggerId = r[QStringLiteral("wpt")][QStringLiteral("number")].toInt(); } else if (reason == QLatin1String("read-watchpoint-trigger")) { debuggerId = r[QStringLiteral("hw-rwpt")][QStringLiteral("number")].toInt(); } else if (reason == QLatin1String("access-watchpoint-trigger")) { debuggerId = r[QStringLiteral("hw-awpt")][QStringLiteral("number")].toInt(); } if (debuggerId < 0) return; int row = rowFromDebuggerId(debuggerId); if (row < 0) return; QString msg; if (r.hasField(QStringLiteral("value"))) { if (r[QStringLiteral("value")].hasField(QStringLiteral("old"))) { msg += i18n("
Old value: %1", r["value"]["old"].literal()); } if (r[QStringLiteral("value")].hasField(QStringLiteral("new"))) { msg += i18n("
New value: %1", r["value"]["new"].literal()); } } notifyHit(row, msg); } diff --git a/debuggers/common/midebugsession.cpp b/debuggers/common/midebugsession.cpp index 729d98105c..32856d8382 100644 --- a/debuggers/common/midebugsession.cpp +++ b/debuggers/common/midebugsession.cpp @@ -1,1315 +1,1315 @@ /* * Common code for debugger support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * Copyright 2016 Aetf * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "midebugsession.h" #include "debuglog.h" #include "midebugger.h" #include "mivariable.h" #include "mi/mi.h" #include "mi/micommand.h" #include "mi/micommandqueue.h" #include "stty.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; using namespace KDevMI; using namespace KDevMI::MI; MIDebugSession::MIDebugSession(MIDebuggerPlugin *plugin) : m_procLineMaker(new ProcessLineMaker(this)) , m_commandQueue(new CommandQueue) , m_sessionState(NotStartedState) , m_debugger(nullptr) , m_debuggerState(s_dbgNotStarted | s_appNotStarted) , m_stateReloadInProgress(false) , m_stateReloadNeeded(false) , m_tty(nullptr) , m_hasCrashed(false) , m_sourceInitFile(true) , m_plugin(plugin) { // setup signals connect(m_procLineMaker, &ProcessLineMaker::receivedStdoutLines, this, &MIDebugSession::inferiorStdoutLines); connect(m_procLineMaker, &ProcessLineMaker::receivedStderrLines, this, &MIDebugSession::inferiorStderrLines); // forward tty output to process line maker connect(this, &MIDebugSession::inferiorTtyStdout, m_procLineMaker, &ProcessLineMaker::slotReceivedStdout); connect(this, &MIDebugSession::inferiorTtyStderr, m_procLineMaker, &ProcessLineMaker::slotReceivedStderr); // FIXME: see if this still works //connect(statusBarIndicator, SIGNAL(doubleClicked()), // controller, SLOT(explainDebuggerStatus())); // FIXME: reimplement / re-enable //connect(this, SIGNAL(addWatchVariable(QString)), controller->variables(), SLOT(slotAddWatchVariable(QString))); //connect(this, SIGNAL(evaluateExpression(QString)), controller->variables(), SLOT(slotEvaluateExpression(QString))); } MIDebugSession::~MIDebugSession() { qCDebug(DEBUGGERCOMMON) << "Destroying MIDebugSession"; // Deleting the session involves shutting down gdb nicely. // When were attached to a process, we must first detach so that the process // can continue running as it was before being attached. gdb is quite slow to // detach from a process, so we must process events within here to get a "clean" // shutdown. if (!debuggerStateIsOn(s_dbgNotStarted)) { stopDebugger(); } } IDebugSession::DebuggerState MIDebugSession::state() const { return m_sessionState; } QMap & MIDebugSession::variableMapping() { return m_allVariables; } MIVariable* MIDebugSession::findVariableByVarobjName(const QString &varobjName) const { if (m_allVariables.count(varobjName) == 0) return nullptr; return m_allVariables.value(varobjName); } void MIDebugSession::markAllVariableDead() { for (auto i = m_allVariables.begin(), e = m_allVariables.end(); i != e; ++i) { i.value()->markAsDead(); } m_allVariables.clear(); } bool MIDebugSession::restartAvaliable() const { if (debuggerStateIsOn(s_attached) || debuggerStateIsOn(s_core)) { return false; } else { return true; } } bool MIDebugSession::startDebugger(ILaunchConfiguration *cfg) { qCDebug(DEBUGGERCOMMON) << "Starting new debugger instance"; if (m_debugger) { qCWarning(DEBUGGERCOMMON) << "m_debugger object still exists"; delete m_debugger; m_debugger = nullptr; } m_debugger = createDebugger(); m_debugger->setParent(this); // output signals connect(m_debugger, &MIDebugger::applicationOutput, this, [this](const QString &output) { auto lines = output.split(QRegularExpression(QStringLiteral("[\r\n]")), QString::SkipEmptyParts); for (auto &line : lines) { int p = line.length(); while (p >= 1 && (line[p-1] == '\r' || line[p-1] == '\n')) p--; if (p != line.length()) line.remove(p, line.length() - p); } emit inferiorStdoutLines(lines); }); connect(m_debugger, &MIDebugger::userCommandOutput, this, &MIDebugSession::debuggerUserCommandOutput); connect(m_debugger, &MIDebugger::internalCommandOutput, this, &MIDebugSession::debuggerInternalCommandOutput); connect(m_debugger, &MIDebugger::debuggerInternalOutput, this, &MIDebugSession::debuggerInternalOutput); // state signals connect(m_debugger, &MIDebugger::programStopped, this, &MIDebugSession::inferiorStopped); connect(m_debugger, &MIDebugger::programRunning, this, &MIDebugSession::inferiorRunning); // internal handlers connect(m_debugger, &MIDebugger::ready, this, &MIDebugSession::slotDebuggerReady); connect(m_debugger, &MIDebugger::exited, this, &MIDebugSession::slotDebuggerExited); connect(m_debugger, &MIDebugger::programStopped, this, &MIDebugSession::slotInferiorStopped); connect(m_debugger, &MIDebugger::programRunning, this, &MIDebugSession::slotInferiorRunning); connect(m_debugger, &MIDebugger::notification, this, &MIDebugSession::processNotification); // start the debugger. Do this after connecting all signals so that initial // debugger output, and important events like the debugger died are reported. QStringList extraArguments; if (!m_sourceInitFile) extraArguments << QStringLiteral("--nx"); auto config = cfg ? cfg->config() // FIXME: this is only used when attachToProcess or examineCoreFile. // Change to use a global launch configuration when calling : KConfigGroup(KSharedConfig::openConfig(), "GDB Config"); if (!m_debugger->start(config, extraArguments)) { // debugger failed to start, ensure debugger and session state are correctly updated. setDebuggerStateOn(s_dbgFailedStart); return false; } // FIXME: here, we should wait until the debugger is up and waiting for input. // Then, clear s_dbgNotStarted // It's better to do this right away so that the state bit is always correct. setDebuggerStateOff(s_dbgNotStarted); // Initialise debugger. At this stage debugger is sitting wondering what to do, // and to whom. initializeDebugger(); qCDebug(DEBUGGERCOMMON) << "Debugger instance started"; return true; } bool MIDebugSession::startDebugging(ILaunchConfiguration* cfg, IExecutePlugin* iexec) { qCDebug(DEBUGGERCOMMON) << "Starting new debug session"; Q_ASSERT(cfg); Q_ASSERT(iexec); // Ensure debugger is started first if (debuggerStateIsOn(s_appNotStarted)) { emit showMessage(i18n("Running program"), 1000); } if (debuggerStateIsOn(s_dbgNotStarted)) { if (!startDebugger(cfg)) return false; } if (debuggerStateIsOn(s_shuttingDown)) { qCDebug(DEBUGGERCOMMON) << "Tried to run when debugger shutting down"; return false; } // Only dummy err here, actual erros have been checked already in the job and we don't get here if there were any QString err; QString executable = iexec->executable(cfg, err).toLocalFile(); configInferior(cfg, iexec, executable); // Set up the tty for the inferior bool config_useExternalTerminal = iexec->useTerminal(cfg); QString config_ternimalName = iexec->terminal(cfg); if (!config_ternimalName.isEmpty()) { // the external terminal cmd contains additional arguments, just get the terminal name config_ternimalName = KShell::splitArgs(config_ternimalName).first(); } m_tty.reset(new STTY(config_useExternalTerminal, config_ternimalName)); if (!config_useExternalTerminal) { connect(m_tty.get(), &STTY::OutOutput, this, &MIDebugSession::inferiorTtyStdout); connect(m_tty.get(), &STTY::ErrOutput, this, &MIDebugSession::inferiorTtyStderr); } QString tty(m_tty->getSlave()); if (tty.isEmpty()) { KMessageBox::information(qApp->activeWindow(), m_tty->lastError(), i18n("warning")); m_tty.reset(nullptr); return false; } addCommand(InferiorTtySet, tty); // Change the working directory to the correct one QString dir = iexec->workingDirectory(cfg).toLocalFile(); if (dir.isEmpty()) { dir = QFileInfo(executable).absolutePath(); } addCommand(EnvironmentCd, '"' + dir + '"'); // Set the run arguments QStringList arguments = iexec->arguments(cfg, err); if (!arguments.isEmpty()) addCommand(ExecArguments, KShell::joinArgs(arguments)); // Do other debugger specific config options and actually start the inferior program if (!execInferior(cfg, iexec, executable)) { return false; } QString config_startWith = cfg->config().readEntry(Config::StartWithEntry, QStringLiteral("ApplicationOutput")); if (config_startWith == QLatin1String("GdbConsole")) { emit raiseDebuggerConsoleViews(); } else if (config_startWith == QLatin1String("FrameStack")) { emit raiseFramestackViews(); } else { // ApplicationOutput is raised in DebugJob (by setting job to Verbose/Silent) } return true; } // FIXME: use same configuration process as startDebugging bool MIDebugSession::attachToProcess(int pid) { qCDebug(DEBUGGERCOMMON) << "Attach to process" << pid; emit showMessage(i18n("Attaching to process %1", pid), 1000); if (debuggerStateIsOn(s_dbgNotStarted)) { // FIXME: use global launch configuration rather than nullptr if (!startDebugger(nullptr)) { return false; } } setDebuggerStateOn(s_attached); //set current state to running, after attaching we will get *stopped response setDebuggerStateOn(s_appRunning); addCommand(TargetAttach, QString::number(pid), this, &MIDebugSession::handleTargetAttach, CmdHandlesError); addCommand(new SentinelCommand(breakpointController(), &MIBreakpointController::initSendBreakpoints)); raiseEvent(connected_to_program); emit raiseFramestackViews(); return true; } void MIDebugSession::handleTargetAttach(const MI::ResultRecord& r) { if (r.reason == QLatin1String("error")) { KMessageBox::error( qApp->activeWindow(), i18n("Could not attach debugger:
")+ r[QStringLiteral("msg")].literal(), i18n("Startup error")); stopDebugger(); } } bool MIDebugSession::examineCoreFile(const QUrl &debugee, const QUrl &coreFile) { emit showMessage(i18n("Examining core file %1", coreFile.toLocalFile()), 1000); if (debuggerStateIsOn(s_dbgNotStarted)) { // FIXME: use global launch configuration rather than nullptr if (!startDebugger(nullptr)) { return false; } } // FIXME: support non-local URLs if (!loadCoreFile(nullptr, debugee.toLocalFile(), coreFile.toLocalFile())) { return false; } raiseEvent(program_state_changed); return true; } #define ENUM_NAME(o,e,v) (o::staticMetaObject.enumerator(o::staticMetaObject.indexOfEnumerator(#e)).valueToKey((v))) void MIDebugSession::setSessionState(DebuggerState state) { qCDebug(DEBUGGERCOMMON) << "Session state changed to" << ENUM_NAME(IDebugSession, DebuggerState, state) << "(" << state << ")"; if (state != m_sessionState) { m_sessionState = state; emit stateChanged(state); } } bool MIDebugSession::debuggerStateIsOn(DBGStateFlags state) const { return m_debuggerState & state; } DBGStateFlags MIDebugSession::debuggerState() const { return m_debuggerState; } void MIDebugSession::setDebuggerStateOn(DBGStateFlags stateOn) { DBGStateFlags oldState = m_debuggerState; debuggerStateChange(m_debuggerState, m_debuggerState | stateOn); m_debuggerState |= stateOn; handleDebuggerStateChange(oldState, m_debuggerState); } void MIDebugSession::setDebuggerStateOff(DBGStateFlags stateOff) { DBGStateFlags oldState = m_debuggerState; debuggerStateChange(m_debuggerState, m_debuggerState & ~stateOff); m_debuggerState &= ~stateOff; handleDebuggerStateChange(oldState, m_debuggerState); } void MIDebugSession::setDebuggerState(DBGStateFlags newState) { DBGStateFlags oldState = m_debuggerState; debuggerStateChange(m_debuggerState, newState); m_debuggerState = newState; handleDebuggerStateChange(oldState, m_debuggerState); } void MIDebugSession::debuggerStateChange(DBGStateFlags oldState, DBGStateFlags newState) { int delta = oldState ^ newState; if (delta) { QString out; #define STATE_CHECK(name) \ do { \ if (delta & name) { \ out += ((newState & name) ? " +" : " -"); \ out += #name; \ delta &= ~name; \ } \ } while (0) STATE_CHECK(s_dbgNotStarted); STATE_CHECK(s_appNotStarted); STATE_CHECK(s_programExited); STATE_CHECK(s_attached); STATE_CHECK(s_core); STATE_CHECK(s_shuttingDown); STATE_CHECK(s_dbgBusy); STATE_CHECK(s_appRunning); STATE_CHECK(s_dbgNotListening); STATE_CHECK(s_automaticContinue); #undef STATE_CHECK for (unsigned int i = 0; delta != 0 && i < 32; ++i) { if (delta & (1 << i)) { delta &= ~(1 << i); out += ((1 << i) & newState) ? " +" : " -"; out += QString::number(i); } } qCDebug(DEBUGGERCOMMON) << "Debugger state change:" << out; } } void MIDebugSession::handleDebuggerStateChange(DBGStateFlags oldState, DBGStateFlags newState) { QString message; DebuggerState oldSessionState = state(); DebuggerState newSessionState = oldSessionState; DBGStateFlags changedState = oldState ^ newState; if (newState & s_dbgNotStarted) { if (changedState & s_dbgNotStarted) { message = i18n("Debugger stopped"); emit finished(); } if (oldSessionState != NotStartedState || newState & s_dbgFailedStart) { newSessionState = EndedState; } } else { if (newState & s_appNotStarted) { if (oldSessionState == NotStartedState || oldSessionState == StartingState) { newSessionState = StartingState; } else { newSessionState = StoppedState; } } else if (newState & s_programExited) { if (changedState & s_programExited) { message = i18n("Process exited"); } newSessionState = StoppedState; } else if (newState & s_appRunning) { if (changedState & s_appRunning) { message = i18n("Application is running"); } newSessionState = ActiveState; } else { if (changedState & s_appRunning) { message = i18n("Application is paused"); } newSessionState = PausedState; } } // And now? :-) qCDebug(DEBUGGERCOMMON) << "Debugger state changed to: " << newState << message; if (!message.isEmpty()) emit showMessage(message, 3000); emit debuggerStateChanged(oldState, newState); // must be last, since it can lead to deletion of the DebugSession if (newSessionState != oldSessionState) { setSessionState(newSessionState); } } void MIDebugSession::restartDebugger() { // We implement restart as kill + slotRun, as opposed as plain "run" // command because kill + slotRun allows any special logic in slotRun // to apply for restart. // // That includes: // - checking for out-of-date project // - special setup for remote debugging. // // Had we used plain 'run' command, restart for remote debugging simply // would not work. if (!debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) { // FIXME: s_dbgBusy or m_debugger->isReady()? if (debuggerStateIsOn(s_dbgBusy)) { interruptDebugger(); } // The -exec-abort is not implemented in gdb // addCommand(ExecAbort); addCommand(NonMI, QStringLiteral("kill")); } run(); } void MIDebugSession::stopDebugger() { if (debuggerStateIsOn(s_dbgNotStarted)) { // we are force to stop even before debugger started, just reset qCDebug(DEBUGGERCOMMON) << "Stopping debugger when it's not started"; return; } m_commandQueue->clear(); qCDebug(DEBUGGERCOMMON) << "try stopping debugger"; if (debuggerStateIsOn(s_shuttingDown) || !m_debugger) return; setDebuggerStateOn(s_shuttingDown); qCDebug(DEBUGGERCOMMON) << "stopping debugger"; // Get debugger's attention if it's busy. We need debugger to be at the // command line so we can stop it. if (!m_debugger->isReady()) { qCDebug(DEBUGGERCOMMON) << "debugger busy on shutdown - interruping"; interruptDebugger(); } // If the app is attached then we release it here. This doesn't stop // the app running. if (debuggerStateIsOn(s_attached)) { addCommand(TargetDetach); emit debuggerUserCommandOutput(QStringLiteral("(gdb) detach\n")); } // Now try to stop debugger running. addCommand(GdbExit); emit debuggerUserCommandOutput(QStringLiteral("(gdb) quit")); // We cannot wait forever, kill gdb after 5 seconds if it's not yet quit QPointer guarded_this(this); QTimer::singleShot(5000, [guarded_this](){ if (guarded_this) { if (!guarded_this->debuggerStateIsOn(s_programExited) && guarded_this->debuggerStateIsOn(s_shuttingDown)) { qCDebug(DEBUGGERCOMMON) << "debugger not shutdown - killing"; guarded_this->m_debugger->kill(); guarded_this->setDebuggerState(s_dbgNotStarted | s_appNotStarted); guarded_this->raiseEvent(debugger_exited); } } }); emit reset(); } void MIDebugSession::interruptDebugger() { Q_ASSERT(m_debugger); // Explicitly send the interrupt in case something went wrong with the usual // ensureGdbListening logic. m_debugger->interrupt(); addCommand(ExecInterrupt, QString(), CmdInterrupt); } void MIDebugSession::run() { if (debuggerStateIsOn(s_appNotStarted|s_dbgNotStarted|s_shuttingDown)) return; addCommand(MI::ExecContinue, QString(), CmdMaybeStartsRunning); } void MIDebugSession::runToCursor() { if (IDocument* doc = ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = doc->cursorPosition(); if (cursor.isValid()) runUntil(doc->url(), cursor.line() + 1); } } void MIDebugSession::jumpToCursor() { if (IDocument* doc = ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = doc->cursorPosition(); if (cursor.isValid()) jumpTo(doc->url(), cursor.line() + 1); } } void MIDebugSession::stepOver() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecNext, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepIntoInstruction() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecStepInstruction, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepInto() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecStep, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepOverInstruction() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecNextInstruction, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::stepOut() { if (debuggerStateIsOn(s_appNotStarted|s_shuttingDown)) return; addCommand(ExecFinish, QString(), CmdMaybeStartsRunning | CmdTemporaryRun); } void MIDebugSession::runUntil(const QUrl& url, int line) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!url.isValid()) { addCommand(ExecUntil, QString::number(line), CmdMaybeStartsRunning | CmdTemporaryRun); } else { addCommand(ExecUntil, QStringLiteral("%1:%2").arg(url.toLocalFile()).arg(line), CmdMaybeStartsRunning | CmdTemporaryRun); } } void MIDebugSession::runUntil(const QString& address) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!address.isEmpty()) { addCommand(ExecUntil, QStringLiteral("*%1").arg(address), CmdMaybeStartsRunning | CmdTemporaryRun); } } void MIDebugSession::jumpTo(const QUrl& url, int line) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (url.isValid()) { addCommand(NonMI, QStringLiteral("tbreak %1:%2").arg(url.toLocalFile()).arg(line)); addCommand(NonMI, QStringLiteral("jump %1:%2").arg(url.toLocalFile()).arg(line)); } } void MIDebugSession::jumpToMemoryAddress(const QString& address) { if (debuggerStateIsOn(s_dbgNotStarted|s_shuttingDown)) return; if (!address.isEmpty()) { addCommand(NonMI, QStringLiteral("tbreak *%1").arg(address)); addCommand(NonMI, QStringLiteral("jump *%1").arg(address)); } } void MIDebugSession::addUserCommand(const QString& cmd) { auto usercmd = createUserCommand(cmd); if (!usercmd) return; queueCmd(usercmd); // User command can theoreticall modify absolutely everything, // so need to force a reload. // We can do it right now, and don't wait for user command to finish // since commands used to reload all view will be executed after // user command anyway. if (!debuggerStateIsOn(s_appNotStarted) && !debuggerStateIsOn(s_programExited)) raiseEvent(program_state_changed); } MICommand *MIDebugSession::createUserCommand(const QString &cmd) const { MICommand *res = nullptr; if (!cmd.isEmpty() && cmd[0].isDigit()) { // Add a space to the beginning, so debugger won't get confused if the // command starts with a number (won't mix it up with command token added) res = new UserCommand(MI::NonMI, " " + cmd); } else { res = new UserCommand(MI::NonMI, cmd); } return res; } MICommand *MIDebugSession::createCommand(CommandType type, const QString& arguments, CommandFlags flags) const { return new MICommand(type, arguments, flags); } void MIDebugSession::addCommand(MICommand* cmd) { queueCmd(cmd); } void MIDebugSession::addCommand(MI::CommandType type, const QString& arguments, MI::CommandFlags flags) { queueCmd(createCommand(type, arguments, flags)); } void MIDebugSession::addCommand(MI::CommandType type, const QString& arguments, MI::MICommandHandler *handler, MI::CommandFlags flags) { auto cmd = createCommand(type, arguments, flags); cmd->setHandler(handler); queueCmd(cmd); } void MIDebugSession::addCommand(MI::CommandType type, const QString& arguments, const MI::FunctionCommandHandler::Function& callback, MI::CommandFlags flags) { auto cmd = createCommand(type, arguments, flags); cmd->setHandler(callback); queueCmd(cmd); } // Fairly obvious that we'll add whatever command you give me to a queue // Not quite so obvious though is that if we are going to run again. then any // information requests become redundent and must be removed. // We also try and run whatever command happens to be at the head of // the queue. void MIDebugSession::queueCmd(MICommand *cmd) { if (debuggerStateIsOn(s_dbgNotStarted)) { KMessageBox::information( qApp->activeWindow(), i18n("Gdb command sent when debugger is not running
" "The command was:
%1", cmd->initialString()), i18n("Internal error")); return; } if (m_stateReloadInProgress) cmd->setStateReloading(true); m_commandQueue->enqueue(cmd); qCDebug(DEBUGGERCOMMON) << "QUEUE: " << cmd->initialString() << (m_stateReloadInProgress ? "(state reloading)" : "") << m_commandQueue->count() << "pending"; bool varCommandWithContext= (cmd->type() >= MI::VarAssign && cmd->type() <= MI::VarUpdate && cmd->type() != MI::VarDelete); bool stackCommandWithContext = (cmd->type() >= MI::StackInfoDepth && cmd->type() <= MI::StackListLocals); if (varCommandWithContext || stackCommandWithContext) { if (cmd->thread() == -1) qCDebug(DEBUGGERCOMMON) << "\t--thread will be added on execution"; if (cmd->frame() == -1) qCDebug(DEBUGGERCOMMON) << "\t--frame will be added on execution"; } setDebuggerStateOn(s_dbgBusy); raiseEvent(debugger_busy); executeCmd(); } void MIDebugSession::executeCmd() { Q_ASSERT(m_debugger); if (debuggerStateIsOn(s_dbgNotListening) && m_commandQueue->haveImmediateCommand()) { // We may have to call this even while a command is currently executing, because // debugger can get into a state where a command such as ExecRun does not send a response // while the inferior is running. ensureDebuggerListening(); } if (!m_debugger->isReady()) return; MICommand* currentCmd = m_commandQueue->nextCommand(); if (!currentCmd) return; if (currentCmd->flags() & (CmdMaybeStartsRunning | CmdInterrupt)) { setDebuggerStateOff(s_automaticContinue); } if (currentCmd->flags() & CmdMaybeStartsRunning) { // GDB can be in a state where it is listening for commands while the program is running. // However, when we send a command such as ExecContinue in this state, GDB may return to // the non-listening state without acknowledging that the ExecContinue command has even // finished, let alone sending a new notification about the program's running state. // So let's be extra cautious about ensuring that we will wake GDB up again if required. setDebuggerStateOn(s_dbgNotListening); } bool varCommandWithContext= (currentCmd->type() >= MI::VarAssign && currentCmd->type() <= MI::VarUpdate && currentCmd->type() != MI::VarDelete); bool stackCommandWithContext = (currentCmd->type() >= MI::StackInfoDepth && currentCmd->type() <= MI::StackListLocals); if (varCommandWithContext || stackCommandWithContext) { // Most var commands should be executed in the context // of the selected thread and frame. if (currentCmd->thread() == -1) currentCmd->setThread(frameStackModel()->currentThread()); if (currentCmd->frame() == -1) currentCmd->setFrame(frameStackModel()->currentFrame()); } QString commandText = currentCmd->cmdToSend(); bool bad_command = false; QString message; int length = commandText.length(); // No i18n for message since it's mainly for debugging. if (length == 0) { // The command might decide it's no longer necessary to send // it. if (SentinelCommand* sc = dynamic_cast(currentCmd)) { qCDebug(DEBUGGERCOMMON) << "SEND: sentinel command, not sending"; sc->invokeHandler(); } else { qCDebug(DEBUGGERCOMMON) << "SEND: command " << currentCmd->initialString() << "changed its mind, not sending"; } delete currentCmd; executeCmd(); return; } else { if (commandText[length-1] != '\n') { bad_command = true; - message = QLatin1String("Debugger command does not end with newline"); + message = QStringLiteral("Debugger command does not end with newline"); } } if (bad_command) { KMessageBox::information(qApp->activeWindow(), i18n("Invalid debugger command
%1", message), i18n("Invalid debugger command")); executeCmd(); return; } m_debugger->execute(currentCmd); } void MIDebugSession::ensureDebuggerListening() { Q_ASSERT(m_debugger); // Note: we don't use interruptDebugger() here since // we don't want to queue more commands before queuing a command m_debugger->interrupt(); setDebuggerStateOn(s_interruptSent); if (debuggerStateIsOn(s_appRunning)) setDebuggerStateOn(s_automaticContinue); setDebuggerStateOff(s_dbgNotListening); } void MIDebugSession::destroyCmds() { m_commandQueue->clear(); } // FIXME: I don't fully remember what is the business with // m_stateReloadInProgress and whether we can lift it to the // generic level. void MIDebugSession::raiseEvent(event_t e) { if (e == program_exited || e == debugger_exited) { m_stateReloadInProgress = false; } if (e == program_state_changed) { m_stateReloadInProgress = true; qCDebug(DEBUGGERCOMMON) << "State reload in progress\n"; } IDebugSession::raiseEvent(e); if (e == program_state_changed) { m_stateReloadInProgress = false; } } bool KDevMI::MIDebugSession::hasCrashed() const { return m_hasCrashed; } void MIDebugSession::slotDebuggerReady() { Q_ASSERT(m_debugger); m_stateReloadInProgress = false; executeCmd(); if (m_debugger->isReady()) { /* There is nothing in the command queue and no command is currently executing. */ if (debuggerStateIsOn(s_automaticContinue)) { if (!debuggerStateIsOn(s_appRunning)) { qCDebug(DEBUGGERCOMMON) << "Posting automatic continue"; addCommand(ExecContinue, QString(), CmdMaybeStartsRunning); } setDebuggerStateOff(s_automaticContinue); return; } if (m_stateReloadNeeded && !debuggerStateIsOn(s_appRunning)) { qCDebug(DEBUGGERCOMMON) << "Finishing program stop"; // Set to false right now, so that if 'actOnProgramPauseMI_part2' // sends some commands, we won't call it again when handling replies // from that commands. m_stateReloadNeeded = false; reloadProgramState(); } qCDebug(DEBUGGERCOMMON) << "No more commands"; setDebuggerStateOff(s_dbgBusy); raiseEvent(debugger_ready); } } void MIDebugSession::slotDebuggerExited(bool abnormal, const QString &msg) { /* Technically speaking, GDB is likely not to kill the application, and we should have some backup mechanism to make sure the application is killed by KDevelop. But even if application stays around, we no longer can control it in any way, so mark it as exited. */ setDebuggerStateOn(s_appNotStarted); setDebuggerStateOn(s_dbgNotStarted); setDebuggerStateOn(s_programExited); setDebuggerStateOff(s_shuttingDown); if (!msg.isEmpty()) emit showMessage(msg, 3000); if (abnormal) { /* The error is reported to user in MIDebugger now. KMessageBox::information( KDevelop::ICore::self()->uiController()->activeMainWindow(), i18n("Debugger exited abnormally" "

This is likely a bug in GDB. " "Examine the gdb output window and then stop the debugger"), i18n("Debugger exited abnormally")); */ // FIXME: not sure if the following still applies. // Note: we don't stop the debugger here, becuse that will hide gdb // window and prevent the user from finding the exact reason of the // problem. } /* FIXME: raiseEvent is handled across multiple places where we explicitly * stop/kill the debugger, a better way is to let the debugger itself report * its exited event. */ // raiseEvent(debugger_exited); } void MIDebugSession::slotInferiorStopped(const MI::AsyncRecord& r) { /* By default, reload all state on program stop. */ m_stateReloadNeeded = true; setDebuggerStateOff(s_appRunning); setDebuggerStateOff(s_dbgNotListening); QString reason; if (r.hasField(QStringLiteral("reason"))) reason = r[QStringLiteral("reason")].literal(); if (reason == QLatin1String("exited-normally") || reason == QLatin1String("exited")) { if (r.hasField(QStringLiteral("exit-code"))) { programNoApp(i18n("Exited with return code: %1", r["exit-code"].literal())); } else { programNoApp(i18n("Exited normally")); } m_stateReloadNeeded = false; return; } if (reason == QLatin1String("exited-signalled")) { programNoApp(i18n("Exited on signal %1", r["signal-name"].literal())); m_stateReloadNeeded = false; return; } if (reason == QLatin1String("watchpoint-scope")) { QString number = r[QStringLiteral("wpnum")].literal(); // FIXME: shuld remove this watchpoint // But first, we should consider if removing all // watchpoinst on program exit is the right thing to // do. addCommand(ExecContinue, QString(), CmdMaybeStartsRunning); m_stateReloadNeeded = false; return; } bool wasInterrupt = false; if (reason == QLatin1String("signal-received")) { QString name = r[QStringLiteral("signal-name")].literal(); QString user_name = r[QStringLiteral("signal-meaning")].literal(); // SIGINT is a "break into running program". // We do this when the user set/mod/clears a breakpoint but the // application is running. // And the user does this to stop the program also. if (name == QLatin1String("SIGINT") && debuggerStateIsOn(s_interruptSent)) { wasInterrupt = true; } else { // Whenever we have a signal raised then tell the user, but don't // end the program as we want to allow the user to look at why the // program has a signal that's caused the prog to stop. // Continuing from SIG FPE/SEGV will cause a "Cannot ..." and // that'll end the program. programFinished(i18n("Program received signal %1 (%2)", name, user_name)); m_hasCrashed = true; } } if (!reason.contains(QLatin1String("exited"))) { // FIXME: we should immediately update the current thread and // frame in the framestackmodel, so that any user actions // are in that thread. However, the way current framestack model // is implemented, we can't change thread id until we refresh // the entire list of threads -- otherwise we might set a thread // id that is not already in the list, and it will be upset. //Indicates if program state should be reloaded immediately. bool updateState = false; if (r.hasField(QStringLiteral("frame"))) { const MI::Value& frame = r[QStringLiteral("frame")]; QString file, line, addr; if (frame.hasField(QStringLiteral("fullname"))) file = frame[QStringLiteral("fullname")].literal();; if (frame.hasField(QStringLiteral("line"))) line = frame[QStringLiteral("line")].literal(); if (frame.hasField(QStringLiteral("addr"))) addr = frame[QStringLiteral("addr")].literal(); // gdb counts lines from 1 and we don't setCurrentPosition(QUrl::fromLocalFile(file), line.toInt() - 1, addr); updateState = true; } if (updateState) { reloadProgramState(); } } setDebuggerStateOff(s_interruptSent); if (!wasInterrupt) setDebuggerStateOff(s_automaticContinue); } void MIDebugSession::slotInferiorRunning() { setDebuggerStateOn(s_appRunning); raiseEvent(program_running); if (m_commandQueue->haveImmediateCommand() || (m_debugger->currentCommand() && (m_debugger->currentCommand()->flags() & (CmdImmediately | CmdInterrupt)))) { ensureDebuggerListening(); } else { setDebuggerStateOn(s_dbgNotListening); } } void MIDebugSession::processNotification(const MI::AsyncRecord & async) { if (async.reason == QLatin1String("thread-group-started")) { setDebuggerStateOff(s_appNotStarted | s_programExited); } else if (async.reason == QLatin1String("thread-group-exited")) { setDebuggerStateOn(s_programExited); } else if (async.reason == QLatin1String("library-loaded")) { // do nothing } else if (async.reason == QLatin1String("breakpoint-created")) { breakpointController()->notifyBreakpointCreated(async); } else if (async.reason == QLatin1String("breakpoint-modified")) { breakpointController()->notifyBreakpointModified(async); } else if (async.reason == QLatin1String("breakpoint-deleted")) { breakpointController()->notifyBreakpointDeleted(async); } else { qCDebug(DEBUGGERCOMMON) << "Unhandled notification: " << async.reason; } } void MIDebugSession::reloadProgramState() { raiseEvent(program_state_changed); m_stateReloadNeeded = false; } // There is no app anymore. This can be caused by program exiting // an invalid program specified or ... // gdb is still running though, but only the run command (may) make sense // all other commands are disabled. void MIDebugSession::programNoApp(const QString& msg) { qCDebug(DEBUGGERCOMMON) << msg; setDebuggerState(s_appNotStarted | s_programExited | (m_debuggerState & s_shuttingDown)); destroyCmds(); // The application has existed, but it's possible that // some of application output is still in the pipe. We use // different pipes to communicate with gdb and to get application // output, so "exited" message from gdb might have arrived before // last application output. Get this last bit. // Note: this method can be called when we open an invalid // core file. In that case, tty_ won't be set. if (m_tty){ m_tty->readRemaining(); // Tty is no longer usable, delete it. Without this, QSocketNotifier // will continuously bomd STTY with signals, so we need to either disable // QSocketNotifier, or delete STTY. The latter is simpler, since we can't // reuse it for future debug sessions anyway. m_tty.reset(nullptr); } stopDebugger(); raiseEvent(program_exited); raiseEvent(debugger_exited); emit showMessage(msg, 0); programFinished(msg); } void MIDebugSession::programFinished(const QString& msg) { QString m = QStringLiteral("*** %0 ***").arg(msg.trimmed()); emit inferiorStderrLines(QStringList(m)); /* Also show message in gdb window, so that users who prefer to look at gdb window know what's up. */ emit debuggerUserCommandOutput(m); } void MIDebugSession::explainDebuggerStatus() { MICommand* currentCmd_ = m_debugger->currentCommand(); QString information = i18np("1 command in queue\n", "%1 commands in queue\n", m_commandQueue->count()) + i18ncp("Only the 0 and 1 cases need to be translated", "1 command being processed by gdb\n", "%1 commands being processed by gdb\n", (currentCmd_ ? 1 : 0)) + i18n("Debugger state: %1\n", m_debuggerState); if (currentCmd_) { QString extra = i18n("Current command class: '%1'\n" "Current command text: '%2'\n" "Current command original text: '%3'\n", typeid(*currentCmd_).name(), currentCmd_->cmdToSend(), currentCmd_->initialString()); information += extra; } KMessageBox::information(qApp->activeWindow(), information, i18n("Debugger status")); } // There is no app anymore. This can be caused by program exiting // an invalid program specified or ... // gdb is still running though, but only the run command (may) make sense // all other commands are disabled. void MIDebugSession::handleNoInferior(const QString& msg) { qCDebug(DEBUGGERCOMMON) << msg; setDebuggerState(s_appNotStarted | s_programExited | (debuggerState() & s_shuttingDown)); destroyCmds(); // The application has existed, but it's possible that // some of application output is still in the pipe. We use // different pipes to communicate with gdb and to get application // output, so "exited" message from gdb might have arrived before // last application output. Get this last bit. // Note: this method can be called when we open an invalid // core file. In that case, tty_ won't be set. if (m_tty){ m_tty->readRemaining(); // Tty is no longer usable, delete it. Without this, QSocketNotifier // will continuously bomd STTY with signals, so we need to either disable // QSocketNotifier, or delete STTY. The latter is simpler, since we can't // reuse it for future debug sessions anyway. m_tty.reset(nullptr); } stopDebugger(); raiseEvent(program_exited); raiseEvent(debugger_exited); emit showMessage(msg, 0); handleInferiorFinished(msg); } void MIDebugSession::handleInferiorFinished(const QString& msg) { QString m = QStringLiteral("*** %0 ***").arg(msg.trimmed()); emit inferiorStderrLines(QStringList(m)); /* Also show message in gdb window, so that users who prefer to look at gdb window know what's up. */ emit debuggerUserCommandOutput(m); } // FIXME: connect to debugger's slot. void MIDebugSession::defaultErrorHandler(const MI::ResultRecord& result) { QString msg = result[QStringLiteral("msg")].literal(); if (msg.contains(QLatin1String("No such process"))) { setDebuggerState(s_appNotStarted|s_programExited); raiseEvent(program_exited); return; } KMessageBox::information( qApp->activeWindow(), i18n("Debugger error" "

Debugger reported the following error:" "

%1", result["msg"].literal()), i18n("Debugger error")); // Error most likely means that some change made in GUI // was not communicated to the gdb, so GUI is now not // in sync with gdb. Resync it. // // Another approach is to make each widget reload it content // on errors from commands that it sent, but that's too complex. // Errors are supposed to happen rarely, so full reload on error // is not a big deal. Well, maybe except for memory view, but // it's no auto-reloaded anyway. // // Also, don't reload state on errors appeared during state // reloading! if (!m_debugger->currentCommand()->stateReloading()) raiseEvent(program_state_changed); } void MIDebugSession::setSourceInitFile(bool enable) { m_sourceInitFile = enable; } diff --git a/debuggers/common/registers/modelsmanager.cpp b/debuggers/common/registers/modelsmanager.cpp index 9ac97608aa..2a04ff4c25 100644 --- a/debuggers/common/registers/modelsmanager.cpp +++ b/debuggers/common/registers/modelsmanager.cpp @@ -1,355 +1,355 @@ /* * Class to manage register models. * Copyright (C) 2013 Vlas Puhov * * 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 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 "modelsmanager.h" #include #include #include #include namespace KDevMI { struct Model { Model(); Model(const QString& name, QSharedPointer model, QAbstractItemView* view); bool operator==(const Model& m) const; QString name; QSharedPointer model; QAbstractItemView* view = nullptr; }; class Models { public: QStandardItemModel* addModel(const Model& m); void clear(); bool contains(const QString& name) const; bool contains(QAbstractItemView* view) const; bool contains(QStandardItemModel* model) const; QString nameForView(QAbstractItemView* view) const; ///Returns registered model for @p name, 0 if not registered. QStandardItemModel* modelForName(const QString& name) const; ///Returns registered model for @p view, 0 if not registered. QStandardItemModel* modelForView(QAbstractItemView* view) const; private: ///All models QVector m_models; }; } // end of namespace KDevMI using namespace KDevMI; ModelsManager::ModelsManager(QObject* parent) : QObject(parent), m_models(new Models), m_controller(nullptr), m_config(KSharedConfig::openConfig()->group("Register models")) {} ModelsManager::~ModelsManager() {} QString ModelsManager::addView(QAbstractItemView* view) { if (m_models->contains(view)) { return m_models->nameForView(view); } Q_ASSERT(m_controller); QString name; foreach (const GroupsName & group, m_controller->namesOfRegisterGroups()) { if (!m_models->contains(group.name())) { QStandardItemModel* m = m_models->addModel(Model(group.name(), QSharedPointer(new QStandardItemModel()), view)); view->setModel(m); if (group.type() == flag) { connect(view, &QAbstractItemView::doubleClicked, this, &ModelsManager::flagChanged, Qt::UniqueConnection); } name = group.name(); load(group); break; } } return name; } void ModelsManager::updateModelForGroup(const RegistersGroup& group) { QStandardItemModel* model = m_models->modelForName(group.groupName.name()); if (!model) { return; } disconnect(model, &QStandardItemModel::itemChanged, this, &ModelsManager::itemChanged); model->setRowCount(group.registers.count()); model->setColumnCount(group.registers.first().value.split(' ').size() + 1); //set names and values separately as names don't change so often. if (!model->item(0, 0)) { for (int row = 0; row < group.registers.count(); row++) { const Register& r = group.registers[row]; QStandardItem* n = new QStandardItem(r.name); n->setFlags(Qt::ItemIsEnabled); model->setItem(row, 0, n); } } for (int row = 0; row < group.registers.count(); row++) { const Register& r = group.registers[row]; const QStringList& values = r.value.split(' '); //binary format workaround. Format currentFormat = formats(group.groupName.name()).first(); Mode currentMode = modes(group.groupName.name()).first(); QString prefix; if (currentFormat == Binary && ((currentMode < v4_float || currentMode > v2_double) && (currentMode < f32 || currentMode > f64) && group.groupName.type() != floatPoint)) { - prefix = QLatin1String("0b"); + prefix = QStringLiteral("0b"); } for (int column = 0; column < values.count(); column ++) { QStandardItem* v = new QStandardItem(prefix + values[column]); if (group.groupName.type() == flag) { v->setFlags(Qt::ItemIsEnabled); } model->setItem(row, column + 1, v); } } connect(model, &QStandardItemModel::itemChanged, this, &ModelsManager::itemChanged); } void ModelsManager::flagChanged(const QModelIndex& idx) { QAbstractItemView* view = static_cast(sender()); int row = idx.row(); QStandardItemModel* model = m_models->modelForView(view); QStandardItem* item = model->item(row, 0); Register r; r.name = item->text(); r.value = model->data(idx).toString(); emit registerChanged(r); } QStandardItemModel* Models::addModel(const Model& m) { if (!contains(m.name) && !contains(m.view) && !contains(m.model.data())) { m_models.append(m); return m.model.data(); } return nullptr; } bool Models::contains(const QString& name) const { foreach (const Model & m, m_models) { if (m.name == name) { return true; } } return false; } bool Models::contains(QAbstractItemView* view) const { foreach (const Model & m, m_models) { if (m.view == view) { return true; } } return false; } bool Models::contains(QStandardItemModel* model) const { foreach (const Model & m, m_models) { if (m.model.data() == model) { return true; } } return false; } QStandardItemModel* Models::modelForName(const QString& name) const { foreach (const Model & m, m_models) { if (m.name == name) { return m.model.data(); } } return nullptr; } QStandardItemModel* Models::modelForView(QAbstractItemView* view) const { foreach (const Model & m, m_models) { if (m.view == view) { return m.model.data(); } } return nullptr; } void ModelsManager::itemChanged(QStandardItem* i) { QStandardItemModel* model = static_cast(sender()); int row = i->row(); Register r; r.name = model->item(row, 0)->text(); for (int i = 1; i < model->columnCount(); i++) { r.value += model->item(row, i)->text() + ' '; } r.value = r.value.trimmed(); emit registerChanged(r); } QString Models::nameForView(QAbstractItemView* view) const { foreach (const Model & m, m_models) { if (m.view == view) { return m.name; } } return QString(); } void ModelsManager::setController(IRegisterController* rc) { m_controller = rc; if (!m_controller) { m_models->clear(); } else { connect(this, &ModelsManager::registerChanged, m_controller, &IRegisterController::setRegisterValue); connect(m_controller, &IRegisterController::registersChanged, this, &ModelsManager::updateModelForGroup); } } Model::Model() {} Model::Model(const QString& name, QSharedPointer model, QAbstractItemView* view) : name(name), model(model), view(view) {} bool Model::operator==(const Model& m) const { return m.model == model && m.view == view && m.name == name; } void ModelsManager::updateRegisters(const QString& group) { Q_ASSERT(m_controller); if (group.isEmpty()) { m_controller->updateRegisters(GroupsName()); } else { foreach (const GroupsName & g, m_controller->namesOfRegisterGroups()) { if (g.name() == group) { m_controller->updateRegisters(g); break; } } } } void Models::clear() { m_models.clear(); } void ModelsManager::setFormat(const QString& group, Format format) { foreach (const GroupsName & g, m_controller->namesOfRegisterGroups()) { if (g.name() == group) { m_controller->setFormat(format, g); save(g); break; } } } QVector ModelsManager::formats(const QString& group) const { QVector formats; formats << Raw; foreach (const GroupsName & g, m_controller->namesOfRegisterGroups()) { if (g.name() == group) { formats = m_controller->formats(g); break; } } return formats; } void ModelsManager::save(const GroupsName& g) { KConfigGroup group = m_config.group(g.name()); group.writeEntry("format", static_cast(m_controller->formats(g).first())); group.writeEntry("mode", static_cast(m_controller->modes(g).first())); } void ModelsManager::load(const GroupsName& g) { KConfigGroup group = m_config.group(g.name()); Format format = static_cast(group.readEntry("format", static_cast(m_controller->formats(g).first()))); setFormat(g.name(), format); Mode mode = static_cast(group.readEntry("mode", static_cast(m_controller->modes(g).first()))); setMode(g.name(), mode); } QVector< Mode > ModelsManager::modes(const QString& group) const { QVector modes; foreach (const GroupsName & g, m_controller->namesOfRegisterGroups()) { if (g.name() == group) { modes = m_controller->modes(g); break; } } return modes; } void ModelsManager::setMode(const QString& group, Mode mode) { foreach (const GroupsName & g, m_controller->namesOfRegisterGroups()) { if (g.name() == group) { m_controller->setMode(mode, g); save(g); break; } } } diff --git a/debuggers/common/registers/registercontroller.cpp b/debuggers/common/registers/registercontroller.cpp index 513b544f18..34a1c11a5d 100644 --- a/debuggers/common/registers/registercontroller.cpp +++ b/debuggers/common/registers/registercontroller.cpp @@ -1,406 +1,406 @@ /* * Class to fetch/change/send registers to the debugger. * Copyright 2013 Vlas Puhov * * 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 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 "registercontroller.h" #include "converters.h" #include "debuglog.h" #include "midebugsession.h" #include "mi/mi.h" #include "mi/micommand.h" #include #include using namespace KDevMI::MI; using namespace KDevMI; void IRegisterController::setSession(MIDebugSession* debugSession) { m_debugSession = debugSession; } void IRegisterController::updateRegisters(const GroupsName& group) { if (!m_debugSession || m_debugSession->debuggerStateIsOn(s_dbgNotStarted | s_shuttingDown)) { return; } if (m_pendingGroups.contains(group)) { qCDebug(DEBUGGERCOMMON) << "Already updating " << group.name(); return; } if (group.name().isEmpty()) { foreach (const GroupsName & g, namesOfRegisterGroups()) { IRegisterController::updateRegisters(g); } return; } else { qCDebug(DEBUGGERCOMMON) << "Updating: " << group.name(); m_pendingGroups << group; } QString registers; Format currentFormat = formats(group).first(); switch (currentFormat) { case Binary: - registers = QLatin1String("t "); + registers = QStringLiteral("t "); break; case Octal: - registers = QLatin1String("o "); + registers = QStringLiteral("o "); break; case Decimal : - registers = QLatin1String("d "); + registers = QStringLiteral("d "); break; case Hexadecimal: - registers = QLatin1String("x "); + registers = QStringLiteral("x "); break; case Raw: - registers = QLatin1String("r "); + registers = QStringLiteral("r "); break; case Unsigned: - registers = QLatin1String("u "); + registers = QStringLiteral("u "); break; default: break; } //float point registers have only two reasonable format. Mode currentMode = modes(group).first(); if (((currentMode >= v4_float && currentMode <= v2_double) || (currentMode >= f32 && currentMode <= f64) || group.type() == floatPoint) && currentFormat != Raw) { - registers = QLatin1String("N "); + registers = QStringLiteral("N "); } if (group.type() == flag) { registers += numberForName(group.flagName()); } else { foreach (const QString & name, registerNamesForGroup(group)) { registers += numberForName(name) + ' '; } } //Not initialized yet. They'll be updated afterwards. if (registers.contains(QLatin1String("-1"))) { qCDebug(DEBUGGERCOMMON) << "Will update later"; m_pendingGroups.clear(); return; } void (IRegisterController::* handler)(const ResultRecord&); if (group.type() == structured && currentFormat != Raw) { handler = &IRegisterController::structuredRegistersHandler; } else { handler = &IRegisterController::generalRegistersHandler; } m_debugSession->addCommand(DataListRegisterValues, registers, this, handler); } void IRegisterController::registerNamesHandler(const ResultRecord& r) { const Value& names = r[QStringLiteral("register-names")]; m_rawRegisterNames.clear(); for (int i = 0; i < names.size(); ++i) { const Value& entry = names[i]; m_rawRegisterNames.push_back(entry.literal()); } //When here probably request for updating registers was sent, but m_rawRegisterNames were not initialized yet, so it wasn't successful. Update everything once again. updateRegisters(); } void IRegisterController::generalRegistersHandler(const ResultRecord& r) { Q_ASSERT(!m_rawRegisterNames.isEmpty()); QString registerName; const Value& values = r[QStringLiteral("register-values")]; for (int i = 0; i < values.size(); ++i) { const Value& entry = values[i]; int number = entry[QStringLiteral("number")].literal().toInt(); Q_ASSERT(m_rawRegisterNames.size() > number); if (!m_rawRegisterNames[number].isEmpty()) { if (registerName.isEmpty()) { registerName = m_rawRegisterNames[number]; } const QString value = entry[QStringLiteral("value")].literal(); m_registers.insert(m_rawRegisterNames[number], value); } } GroupsName group = groupForRegisterName(registerName); if (m_pendingGroups.contains(group)) { emit registersChanged(registersFromGroup(group)); m_pendingGroups.remove(m_pendingGroups.indexOf(group)); } } void IRegisterController::setRegisterValue(const Register& reg) { Q_ASSERT(!m_registers.isEmpty()); const GroupsName group = groupForRegisterName(reg.name); if (!group.name().isEmpty()) { setRegisterValueForGroup(group, reg); } } QString IRegisterController::registerValue(const QString& name) const { QString value; if (!name.isEmpty()) { if (m_registers.contains(name)) { value = m_registers.value(name); } } return value; } bool IRegisterController::initializeRegisters() { if (!m_debugSession || m_debugSession->debuggerStateIsOn(s_dbgNotStarted | s_shuttingDown)) { return false; } m_debugSession->addCommand(DataListRegisterNames, QLatin1String(""), this, &IRegisterController::registerNamesHandler); return true; } GroupsName IRegisterController::groupForRegisterName(const QString& name) const { foreach (const GroupsName & group, namesOfRegisterGroups()) { const QStringList registersInGroup = registerNamesForGroup(group); if (group.flagName() == name) { return group; } foreach (const QString & n, registersInGroup) { if (n == name) { return group; } } } return GroupsName(); } void IRegisterController::updateValuesForRegisters(RegistersGroup* registers) const { Q_ASSERT(!m_registers.isEmpty()); for (int i = 0; i < registers->registers.size(); i++) { if (m_registers.contains(registers->registers[i].name)) { registers->registers[i].value = m_registers.value(registers->registers[i].name); } } } void IRegisterController::setFlagRegister(const Register& reg, const FlagRegister& flag) { quint32 flagsValue = registerValue(flag.registerName).toUInt(nullptr, 16); const int idx = flag.flags.indexOf(reg.name); if (idx != -1) { flagsValue ^= static_cast(qPow(2, flag.bits[idx].toUInt())); setGeneralRegister(Register(flag.registerName, QStringLiteral("0x%1").arg(flagsValue, 0, 16)), flag.groupName); } else { updateRegisters(flag.groupName); qCDebug(DEBUGGERCOMMON) << reg.name << ' ' << reg.value << "is incorrect flag name/value"; } } void IRegisterController::setGeneralRegister(const Register& reg, const GroupsName& group) { if (!m_debugSession || m_debugSession->debuggerStateIsOn(s_dbgNotStarted | s_shuttingDown)) { return; } const QString command = QStringLiteral("set var $%1=%2").arg(reg.name).arg(reg.value); qCDebug(DEBUGGERCOMMON) << "Setting register: " << command; m_debugSession->addCommand(NonMI, command); updateRegisters(group); } IRegisterController::IRegisterController(MIDebugSession* debugSession, QObject* parent) : QObject(parent), m_debugSession(debugSession) {} IRegisterController::~IRegisterController() {} void IRegisterController::updateFlagValues(RegistersGroup* flagsGroup, const FlagRegister& flagRegister) const { const quint32 flagsValue = registerValue(flagRegister.registerName).toUInt(nullptr, 16); for (int idx = 0; idx < flagRegister.flags.count(); idx++) { flagsGroup->registers[idx].value = ((flagsValue >> flagRegister.bits[idx].toInt()) & 1) ? "1" : "0"; } } QVector IRegisterController::formats(const GroupsName& group) { int idx = -1; foreach (const GroupsName & g, namesOfRegisterGroups()) { if (g == group) { idx = g.index(); } } Q_ASSERT(idx != -1); return m_formatsModes[idx].formats; } GroupsName IRegisterController::createGroupName(const QString& name, int idx, RegisterType t, const QString& flag) const { return GroupsName(name, idx, t, flag); } void IRegisterController::setFormat(Format f, const GroupsName& group) { foreach (const GroupsName & g, namesOfRegisterGroups()) { if (g == group) { int i = m_formatsModes[g.index()].formats.indexOf(f); if (i != -1) { m_formatsModes[g.index()].formats.remove(i); m_formatsModes[g.index()].formats.prepend(f); } } } } QString IRegisterController::numberForName(const QString& name) const { //Requests for number come in order(if the previous was, let's say 10, then most likely the next one will be 11) static int previousNumber = -1; if (m_rawRegisterNames.isEmpty()) { previousNumber = -1; return QString::number(previousNumber); } if (previousNumber != -1 && m_rawRegisterNames.size() > ++previousNumber) { if (m_rawRegisterNames[previousNumber] == name) { return QString::number(previousNumber); } } for (int number = 0; number < m_rawRegisterNames.size(); number++) { if (name == m_rawRegisterNames[number]) { previousNumber = number; return QString::number(number); } } previousNumber = -1; return QString::number(previousNumber); } void IRegisterController::setStructuredRegister(const Register& reg, const GroupsName& group) { Register r = reg; r.value = r.value.trimmed(); r.value.replace(' ', ','); if (r.value.contains(',')) { r.value.append('}'); r.value.prepend('{'); } r.name += '.' + Converters::modeToString(m_formatsModes[group.index()].modes.first()); setGeneralRegister(r, group); } void IRegisterController::structuredRegistersHandler(const ResultRecord& r) { //Parsing records in format like: //{u8 = {0, 0, 128, 146, 0, 48, 197, 65}, u16 = {0, 37504, 12288, 16837}, u32 = {2457862144, 1103441920}, u64 = 4739246961893310464, f32 = {-8.07793567e-28, 24.6484375}, f64 = 710934821} //{u8 = {0 }, u16 = {0, 0, 0, 0, 0, 0, 0, 0}, u32 = {0, 0, 0, 0}, u64 = {0, 0}, f32 = {0, 0, 0, 0}, f64 = {0, 0}} QRegExp rx("^\\s*=\\s*\\{(.*)\\}"); rx.setMinimal(true); QString registerName; Mode currentMode = LAST_MODE; GroupsName group; const Value& values = r[QStringLiteral("register-values")]; Q_ASSERT(!m_rawRegisterNames.isEmpty()); for (int i = 0; i < values.size(); ++i) { const Value& entry = values[i]; int number = entry[QStringLiteral("number")].literal().toInt(); registerName = m_rawRegisterNames[number]; if (currentMode == LAST_MODE) { group = groupForRegisterName(registerName); currentMode = modes(group).first(); } QString record = entry[QStringLiteral("value")].literal(); int start = record.indexOf(Converters::modeToString(currentMode)); Q_ASSERT(start != -1); start += Converters::modeToString(currentMode).size(); QString value = record.right(record.size() - start); int idx = rx.indexIn(value); value = rx.cap(1); if (idx == -1) { //if here then value without braces: u64 = 4739246961893310464, f32 = {-8.07793567e-28, 24.6484375}, f64 = 710934821} QRegExp rx2("=\\s+(.*)(\\}|,)"); rx2.setMinimal(true); rx2.indexIn(record, start); value = rx2.cap(1); } value = value.trimmed().remove(','); m_registers.insert(registerName, value); } if (m_pendingGroups.contains(group)) { emit registersChanged(registersFromGroup(group)); m_pendingGroups.remove(m_pendingGroups.indexOf(group)); } } QVector< Mode > IRegisterController::modes(const GroupsName& group) { int idx = -1; foreach (const GroupsName & g, namesOfRegisterGroups()) { if (g == group) { idx = g.index(); } } Q_ASSERT(idx != -1); return m_formatsModes[idx].modes; } void IRegisterController::setMode(Mode m, const GroupsName& group) { foreach (const GroupsName & g, namesOfRegisterGroups()) { if (g == group) { int i = m_formatsModes[g.index()].modes.indexOf(m); if (i != -1) { m_formatsModes[g.index()].modes.remove(i); m_formatsModes[g.index()].modes.prepend(m); } } } } diff --git a/debuggers/common/registers/registercontroller_arm.cpp b/debuggers/common/registers/registercontroller_arm.cpp index 9fecf1134e..dcb048a47f 100644 --- a/debuggers/common/registers/registercontroller_arm.cpp +++ b/debuggers/common/registers/registercontroller_arm.cpp @@ -1,185 +1,185 @@ /* * Class to fetch/change/send registers to the debugger for arm architecture. * Copyright 2013 Vlas Puhov * * 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 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 "registercontroller_arm.h" #include "debuglog.h" #include using namespace KDevMI; QVector RegisterController_Arm::m_registerNames; FlagRegister RegisterController_Arm::m_cpsr; void RegisterController_Arm::updateValuesForRegisters(RegistersGroup* registers) const { qCDebug(DEBUGGERCOMMON) << "Updating values for registers: " << registers->groupName.name(); if (registers->groupName == enumToGroupName(Flags)) { updateFlagValues(registers, m_cpsr); } else { IRegisterController::updateValuesForRegisters(registers); } } RegistersGroup RegisterController_Arm::registersFromGroup(const GroupsName& group) const { RegistersGroup registers; registers.groupName = group; registers.format = m_formatsModes[group.index()].formats.first(); foreach (const QString & name, registerNamesForGroup(group)) { registers.registers.append(Register(name, QString())); } updateValuesForRegisters(®isters); return registers; } QVector RegisterController_Arm::namesOfRegisterGroups() const { static const QVector registerGroups = QVector() << enumToGroupName(General) << enumToGroupName(Flags) << enumToGroupName(VFP_single) << enumToGroupName(VFP_double) << enumToGroupName(VFP_quad); return registerGroups; } void RegisterController_Arm::setRegisterValueForGroup(const GroupsName& group, const Register& reg) { if (group == enumToGroupName(General)) { setGeneralRegister(reg, group); } else if (group == enumToGroupName(Flags)) { setFlagRegister(reg, m_cpsr); } else if (group == enumToGroupName(VFP_single)) { setVFPS_Register(reg); } else if (group == enumToGroupName(VFP_double)) { setVFPD_Register(reg); } else if (group == enumToGroupName(VFP_quad)) { setVFPQ_Register(reg); } } void RegisterController_Arm::setVFPS_Register(const Register& reg) { setGeneralRegister(reg, enumToGroupName(VFP_single)); } void RegisterController_Arm::setVFPD_Register(const Register& reg) { setStructuredRegister(reg, enumToGroupName(VFP_double)); } void RegisterController_Arm::setVFPQ_Register(const Register& reg) { setStructuredRegister(reg, enumToGroupName(VFP_quad)); } void RegisterController_Arm::updateRegisters(const GroupsName& group) { if (!m_registerNamesInitialized) { if (initializeRegisters()) { m_registerNamesInitialized = true; } } IRegisterController::updateRegisters(group); } GroupsName RegisterController_Arm::enumToGroupName(ArmRegisterGroups group) const { static const GroupsName groups[LAST_REGISTER] = { createGroupName(i18n("General"), General) , createGroupName(i18n("Flags"), Flags, flag, m_cpsr.registerName), createGroupName(i18n("VFP single-word"), VFP_single, floatPoint), createGroupName(i18n("VFP double-word"), VFP_double, structured), createGroupName(i18n("VFP quad-word"), VFP_quad, structured)}; return groups[group]; } RegisterController_Arm::RegisterController_Arm(MIDebugSession* debugSession, QObject* parent) : IRegisterController(debugSession, parent), m_registerNamesInitialized(false) { if (m_registerNames.isEmpty()) { for (int i = 0; i < static_cast(LAST_REGISTER); i++) { m_registerNames.append(QStringList()); } initRegisterNames(); } int n = 0; while (n++ < namesOfRegisterGroups().size()) { m_formatsModes.append(FormatsModes()); } m_formatsModes[VFP_double].formats.append(Binary); m_formatsModes[VFP_double].formats.append(Decimal); m_formatsModes[VFP_double].formats.append(Hexadecimal); m_formatsModes[VFP_double].formats.append(Octal); m_formatsModes[VFP_double].formats.append(Unsigned); m_formatsModes[VFP_double].modes.append(u32); m_formatsModes[VFP_double].modes.append(u64); m_formatsModes[VFP_double].modes.append(f32); m_formatsModes[VFP_double].modes.append(f64); m_formatsModes[Flags].formats.append(Raw); m_formatsModes[Flags].modes.append(natural); m_formatsModes[VFP_single].formats.append(Decimal); m_formatsModes[VFP_single].modes.append(natural); m_formatsModes[VFP_quad] = m_formatsModes[VFP_double]; m_formatsModes[General].formats.append(Raw); m_formatsModes[General].formats << m_formatsModes[VFP_double].formats; m_formatsModes[General].modes.append(natural); } void RegisterController_Arm::initRegisterNames() { for (int i = 0; i < 32; i++) { m_registerNames[VFP_single] << ("s" + QString::number(i)); } - m_cpsr.registerName = QLatin1String("cpsr"); + m_cpsr.registerName = QStringLiteral("cpsr"); m_cpsr.flags << QStringLiteral("Q") << QStringLiteral("V") << QStringLiteral("C") << QStringLiteral("Z") << QStringLiteral("N"); m_cpsr.bits << QStringLiteral("27") << QStringLiteral("28") << QStringLiteral("29") << QStringLiteral("30") << QStringLiteral("31"); m_cpsr.groupName = enumToGroupName(Flags); m_registerNames[Flags] = m_cpsr.flags; for (int i = 0; i < 13; i++) { m_registerNames[General] << ("r" + QString::number(i)); } m_registerNames[General] << QStringLiteral("sp") << QStringLiteral("lr") << QStringLiteral("pc"); for (int i = 0; i < 32; i++) { m_registerNames[VFP_double] << ("d" + QString::number(i)); } for (int i = 0; i < 16; i++) { m_registerNames[VFP_quad] << ("q" + QString::number(i)); } } QStringList RegisterController_Arm::registerNamesForGroup(const GroupsName& group) const { for (int i = 0; i < static_cast(LAST_REGISTER); i++) { if (group == enumToGroupName(static_cast(i))) { return m_registerNames[i]; } } return QStringList(); } diff --git a/debuggers/common/registers/registercontroller_x86.cpp b/debuggers/common/registers/registercontroller_x86.cpp index 779ba06bab..ccf8727d90 100644 --- a/debuggers/common/registers/registercontroller_x86.cpp +++ b/debuggers/common/registers/registercontroller_x86.cpp @@ -1,243 +1,243 @@ /* * Class to fetch/change/send registers to the debugger for x86, x86_64 architectures. * Copyright 2013 Vlas Puhov * * 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 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 "registercontroller_x86.h" #include "debuglog.h" #include using namespace KDevMI; QVector RegisterControllerGeneral_x86::m_registerNames; FlagRegister RegisterControllerGeneral_x86::m_eflags; void RegisterControllerGeneral_x86::updateValuesForRegisters(RegistersGroup* registers) const { qCDebug(DEBUGGERCOMMON) << "Updating values for registers: " << registers->groupName.name(); if (registers->groupName == enumToGroupName(Flags)) { updateFlagValues(registers, m_eflags); } else { IRegisterController::updateValuesForRegisters(registers); } } RegistersGroup RegisterControllerGeneral_x86::registersFromGroup(const GroupsName& group) const { RegistersGroup registers; registers.groupName = group; registers.format = m_formatsModes[group.index()].formats.first(); foreach (const QString & name, registerNamesForGroup(group)) { registers.registers.append(Register(name, QString())); } updateValuesForRegisters(®isters); return registers; } QVector RegisterControllerGeneral_x86::namesOfRegisterGroups() const { static const QVector registerGroups = QVector() << enumToGroupName(General) << enumToGroupName(Flags) << enumToGroupName(FPU) << enumToGroupName(XMM) << enumToGroupName(Segment); return registerGroups; } void RegisterControllerGeneral_x86::setRegisterValueForGroup(const GroupsName& group, const Register& reg) { if (group == enumToGroupName(General)) { setGeneralRegister(reg, group); } else if (group == enumToGroupName(Flags)) { setFlagRegister(reg, m_eflags); } else if (group == enumToGroupName(FPU)) { setFPURegister(reg); } else if (group == enumToGroupName(XMM)) { setXMMRegister(reg); } else if (group == enumToGroupName(Segment)) { setSegmentRegister(reg); } } void RegisterControllerGeneral_x86::setFPURegister(const Register& reg) { setGeneralRegister(reg, enumToGroupName(FPU)); } void RegisterControllerGeneral_x86::setXMMRegister(const Register& reg) { setStructuredRegister(reg, enumToGroupName(XMM)); } void RegisterControllerGeneral_x86::setSegmentRegister(const Register& reg) { setGeneralRegister(reg, enumToGroupName(Segment)); } void RegisterControllerGeneral_x86::updateRegisters(const GroupsName& group) { if (!m_registerNamesInitialized) { if (initializeRegisters()) { m_registerNamesInitialized = true; } } IRegisterController::updateRegisters(group); } GroupsName RegisterControllerGeneral_x86::enumToGroupName(X86RegisterGroups group) const { static const GroupsName groups[LAST_REGISTER] = { createGroupName(i18n("General"), General), createGroupName(i18n("Flags"), Flags, flag, m_eflags.registerName), createGroupName(i18n("FPU"), FPU, floatPoint), createGroupName(i18n("XMM"), XMM, structured), createGroupName(i18n("Segment"), Segment)}; return groups[group]; } RegisterController_x86::RegisterController_x86(MIDebugSession* debugSession, QObject* parent) : RegisterControllerGeneral_x86(debugSession, parent) { initRegisterNames(); } RegisterController_x86_64::RegisterController_x86_64(MIDebugSession* debugSession, QObject* parent) : RegisterControllerGeneral_x86(debugSession, parent) { initRegisterNames(); } RegisterControllerGeneral_x86::RegisterControllerGeneral_x86(MIDebugSession* debugSession, QObject* parent) : IRegisterController(debugSession, parent), m_registerNamesInitialized(false) { if (m_registerNames.isEmpty()) { for (int i = 0; i < static_cast(LAST_REGISTER); i++) { m_registerNames.append(QStringList()); } initRegisterNames(); } int n = 0; while (n++ < namesOfRegisterGroups().size()) { m_formatsModes.append(FormatsModes()); } m_formatsModes[XMM].formats.append(Binary); m_formatsModes[XMM].formats.append(Decimal); m_formatsModes[XMM].formats.append(Hexadecimal); m_formatsModes[XMM].formats.append(Octal); m_formatsModes[XMM].formats.append(Unsigned); m_formatsModes[XMM].modes.append(v4_float); m_formatsModes[XMM].modes.append(v2_double); m_formatsModes[XMM].modes.append(v4_int32); m_formatsModes[XMM].modes.append(v2_int64); m_formatsModes[Flags].formats.append(Raw); m_formatsModes[Flags].modes.append(natural); m_formatsModes[FPU].formats.append(Decimal); m_formatsModes[FPU].modes.append(natural); m_formatsModes[General].modes.append(natural); m_formatsModes[General].formats.append(Raw); m_formatsModes[General].formats << m_formatsModes[XMM].formats; m_formatsModes[Segment] = m_formatsModes[General]; } void RegisterControllerGeneral_x86::initRegisterNames() { for (int i = 0; i < 8; i++) { m_registerNames[FPU] << ("st" + QString::number(i)); } m_registerNames[Flags] = QStringList{ QStringLiteral("C"), QStringLiteral("P"), QStringLiteral("A"), QStringLiteral("Z"), QStringLiteral("S"), QStringLiteral("T"), QStringLiteral("D"), QStringLiteral("O") }; m_registerNames[Segment] << QStringLiteral("cs") << QStringLiteral("ss") << QStringLiteral("ds") << QStringLiteral("es") << QStringLiteral("fs") << QStringLiteral("gs"); m_eflags.flags = m_registerNames[Flags]; m_eflags.bits << QStringLiteral("0") << QStringLiteral("2") << QStringLiteral("4") << QStringLiteral("6") << QStringLiteral("7") << QStringLiteral("8") << QStringLiteral("10") << QStringLiteral("11"); - m_eflags.registerName = QLatin1String("eflags"); + m_eflags.registerName = QStringLiteral("eflags"); m_eflags.groupName = enumToGroupName(Flags); } void RegisterController_x86::initRegisterNames() { m_registerNames[General] = QStringList{ QStringLiteral("eax"), QStringLiteral("ebx"), QStringLiteral("ecx"), QStringLiteral("edx"), QStringLiteral("esi"), QStringLiteral("edi"), QStringLiteral("ebp"), QStringLiteral("esp"), QStringLiteral("eip") }; m_registerNames[XMM].clear(); for (int i = 0; i < 8; i++) { m_registerNames[XMM] << ("xmm" + QString::number(i)); } } void RegisterController_x86_64::initRegisterNames() { m_registerNames[General] = QStringList{ QStringLiteral("rax"), QStringLiteral("rbx"), QStringLiteral("rcx"), QStringLiteral("rdx"), QStringLiteral("rsi"), QStringLiteral("rdi"), QStringLiteral("rbp"), QStringLiteral("rsp"), QStringLiteral("r8"), QStringLiteral("r9"), QStringLiteral("r10"), QStringLiteral("r11"), QStringLiteral("r12"), QStringLiteral("r13"), QStringLiteral("r14"), QStringLiteral("r15"), QStringLiteral("rip") }; m_registerNames[XMM].clear(); for (int i = 0; i < 16; i++) { m_registerNames[XMM] << ("xmm" + QString::number(i)); } } QStringList RegisterControllerGeneral_x86::registerNamesForGroup(const GroupsName& group) const { for (int i = 0; i < static_cast(LAST_REGISTER); i++) { if (group == enumToGroupName(static_cast(i))) { return m_registerNames[i]; } } return QStringList(); } diff --git a/debuggers/gdb/printers/tests/qmapstring.cpp b/debuggers/gdb/printers/tests/qmapstring.cpp index 9a28c92589..e6ea89125a 100644 --- a/debuggers/gdb/printers/tests/qmapstring.cpp +++ b/debuggers/gdb/printers/tests/qmapstring.cpp @@ -1,10 +1,10 @@ #include #include int main() { QMap m; - m[QStringLiteral("10")] = QLatin1String("100"); - m[QStringLiteral("20")] = QLatin1String("200"); - m[QStringLiteral("30")] = QLatin1String("300"); + m[QStringLiteral("10")] = QStringLiteral("100"); + m[QStringLiteral("20")] = QStringLiteral("200"); + m[QStringLiteral("30")] = QStringLiteral("300"); return 0; } diff --git a/debuggers/gdb/unittests/test_gdb.cpp b/debuggers/gdb/unittests/test_gdb.cpp index 00a7aa615d..114872a34a 100644 --- a/debuggers/gdb/unittests/test_gdb.cpp +++ b/debuggers/gdb/unittests/test_gdb.cpp @@ -1,2112 +1,2112 @@ /* Copyright 2009 Niko Sams Copyright 2013 Vlas Puhov 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 "test_gdb.h" #include "debugsession.h" #include "gdbframestackmodel.h" #include "mi/micommand.h" #include "mi/milexer.h" #include "mi/miparser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using KDevelop::AutoTestShell; namespace KDevMI { namespace GDB { QUrl findExecutable(const QString& name) { QFileInfo info(qApp->applicationDirPath() + "/unittests/" + name); Q_ASSERT(info.exists()); Q_ASSERT(info.isExecutable()); return QUrl::fromLocalFile(info.canonicalFilePath()); } QString findSourceFile(const QString& name) { QFileInfo info(QFileInfo(__FILE__).dir().absoluteFilePath(name)); Q_ASSERT(info.exists()); return info.canonicalFilePath(); } static bool isAttachForbidden(const char * file, int line) { // if on linux, ensure we can actually attach QFile canRun(QStringLiteral("/proc/sys/kernel/yama/ptrace_scope")); if (canRun.exists()) { if (!canRun.open(QIODevice::ReadOnly)) { QTest::qFail("Something is wrong: /proc/sys/kernel/yama/ptrace_scope exists but cannot be read", file, line); return true; } if (canRun.read(1).toInt() != 0) { QTest::qSkip("ptrace attaching not allowed, skipping test. To enable it, set /proc/sys/kernel/yama/ptrace_scope to 0.", file, line); return true; } } return false; } #define SKIP_IF_ATTACH_FORBIDDEN() \ do { \ if (isAttachForbidden(__FILE__, __LINE__)) \ return; \ } while(0) void GdbTest::initTestCase() { AutoTestShell::init(); KDevelop::TestCore::initialize(KDevelop::Core::NoUi); m_iface = KDevelop::ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"), QStringLiteral("kdevexecute"))->extension(); Q_ASSERT(m_iface); } void GdbTest::cleanupTestCase() { KDevelop::TestCore::shutdown(); } void GdbTest::init() { //remove all breakpoints - so we can set our own in the test KConfigGroup breakpoints = KSharedConfig::openConfig()->group("breakpoints"); breakpoints.writeEntry("number", 0); breakpoints.sync(); KDevelop::BreakpointModel* m = KDevelop::ICore::self()->debugController()->breakpointModel(); m->removeRows(0, m->rowCount()); KDevelop::VariableCollection *vc = KDevelop::ICore::self()->debugController()->variableCollection(); for (int i=0; i < vc->watches()->childCount(); ++i) { delete vc->watches()->child(i); } vc->watches()->clear(); } class WritableEnvironmentProfileList : public KDevelop::EnvironmentProfileList { public: explicit WritableEnvironmentProfileList(KConfig* config) : EnvironmentProfileList(config) {} using EnvironmentProfileList::variables; using EnvironmentProfileList::saveSettings; using EnvironmentProfileList::removeProfile; }; class TestLaunchConfiguration : public KDevelop::ILaunchConfiguration { public: explicit TestLaunchConfiguration(const QUrl& executable = findExecutable(QStringLiteral("debugee")), const QUrl& workingDirectory = QUrl()) { qDebug() << "FIND" << executable; c = new KConfig(); c->deleteGroup("launch"); cfg = c->group("launch"); cfg.writeEntry("isExecutable", true); cfg.writeEntry("Executable", executable); cfg.writeEntry("Working Directory", workingDirectory); } ~TestLaunchConfiguration() override { delete c; } const KConfigGroup config() const override { return cfg; } KConfigGroup config() override { return cfg; }; QString name() const override { return QStringLiteral("Test-Launch"); } KDevelop::IProject* project() const override { return nullptr; } KDevelop::LaunchConfigurationType* type() const override { return nullptr; } KConfig *rootConfig() { return c; } private: KConfigGroup cfg; KConfig *c; }; class TestFrameStackModel : public GdbFrameStackModel { public: explicit TestFrameStackModel(DebugSession* session) : GdbFrameStackModel(session), fetchFramesCalled(0), fetchThreadsCalled(0) {} int fetchFramesCalled; int fetchThreadsCalled; void fetchFrames(int threadNumber, int from, int to) override { fetchFramesCalled++; GdbFrameStackModel::fetchFrames(threadNumber, from, to); } void fetchThreads() override { fetchThreadsCalled++; GdbFrameStackModel::fetchThreads(); } }; class TestDebugSession : public DebugSession { Q_OBJECT public: TestDebugSession() : DebugSession() { setSourceInitFile(false); setAutoDisableASLR(false); m_frameStackModel = new TestFrameStackModel(this); KDevelop::ICore::self()->debugController()->addSession(this); } QUrl url() { return currentUrl(); } int line() { return currentLine(); } TestFrameStackModel* frameStackModel() const override { return m_frameStackModel; } private: TestFrameStackModel* m_frameStackModel; }; class TestWaiter { public: TestWaiter(DebugSession * session_, const char * condition_, const char * file_, int line_) : session(session_) , condition(condition_) , file(file_) , line(line_) { stopWatch.start(); } bool waitUnless(bool ok) { if (ok) { qDebug() << "Condition " << condition << " reached in " << file << ':' << line; return false; } if (stopWatch.elapsed() > 5000) { QTest::qFail(qPrintable(QString("Timeout before reaching condition %0").arg(condition)), file, line); return false; } QTest::qWait(100); if (!session) { QTest::qFail(qPrintable(QString("Session ended without reaching condition %0").arg(condition)), file, line); return false; } return true; } private: QTime stopWatch; QPointer session; const char * condition; const char * file; int line; }; #define WAIT_FOR_STATE(session, state) \ do { if (!waitForState((session), (state), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR_STATE_AND_IDLE(session, state) \ do { if (!waitForState((session), (state), __FILE__, __LINE__, true)) return; } while (0) #define WAIT_FOR(session, condition) \ do { \ TestWaiter w((session), #condition, __FILE__, __LINE__); \ while (w.waitUnless((condition))) /* nothing */ ; \ } while(0) #define COMPARE_DATA(index, expected) \ do { if(!compareData((index), (expected), __FILE__, __LINE__)) return; } while (0) bool compareData(QModelIndex index, const QString& expected, const char *file, int line) { QString s = index.model()->data(index, Qt::DisplayRole).toString(); if (s != expected) { QTest::qFail(qPrintable(QString("'%0' didn't match expected '%1' in %2:%3") .arg(s).arg(expected).arg(file).arg(line)), file, line); return false; } return true; } static const QString debugeeFileName = findSourceFile(QStringLiteral("debugee.cpp")); KDevelop::BreakpointModel* breakpoints() { return KDevelop::ICore::self()->debugController()->breakpointModel(); } void GdbTest::testStdOut() { TestDebugSession *session = new TestDebugSession; QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); TestLaunchConfiguration cfg; session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); { QCOMPARE(outputSpy.count(), 1); QList arguments = outputSpy.takeFirst(); QCOMPARE(arguments.count(), 1); QCOMPARE(arguments.first().toStringList(), QStringList() << "Hello, world!" << "Hello"); } } void GdbTest::testEnvironmentSet() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debugeeechoenv"))); cfg.config().writeEntry("EnvironmentGroup", "GdbTestGroup"); WritableEnvironmentProfileList envProfiles(cfg.rootConfig()); envProfiles.removeProfile(QStringLiteral("GdbTestGroup")); auto &envs = envProfiles.variables(QStringLiteral("GdbTestGroup")); - envs[QStringLiteral("VariableA")] = QLatin1String("-A' \" complex --value"); - envs[QStringLiteral("VariableB")] = QLatin1String("-B' \" complex --value"); + envs[QStringLiteral("VariableA")] = QStringLiteral("-A' \" complex --value"); + envs[QStringLiteral("VariableB")] = QStringLiteral("-B' \" complex --value"); envProfiles.saveSettings(cfg.rootConfig()); QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); QVERIFY(outputSpy.count() > 0); QStringList outputLines; while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); for (const auto &item : arguments) { outputLines.append(item.toStringList()); } } QCOMPARE(outputLines, QStringList() << "-A' \" complex --value" << "-B' \" complex --value"); } void GdbTest::testBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); } void GdbTest::testDisableBreakpoint() { //Description: We must stop only on the third breakpoint int firstBreakLine=28; int secondBreakLine=23; int thirdBreakLine=24; int fourthBreakLine=31; TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b; b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), firstBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //this is needed to emulate debug from GUI. If we are in edit mode, the debugSession doesn't exist. KDevelop::ICore::self()->debugController()->breakpointModel()->blockSignals(true); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), secondBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //all disabled breakpoints were added KDevelop::Breakpoint * thirdBreak = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), thirdBreakLine); KDevelop::ICore::self()->debugController()->breakpointModel()->blockSignals(false); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), thirdBreak->line()); //disable existing breakpoint thirdBreak->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //add another disabled breakpoint b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), fourthBreakLine); QTest::qWait(300); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); QTest::qWait(300); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testChangeLocationBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 27); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); QTest::qWait(100); b->setLine(28); QTest::qWait(100); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 28); QTest::qWait(500); breakpoints()->setData(breakpoints()->index(0, KDevelop::Breakpoint::LocationColumn), QString(debugeeFileName+":30")); QCOMPARE(b->line(), 29); QTest::qWait(100); QCOMPARE(b->line(), 29); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testDeleteBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; QCOMPARE(breakpoints()->rowCount(), 0); //add breakpoint before startDebugging breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QCOMPARE(breakpoints()->rowCount(), 1); breakpoints()->removeRow(0); QCOMPARE(breakpoints()->rowCount(), 0); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testPendingBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile(QStringLiteral("test_gdb.cpp"))), 10); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::PendingState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testUpdateBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; // breakpoint 1: line 29 KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); QCOMPARE(breakpoints()->rowCount(), 1); session->startDebugging(&cfg, m_iface); // breakpoint 2: line 28 //insert custom command as user might do it using GDB console session->addCommand(new MI::UserCommand(MI::NonMI, "break "+debugeeFileName+":28")); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at line 28 session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop after step QCOMPARE(breakpoints()->rowCount(), 2); b = breakpoints()->breakpoint(1); QCOMPARE(b->url(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(b->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); // stop at line 29 session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testIgnoreHitsBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); b1->setIgnoreHits(1); KDevelop::Breakpoint * b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); session->startDebugging(&cfg, m_iface); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b2->hitCount() == 1); b2->setIgnoreHits(1); session->run(); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b1->hitCount() == 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testConditionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 39); b->setCondition(QStringLiteral("x[0] == 'H'")); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 23); b->setCondition(QStringLiteral("i==2")); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR(session, session->state() == DebugSession::PausedState && session->line() == 24); b->setCondition(QStringLiteral("i == 0")); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 39); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnWriteBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addWatchpoint(QStringLiteral("i")); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnWriteWithConditionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); KDevelop::Breakpoint *b = breakpoints()->addWatchpoint(QStringLiteral("i")); b->setCondition(QStringLiteral("i==2")); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnReadBreakpoint() { /* test disabled because of gdb bug: http://sourceware.org/bugzilla/show_bug.cgi?id=10136 TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b = breakpoints()->addReadWatchpoint("foo::i"); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); */ } void GdbTest::testBreakOnReadBreakpoint2() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addReadWatchpoint(QStringLiteral("i")); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakOnAccessBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); breakpoints()->addAccessWatchpoint(QStringLiteral("i")); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 22); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debugeeslow"))); QString fileName = findSourceFile(QStringLiteral("debugeeslow.cpp")); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::ActiveState); QTest::qWait(2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QTest::qWait(500); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 25); b->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointWhileRunningMultiple() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debugeeslow"))); QString fileName = findSourceFile(QStringLiteral("debugeeslow.cpp")); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::ActiveState); QTest::qWait(2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 24); KDevelop::Breakpoint *b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QTest::qWait(500); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QCOMPARE(session->line(), 25); b1->setDeleted(); b2->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testInsertBreakpointFunctionName() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QStringLiteral("main")); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testManualBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QStringLiteral("main")); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); breakpoints()->removeRows(0, 1); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 0); session->addCommand(MI::NonMI, QStringLiteral("break debugee.cpp:23")); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 1); KDevelop::Breakpoint* b = breakpoints()->breakpoint(0); QCOMPARE(b->line(), 22); session->addCommand(MI::NonMI, QStringLiteral("disable 2")); session->addCommand(MI::NonMI, QStringLiteral("condition 2 i == 1")); session->addCommand(MI::NonMI, QStringLiteral("ignore 2 1")); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->enabled(), false); QCOMPARE(b->condition(), QString("i == 1")); QCOMPARE(b->ignoreHits(), 1); session->addCommand(MI::NonMI, QStringLiteral("delete 2")); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->rowCount(), 0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testShowStepInSource() { TestDebugSession *session = new TestDebugSession; QSignalSpy showStepInSourceSpy(session, &TestDebugSession::showStepInSource); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); { QCOMPARE(showStepInSourceSpy.count(), 3); QList arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 29); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 22); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 23); } } void GdbTest::testStack() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); QCOMPARE(stackModel->rowCount(tIdx), 2); QCOMPARE(stackModel->columnCount(tIdx), 3); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "foo"); COMPARE_DATA(stackModel->index(0, 2, tIdx), debugeeFileName+":23"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(1, 2, tIdx), debugeeFileName+":29"); session->stepOut(); WAIT_FOR_STATE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(0, 2, tIdx), debugeeFileName+":30"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackFetchMore() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debugeerecursion"))); QString fileName = findSourceFile(QStringLiteral("debugeerecursion.cpp")); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->frameStackModel()->fetchFramesCalled, 1); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); QCOMPARE(stackModel->rowCount(tIdx), 21); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "foo"); COMPARE_DATA(stackModel->index(0, 2, tIdx), fileName+":26"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "foo"); COMPARE_DATA(stackModel->index(1, 2, tIdx), fileName+":24"); COMPARE_DATA(stackModel->index(2, 0, tIdx), "2"); COMPARE_DATA(stackModel->index(2, 1, tIdx), "foo"); COMPARE_DATA(stackModel->index(2, 2, tIdx), fileName+":24"); COMPARE_DATA(stackModel->index(19, 0, tIdx), "19"); COMPARE_DATA(stackModel->index(20, 0, tIdx), "20"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 2); QCOMPARE(stackModel->rowCount(tIdx), 41); COMPARE_DATA(stackModel->index(20, 0, tIdx), "20"); COMPARE_DATA(stackModel->index(21, 0, tIdx), "21"); COMPARE_DATA(stackModel->index(22, 0, tIdx), "22"); COMPARE_DATA(stackModel->index(39, 0, tIdx), "39"); COMPARE_DATA(stackModel->index(40, 0, tIdx), "40"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 3); QCOMPARE(stackModel->rowCount(tIdx), 121); COMPARE_DATA(stackModel->index(40, 0, tIdx), "40"); COMPARE_DATA(stackModel->index(41, 0, tIdx), "41"); COMPARE_DATA(stackModel->index(42, 0, tIdx), "42"); COMPARE_DATA(stackModel->index(119, 0, tIdx), "119"); COMPARE_DATA(stackModel->index(120, 0, tIdx), "120"); stackModel->fetchMoreFrames(); QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); COMPARE_DATA(stackModel->index(120, 0, tIdx), "120"); COMPARE_DATA(stackModel->index(121, 0, tIdx), "121"); COMPARE_DATA(stackModel->index(122, 0, tIdx), "122"); COMPARE_DATA(stackModel->index(300, 0, tIdx), "300"); COMPARE_DATA(stackModel->index(300, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(300, 2, tIdx), fileName+":30"); stackModel->fetchMoreFrames(); //nothing to fetch, we are at the end QTest::qWait(200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackDeactivateAndActive() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); session->stepOut(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(200); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(0, 2, tIdx), debugeeFileName+":30"); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testStackSwitchThread() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debugeethreads"))); QString fileName = findSourceFile(QStringLiteral("debugeethreads.cpp")); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->rowCount(), 4); QModelIndex tIdx = stackModel->index(0,0); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(0, 2, tIdx), fileName+":39"); tIdx = stackModel->index(1,0); QVERIFY(stackModel->data(tIdx).toString().startsWith("#2 at ")); stackModel->setCurrentThread(2); QTest::qWait(200); int rows = stackModel->rowCount(tIdx); QVERIFY(rows > 3); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testAttach() { SKIP_IF_ATTACH_FORBIDDEN(); QString fileName = findSourceFile(QStringLiteral("debugeeslow.cpp")); KProcess debugeeProcess; debugeeProcess << QStringLiteral("nice") << findExecutable(QStringLiteral("debugeeslow")).toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); QTest::qWait(100); TestDebugSession *session = new TestDebugSession; session->attachToProcess(debugeeProcess.pid()); WAIT_FOR_STATE(session, DebugSession::PausedState); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 34); QTest::qWait(100); session->run(); QTest::qWait(2000); WAIT_FOR_STATE(session, DebugSession::PausedState); if (session->line() < 34 || session->line() < 35) { QCOMPARE(session->line(), 34); } session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testManualAttach() { SKIP_IF_ATTACH_FORBIDDEN(); QString fileName = findSourceFile(QStringLiteral("debugeeslow.cpp")); KProcess debugeeProcess; debugeeProcess << QStringLiteral("nice") << findExecutable(QStringLiteral("debugeeslow")).toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; cfg.config().writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(findSourceFile(QStringLiteral("gdb_script_empty")))); QVERIFY(session->startDebugging(&cfg, m_iface)); session->addCommand(MI::NonMI, QStringLiteral("attach %0").arg(debugeeProcess.pid())); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); QTest::qWait(2000); // give the slow inferior some extra time to run WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testCoreFile() { QFile f(QStringLiteral("core")); if (f.exists()) f.remove(); KProcess debugeeProcess; debugeeProcess.setOutputChannelMode(KProcess::MergedChannels); debugeeProcess << QStringLiteral("bash") << QStringLiteral("-c") << "ulimit -c unlimited; " + findExecutable(QStringLiteral("debugeecrash")).toLocalFile(); debugeeProcess.start(); debugeeProcess.waitForFinished(); qDebug() << debugeeProcess.readAll(); bool coreFileFound = QFile::exists(QStringLiteral("core")); if (!coreFileFound) { // Try to use coredumpctl auto coredumpctl = QStandardPaths::findExecutable(QStringLiteral("coredumpctl")); if (!coredumpctl.isEmpty()) { QFileInfo fi(QStringLiteral("core")); KProcess::execute(coredumpctl, {"-1", "-o", fi.absoluteFilePath(), "dump", "debugeecrash"}); coreFileFound = fi.exists(); } } if (!coreFileFound) QSKIP("no core dump found, check your system configuration (see /proc/sys/kernel/core_pattern).", SkipSingle); TestDebugSession *session = new TestDebugSession; session->examineCoreFile(findExecutable(QStringLiteral("debugeecrash")), QUrl::fromLocalFile(QDir::currentPath()+"/core")); TestFrameStackModel *stackModel = session->frameStackModel(); WAIT_FOR_STATE(session, DebugSession::StoppedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo"); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } KDevelop::VariableCollection *variableCollection() { return KDevelop::ICore::self()->debugController()->variableCollection(); } void GdbTest::testVariablesLocals() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 22); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); QCOMPARE(variableCollection()->rowCount(), 2); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "0"); session->run(); QTest::qWait(1000); WAIT_FOR_STATE(session, DebugSession::PausedState); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesLocalsStruct() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); QModelIndex i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); int structIndex = 0; for(int j=0; j<3; ++j) { if (variableCollection()->index(j, 0, i).data().toString() == QLatin1String("ts")) { structIndex = j; } } COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(structIndex, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); QTest::qWait(100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatches() { TestDebugSession *session = new TestDebugSession; KDevelop::ICore::self()->debugController()->variableCollection()->variableWidgetShown(); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(QStringLiteral("ts")); QTest::qWait(300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); QTest::qWait(100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(100); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatchesQuotes() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; // the unquoted string (the actual content): t\"t // quoted string (what we would write as a c string): "t\\\"t" // written in source file: R"("t\\\"t")" const QString testString(QStringLiteral("t\\\"t")); // the actual content const QString quotedTestString(QStringLiteral(R"("t\\\"t")")); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); variableCollection()->watches()->add(quotedTestString); //just a constant string QTest::qWait(300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), quotedTestString); COMPARE_DATA(variableCollection()->index(0, 1, i), "[" + QString::number(testString.length() + 1) + "]"); QModelIndex testStr = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, testStr), "..."); variableCollection()->expanded(testStr); QTest::qWait(100); int len = testString.length(); for (int ind = 0; ind < len; ind++) { COMPARE_DATA(variableCollection()->index(ind, 0, testStr), QString::number(ind)); QChar c = testString.at(ind); QString value = QString::number(c.toLatin1()) + " '"; if (c == '\\') value += QLatin1String("\\\\"); else if (c == '\'') value += QLatin1String("\\'"); else value += c; value += QLatin1String("'"); COMPARE_DATA(variableCollection()->index(ind, 1, testStr), value); } COMPARE_DATA(variableCollection()->index(len, 0, testStr), QString::number(len)); COMPARE_DATA(variableCollection()->index(len, 1, testStr), "0 '\\000'"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesWatchesTwoSessions() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(QStringLiteral("ts")); QTest::qWait(300); QModelIndex ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); variableCollection()->expanded(ts); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope QCOMPARE(variableCollection()->watches()->childCount(), 1); KDevelop::Variable* v = dynamic_cast(variableCollection()->watches()->child(0)); QVERIFY(v); QVERIFY(!v->inScope()); QCOMPARE(v->childCount(), 3); v = dynamic_cast(v->child(0)); QVERIFY(!v->inScope()); //start a second debug session session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(300); QCOMPARE(variableCollection()->watches()->childCount(), 1); ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); v = dynamic_cast(variableCollection()->watches()->child(0)); QVERIFY(v); QVERIFY(v->inScope()); QCOMPARE(v->childCount(), 3); v = dynamic_cast(v->child(0)); QVERIFY(v->inScope()); QCOMPARE(v->data(1, Qt::DisplayRole).toString(), QString::number(0)); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope v = dynamic_cast(variableCollection()->watches()->child(0)); QVERIFY(!v->inScope()); QVERIFY(!dynamic_cast(v->child(0))->inScope()); } void GdbTest::testVariablesStopDebugger() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); session->stopDebugger(); QTest::qWait(300); } void GdbTest::testVariablesStartSecondSession() { QPointer session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QPointer session2 = new TestDebugSession; session2->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session2->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session2, DebugSession::PausedState); session2->run(); WAIT_FOR_STATE(session2, DebugSession::EndedState); } void GdbTest::testVariablesSwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); QTest::qWait(200); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); COMPARE_DATA(variableCollection()->index(2, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(2, 1, i), "1"); COMPARE_DATA(variableCollection()->index(3, 0, i), "argv"); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testVariablesQuicklySwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(500); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "i"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); QTest::qWait(300); stackModel->setCurrentFrame(0); QTest::qWait(1); stackModel->setCurrentFrame(1); QTest::qWait(1); stackModel->setCurrentFrame(0); QTest::qWait(1); stackModel->setCurrentFrame(1); QTest::qWait(500); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); QStringList locs; for (int j = 0; j < variableCollection()->rowCount(i); ++j) { locs << variableCollection()->index(j, 0, i).data().toString(); } QVERIFY(locs.contains("argc")); QVERIFY(locs.contains("argv")); QVERIFY(locs.contains("x")); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testSegfaultDebugee() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debugeecrash"))); QString fileName = findSourceFile(QStringLiteral("debugeecrash.cpp")); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 23); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 24); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testSwitchFrameGdbConsole() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->currentFrame(), 0); stackModel->setCurrentFrame(1); QCOMPARE(stackModel->currentFrame(), 1); QTest::qWait(500); QCOMPARE(stackModel->currentFrame(), 1); session->addUserCommand(QStringLiteral("print x")); QTest::qWait(500); //currentFrame must not reset to 0; Bug 222882 QCOMPARE(stackModel->currentFrame(), 1); } //Bug 201771 void GdbTest::testInsertAndRemoveBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debugeeslow"))); QString fileName = findSourceFile(QStringLiteral("debugeeslow.cpp")); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::ActiveState); QTest::qWait(2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); b->setDeleted(); WAIT_FOR_STATE(session, DebugSession::EndedState); } //Bug 274390 void GdbTest::testCommandOrderFastStepping() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debugeeqt"))); breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); for(int i=0; i<20; i++) { session->stepInto(); } WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testPickupManuallyInsertedBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); session->addCommand(MI::NonMI, QStringLiteral("break debugee.cpp:32")); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); //wait for breakpoints update QCOMPARE(breakpoints()->breakpoints().count(), 2); QCOMPARE(breakpoints()->rowCount(), 2); KDevelop::Breakpoint *b = breakpoints()->breakpoint(1); QVERIFY(b); QCOMPARE(b->line(), 31); //we start with 0, gdb with 1 QCOMPARE(b->url().fileName(), QString("debugee.cpp")); } //Bug 270970 void GdbTest::testPickupManuallyInsertedBreakpointOnlyOnce() { TestDebugSession *session = new TestDebugSession; //inject here, so it behaves similar like a command from .gdbinit QTemporaryFile configScript; configScript.open(); configScript.write(QStringLiteral("file %0\n").arg(findExecutable(QStringLiteral("debugee")).toLocalFile()).toLocal8Bit()); configScript.write("break debugee.cpp:32\n"); configScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(QStringLiteral("debugee.cpp")), 31); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testPickupCatchThrowOnlyOnce() { QTemporaryFile configScript; configScript.open(); configScript.write("catch throw\n"); configScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbConfigEntry, QUrl::fromLocalFile(configScript.fileName())); for (int i = 0; i < 2; ++i) { TestDebugSession* session = new TestDebugSession; QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::EndedState); } QCOMPARE(breakpoints()->rowCount(), 1); //one from kdevelop, one from runScript } void GdbTest::testRunGdbScript() { TestDebugSession *session = new TestDebugSession; QTemporaryFile runScript; runScript.open(); runScript.write("file " + findExecutable(QStringLiteral("debugee")).toLocalFile().toUtf8() + "\n"); runScript.write("break main\n"); runScript.write("run\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testRemoteDebug() { const QString gdbserverExecutable = QStandardPaths::findExecutable(QStringLiteral("gdbserver")); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } TestDebugSession *session = new TestDebugSession; QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); shellScript.open(); shellScript.write("gdbserver localhost:2345 " + findExecutable(QStringLiteral("debugee")).toLocalFile().toUtf8() + "\n"); shellScript.close(); shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) QTemporaryFile runScript(QDir::currentPath()+"/runscript"); runScript.open(); runScript.write("file " + findExecutable(QStringLiteral("debugee")).toLocalFile().toUtf8() + "\n"); runScript.write("target remote localhost:2345\n"); runScript.write("break debugee.cpp:30\n"); runScript.write("continue\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QFile::remove(shellScript.fileName()+"-copy"); } void GdbTest::testRemoteDebugInsertBreakpoint() { const QString gdbserverExecutable = QStandardPaths::findExecutable(QStringLiteral("gdbserver")); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } TestDebugSession *session = new TestDebugSession; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 35); QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); shellScript.open(); shellScript.write("gdbserver localhost:2345 " + findExecutable(QStringLiteral("debugee")).toLocalFile().toUtf8() + "\n"); shellScript.close(); shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) QTemporaryFile runScript(QDir::currentPath()+"/runscript"); runScript.open(); runScript.write("file " + findExecutable(QStringLiteral("debugee")).toLocalFile().toUtf8() + '\n'); runScript.write("target remote localhost:2345\n"); runScript.write("break debugee.cpp:30\n"); runScript.write("continue\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbShellEntry, QUrl::fromLocalFile(shellScript.fileName()+"-copy")); grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QFile::remove(shellScript.fileName()+"-copy"); } void GdbTest::testRemoteDebugInsertBreakpointPickupOnlyOnce() { const QString gdbserverExecutable = QStandardPaths::findExecutable(QStringLiteral("gdbserver")); if (gdbserverExecutable.isEmpty()) { QSKIP("Skipping, gdbserver not available", SkipSingle); } TestDebugSession *session = new TestDebugSession; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 35); QTemporaryFile shellScript(QDir::currentPath()+"/shellscript"); shellScript.open(); shellScript.write("gdbserver localhost:2345 "+findExecutable(QStringLiteral("debugee")).toLocalFile().toLatin1()+"\n"); shellScript.close(); shellScript.setPermissions(shellScript.permissions() | QFile::ExeUser); QFile::copy(shellScript.fileName(), shellScript.fileName()+"-copy"); //to avoid "Text file busy" on executing (why?) QTemporaryFile runScript(QDir::currentPath()+"/runscript"); runScript.open(); runScript.write("file "+findExecutable(QStringLiteral("debugee")).toLocalFile().toLatin1()+"\n"); runScript.write("target remote localhost:2345\n"); runScript.write("break debugee.cpp:30\n"); runScript.write("continue\n"); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::RemoteGdbShellEntry, QUrl::fromLocalFile((shellScript.fileName()+"-copy"))); grp.writeEntry(Config::RemoteGdbRunEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //************************** second session session = new TestDebugSession; QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); QCOMPARE(breakpoints()->breakpoints().count(), 2); //one from kdevelop, one from runScript session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QFile::remove(shellScript.fileName()+"-copy"); } void GdbTest::testBreakpointWithSpaceInPath() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debugeespace"))); KConfigGroup grp = cfg.config(); QString fileName = findSourceFile(QStringLiteral("debugee space.cpp")); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 20); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 20); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBreakpointDisabledOnStart() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28) ->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 29); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 31); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 29); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 31); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testCatchpoint() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debugeeexception"))); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile(QStringLiteral("debugeeexception.cpp"))), 29); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); TestFrameStackModel* fsModel = session->frameStackModel(); QCOMPARE(fsModel->currentFrame(), 0); QCOMPARE(session->line(), 29); session->addCommand(MI::NonMI, QStringLiteral("catch throw")); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(1000); const QList frames = fsModel->frames(fsModel->currentThread()); QVERIFY(frames.size() >= 2); // frame 0 is somewhere inside libstdc++ QCOMPARE(frames[1].file, QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp"))); QCOMPARE(frames[1].line, 22); QCOMPARE(breakpoints()->rowCount(),2); QVERIFY(!breakpoints()->breakpoint(0)->location().isEmpty()); QVERIFY(!breakpoints()->breakpoint(1)->location().isEmpty()); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testThreadAndFrameInfo() { // Check if --thread is added to user commands TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debugeethreads"))); QString fileName = findSourceFile(QStringLiteral("debugeethreads.cpp")); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QSignalSpy outputSpy(session, &TestDebugSession::debuggerUserCommandOutput); session->addCommand(new MI::UserCommand(MI::ThreadInfo,QLatin1String(""))); - session->addCommand(new MI::UserCommand(MI::StackListLocals, QLatin1String("0"))); + session->addCommand(new MI::UserCommand(MI::StackListLocals, QStringLiteral("0"))); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // wait for command finish // outputs should be // 1. -thread-info // 2. ^done for thread-info // 3. -stack-list-locals // 4. ^done for -stack-list-locals QCOMPARE(outputSpy.count(), 4); QVERIFY(outputSpy.at(2).at(0).toString().contains(QLatin1String("--thread 1"))); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::parseBug304730() { MI::FileSymbol file; file.contents = QByteArray("^done,bkpt={" "number=\"1\",type=\"breakpoint\",disp=\"keep\",enabled=\"y\",addr=\"\",times=\"0\"," "original-location=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp:231\"}," "{number=\"1.1\",enabled=\"y\",addr=\"0x081d84aa\"," "func=\"PatchMatch, 2u> >" "::Propagation(ForwardPropagationNeighbors)\"," "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}," "{number=\"1.2\",enabled=\"y\",addr=\"0x081d8ae2\"," "func=\"PatchMatch, 2u> >" "::Propagation(BackwardPropagationNeighbors)\"," "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}," "{number=\"1.3\",enabled=\"y\",addr=\"0x081d911a\"," "func=\"PatchMatch, 2u> >" "::Propagation(AllowedPropagationNeighbors)\"," "file=\"/media/portable/Projects/BDSInpainting/Drivers/../PatchMatch/PatchMatch.hpp\"," "fullname=\"/media/portable/Projects/BDSInpainting/PatchMatch/PatchMatch.hpp\",line=\"231\"}"); MI::MIParser parser; std::unique_ptr record(parser.parse(&file)); QVERIFY(record.get() != nullptr); } void GdbTest::testMultipleLocationsBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("debugeemultilocbreakpoint"))); breakpoints()->addCodeBreakpoint(QStringLiteral("aPlusB")); //TODO check if the additional location breakpoint is added session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->line(), 19); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->line(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testBug301287() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(QStringLiteral("argc")); QTest::qWait(300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //start second debug session (same cfg) session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QTest::qWait(300); i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testMultipleBreakpoint() { TestDebugSession *session = new TestDebugSession; //there'll be about 3-4 breakpoints, but we treat it like one. TestLaunchConfiguration c(findExecutable(QStringLiteral("debugeemultiplebreakpoint"))); KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QStringLiteral("debugeemultiplebreakpoint.cpp:52")); session->startDebugging(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testRegularExpressionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable(QStringLiteral("debugeemultilocbreakpoint"))); breakpoints()->addCodeBreakpoint(QStringLiteral("main")); session->startDebugging(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); session->addCommand(MI::NonMI, QStringLiteral("rbreak .*aPl.*B")); QTest::qWait(100); session->run(); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 3); session->addCommand(MI::BreakDelete, QLatin1String("")); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testChangeBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable(QStringLiteral("debugeeslow"))); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QStringLiteral("debugeeslow.cpp:25")); session->startDebugging(&c, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QVERIFY(session->currentLine() >= 24 && session->currentLine() <= 26 ); session->run(); WAIT_FOR_STATE(session, DebugSession::ActiveState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //to make one loop QTest::qWait(2000); WAIT_FOR_STATE(session, DebugSession::ActiveState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::PausedState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); QTest::qWait(100); WAIT_FOR_STATE(session, DebugSession::EndedState); } void GdbTest::testDebugInExternalTerminal() { TestLaunchConfiguration cfg; foreach (const QString & console, QStringList() << "konsole" << "xterm" << "xfce4-terminal" << "gnome-terminal") { TestDebugSession* session = nullptr; if (QStandardPaths::findExecutable(console).isEmpty()) { continue; } session = new TestDebugSession(); cfg.config().writeEntry("External Terminal"/*ExecutePlugin::terminalEntry*/, console); cfg.config().writeEntry("Use External Terminal"/*ExecutePlugin::useTerminalEntry*/, true); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 28); session->startDebugging(&cfg, m_iface); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } } // see: https://bugs.kde.org/show_bug.cgi?id=339231 void GdbTest::testPathWithSpace() { #ifdef HAVE_PATH_WITH_SPACES_TEST TestDebugSession* session = new TestDebugSession; auto debugee = findExecutable(QStringLiteral("path with space/spacedebugee")); TestLaunchConfiguration c(debugee, KIO::upUrl(debugee)); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QStringLiteral("spacedebugee.cpp:30")); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); session->startDebugging(&c, m_iface); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); #endif } bool GdbTest::waitForState(DebugSession *session, DebugSession::DebuggerState state, const char *file, int line, bool waitForIdle) { QPointer s(session); //session can get deleted in DebugController QTime stopWatch; stopWatch.start(); // legacy behavior for tests that implicitly may require waiting for idle, // but which were written before waitForIdle was added waitForIdle = waitForIdle || state != MIDebugSession::EndedState; while (s && (s->state() != state || (waitForIdle && s->debuggerStateIsOn(s_dbgBusy)))) { if (stopWatch.elapsed() > 5000) { qWarning() << "current state" << s->state() << "waiting for" << state; QTest::qFail(qPrintable(QString("Timeout before reaching state %0").arg(state)), file, line); return false; } QTest::qWait(20); } // NOTE: don't wait anymore after leaving the loop. Waiting re-enters event loop and // may change session state. if (!s && state != MIDebugSession::EndedState) { QTest::qFail(qPrintable(QString("Session ended before reaching state %0").arg(state)), file, line); return false; } qDebug() << "Reached state " << state << " in " << file << ':' << line; return true; } } // end of namespace GDB } // end of namespace KDevMI QTEST_MAIN(KDevMI::GDB::GdbTest) #include "test_gdb.moc" #include "moc_test_gdb.cpp" diff --git a/debuggers/lldb/lldbcommand.cpp b/debuggers/lldb/lldbcommand.cpp index 2081e050d0..3860fe82fd 100644 --- a/debuggers/lldb/lldbcommand.cpp +++ b/debuggers/lldb/lldbcommand.cpp @@ -1,229 +1,229 @@ /* * LLDB specific version of MI command * Copyright 2016 Aetf * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "lldbcommand.h" using namespace KDevMI::LLDB; using namespace KDevMI::MI; LldbCommand::LldbCommand(CommandType type, const QString& arguments, CommandFlags flags) : MICommand(type, arguments, flags) { } LldbCommand::~LldbCommand() { } QString LldbCommand::miCommand() const { if (!overrideCmd.isEmpty()) { return overrideCmd; } QString command; bool isMI = false; // TODO: find alternatives to the following command which are not supported in lldb-mi switch(type()) { case BreakCommands: command = QLatin1String(""); break; case BreakInfo: command = QLatin1String(""); break; case BreakInsert: // in lldb-mi, '-f' must be the last option switch right before location - command = QLatin1String("break-insert"); + command = QStringLiteral("break-insert"); isMI = true; break; case BreakList: command = QLatin1String(""); break; case BreakWatch: - command = QLatin1String("break set var"); + command = QStringLiteral("break set var"); break; case DataListChangedRegisters: - command = QLatin1String("data-list-changed-registers"); + command = QStringLiteral("data-list-changed-registers"); break; case DataReadMemory: // not implemented, deprecated - command = QLatin1String("data-read-memory"); + command = QStringLiteral("data-read-memory"); break; case DataWriteRegisterVariables: - command = QLatin1String("data-write-register-values"); + command = QStringLiteral("data-write-register-values"); break; case EnableTimings: - command = QLatin1String("enable-timings"); + command = QStringLiteral("enable-timings"); break; case EnvironmentDirectory: - command = QLatin1String("environment-directory"); + command = QStringLiteral("environment-directory"); break; case EnvironmentPath: - command = QLatin1String("environment-path"); + command = QStringLiteral("environment-path"); break; case EnvironmentPwd: - command = QLatin1String("environment-pwd"); + command = QStringLiteral("environment-pwd"); break; case ExecUntil: // TODO: write test case for this - command = QLatin1String("thread until"); + command = QStringLiteral("thread until"); break; case FileExecFile: - command = QLatin1String("file-exec-file");//"exec-file" + command = QStringLiteral("file-exec-file");//"exec-file" break; case FileListExecSourceFile: - command = QLatin1String("file-list-exec-source-file"); + command = QStringLiteral("file-list-exec-source-file"); break; case FileListExecSourceFiles: - command = QLatin1String("file-list-exec-source-files"); + command = QStringLiteral("file-list-exec-source-files"); break; case FileSymbolFile: - command = QLatin1String("file-symbol-file");//"symbol-file" + command = QStringLiteral("file-symbol-file");//"symbol-file" break; case GdbVersion: - command = QLatin1String("gdb-version");//"show version" + command = QStringLiteral("gdb-version");//"show version" break; case InferiorTtyShow: - command = QLatin1String("inferior-tty-show"); + command = QStringLiteral("inferior-tty-show"); break; case SignalHandle: - command = QLatin1String("process handle"); + command = QStringLiteral("process handle"); break; case TargetDisconnect: - command = QLatin1String("target-disconnect");//"disconnect" + command = QStringLiteral("target-disconnect");//"disconnect" break; case TargetDownload: - command = QLatin1String("target-download"); + command = QStringLiteral("target-download"); break; case ThreadListIds: - command = QLatin1String("thread-list-ids"); + command = QStringLiteral("thread-list-ids"); break; case ThreadSelect: - command = QLatin1String("thread-select"); + command = QStringLiteral("thread-select"); break; case TraceFind: - command = QLatin1String("trace-find"); + command = QStringLiteral("trace-find"); break; case TraceStart: - command = QLatin1String("trace-start"); + command = QStringLiteral("trace-start"); break; case TraceStop: - command = QLatin1String("trace-stop"); + command = QStringLiteral("trace-stop"); break; case VarInfoNumChildren: - command = QLatin1String("var-info-num-children"); + command = QStringLiteral("var-info-num-children"); break; case VarInfoType: - command = QLatin1String("var-info-type"); + command = QStringLiteral("var-info-type"); break; case VarSetFrozen: - command = QLatin1String("var-set-frozen"); + command = QStringLiteral("var-set-frozen"); break; case VarShowFormat: - command = QLatin1String("var-show-format"); + command = QStringLiteral("var-show-format"); break; default: return MICommand::miCommand(); } if (isMI) { command.prepend('-'); } return command; } QString LldbCommand::cmdToSend() { switch (type()) { // -gdb-set is only partially implemented case GdbSet: { QString env_name = QStringLiteral("environment "); QString disassembly_flavor = QStringLiteral("disassembly-flavor "); if (command_.startsWith(env_name)) { command_ = command_.mid(env_name.length()); - overrideCmd = QLatin1String("settings set target.env-vars"); + overrideCmd = QStringLiteral("settings set target.env-vars"); } else if (command_.startsWith(disassembly_flavor)) { command_ = command_.mid(disassembly_flavor.length()); - overrideCmd = QLatin1String("settings set target.x86-disassembly-flavor"); + overrideCmd = QStringLiteral("settings set target.x86-disassembly-flavor"); } break; } // find the position to insert '-f' case BreakInsert: { if (!overrideCmd.isEmpty()) { // already done break; } int p = command_.length() - 1; bool quoted = false; if (command_[p] == '"') { quoted = true; // should always be the case } --p; for (; p >= 0; --p) { // find next '"' or ' ' if (quoted) { if (command_[p] == '"' && (p == 0 || command_[p-1] != '\\')) break; } else { if (command_[p] == ' ') break; } } if (p < 0) p = 0; // this means the command is malformated, we proceed anyway. // move other switches like '-d' '-c' into miCommand part overrideCmd = miCommand() + " " + command_.left(p); command_ = "-f " + command_.mid(p, command_.length()); break; } case BreakWatch: if (command_.startsWith(QLatin1String("-r "))) { command_ = "-w read " + command_.mid(3); } else if (command_.startsWith(QLatin1String("-a "))) { command_ = "-w read_write " + command_.mid(3); } break; case StackListArguments: // some times when adding the command, the current frame is invalid, // but is valid at sending time if (command_.endsWith(QLatin1String("-1 -1"))) { command_.replace(QLatin1String("-1 -1"), QStringLiteral("%1 %1").arg(frame())); } break; default: break; } return MICommand::cmdToSend(); } diff --git a/debuggers/lldb/unittests/debugees/qmapstring.cpp b/debuggers/lldb/unittests/debugees/qmapstring.cpp index 9a28c92589..e6ea89125a 100644 --- a/debuggers/lldb/unittests/debugees/qmapstring.cpp +++ b/debuggers/lldb/unittests/debugees/qmapstring.cpp @@ -1,10 +1,10 @@ #include #include int main() { QMap m; - m[QStringLiteral("10")] = QLatin1String("100"); - m[QStringLiteral("20")] = QLatin1String("200"); - m[QStringLiteral("30")] = QLatin1String("300"); + m[QStringLiteral("10")] = QStringLiteral("100"); + m[QStringLiteral("20")] = QStringLiteral("200"); + m[QStringLiteral("30")] = QStringLiteral("300"); return 0; } diff --git a/debuggers/lldb/unittests/test_lldb.cpp b/debuggers/lldb/unittests/test_lldb.cpp index f8e1948abd..ddfc477d85 100644 --- a/debuggers/lldb/unittests/test_lldb.cpp +++ b/debuggers/lldb/unittests/test_lldb.cpp @@ -1,1884 +1,1884 @@ /* * Unit tests for LLDB debugger plugin Copyright 2009 Niko Sams Copyright 2013 Vlas Puhov * Copyright 2016 Aetf * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "test_lldb.h" #include "controllers/framestackmodel.h" #include "debugsession.h" #include "testhelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define WAIT_FOR_STATE(session, state) \ do { if (!KDevMI::LLDB::waitForState((session), (state), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR_STATE_AND_IDLE(session, state) \ do { if (!KDevMI::LLDB::waitForState((session), (state), __FILE__, __LINE__, true)) return; } while (0) #define WAIT_FOR_A_WHILE(session, ms) \ do { if (!KDevMI::LLDB::waitForAWhile((session), (ms), __FILE__, __LINE__)) return; } while (0) #define WAIT_FOR(session, condition) \ do { \ KDevMI::LLDB::TestWaiter w((session), #condition, __FILE__, __LINE__); \ while (w.waitUnless((condition))) /* nothing */ ; \ } while(0) #define COMPARE_DATA(index, expected) \ do { if (!KDevMI::LLDB::compareData((index), (expected), __FILE__, __LINE__)) return; } while (0) #define SKIP_IF_ATTACH_FORBIDDEN() \ do { \ if (isAttachForbidden(__FILE__, __LINE__)) \ return; \ } while(0) #define findSourceFile(name) \ findSourceFile(__FILE__, (name)) using namespace KDevelop; using namespace KDevMI::LLDB; namespace { class WritableEnvironmentProfileList : public EnvironmentProfileList { public: explicit WritableEnvironmentProfileList(KConfig* config) : EnvironmentProfileList(config) {} using EnvironmentProfileList::variables; using EnvironmentProfileList::saveSettings; using EnvironmentProfileList::removeProfile; }; class TestLaunchConfiguration : public ILaunchConfiguration { public: explicit TestLaunchConfiguration(const QUrl& executable = findExecutable(QStringLiteral("lldb_debugee")), const QUrl& workingDirectory = QUrl()) { qDebug() << "FIND" << executable; c = new KConfig(); c->deleteGroup("launch"); cfg = c->group("launch"); cfg.writeEntry("isExecutable", true); cfg.writeEntry("Executable", executable); cfg.writeEntry("Working Directory", workingDirectory); } ~TestLaunchConfiguration() override { delete c; } const KConfigGroup config() const override { return cfg; } KConfigGroup config() override { return cfg; }; QString name() const override { return QStringLiteral("Test-Launch"); } KDevelop::IProject* project() const override { return nullptr; } KDevelop::LaunchConfigurationType* type() const override { return nullptr; } KConfig *rootConfig() { return c; } private: KConfigGroup cfg; KConfig *c; }; class TestFrameStackModel : public LldbFrameStackModel { public: explicit TestFrameStackModel(DebugSession* session) : LldbFrameStackModel(session), fetchFramesCalled(0), fetchThreadsCalled(0) {} void fetchFrames(int threadNumber, int from, int to) override { fetchFramesCalled++; LldbFrameStackModel::fetchFrames(threadNumber, from, to); } void fetchThreads() override { fetchThreadsCalled++; LldbFrameStackModel::fetchThreads(); } int fetchFramesCalled; int fetchThreadsCalled; }; class TestDebugSession : public DebugSession { Q_OBJECT public: TestDebugSession() : DebugSession() { setSourceInitFile(false); m_frameStackModel = new TestFrameStackModel(this); setFormatterPath(findSourceFile("../formatters/all.py")); KDevelop::ICore::self()->debugController()->addSession(this); } TestFrameStackModel* frameStackModel() const override { return m_frameStackModel; } private: TestFrameStackModel* m_frameStackModel; }; } // end of anonymous namespace BreakpointModel* LldbTest::breakpoints() { return m_core->debugController()->breakpointModel(); } VariableCollection *LldbTest::variableCollection() { return m_core->debugController()->variableCollection(); } Variable *LldbTest::watchVariableAt(int i) { auto watchRoot = variableCollection()->indexForItem(variableCollection()->watches(), 0); auto idx = variableCollection()->index(i, 0, watchRoot); return dynamic_cast(variableCollection()->itemForIndex(idx)); } QModelIndex LldbTest::localVariableIndexAt(int i, int col) { auto localRoot = variableCollection()->indexForItem(variableCollection()->locals(), 0); return variableCollection()->index(i, col, localRoot); } // Called before the first testfunction is executed void LldbTest::initTestCase() { AutoTestShell::init(); m_core = TestCore::initialize(Core::NoUi); m_iface = m_core->pluginController() ->pluginForExtension(QStringLiteral("org.kdevelop.IExecutePlugin"), QStringLiteral("kdevexecute")) ->extension(); Q_ASSERT(m_iface); m_debugeeFileName = findSourceFile("debugee.cpp"); } // Called after the last testfunction was executed void LldbTest::cleanupTestCase() { TestCore::shutdown(); } // Called before each testfunction is executed void LldbTest::init() { //remove all breakpoints - so we can set our own in the test KConfigGroup bpCfg = KSharedConfig::openConfig()->group("breakpoints"); bpCfg.writeEntry("number", 0); bpCfg.sync(); breakpoints()->removeRows(0, breakpoints()->rowCount()); while (variableCollection()->watches()->childCount() > 0) { auto var = watchVariableAt(0); if (!var) break; var->die(); } } void LldbTest::cleanup() { // Called after every testfunction } void LldbTest::testStdout() { TestDebugSession *session = new TestDebugSession; QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); TestLaunchConfiguration cfg; QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); QVERIFY(outputSpy.count() > 0); QStringList outputLines; while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); for (const auto &item : arguments) { outputLines.append(item.toStringList()); } } QCOMPARE(outputLines, QStringList() << "Hello, world!" << "Hello"); } void LldbTest::testEnvironmentSet() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("lldb_debugeeechoenv"))); cfg.config().writeEntry("EnvironmentGroup", "LldbTestGroup"); WritableEnvironmentProfileList envProfiles(cfg.rootConfig()); envProfiles.removeProfile(QStringLiteral("LldbTestGroup")); auto &envs = envProfiles.variables(QStringLiteral("LldbTestGroup")); - envs[QStringLiteral("VariableA")] = QLatin1String("-A' \" complex --value"); - envs[QStringLiteral("VariableB")] = QLatin1String("-B' \" complex --value"); + envs[QStringLiteral("VariableA")] = QStringLiteral("-A' \" complex --value"); + envs[QStringLiteral("VariableB")] = QStringLiteral("-B' \" complex --value"); envProfiles.saveSettings(cfg.rootConfig()); QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); QVERIFY(outputSpy.count() > 0); QStringList outputLines; while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); for (const auto &item : arguments) { outputLines.append(item.toStringList()); } } QCOMPARE(outputLines, QStringList() << "-A' \" complex --value" << "-B' \" complex --value"); } void LldbTest::testBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 29); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); QCOMPARE(session->currentLine(), 29); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); } void LldbTest::testBreakOnStart() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; cfg.config().writeEntry(KDevMI::Config::BreakOnStartEntry, true); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); // line 28 is the start of main function in debugee.cpp QCOMPARE(session->currentLine(), 27); // currentLine is zero-based session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testDisableBreakpoint() { QSKIP("Skipping... In lldb-mi -d flag has no effect when mixed with -f"); //Description: We must stop only on the third breakpoint int firstBreakLine=28; int secondBreakLine=23; int thirdBreakLine=24; int fourthBreakLine=31; TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint *b; b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), firstBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //this is needed to emulate debug from GUI. If we are in edit mode, the debugSession doesn't exist. m_core->debugController()->breakpointModel()->blockSignals(true); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), secondBreakLine); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //all disabled breakpoints were added auto *thirdBreak = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), thirdBreakLine); m_core->debugController()->breakpointModel()->blockSignals(false); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), thirdBreak->line()); //disable existing breakpoint thirdBreak->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //add another disabled breakpoint b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), fourthBreakLine); WAIT_FOR_A_WHILE(session, 300); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); WAIT_FOR_A_WHILE(session, 300); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testChangeLocationBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; auto *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 27); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 27); WAIT_FOR_A_WHILE(session, 100); b->setLine(28); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_A_WHILE(session, 100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 28); WAIT_FOR_A_WHILE(session, 500); breakpoints()->setData(breakpoints()->index(0, KDevelop::Breakpoint::LocationColumn), QString(m_debugeeFileName+":30")); QCOMPARE(b->line(), 29); WAIT_FOR_A_WHILE(session, 100); QCOMPARE(b->line(), 29); session->run(); WAIT_FOR_A_WHILE(session, 100); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 29); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testDeleteBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; QCOMPARE(breakpoints()->rowCount(), 0); //add breakpoint before startDebugging breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); QCOMPARE(breakpoints()->rowCount(), 1); breakpoints()->removeRow(0); QCOMPARE(breakpoints()->rowCount(), 0); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 22); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testPendingBreakpoint() { QSKIP("Skipping... Pending breakpoint not work on lldb-mi"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); auto * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("test_lldb.cpp")), 10); QCOMPARE(b->state(), Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QCOMPARE(b->state(), Breakpoint::PendingState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testUpdateBreakpoint() { // Description: user might insert breakpoints using lldb console. model should // pick up the manually set breakpoint TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; // break at line 29 auto b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); QCOMPARE(breakpoints()->rowCount(), 1); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at line 29 session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop after step QCOMPARE(session->currentLine(), 23-1); // at the beginning of foo():23: ++i; session->addUserCommand(QStringLiteral("break set --file %1 --line %2").arg(m_debugeeFileName).arg(33)); WAIT_FOR_A_WHILE(session, 20); QCOMPARE(breakpoints()->rowCount(), 2); b = breakpoints()->breakpoint(1); QCOMPARE(b->url(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(b->line(), 33-1); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); // stop at line 25 QCOMPARE(session->currentLine(), 33-1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testIgnoreHitsBreakpoint() { QSKIP("Skipping... lldb-mi doesn't provide breakpoint hit count update"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; KDevelop::Breakpoint * b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); b1->setIgnoreHits(1); KDevelop::Breakpoint * b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 22); QVERIFY(session->startDebugging(&cfg, m_iface)); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b2->hitCount() == 1); b2->setIgnoreHits(1); session->run(); //WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR(session, session->state() == DebugSession::PausedState && b1->hitCount() == 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testConditionBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; auto b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 39); b->setCondition(QStringLiteral("x[0] == 'H'")); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 23); b->setCondition(QStringLiteral("i==2")); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR(session, session->state() == DebugSession::PausedState && session->currentLine() == 24); b->setCondition(QStringLiteral("i == 0")); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 39); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnWriteBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); breakpoints()->addWatchpoint(QStringLiteral("i")); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnWriteWithConditionBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); KDevelop::Breakpoint *b = breakpoints()->addWatchpoint(QStringLiteral("i")); b->setCondition(QStringLiteral("i==2")); QTest::qWait(100); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnReadBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addReadWatchpoint(QStringLiteral("foo::i")); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnReadBreakpoint2() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); breakpoints()->addReadWatchpoint(QStringLiteral("i")); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 22); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakOnAccessBreakpoint() { QSKIP("Skipping... lldb-mi doesn't have proper watchpoint support"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); breakpoints()->addAccessWatchpoint(QStringLiteral("i")); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 22); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testInsertBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("lldb_debugeeslow"))); QString fileName = findSourceFile("debugeeslow.cpp"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::ActiveState); WAIT_FOR_A_WHILE(session, 2000); qDebug() << "adding breakpoint"; KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); WAIT_FOR_A_WHILE(session, 500); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(session->currentLine(), 25); b->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testInsertBreakpointWhileRunningMultiple() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("lldb_debugeeslow"))); QString fileName = findSourceFile("debugeeslow.cpp"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::ActiveState); WAIT_FOR_A_WHILE(session, 2000); qDebug() << "adding breakpoint"; auto b1 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 24); auto b2 = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); WAIT_FOR_A_WHILE(session, 500); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(session->currentLine(), 24); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(session->currentLine(), 25); b1->setDeleted(); b2->setDeleted(); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testInsertBreakpointFunctionName() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 27); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testManualBreakpoint() { QSKIP("Skipping... lldb-mi output malformated response which breaks this"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 27); breakpoints()->removeRows(0, 1); WAIT_FOR_A_WHILE(session, 100); QCOMPARE(breakpoints()->rowCount(), 0); session->addCommand(MI::NonMI, QStringLiteral("break set --file debugee.cpp --line 23")); WAIT_FOR_A_WHILE(session, 100); QCOMPARE(breakpoints()->rowCount(), 1); auto b = breakpoints()->breakpoint(0); QCOMPARE(b->line(), 22); session->addCommand(MI::NonMI, QStringLiteral("break disable 2")); session->addCommand(MI::NonMI, QStringLiteral("break modify -c 'i == 1' 2")); session->addCommand(MI::NonMI, QStringLiteral("break modify -i 1 2")); WAIT_FOR_A_WHILE(session, 1000); QCOMPARE(b->enabled(), false); QCOMPARE(b->condition(), QString("i == 1")); QCOMPARE(b->ignoreHits(), 1); session->addCommand(MI::NonMI, QStringLiteral("break delete 2")); WAIT_FOR_A_WHILE(session, 100); QCOMPARE(breakpoints()->rowCount(), 0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } //Bug 201771 void LldbTest::testInsertAndRemoveBreakpointWhileRunning() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("lldb_debugeeslow"))); QString fileName = findSourceFile("debugeeslow.cpp"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::ActiveState); WAIT_FOR_A_WHILE(session, 1000); KDevelop::Breakpoint *b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); WAIT_FOR_A_WHILE(session, 200); // wait for feedback notification from lldb-mi b->setDeleted(); WAIT_FOR_A_WHILE(session, 3000); // give slow debugee extra time to run WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testPickupManuallyInsertedBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->addCommand(MI::NonMI, QStringLiteral("break set --file debugee.cpp --line 32")); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 2); QCOMPARE(breakpoints()->rowCount(), 2); KDevelop::Breakpoint *b = breakpoints()->breakpoint(1); QVERIFY(b); QCOMPARE(b->line(), 31); //we start with 0, gdb with 1 QCOMPARE(b->url().fileName(), QString("debugee.cpp")); } //Bug 270970 void LldbTest::testPickupManuallyInsertedBreakpointOnlyOnce() { TestDebugSession *session = new TestDebugSession; QString sourceFile = findSourceFile("debugee.cpp"); //inject here, so it behaves similar like a command from .lldbinit QTemporaryFile configScript; configScript.open(); configScript.write(QStringLiteral("break set --file %0 --line 32\n").arg(sourceFile).toLocal8Bit()); configScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::LldbConfigScriptEntry, QUrl::fromLocalFile(configScript.fileName())); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(QStringLiteral("debugee.cpp")), 31); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakpointWithSpaceInPath() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("lldb_debugeespace"))); KConfigGroup grp = cfg.config(); QString fileName = findSourceFile("debugee space.cpp"); KDevelop::Breakpoint * b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 20); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 20); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBreakpointDisabledOnStart() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; auto b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 23); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 29); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 34); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 29); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 34); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testMultipleLocationsBreakpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("lldb_debugeemultilocbreakpoint"))); breakpoints()->addCodeBreakpoint(QStringLiteral("aPlusB")); //TODO check if the additional location breakpoint is added QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 19); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testMultipleBreakpoint() { TestDebugSession *session = new TestDebugSession; //there'll be about 3-4 breakpoints, but we treat it like one. TestLaunchConfiguration c(findExecutable(QStringLiteral("lldb_debugeemultiplebreakpoint"))); auto b = breakpoints()->addCodeBreakpoint(QStringLiteral("debugeemultiplebreakpoint.cpp:52")); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 1); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testRegularExpressionBreakpoint() { QSKIP("Skipping... lldb has only one breakpoint for multiple locations" " (and lldb-mi returns the first one), not support this yet"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable(QStringLiteral("lldb_debugeemultilocbreakpoint"))); breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->addCommand(MI::NonMI, QStringLiteral("break set --func-regex .*aPl.*B")); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(breakpoints()->breakpoints().count(), 3); session->addCommand(MI::BreakDelete, QLatin1String("")); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testChangeBreakpointWhileRunning() { QSKIP("Skipping... lldb-mi command -break-enable doesn't enable breakpoint"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration c(findExecutable(QStringLiteral("lldb_debugeeslow"))); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QStringLiteral("debugeeslow.cpp:25")); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QVERIFY(session->currentLine() >= 24 && session->currentLine() <= 26 ); session->run(); WAIT_FOR_STATE(session, DebugSession::ActiveState); qDebug() << "Disabling breakpoint"; b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); //to make one loop WAIT_FOR_A_WHILE(session, 2500); qDebug() << "Waiting for active"; WAIT_FOR_STATE(session, DebugSession::ActiveState); qDebug() << "Enabling breakpoint"; // Use native user command works, but not through -break-enable, which is triggered by setData session->addCommand(MI::NonMI, QStringLiteral("break enable")); //b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Checked); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); b->setData(KDevelop::Breakpoint::EnableColumn, Qt::Unchecked); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testCatchpoint() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("lldb_debugeeexception"))); session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel* fsModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp")), 29); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(fsModel->currentFrame(), 0); QCOMPARE(session->currentLine(), 29); session->addCommand(MI::NonMI, QStringLiteral("break set -E c++")); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); const auto frames = fsModel->frames(fsModel->currentThread()); QVERIFY(frames.size() >= 2); // frame 0 is somewhere inside libstdc++ QCOMPARE(frames[1].file, QUrl::fromLocalFile(findSourceFile("debugeeexception.cpp"))); QCOMPARE(frames[1].line, 22); QCOMPARE(breakpoints()->rowCount(),2); QVERIFY(!breakpoints()->breakpoint(0)->location().isEmpty()); QVERIFY(!breakpoints()->breakpoint(1)->location().isEmpty()); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testShowStepInSource() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; QSignalSpy showStepInSourceSpy(session, &TestDebugSession::showStepInSource); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 29); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); { QCOMPARE(showStepInSourceSpy.count(), 3); QList arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 29); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 22); arguments = showStepInSourceSpy.takeFirst(); QCOMPARE(arguments.first().value(), QUrl::fromLocalFile(m_debugeeFileName)); QCOMPARE(arguments.at(1).toInt(), 23); } } void LldbTest::testStack() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo()"); QCOMPARE(stackModel->rowCount(tIdx), 4); QCOMPARE(stackModel->columnCount(tIdx), 3); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "foo()"); COMPARE_DATA(stackModel->index(0, 2, tIdx), m_debugeeFileName+":23"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(1, 2, tIdx), m_debugeeFileName+":29"); COMPARE_DATA(stackModel->index(2, 0, tIdx), "2"); COMPARE_DATA(stackModel->index(2, 1, tIdx), "__libc_start_main"); COMPARE_DATA(stackModel->index(3, 0, tIdx), "3"); COMPARE_DATA(stackModel->index(3, 1, tIdx), "_start"); session->stepOut(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 3); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(0, 2, tIdx), m_debugeeFileName+":30"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "__libc_start_main"); COMPARE_DATA(stackModel->index(2, 0, tIdx), "2"); COMPARE_DATA(stackModel->index(2, 1, tIdx), "_start"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testStackFetchMore() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("lldb_debugeerecursion"))); QString fileName = findSourceFile("debugeerecursion.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 25); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->frameStackModel()->fetchFramesCalled, 1); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo()"); QCOMPARE(stackModel->rowCount(tIdx), 21); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "foo()"); COMPARE_DATA(stackModel->index(0, 2, tIdx), fileName+":26"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "foo()"); COMPARE_DATA(stackModel->index(1, 2, tIdx), fileName+":24"); COMPARE_DATA(stackModel->index(2, 0, tIdx), "2"); COMPARE_DATA(stackModel->index(2, 1, tIdx), "foo()"); COMPARE_DATA(stackModel->index(2, 2, tIdx), fileName+":24"); COMPARE_DATA(stackModel->index(19, 0, tIdx), "19"); COMPARE_DATA(stackModel->index(20, 0, tIdx), "20"); stackModel->fetchMoreFrames(); WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 2); QCOMPARE(stackModel->rowCount(tIdx), 41); COMPARE_DATA(stackModel->index(20, 0, tIdx), "20"); COMPARE_DATA(stackModel->index(21, 0, tIdx), "21"); COMPARE_DATA(stackModel->index(22, 0, tIdx), "22"); COMPARE_DATA(stackModel->index(39, 0, tIdx), "39"); COMPARE_DATA(stackModel->index(40, 0, tIdx), "40"); stackModel->fetchMoreFrames(); WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 3); QCOMPARE(stackModel->rowCount(tIdx), 121); COMPARE_DATA(stackModel->index(40, 0, tIdx), "40"); COMPARE_DATA(stackModel->index(41, 0, tIdx), "41"); COMPARE_DATA(stackModel->index(42, 0, tIdx), "42"); COMPARE_DATA(stackModel->index(119, 0, tIdx), "119"); COMPARE_DATA(stackModel->index(120, 0, tIdx), "120"); stackModel->fetchMoreFrames(); WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); COMPARE_DATA(stackModel->index(120, 0, tIdx), "120"); COMPARE_DATA(stackModel->index(121, 0, tIdx), "121"); COMPARE_DATA(stackModel->index(122, 0, tIdx), "122"); COMPARE_DATA(stackModel->index(298, 0, tIdx), "298"); COMPARE_DATA(stackModel->index(298, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(298, 2, tIdx), fileName+":30"); COMPARE_DATA(stackModel->index(299, 0, tIdx), "299"); COMPARE_DATA(stackModel->index(299, 1, tIdx), "__libc_start_main"); COMPARE_DATA(stackModel->index(300, 0, tIdx), "300"); COMPARE_DATA(stackModel->index(300, 1, tIdx), "_start"); stackModel->fetchMoreFrames(); //nothing to fetch, we are at the end WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testStackDeactivateAndActive() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 21); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); QModelIndex tIdx = stackModel->index(0,0); session->stepOut(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 3); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(0, 2, tIdx), m_debugeeFileName+":30"); COMPARE_DATA(stackModel->index(1, 0, tIdx), "1"); COMPARE_DATA(stackModel->index(1, 1, tIdx), "__libc_start_main"); COMPARE_DATA(stackModel->index(2, 0, tIdx), "2"); COMPARE_DATA(stackModel->index(2, 1, tIdx), "_start"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testStackSwitchThread() { QSKIP("Skipping... lldb-mi crashes when break at a location with multiple threads running"); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("lldb_debugeethreads"))); QString fileName = findSourceFile("debugeethreads.cpp"); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->rowCount(), 4); QModelIndex tIdx = stackModel->index(0,0); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 1); COMPARE_DATA(stackModel->index(0, 0, tIdx), "0"); COMPARE_DATA(stackModel->index(0, 1, tIdx), "main"); COMPARE_DATA(stackModel->index(0, 2, tIdx), fileName+":39"); tIdx = stackModel->index(1,0); QVERIFY(stackModel->data(tIdx).toString().startsWith("#2 at ")); stackModel->setCurrentThread(2); WAIT_FOR_A_WHILE(session, 200); int rows = stackModel->rowCount(tIdx); QVERIFY(rows > 3); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testAttach() { SKIP_IF_ATTACH_FORBIDDEN(); QString fileName = findSourceFile("debugeeslow.cpp"); KProcess debugeeProcess; debugeeProcess << QStringLiteral("nice") << findExecutable(QStringLiteral("lldb_debugeeslow")).toLocalFile(); debugeeProcess.start(); QVERIFY(debugeeProcess.waitForStarted()); QTest::qWait(100); TestDebugSession *session = new TestDebugSession; session->attachToProcess(debugeeProcess.pid()); WAIT_FOR_A_WHILE(session, 100); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 35); // lldb-mi sliently stops when attaching to a process. Force it continue to run. session->addCommand(MI::ExecContinue, QString(), MI::CmdMaybeStartsRunning); WAIT_FOR_A_WHILE(session, 2000); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testRemoteDebugging() { KProcess gdbServer; gdbServer << QStringLiteral("lldb-server") << QStringLiteral("gdbserver") << QStringLiteral("*:1234"); gdbServer.start(); QVERIFY(gdbServer.waitForStarted()); TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; cfg.config().writeEntry(Config::LldbRemoteDebuggingEntry, true); cfg.config().writeEntry(Config::LldbRemoteServerEntry, "localhost:1234"); cfg.config().writeEntry(Config::LldbRemotePathEntry, "/tmp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 34); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testCoreFile() { QFile f(QStringLiteral("core")); if (f.exists()) f.remove(); KProcess debugeeProcess; debugeeProcess.setOutputChannelMode(KProcess::MergedChannels); debugeeProcess << QStringLiteral("bash") << QStringLiteral("-c") << "ulimit -c unlimited; " + findExecutable(QStringLiteral("lldb_crash")).toLocalFile(); debugeeProcess.start(); debugeeProcess.waitForFinished(); qDebug() << debugeeProcess.readAll(); bool coreFileFound = QFile::exists(QStringLiteral("core")); if (!coreFileFound) { // Try to use coredumpctl auto coredumpctl = QStandardPaths::findExecutable(QStringLiteral("coredumpctl")); if (!coredumpctl.isEmpty()) { QFileInfo fi(QStringLiteral("core")); KProcess::execute(coredumpctl, {"-1", "-o", fi.absoluteFilePath(), "dump", "lldb_crash"}); coreFileFound = fi.exists(); } } if (!coreFileFound) QSKIP("no core dump found, check your system configuration (see /proc/sys/kernel/core_pattern).", SkipSingle); TestDebugSession *session = new TestDebugSession; session->examineCoreFile(findExecutable(QStringLiteral("lldb_crash")), QUrl::fromLocalFile(QDir::currentPath()+"/core")); TestFrameStackModel *stackModel = session->frameStackModel(); WAIT_FOR_STATE(session, DebugSession::StoppedState); QModelIndex tIdx = stackModel->index(0,0); QCOMPARE(stackModel->rowCount(QModelIndex()), 1); QCOMPARE(stackModel->columnCount(QModelIndex()), 3); COMPARE_DATA(tIdx, "#1 at foo()"); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesLocals() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(variableCollection()->rowCount(), 2); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); COMPARE_DATA(variableCollection()->index(0, 1, i), "2"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesLocalsStruct() { TestDebugSession *session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestLaunchConfiguration cfg; breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 1000); QModelIndex i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); int structIndex = 0; for(int j=0; j<3; ++j) { if (variableCollection()->index(j, 0, i).data().toString() == QLatin1String("ts")) { structIndex = j; } } COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(structIndex, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); WAIT_FOR_A_WHILE(session, 100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 1000); COMPARE_DATA(variableCollection()->index(structIndex, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(structIndex, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesWatches() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; m_core->debugController()->variableCollection()->variableWidgetShown(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(QStringLiteral("ts")); WAIT_FOR_A_WHILE(session, 300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); QModelIndex ts = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, ts), "..."); variableCollection()->expanded(ts); WAIT_FOR_A_WHILE(session, 100); COMPARE_DATA(variableCollection()->index(0, 0, ts), "a"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "0"); COMPARE_DATA(variableCollection()->index(1, 0, ts), "b"); COMPARE_DATA(variableCollection()->index(1, 1, ts), "1"); COMPARE_DATA(variableCollection()->index(2, 0, ts), "c"); COMPARE_DATA(variableCollection()->index(2, 1, ts), "2"); session->stepInto(); WAIT_FOR_STATE(session, DebugSession::PausedState); WAIT_FOR_A_WHILE(session, 100); COMPARE_DATA(variableCollection()->index(0, 0, i), "ts"); COMPARE_DATA(variableCollection()->index(0, 1, i), "{...}"); COMPARE_DATA(variableCollection()->index(0, 1, ts), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesWatchesQuotes() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); // the unquoted string (the actual content): t\"t // quoted string (what we would write as a c string): "t\\\"t" // written in source file: R"("t\\\"t")" const QString testString(QStringLiteral("t\\\"t")); // the actual content const QString quotedTestString(QStringLiteral(R"("t\\\"t")")); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, DebugSession::PausedState); variableCollection()->watches()->add(quotedTestString); //just a constant string WAIT_FOR_A_WHILE(session, 3000); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), quotedTestString); COMPARE_DATA(variableCollection()->index(0, 1, i), quotedTestString); QModelIndex testStr = variableCollection()->index(0, 0, i); COMPARE_DATA(variableCollection()->index(0, 0, testStr), "..."); variableCollection()->expanded(testStr); WAIT_FOR_A_WHILE(session, 100); int len = testString.length(); for (int ind = 0; ind < len; ind++) { COMPARE_DATA(variableCollection()->index(ind, 0, testStr), QStringLiteral("[%0]").arg(ind)); QChar c = testString.at(ind); QString value = QString::number(c.toLatin1()) + " '" + c + "'"; COMPARE_DATA(variableCollection()->index(ind, 1, testStr), value); } COMPARE_DATA(variableCollection()->index(len, 0, testStr), QStringLiteral("[%0]").arg(len)); COMPARE_DATA(variableCollection()->index(len, 1, testStr), "0 '\\0'"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesWatchesTwoSessions() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); variableCollection()->watches()->add(QStringLiteral("ts")); WAIT_FOR_A_WHILE(session, 300); QModelIndex ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); variableCollection()->expanded(ts); WAIT_FOR_A_WHILE(session, 100); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope QCOMPARE(variableCollection()->watches()->childCount(), 1); auto v = dynamic_cast(watchVariableAt(0)); QVERIFY(v); QVERIFY(!v->inScope()); QCOMPARE(v->childCount(), 3); v = dynamic_cast(v->child(0)); QVERIFY(!v->inScope()); //start a second debug session session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(variableCollection()->watches()->childCount(), 1); ts = variableCollection()->index(0, 0, variableCollection()->index(0, 0)); v = dynamic_cast(watchVariableAt(0)); QVERIFY(v); QVERIFY(v->inScope()); QCOMPARE(v->childCount(), 3); v = dynamic_cast(v->child(0)); QVERIFY(v->inScope()); COMPARE_DATA(variableCollection()->indexForItem(v, 1), QString::number(0)); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //check if variable is marked as out-of-scope v = dynamic_cast(watchVariableAt(0)); QVERIFY(!v->inScope()); QVERIFY(!dynamic_cast(v->child(0))->inScope()); } void LldbTest::testVariablesStopDebugger() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesStartSecondSession() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesSwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); // only non-static variable works COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 200); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); COMPARE_DATA(variableCollection()->index(2, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(2, 1, i), "1"); COMPARE_DATA(variableCollection()->index(3, 0, i), "argv"); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testVariablesQuicklySwitchFrame() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QModelIndex i = variableCollection()->index(1, 0); COMPARE_DATA(i, "Locals"); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "j"); // only non-static variable works COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 300); stackModel->setCurrentFrame(0); WAIT_FOR_A_WHILE(session, 1); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 1); stackModel->setCurrentFrame(0); WAIT_FOR_A_WHILE(session, 1); stackModel->setCurrentFrame(1); WAIT_FOR_A_WHILE(session, 500); i = variableCollection()->index(1, 0); QCOMPARE(variableCollection()->rowCount(i), 4); QStringList locs; for (int j = 0; j < variableCollection()->rowCount(i); ++j) { locs << variableCollection()->index(j, 0, i).data().toString(); } QVERIFY(locs.contains("argc")); QVERIFY(locs.contains("argv")); QVERIFY(locs.contains("x")); breakpoints()->removeRow(0); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testSwitchFrameLldbConsole() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; TestFrameStackModel *stackModel = session->frameStackModel(); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 24); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(stackModel->currentFrame(), 0); stackModel->setCurrentFrame(1); QCOMPARE(stackModel->currentFrame(), 1); WAIT_FOR_A_WHILE(session, 500); QCOMPARE(stackModel->currentFrame(), 1); session->addUserCommand(QStringLiteral("print i")); WAIT_FOR_A_WHILE(session, 500); //currentFrame must not reset to 0; Bug 222882 QCOMPARE(stackModel->currentFrame(), 1); } void LldbTest::testSegfaultDebugee() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("lldb_crash"))); session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); QString fileName = findSourceFile("debugeecrash.cpp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 23); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 23); session->run(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 24); session->stopDebugger(); WAIT_FOR_STATE(session, DebugSession::EndedState); } //Bug 274390 void LldbTest::testCommandOrderFastStepping() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable(QStringLiteral("lldb_debugeeqt"))); breakpoints()->addCodeBreakpoint(QStringLiteral("main")); QVERIFY(session->startDebugging(&cfg, m_iface)); for(int i=0; i<20; i++) { session->stepInto(); } WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testRunLldbScript() { TestDebugSession *session = new TestDebugSession; QTemporaryFile runScript; runScript.open(); runScript.write(QStringLiteral("break set --file %1 --line 35\n").arg(findSourceFile("debugee.cpp")).toUtf8()); runScript.close(); TestLaunchConfiguration cfg; KConfigGroup grp = cfg.config(); grp.writeEntry(Config::LldbConfigScriptEntry, QUrl::fromLocalFile(runScript.fileName())); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 35); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testBug301287() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); variableCollection()->watches()->add(QStringLiteral("argc")); WAIT_FOR_A_WHILE(session, 300); QModelIndex i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); //start second debug session (same cfg) session = new TestDebugSession; session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); i = variableCollection()->index(0, 0); QCOMPARE(variableCollection()->rowCount(i), 1); COMPARE_DATA(variableCollection()->index(0, 0, i), "argc"); COMPARE_DATA(variableCollection()->index(0, 1, i), "1"); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void LldbTest::testDebugInExternalTerminal() { TestLaunchConfiguration cfg; foreach (const QString & console, QStringList() << "konsole" << "xterm" << "xfce4-terminal" << "gnome-terminal") { if (QStandardPaths::findExecutable(console).isEmpty()) { continue; } TestDebugSession* session = new TestDebugSession(); cfg.config().writeEntry("External Terminal"/*ExecutePlugin::terminalEntry*/, console); cfg.config().writeEntry("Use External Terminal"/*ExecutePlugin::useTerminalEntry*/, true); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 28); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->stepInto(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } } void LldbTest::testSpecialPath() { #ifndef HAVE_PATH_WITH_SPACES_TEST QSKIP("Skipping... special path test," " this CMake version would create a faulty build.ninja file. Upgrade to at least CMake v3.0"); #endif QSKIP("Skipping... lldb-mi itself can't handle path with space in application dir"); TestDebugSession* session = new TestDebugSession; auto debugee = findExecutable(QStringLiteral("path with space/lldb_spacedebugee")); TestLaunchConfiguration c(debugee, KIO::upUrl(debugee)); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint(QStringLiteral("spacedebugee.cpp:30")); QCOMPARE(b->state(), KDevelop::Breakpoint::NotStartedState); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(b->state(), KDevelop::Breakpoint::CleanState); session->run(); WAIT_FOR_STATE(session, DebugSession::EndedState); } void KDevMI::LLDB::LldbTest::testEnvironmentCd() { TestDebugSession *session = new TestDebugSession; QSignalSpy outputSpy(session, &TestDebugSession::inferiorStdoutLines); auto path = KIO::upUrl(findExecutable(QStringLiteral("path with space/lldb_spacedebugee"))); TestLaunchConfiguration cfg(findExecutable(QStringLiteral("lldb_debugeepath")), path); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE(session, KDevelop::IDebugSession::EndedState); QVERIFY(outputSpy.count() > 0); QStringList outputLines; while (outputSpy.count() > 0) { QList arguments = outputSpy.takeFirst(); for (const auto &item : arguments) { outputLines.append(item.toStringList()); } } QCOMPARE(outputLines, QStringList() << path.toLocalFile()); } QTEST_MAIN(KDevMI::LLDB::LldbTest); #include "test_lldb.moc" diff --git a/documentation/qthelp/qthelpdocumentation.cpp b/documentation/qthelp/qthelpdocumentation.cpp index 5d61aff357..073b1a2b1b 100644 --- a/documentation/qthelp/qthelpdocumentation.cpp +++ b/documentation/qthelp/qthelpdocumentation.cpp @@ -1,352 +1,352 @@ /* This file is part of KDevelop Copyright 2009 Aleix Pol Copyright 2009 David Nolden Copyright 2010 Benjamin Port This library 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 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 "qthelpdocumentation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "qthelpnetwork.h" #include "qthelpproviderabstract.h" using namespace KDevelop; namespace { #if QT_VERSION >= 0x050500 int indexOf(const QString& str, const QRegularExpression& re, int from, QRegularExpressionMatch* rmatch) { return str.indexOf(re, from, rmatch); } int lastIndexOf(const QString& str, const QRegularExpression& re, int from, QRegularExpressionMatch* rmatch) { return str.lastIndexOf(re, from, rmatch); } #else int indexOf(const QString& str, const QRegularExpression& re, int from, QRegularExpressionMatch* rmatch) { if (!re.isValid()) { qWarning("QString::indexOf: invalid QRegularExpression object"); return -1; } QRegularExpressionMatch match = re.match(str, from); if (match.hasMatch()) { const int ret = match.capturedStart(); if (rmatch) *rmatch = qMove(match); return ret; } return -1; } int lastIndexOf(const QString &str, const QRegularExpression &re, int from, QRegularExpressionMatch *rmatch) { if (!re.isValid()) { qWarning("QString::lastIndexOf: invalid QRegularExpression object"); return -1; } int endpos = (from < 0) ? (str.size() + from + 1) : (from + 1); QRegularExpressionMatchIterator iterator = re.globalMatch(str); int lastIndex = -1; while (iterator.hasNext()) { QRegularExpressionMatch match = iterator.next(); int start = match.capturedStart(); if (start < endpos) { lastIndex = start; if (rmatch) *rmatch = qMove(match); } else { break; } } return lastIndex; } #endif } QtHelpProviderAbstract* QtHelpDocumentation::s_provider=nullptr; QtHelpDocumentation::QtHelpDocumentation(const QString& name, const QMap& info) : m_provider(s_provider), m_name(name), m_info(info), m_current(info.constBegin()), lastView(nullptr) {} QtHelpDocumentation::QtHelpDocumentation(const QString& name, const QMap& info, const QString& key) : m_provider(s_provider), m_name(name), m_info(info), m_current(m_info.find(key)), lastView(nullptr) { Q_ASSERT(m_current!=m_info.constEnd()); } QtHelpDocumentation::~QtHelpDocumentation() { } QString QtHelpDocumentation::description() const { QUrl url(m_current.value()); QByteArray data = m_provider->engine()->fileData(url); //Extract a short description from the html data const QString dataString = QString::fromLatin1(data); ///@todo encoding const QString fragment = url.fragment(); const QString p = QStringLiteral("((\\\")|(\\\'))"); const QString optionalSpace = QStringLiteral("( )*"); const QString exp = QString("< a name = " + p + fragment + p + " > < / a >").replace(' ', optionalSpace); const QRegularExpression findFragment(exp); QRegularExpressionMatch findFragmentMatch; int pos = indexOf(dataString, findFragment, 0, &findFragmentMatch); if(fragment.isEmpty()) { pos = 0; } else { //Check if there is a title opening-tag right before the fragment, and if yes add it, so we have a nicely formatted caption const QString titleRegExp = QStringLiteral("< h\\d class = \".*\" >").replace(' ', optionalSpace); const QRegularExpression findTitle(titleRegExp); const QRegularExpressionMatch match = findTitle.match(dataString, pos); const int titleStart = match.capturedStart(); const int titleEnd = titleStart + match.capturedEnd(); if(titleStart != -1) { const QStringRef between = dataString.midRef(titleEnd, pos-titleEnd).trimmed(); if(between.isEmpty()) pos = titleStart; } } if(pos != -1) { const QString exp = QString("< a name = " + p + "((\\S)*)" + p + " > < / a >").replace(' ', optionalSpace); const QRegularExpression nextFragmentExpression(exp); int endPos = dataString.indexOf(nextFragmentExpression, pos+(fragment.size() ? findFragmentMatch.capturedLength() : 0)); if(endPos == -1) { endPos = dataString.size(); } { //Find the end of the last paragraph or newline, so we don't add prefixes of the following fragment const QString newLineRegExp = QStringLiteral ("< br / > | < / p >").replace(' ', optionalSpace); const QRegularExpression lastNewLine(newLineRegExp); QRegularExpressionMatch match; const int newEnd = lastIndexOf(dataString, lastNewLine, endPos, &match); if(match.isValid() && newEnd > pos) endPos = newEnd + match.capturedLength(); } { //Find the title, and start from there const QString titleRegExp = QStringLiteral("< h\\d class = \"title\" >").replace(' ', optionalSpace); const QRegularExpression findTitle(titleRegExp); const QRegularExpressionMatch match = findTitle.match(dataString); if (match.isValid()) pos = qBound(pos, match.capturedStart(), endPos); } QString thisFragment = dataString.mid(pos, endPos - pos); { //Completely remove the first large header found, since we don't need a header const QString headerRegExp = QStringLiteral("< h\\d.*>.*?< / h\\d >").replace(' ', optionalSpace); const QRegularExpression findHeader(headerRegExp); const QRegularExpressionMatch match = findHeader.match(thisFragment); if(match.isValid()) { thisFragment.remove(match.capturedStart(), match.capturedLength()); } } { //Replace all gigantic header-font sizes with { const QString sizeRegExp = QStringLiteral("< h\\d ").replace(' ', optionalSpace); const QRegularExpression findSize(sizeRegExp); - thisFragment.replace(findSize, QLatin1String("").replace(' ', optionalSpace); const QRegularExpression closeSize(sizeCloseRegExp); - thisFragment.replace(closeSize, QLatin1String("
")); + thisFragment.replace(closeSize, QStringLiteral("

")); } } { //Replace paragraphs by newlines const QString begin = QStringLiteral("< p >").replace(' ', optionalSpace); const QRegularExpression findBegin(begin); thisFragment.replace(findBegin, {}); const QString end = QStringLiteral("< /p >").replace(' ', optionalSpace); const QRegularExpression findEnd(end); - thisFragment.replace(findEnd, QLatin1String("
")); + thisFragment.replace(findEnd, QStringLiteral("
")); } { //Remove links, because they won't work const QString link = QString("< a href = " + p + ".*?" + p).replace(' ', optionalSpace); const QRegularExpression exp(link, QRegularExpression::CaseInsensitiveOption); - thisFragment.replace(exp, QLatin1String("open(); QTextStream ts(file); ts << "html { background: white !important; }\n"; if (url.scheme() == QLatin1String("qthelp") && url.host().startsWith(QLatin1String("com.trolltech.qt."))) { ts << ".content .toc + .title + p { clear:left; }\n" << "#qtdocheader .qtref { position: absolute !important; top: 5px !important; right: 0 !important; }\n"; } file->close(); view->setOverrideCss(QUrl::fromLocalFile(file->fileName())); delete m_lastStyleSheet.data(); m_lastStyleSheet = file; } QWidget* QtHelpDocumentation::documentationWidget(DocumentationFindWidget* findWidget, QWidget* parent) { if(m_info.isEmpty()) { //QtHelp sometimes has empty info maps. e.g. availableaudioeffects i 4.5.2 return new QLabel(i18n("Could not find any documentation for '%1'", m_name), parent); } else { StandardDocumentationView* view = new StandardDocumentationView(findWidget, parent); view->initZoom(m_provider->name()); if (!m_sharedQNAM) { m_sharedQNAM.reset(new HelpNetworkAccessManager(m_provider->engine())); } view->setNetworkAccessManager(m_sharedQNAM.data()); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(view, &StandardDocumentationView::customContextMenuRequested, this, &QtHelpDocumentation::viewContextMenuRequested); setUserStyleSheet(view, m_current.value()); view->load(m_current.value()); lastView = view; return view; } } void QtHelpDocumentation::viewContextMenuRequested(const QPoint& pos) { StandardDocumentationView* view = qobject_cast(sender()); if (!view) return; QMenu menu; QAction* copyAction = view->copyAction(); copyAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); menu.addAction(copyAction); if (m_info.count() > 1) { menu.addSeparator(); QActionGroup* actionGroup = new QActionGroup(&menu); foreach(const QString& name, m_info.keys()) { QtHelpAlternativeLink* act=new QtHelpAlternativeLink(name, this, actionGroup); act->setCheckable(true); act->setChecked(name==m_current.key()); menu.addAction(act); } } menu.exec(view->mapToGlobal(pos)); } void QtHelpDocumentation::jumpedTo(const QUrl& newUrl) { Q_ASSERT(lastView); m_provider->jumpedTo(newUrl); setUserStyleSheet(lastView, newUrl); lastView->load(newUrl); } IDocumentationProvider* QtHelpDocumentation::provider() const { return m_provider; } QtHelpAlternativeLink::QtHelpAlternativeLink(const QString& name, const QtHelpDocumentation* doc, QObject* parent) : QAction(name, parent), mDoc(doc), mName(name) { connect(this, &QtHelpAlternativeLink::triggered, this, &QtHelpAlternativeLink::showUrl); } void QtHelpAlternativeLink::showUrl() { IDocumentation::Ptr newDoc(new QtHelpDocumentation(mName, mDoc->info(), mName)); ICore::self()->documentationController()->showDocumentation(newDoc); } HomeDocumentation::HomeDocumentation() : m_provider(QtHelpDocumentation::s_provider) { } QWidget* HomeDocumentation::documentationWidget(DocumentationFindWidget*, QWidget* parent) { QTreeView* w=new QTreeView(parent); w->header()->setVisible(false); w->setModel(m_provider->engine()->contentModel()); connect(w, &QTreeView::clicked, this, &HomeDocumentation::clicked); return w; } void HomeDocumentation::clicked(const QModelIndex& idx) { QHelpContentModel* model = m_provider->engine()->contentModel(); QHelpContentItem* it=model->contentItemAt(idx); QMap info; info.insert(it->title(), it->url()); IDocumentation::Ptr newDoc(new QtHelpDocumentation(it->title(), info)); ICore::self()->documentationController()->showDocumentation(newDoc); } QString HomeDocumentation::name() const { return i18n("QtHelp Home Page"); } IDocumentationProvider* HomeDocumentation::provider() const { return m_provider; } diff --git a/documentation/qthelp/qthelpnetwork.h b/documentation/qthelp/qthelpnetwork.h index 4119899350..41cbc93781 100644 --- a/documentation/qthelp/qthelpnetwork.h +++ b/documentation/qthelp/qthelpnetwork.h @@ -1,113 +1,113 @@ /* This file is part of KDevelop Copyright 2009 Aleix Pol This library 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 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. */ #ifndef QTHELPNETWORK_H #define QTHELPNETWORK_H #include "debug.h" #include #include #include #include #include class HelpNetworkReply : public QNetworkReply { public: HelpNetworkReply(const QNetworkRequest &request, const QByteArray &fileData, const QString &mimeType); void abort() override {} qint64 bytesAvailable() const override { return data.length() + QNetworkReply::bytesAvailable(); } protected: qint64 readData(char *data, qint64 maxlen) override; private: QByteArray data; qint64 origLen; }; HelpNetworkReply::HelpNetworkReply(const QNetworkRequest &request, const QByteArray &fileData, const QString &mimeType) : data(fileData), origLen(fileData.length()) { setRequest(request); setOpenMode(QIODevice::ReadOnly); // Instantly finish processing if data is empty. Without this code the loadFinished() // signal will never be emitted by the corresponding QWebView. if (!origLen) { qCDebug(QTHELP) << "Empty data for" << request.url().toDisplayString(); QTimer::singleShot(0, this, &QNetworkReply::finished); } // Fix broken CSS images (tested on Qt 5.5.1 and 5.7.0) if (request.url().fileName() == QStringLiteral("offline.css")) { data.replace("../images", "images"); } setHeader(QNetworkRequest::ContentTypeHeader, mimeType); setHeader(QNetworkRequest::ContentLengthHeader, QByteArray::number(origLen)); QTimer::singleShot(0, this, &QNetworkReply::metaDataChanged); QTimer::singleShot(0, this, &QIODevice::readyRead); } qint64 HelpNetworkReply::readData(char *buffer, qint64 maxlen) { qint64 len = qMin(qint64(data.length()), maxlen); if (len) { memcpy(buffer, data.constData(), len); data.remove(0, len); } if (!data.length()) QTimer::singleShot(0, this, &QNetworkReply::finished); return len; } class HelpNetworkAccessManager : public QNetworkAccessManager { public: explicit HelpNetworkAccessManager(QHelpEngineCore *engine, QObject *parent = nullptr) : QNetworkAccessManager(parent), m_helpEngine(engine) {} protected: virtual QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData = nullptr) override; private: QHelpEngineCore *m_helpEngine; }; QNetworkReply *HelpNetworkAccessManager::createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) { QString scheme = request.url().scheme(); if (scheme == QLatin1String("qthelp") || scheme == QLatin1String("about")) { QString mimeType = QMimeDatabase().mimeTypeForUrl(request.url()).name(); if (mimeType == QLatin1String("application/x-extension-html")) { // see also: https://bugs.kde.org/show_bug.cgi?id=288277 // firefox seems to add this bullshit mimetype above // which breaks displaying of qthelp documentation :( - mimeType = QLatin1String("text/html"); + mimeType = QStringLiteral("text/html"); } return new HelpNetworkReply(request, m_helpEngine->fileData(request.url()), mimeType); } return QNetworkAccessManager::createRequest(op, request, outgoingData); } #endif diff --git a/formatters/astyle/astyle_formatter.cpp b/formatters/astyle/astyle_formatter.cpp index 1f904a4358..8cb23fd8a1 100644 --- a/formatters/astyle/astyle_formatter.cpp +++ b/formatters/astyle/astyle_formatter.cpp @@ -1,527 +1,527 @@ /* This file is part of KDevelop * Copyright (C) 2008 Cédric Pasteur Copyright (C) 2001 Matthias Hölzer-Klüpfel 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 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "astyle_formatter.h" #include #include #include #include "astyle_stringiterator.h" #include "debug.h" using namespace KDevelop; AStyleFormatter::AStyleFormatter() : ASFormatter() { } QString AStyleFormatter::formatSource(const QString &text, const QString& leftContext, const QString& rightContext) { QString useText = leftContext + text + rightContext; AStyleStringIterator is(useText); QString output; QTextStream os(&output, QIODevice::WriteOnly); init(&is); while(hasMoreLines()) os << QString::fromUtf8(nextLine().c_str()) << endl; init(nullptr); return extractFormattedTextFromContext(output, text, leftContext, rightContext, m_options[QStringLiteral("FillCount")].toInt()); } void AStyleFormatter::setOption(const QString &key, const QVariant &value) { m_options[key] = value; } void AStyleFormatter::updateFormatter() { qCDebug(ASTYLE) << "Updating option with: " << ISourceFormatter::optionMapToString(m_options) << endl; // fill int wsCount = m_options[QStringLiteral("FillCount")].toInt(); if(m_options[QStringLiteral("Fill")].toString() == QLatin1String("Tabs")) { ///TODO: rename FillForce somehow... bool force = m_options[QStringLiteral("FillForce")].toBool(); AStyleFormatter::setTabSpaceConversionMode(false); AStyleFormatter::setTabIndentation(wsCount, force ); - m_indentString = QLatin1String("\t"); + m_indentString = QStringLiteral("\t"); } else { AStyleFormatter::setSpaceIndentation(wsCount); m_indentString = QLatin1String(""); m_indentString.fill(' ', wsCount); AStyleFormatter::setTabSpaceConversionMode(m_options[QStringLiteral("FillForce")].toBool()); } AStyleFormatter::setEmptyLineFill(m_options[QStringLiteral("Fill_EmptyLines")].toBool()); // indent AStyleFormatter::setSwitchIndent(m_options[QStringLiteral("IndentSwitches")].toBool()); AStyleFormatter::setClassIndent(m_options[QStringLiteral("IndentClasses")].toBool()); AStyleFormatter::setCaseIndent(m_options[QStringLiteral("IndentCases")].toBool()); AStyleFormatter::setBracketIndent(m_options[QStringLiteral("IndentBrackets")].toBool()); AStyleFormatter::setNamespaceIndent(m_options[QStringLiteral("IndentNamespaces")].toBool()); AStyleFormatter::setLabelIndent(m_options[QStringLiteral("IndentLabels")].toBool()); AStyleFormatter::setBlockIndent(m_options[QStringLiteral("IndentBlocks")].toBool()); AStyleFormatter::setPreprocessorIndent(m_options[QStringLiteral("IndentPreprocessors")].toBool()); // continuation AStyleFormatter::setMaxInStatementIndentLength(m_options[QStringLiteral("MaxStatement")].toInt()); if(m_options[QStringLiteral("MinConditional")].toInt() != -1) AStyleFormatter::setMinConditionalIndentLength(m_options[QStringLiteral("MinConditional")].toInt()); // brackets QString s = m_options[QStringLiteral("Brackets")].toString(); if(s == QLatin1String("Break")) AStyleFormatter::setBracketFormatMode(astyle::BREAK_MODE); else if(s == QLatin1String("Attach")) AStyleFormatter::setBracketFormatMode(astyle::ATTACH_MODE); else if(s == QLatin1String("Linux")) AStyleFormatter::setBracketFormatMode(astyle::LINUX_MODE); else if(s == QLatin1String("Stroustrup")) AStyleFormatter::setBracketFormatMode(astyle::STROUSTRUP_MODE); else if(s == QLatin1String("Horstmann") || s == QLatin1String("RunInMode")) AStyleFormatter::setBracketFormatMode(astyle::RUN_IN_MODE); else AStyleFormatter::setBracketFormatMode(astyle::NONE_MODE); AStyleFormatter::setBreakClosingHeaderBracketsMode(m_options[QStringLiteral("BracketsCloseHeaders")].toBool()); // blocks AStyleFormatter::setBreakBlocksMode(m_options[QStringLiteral("BlockBreak")].toBool()); AStyleFormatter::setBreakClosingHeaderBlocksMode(m_options[QStringLiteral("BlockBreakAll")].toBool()); AStyleFormatter::setBreakElseIfsMode(m_options[QStringLiteral("BlockIfElse")].toBool()); // padding AStyleFormatter::setOperatorPaddingMode(m_options[QStringLiteral("PadOperators")].toBool()); AStyleFormatter::setParensInsidePaddingMode(m_options[QStringLiteral("PadParenthesesIn")].toBool()); AStyleFormatter::setParensOutsidePaddingMode(m_options[QStringLiteral("PadParenthesesOut")].toBool()); AStyleFormatter::setParensHeaderPaddingMode(m_options[QStringLiteral("PadParenthesesHeader")].toBool()); AStyleFormatter::setParensUnPaddingMode(m_options[QStringLiteral("PadParenthesesUn")].toBool()); // oneliner AStyleFormatter::setBreakOneLineBlocksMode(!m_options[QStringLiteral("KeepBlocks")].toBool()); AStyleFormatter::setSingleStatementsMode(!m_options[QStringLiteral("KeepStatements")].toBool()); // pointer s = m_options[QStringLiteral("PointerAlign")].toString(); if(s == QLatin1String("Name")) AStyleFormatter::setPointerAlignment(astyle::PTR_ALIGN_NAME); else if(s == QLatin1String("Middle")) AStyleFormatter::setPointerAlignment(astyle::PTR_ALIGN_MIDDLE); else if(s == QLatin1String("Type")) AStyleFormatter::setPointerAlignment(astyle::PTR_ALIGN_TYPE); else AStyleFormatter::setPointerAlignment(astyle::PTR_ALIGN_NONE); } void AStyleFormatter::resetStyle() { setSpaceIndentation(4); setBracketFormatMode(astyle::NONE_MODE); setBreakOneLineBlocksMode(true); setSingleStatementsMode(true); // blocks setBreakBlocksMode(false); setBreakClosingHeaderBlocksMode(false); setBreakElseIfsMode(false); setBreakClosingHeaderBracketsMode(false); //indent setTabIndentation(4, false); setEmptyLineFill(false); setMaxInStatementIndentLength(40); setMinConditionalIndentLength(-1); setSwitchIndent(true); setClassIndent(true); setCaseIndent(false); setBracketIndent(false); setNamespaceIndent(true); setLabelIndent(true); setBlockIndent(false); setPreprocessorIndent(false); //padding setOperatorPaddingMode(false); setParensInsidePaddingMode(true); setParensOutsidePaddingMode(true); setParensHeaderPaddingMode(true); setParensUnPaddingMode(true); } bool AStyleFormatter::predefinedStyle( const QString & style ) { if(style == QLatin1String("ANSI")) { resetStyle(); setBracketIndent(false); setSpaceIndentation(4); setBracketFormatMode(astyle::BREAK_MODE); setClassIndent(false); setSwitchIndent(false); setNamespaceIndent(false); return true; } else if(style == QLatin1String("K&R")) { resetStyle(); setBracketIndent(false); setSpaceIndentation(4); setBracketFormatMode(astyle::ATTACH_MODE); setClassIndent(false); setSwitchIndent(false); setNamespaceIndent(false); return true; } else if(style == QLatin1String("Linux")) { resetStyle(); setBracketIndent(false); setSpaceIndentation(8); setBracketFormatMode(astyle::LINUX_MODE); setClassIndent(false); setSwitchIndent(false); setNamespaceIndent(false); return true; } else if(style == QLatin1String("GNU")) { resetStyle(); setBlockIndent(true); setSpaceIndentation(2); setBracketFormatMode(astyle::BREAK_MODE); setClassIndent(false); setSwitchIndent(false); setNamespaceIndent(false); return true; } else if(style == QLatin1String("Java")) { resetStyle(); setJavaStyle(); setBracketIndent(false); setSpaceIndentation(4); setBracketFormatMode(astyle::ATTACH_MODE); setSwitchIndent(false); return true; } else if (style == QLatin1String("Stroustrup")) { resetStyle(); setBracketFormatMode(astyle::STROUSTRUP_MODE); setBlockIndent(false); setBracketIndent(false); setSpaceIndentation(5); setClassIndent(false); setSwitchIndent(false); setNamespaceIndent(false); return true; } else if (style == QLatin1String("Horstmann")) { resetStyle(); setBracketFormatMode(astyle::RUN_IN_MODE); setBlockIndent(false); setBracketIndent(false); setSwitchIndent(true); setSpaceIndentation(3); setClassIndent(false); setNamespaceIndent(false); return true; } else if (style == QLatin1String("Whitesmith")) { resetStyle(); setSpaceIndentation(4); setBracketFormatMode(astyle::BREAK_MODE); setBlockIndent(false); setBracketIndent(true); setClassIndent(true); setSwitchIndent(true); setNamespaceIndent(false); return true; } else if (style == QLatin1String("Banner")) { resetStyle(); setSpaceIndentation(4); setBracketFormatMode(astyle::ATTACH_MODE); setBlockIndent(false); setBracketIndent(true); setClassIndent(true); setSwitchIndent(true); setNamespaceIndent(false); return true; } else if (style == QLatin1String("1TBS")) { resetStyle(); setSpaceIndentation(4); setBracketFormatMode(astyle::LINUX_MODE); setBlockIndent(false); setBracketIndent(false); setAddBracketsMode(true); setClassIndent(false); setSwitchIndent(false); setNamespaceIndent(false); return true; } else if (style == QLatin1String("KDELibs")) { // http://techbase.kde.org/Policies/Kdelibs_Coding_Style resetStyle(); setSpaceIndentation(4); setBracketFormatMode(astyle::LINUX_MODE); setPointerAlignment(astyle::PTR_ALIGN_TYPE); setLabelIndent(true); setOperatorPaddingMode(true); setParensInsidePaddingMode(false); setParensOutsidePaddingMode(false); setParensHeaderPaddingMode(true); setParensUnPaddingMode(true); setSingleStatementsMode(false); setTabSpaceConversionMode(true); setPreprocessorIndent(true); setSwitchIndent(false); setClassIndent(false); setNamespaceIndent(false); return true; } else if (style == QLatin1String("Qt")) { // https://wiki.qt.io/Qt_Coding_Style resetStyle(); setPointerAlignment(astyle::PTR_ALIGN_NAME); setOperatorPaddingMode(true); setBracketFormatMode(astyle::LINUX_MODE); setSwitchIndent(false); setParensInsidePaddingMode(false); setParensOutsidePaddingMode(false); setParensHeaderPaddingMode(true); setParensUnPaddingMode(true); setSpaceIndentation(4); setClassIndent(false); setNamespaceIndent(false); return true; } return false; } QVariant AStyleFormatter::option(const QString &key) { if(!m_options.contains(key)) qCDebug(ASTYLE) << "Missing option name " << key << endl; return m_options[key]; } QString AStyleFormatter::indentString() { return QString(getIndentString().c_str()); } void AStyleFormatter::loadStyle(const QString &content) { m_options = ISourceFormatter::stringToOptionMap(content); updateFormatter(); } QString AStyleFormatter::saveStyle() { return ISourceFormatter::optionMapToString(m_options); } void AStyleFormatter::setTabIndentation(int length, bool forceTabs) { ASFormatter::setTabIndentation(length, forceTabs); m_options[QStringLiteral("Fill")] = "Tabs"; m_options[QStringLiteral("FillForce")] = forceTabs; m_options[QStringLiteral("FillCount")] = length; } void AStyleFormatter::setSpaceIndentation(int length) { ASFormatter::setSpaceIndentation(length); m_options[QStringLiteral("Fill")] = "Spaces"; m_options[QStringLiteral("FillCount")] = length; } void AStyleFormatter::setTabSpaceConversionMode(bool mode) { m_options[QStringLiteral("FillForce")] = mode; ASFormatter::setTabSpaceConversionMode(mode); } void AStyleFormatter::setFillEmptyLines(bool on) { m_options[QStringLiteral("FillEmptyLines")] = on; ASFormatter::setEmptyLineFill(on); } void AStyleFormatter::setBlockIndent(bool on) { m_options[QStringLiteral("IndentBlocks")] = on; ASFormatter::setBlockIndent(on); } void AStyleFormatter::setBracketIndent(bool on) { m_options[QStringLiteral("IndentBrackets")] = on; ASFormatter::setBracketIndent(on); } void AStyleFormatter::setCaseIndent(bool on) { m_options[QStringLiteral("IndentCases")] = on; ASFormatter::setCaseIndent(on); } void AStyleFormatter::setClassIndent(bool on) { m_options[QStringLiteral("IndentClasses")] = on; ASFormatter::setClassIndent(on); } void AStyleFormatter::setLabelIndent(bool on) { m_options[QStringLiteral("IndentLabels")] = on; ASFormatter::setLabelIndent(on); } void AStyleFormatter::setNamespaceIndent(bool on) { m_options[QStringLiteral("IndentNamespaces")] = on; ASFormatter::setNamespaceIndent(on); } void AStyleFormatter::setPreprocessorIndent(bool on) { m_options[QStringLiteral("IndentPreprocessors")] = on; ASFormatter::setPreprocDefineIndent(on); } void AStyleFormatter::setSwitchIndent(bool on) { m_options[QStringLiteral("IndentSwitches")] = on; ASFormatter::setSwitchIndent(on); } void AStyleFormatter::setMaxInStatementIndentLength(int max) { m_options[QStringLiteral("MaxStatement")] = max; ASFormatter::setMaxInStatementIndentLength(max); } void AStyleFormatter::setMinConditionalIndentLength(int min) { m_options[QStringLiteral("MinConditional")] = min; ASFormatter::setMinConditionalIndentOption(min); ASFormatter::setMinConditionalIndentLength(); } void AStyleFormatter::setBracketFormatMode(astyle::BracketMode mode) { switch (mode) { case astyle::NONE_MODE: m_options[QStringLiteral("Brackets")] = ""; break; case astyle::ATTACH_MODE: m_options[QStringLiteral("Brackets")] = "Attach"; break; case astyle::BREAK_MODE: m_options[QStringLiteral("Brackets")] = "Break"; break; case astyle::LINUX_MODE: m_options[QStringLiteral("Brackets")] = "Linux"; break; case astyle::STROUSTRUP_MODE: m_options[QStringLiteral("Brackets")] = "Stroustrup"; break; case astyle::RUN_IN_MODE: m_options[QStringLiteral("Brackets")] = "RunInMode"; break; } ASFormatter::setBracketFormatMode(mode); } void AStyleFormatter::setBreakClosingHeaderBracketsMode(bool state) { m_options[QStringLiteral("BracketsCloseHeaders")] = state; ASFormatter::setBreakClosingHeaderBracketsMode(state); } void AStyleFormatter::setBreakBlocksMode(bool state) { m_options[QStringLiteral("BlockBreak")] = state; ASFormatter::setBreakBlocksMode(state); } void AStyleFormatter::setBreakElseIfsMode(bool state) { m_options[QStringLiteral("BlockIfElse")] = state; ASFormatter::setBreakElseIfsMode(state); } void AStyleFormatter::setBreakClosingHeaderBlocksMode(bool state) { m_options[QStringLiteral("BlockBreakAll")] = state; ASFormatter::setBreakClosingHeaderBlocksMode(state); } void AStyleFormatter::setOperatorPaddingMode(bool mode) { m_options[QStringLiteral("PadOperators")] = mode; ASFormatter::setOperatorPaddingMode(mode); } void AStyleFormatter::setParensOutsidePaddingMode(bool mode) { m_options[QStringLiteral("PadParenthesesOut")] = mode; ASFormatter::setParensOutsidePaddingMode(mode); } void AStyleFormatter::setParensInsidePaddingMode(bool mode) { m_options[QStringLiteral("PadParenthesesIn")] = mode; ASFormatter::setParensInsidePaddingMode(mode); } void AStyleFormatter::setParensHeaderPaddingMode(bool mode) { m_options[QStringLiteral("PadParenthesesHeader")] = mode; ASFormatter::setParensHeaderPaddingMode(mode); } void AStyleFormatter::setParensUnPaddingMode(bool state) { m_options[QStringLiteral("PadParenthesesUn")] = state; ASFormatter::setParensUnPaddingMode(state); } void AStyleFormatter::setBreakOneLineBlocksMode(bool state) { m_options[QStringLiteral("KeepBlocks")] = !state; ASFormatter::setBreakOneLineBlocksMode(state); } void AStyleFormatter::setSingleStatementsMode(bool state) { m_options[QStringLiteral("KeepStatements")] = !state; ASFormatter::setSingleStatementsMode(state); } void AStyleFormatter::setPointerAlignment(astyle::PointerAlign alignment) { switch (alignment) { case astyle::PTR_ALIGN_NONE: m_options[QStringLiteral("PointerAlign")] = "None"; break; case astyle::PTR_ALIGN_NAME: m_options[QStringLiteral("PointerAlign")] = "Name"; break; case astyle::PTR_ALIGN_MIDDLE: m_options[QStringLiteral("PointerAlign")] = "Middle"; break; case astyle::PTR_ALIGN_TYPE: m_options[QStringLiteral("PointerAlign")] = "Type"; break; } ASFormatter::setPointerAlignment(alignment); } diff --git a/formatters/astyle/lib/ASFormatter.cpp b/formatters/astyle/lib/ASFormatter.cpp old mode 100755 new mode 100644 index 06f11bd40d..1385d4904f --- a/formatters/astyle/lib/ASFormatter.cpp +++ b/formatters/astyle/lib/ASFormatter.cpp @@ -1,6590 +1,6590 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ASFormatter.cpp * * Copyright (C) 2006-2013 by Jim Pattee * Copyright (C) 1998-2002 by Tal Davidson * * * This file is a part of Artistic Style - an indentation and * reformatting tool for C, C++, C# and Java source files. * * * Artistic Style is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Artistic Style 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Artistic Style. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "astyle.h" #include #include namespace astyle { /** * Constructor of ASFormatter */ ASFormatter::ASFormatter() { sourceIterator = NULL; enhancer = new ASEnhancer; preBracketHeaderStack = NULL; bracketTypeStack = NULL; parenStack = NULL; structStack = NULL; questionMarkStack = NULL; lineCommentNoIndent = false; formattingStyle = STYLE_NONE; bracketFormatMode = NONE_MODE; pointerAlignment = PTR_ALIGN_NONE; referenceAlignment = REF_SAME_AS_PTR; objCColonPadMode = COLON_PAD_NO_CHANGE; lineEnd = LINEEND_DEFAULT; maxCodeLength = string::npos; shouldPadOperators = false; shouldPadParensOutside = false; shouldPadFirstParen = false; shouldPadParensInside = false; shouldPadHeader = false; shouldStripCommentPrefix = false; shouldUnPadParens = false; shouldAttachClosingBracket = false; shouldBreakOneLineBlocks = true; shouldBreakOneLineStatements = true; shouldConvertTabs = false; shouldIndentCol1Comments = false; shouldCloseTemplates = false; shouldAttachExternC = false; shouldAttachNamespace = false; shouldAttachClass = false; shouldAttachInline = false; shouldBreakBlocks = false; shouldBreakClosingHeaderBlocks = false; shouldBreakClosingHeaderBrackets = false; shouldDeleteEmptyLines = false; shouldBreakElseIfs = false; shouldBreakLineAfterLogical = false; shouldAddBrackets = false; shouldAddOneLineBrackets = false; shouldRemoveBrackets = false; shouldPadMethodColon = false; shouldPadMethodPrefix = false; shouldUnPadMethodPrefix = false; // initialize ASFormatter member vectors formatterFileType = 9; // reset to an invalid type headers = new vector; nonParenHeaders = new vector; preDefinitionHeaders = new vector; preCommandHeaders = new vector; operators = new vector; assignmentOperators = new vector; castOperators = new vector; } /** * Destructor of ASFormatter */ ASFormatter::~ASFormatter() { // delete ASFormatter stack vectors deleteContainer(preBracketHeaderStack); deleteContainer(bracketTypeStack); deleteContainer(parenStack); deleteContainer(structStack); deleteContainer(questionMarkStack); // delete ASFormatter member vectors formatterFileType = 9; // reset to an invalid type delete headers; delete nonParenHeaders; delete preDefinitionHeaders; delete preCommandHeaders; delete operators; delete assignmentOperators; delete castOperators; // delete ASBeautifier member vectors // must be done when the ASFormatter object is deleted (not ASBeautifier) ASBeautifier::deleteBeautifierVectors(); delete enhancer; } /** * initialize the ASFormatter. * * init() should be called every time a ASFormatter object is to start * formatting a NEW source file. * init() receives a pointer to a ASSourceIterator object that will be * used to iterate through the source code. * * @param sourceIterator a pointer to the ASSourceIterator or ASStreamIterator object. */ void ASFormatter::init(ASSourceIterator* si) { buildLanguageVectors(); fixOptionVariableConflicts(); ASBeautifier::init(si); enhancer->init(getFileType(), getIndentLength(), getTabLength(), getIndentString() == "\t" ? true : false, getForceTabIndentation(), getCaseIndent(), getPreprocDefineIndent(), getEmptyLineFill()); sourceIterator = si; initContainer(preBracketHeaderStack, new vector); initContainer(parenStack, new vector); initContainer(structStack, new vector); initContainer(questionMarkStack, new vector); parenStack->push_back(0); // parenStack must contain this default entry initContainer(bracketTypeStack, new vector); bracketTypeStack->push_back(NULL_TYPE); // bracketTypeStack must contain this default entry clearFormattedLineSplitPoints(); currentHeader = NULL; currentLine = ""; readyFormattedLine = ""; formattedLine = ""; currentChar = ' '; previousChar = ' '; previousCommandChar = ' '; previousNonWSChar = ' '; quoteChar = '"'; charNum = 0; checksumIn = 0; checksumOut = 0; currentLineFirstBracketNum = string::npos; formattedLineCommentNum = 0; leadingSpaces = 0; previousReadyFormattedLineLength = string::npos; preprocBracketTypeStackSize = 0; spacePadNum = 0; nextLineSpacePadNum = 0; templateDepth = 0; traceLineNumber = 0; squareBracketCount = 0; horstmannIndentChars = 0; tabIncrementIn = 0; previousBracketType = NULL_TYPE; previousOperator = NULL; isVirgin = true; isInLineComment = false; isInComment = false; isInCommentStartLine = false; noTrimCommentContinuation = false; isInPreprocessor = false; isInPreprocessorBeautify = false; doesLineStartComment = false; lineEndsInCommentOnly = false; lineIsLineCommentOnly = false; lineIsEmpty = false; isImmediatelyPostCommentOnly = false; isImmediatelyPostEmptyLine = false; isInQuote = false; isInVerbatimQuote = false; haveLineContinuationChar = false; isInQuoteContinuation = false; isHeaderInMultiStatementLine = false; isSpecialChar = false; isNonParenHeader = false; foundNamespaceHeader = false; foundClassHeader = false; foundStructHeader = false; foundInterfaceHeader = false; foundPreDefinitionHeader = false; foundPreCommandHeader = false; foundPreCommandMacro = false; foundCastOperator = false; foundQuestionMark = false; isInLineBreak = false; endOfAsmReached = false; endOfCodeReached = false; isInEnum = false; isInExecSQL = false; isInAsm = false; isInAsmOneLine = false; isInAsmBlock = false; isLineReady = false; elseHeaderFollowsComments = false; caseHeaderFollowsComments = false; isPreviousBracketBlockRelated = false; isInPotentialCalculation = false; shouldReparseCurrentChar = false; needHeaderOpeningBracket = false; shouldBreakLineAtNextChar = false; shouldKeepLineUnbroken = false; passedSemicolon = false; passedColon = false; isImmediatelyPostNonInStmt = false; isCharImmediatelyPostNonInStmt = false; isInTemplate = false; isImmediatelyPostComment = false; isImmediatelyPostLineComment = false; isImmediatelyPostEmptyBlock = false; isImmediatelyPostPreprocessor = false; isImmediatelyPostReturn = false; isImmediatelyPostThrow = false; isImmediatelyPostOperator = false; isImmediatelyPostTemplate = false; isImmediatelyPostPointerOrReference = false; isCharImmediatelyPostReturn = false; isCharImmediatelyPostThrow = false; isCharImmediatelyPostOperator = false; isCharImmediatelyPostComment = false; isPreviousCharPostComment = false; isCharImmediatelyPostLineComment = false; isCharImmediatelyPostOpenBlock = false; isCharImmediatelyPostCloseBlock = false; isCharImmediatelyPostTemplate = false; isCharImmediatelyPostPointerOrReference = false; isInObjCMethodDefinition = false; isInObjCInterface = false; isInObjCSelector = false; breakCurrentOneLineBlock = false; shouldRemoveNextClosingBracket = false; isInHorstmannRunIn = false; currentLineBeginsWithBracket = false; isPrependPostBlockEmptyLineRequested = false; isAppendPostBlockEmptyLineRequested = false; prependEmptyLine = false; appendOpeningBracket = false; foundClosingHeader = false; isImmediatelyPostHeader = false; isInHeader = false; isInCase = false; isJavaStaticConstructor = false; } /** * build vectors for each programming language * depending on the file extension. */ void ASFormatter::buildLanguageVectors() { if (getFileType() == formatterFileType) // don't build unless necessary return; formatterFileType = getFileType(); headers->clear(); nonParenHeaders->clear(); preDefinitionHeaders->clear(); preCommandHeaders->clear(); operators->clear(); assignmentOperators->clear(); castOperators->clear(); ASResource::buildHeaders(headers, getFileType()); ASResource::buildNonParenHeaders(nonParenHeaders, getFileType()); ASResource::buildPreDefinitionHeaders(preDefinitionHeaders, getFileType()); ASResource::buildPreCommandHeaders(preCommandHeaders, getFileType()); if (operators->empty()) ASResource::buildOperators(operators, getFileType()); if (assignmentOperators->empty()) ASResource::buildAssignmentOperators(assignmentOperators); if (castOperators->empty()) ASResource::buildCastOperators(castOperators); } /** * set the variables for each predefined style. * this will override any previous settings. */ void ASFormatter::fixOptionVariableConflicts() { if (formattingStyle == STYLE_ALLMAN) { setBracketFormatMode(BREAK_MODE); } else if (formattingStyle == STYLE_JAVA) { setBracketFormatMode(ATTACH_MODE); } else if (formattingStyle == STYLE_KR) { setBracketFormatMode(LINUX_MODE); } else if (formattingStyle == STYLE_STROUSTRUP) { setBracketFormatMode(STROUSTRUP_MODE); } else if (formattingStyle == STYLE_WHITESMITH) { setBracketFormatMode(BREAK_MODE); setBracketIndent(true); setClassIndent(true); setSwitchIndent(true); } else if (formattingStyle == STYLE_BANNER) { setBracketFormatMode(ATTACH_MODE); setBracketIndent(true); setClassIndent(true); setSwitchIndent(true); } else if (formattingStyle == STYLE_GNU) { setBracketFormatMode(BREAK_MODE); setBlockIndent(true); } else if (formattingStyle == STYLE_LINUX) { setBracketFormatMode(LINUX_MODE); // always for Linux style setMinConditionalIndentOption(MINCOND_ONEHALF); } else if (formattingStyle == STYLE_HORSTMANN) { setBracketFormatMode(RUN_IN_MODE); setSwitchIndent(true); } else if (formattingStyle == STYLE_1TBS) { setBracketFormatMode(LINUX_MODE); setAddBracketsMode(true); setRemoveBracketsMode(false); } else if (formattingStyle == STYLE_GOOGLE) { setBracketFormatMode(ATTACH_MODE); setModifierIndent(true); setClassIndent(false); } else if (formattingStyle == STYLE_PICO) { setBracketFormatMode(RUN_IN_MODE); setAttachClosingBracket(true); setSwitchIndent(true); setBreakOneLineBlocksMode(false); setSingleStatementsMode(false); // add-brackets won't work for pico, but it could be fixed if necessary // both options should be set to true if (shouldAddBrackets) shouldAddOneLineBrackets = true; } else if (formattingStyle == STYLE_LISP) { setBracketFormatMode(ATTACH_MODE); setAttachClosingBracket(true); setSingleStatementsMode(false); // add-one-line-brackets won't work for lisp // only shouldAddBrackets should be set to true if (shouldAddOneLineBrackets) { shouldAddBrackets = true; shouldAddOneLineBrackets = false; } } setMinConditionalIndentLength(); // if not set by indent=force-tab-x set equal to indentLength if (!getTabLength()) setDefaultTabLength(); // add-one-line-brackets implies keep-one-line-blocks if (shouldAddOneLineBrackets) setBreakOneLineBlocksMode(false); // don't allow add-brackets and remove-brackets if (shouldAddBrackets || shouldAddOneLineBrackets) setRemoveBracketsMode(false); // don't allow indent-classes and indent-modifiers if (getClassIndent()) setModifierIndent(false); } /** * get the next formatted line. * * @return formatted line. */ string ASFormatter::nextLine() { const string* newHeader; bool isInVirginLine = isVirgin; isCharImmediatelyPostComment = false; isPreviousCharPostComment = false; isCharImmediatelyPostLineComment = false; isCharImmediatelyPostOpenBlock = false; isCharImmediatelyPostCloseBlock = false; isCharImmediatelyPostTemplate = false; traceLineNumber++; while (!isLineReady) { if (shouldReparseCurrentChar) shouldReparseCurrentChar = false; else if (!getNextChar()) { breakLine(); continue; } else // stuff to do when reading a new character... { // make sure that a virgin '{' at the beginning of the file will be treated as a block... if (isInVirginLine && currentChar == '{' && currentLineBeginsWithBracket && previousCommandChar == ' ') previousCommandChar = '{'; if (isInHorstmannRunIn) isInLineBreak = false; if (!isWhiteSpace(currentChar)) isInHorstmannRunIn = false; isPreviousCharPostComment = isCharImmediatelyPostComment; isCharImmediatelyPostComment = false; isCharImmediatelyPostTemplate = false; isCharImmediatelyPostReturn = false; isCharImmediatelyPostThrow = false; isCharImmediatelyPostOperator = false; isCharImmediatelyPostPointerOrReference = false; isCharImmediatelyPostOpenBlock = false; isCharImmediatelyPostCloseBlock = false; } if (shouldBreakLineAtNextChar) { if (isWhiteSpace(currentChar) && !lineIsEmpty) continue; isInLineBreak = true; shouldBreakLineAtNextChar = false; } if (isInExecSQL && !passedSemicolon) { if (currentChar == ';') passedSemicolon = true; appendCurrentChar(); continue; } if (isInLineComment) { formatLineCommentBody(); continue; } else if (isInComment) { formatCommentBody(); continue; } else if (isInQuote) { formatQuoteBody(); continue; } // not in quote or comment or line comment if (isSequenceReached("//")) { formatLineCommentOpener(); testForTimeToSplitFormattedLine(); continue; } else if (isSequenceReached("/*")) { formatCommentOpener(); testForTimeToSplitFormattedLine(); continue; } else if (currentChar == '"' || currentChar == '\'') { formatQuoteOpener(); testForTimeToSplitFormattedLine(); continue; } // treat these preprocessor statements as a line comment else if (currentChar == '#' && currentLine.find_first_not_of(" \t") == (size_t) charNum) { string preproc = trim(currentLine.c_str() + charNum + 1); if (preproc.length() > 0 && isCharPotentialHeader(preproc, 0) && (findKeyword(preproc, 0, "region") || findKeyword(preproc, 0, "endregion") || findKeyword(preproc, 0, "error") || findKeyword(preproc, 0, "warning") || findKeyword(preproc, 0, "line"))) { currentLine = rtrim(currentLine); // trim the end only // check for horstmann run-in if (formattedLine.length() > 0 && formattedLine[0] == '{') { isInLineBreak = true; isInHorstmannRunIn = false; } if (previousCommandChar == '}') currentHeader = NULL; isInLineComment = true; appendCurrentChar(); continue; } } if (isInPreprocessor) { appendCurrentChar(); continue; } if (isInTemplate && shouldCloseTemplates) { if (previousCommandChar == '<' && isWhiteSpace(currentChar)) continue; if (isWhiteSpace(currentChar) && peekNextChar() == '>') continue; } if (shouldRemoveNextClosingBracket && currentChar == '}') { currentLine[charNum] = currentChar = ' '; shouldRemoveNextClosingBracket = false; assert(adjustChecksumIn(-'}')); // if the line is empty, delete it if (currentLine.find_first_not_of(" \t")) continue; } // handle white space - needed to simplify the rest. if (isWhiteSpace(currentChar)) { appendCurrentChar(); continue; } /* not in MIDDLE of quote or comment or SQL or white-space of any type ... */ // check if in preprocessor // ** isInPreprocessor will be automatically reset at the beginning // of a new line in getnextChar() if (currentChar == '#') { isInPreprocessor = true; // check for horstmann run-in if (formattedLine.length() > 0 && formattedLine[0] == '{') { isInLineBreak = true; isInHorstmannRunIn = false; } processPreprocessor(); // need to fall through here to reset the variables } /* not in preprocessor ... */ if (isImmediatelyPostComment) { caseHeaderFollowsComments = false; isImmediatelyPostComment = false; isCharImmediatelyPostComment = true; } if (isImmediatelyPostLineComment) { caseHeaderFollowsComments = false; isImmediatelyPostLineComment = false; isCharImmediatelyPostLineComment = true; } if (isImmediatelyPostReturn) { isImmediatelyPostReturn = false; isCharImmediatelyPostReturn = true; } if (isImmediatelyPostThrow) { isImmediatelyPostThrow = false; isCharImmediatelyPostThrow = true; } if (isImmediatelyPostOperator) { isImmediatelyPostOperator = false; isCharImmediatelyPostOperator = true; } if (isImmediatelyPostTemplate) { isImmediatelyPostTemplate = false; isCharImmediatelyPostTemplate = true; } if (isImmediatelyPostPointerOrReference) { isImmediatelyPostPointerOrReference = false; isCharImmediatelyPostPointerOrReference = true; } // reset isImmediatelyPostHeader information if (isImmediatelyPostHeader) { // should brackets be added if (currentChar != '{' && shouldAddBrackets) { bool bracketsAdded = addBracketsToStatement(); if (bracketsAdded && !shouldAddOneLineBrackets) { size_t firstText = currentLine.find_first_not_of(" \t"); assert(firstText != string::npos); if ((int) firstText == charNum) breakCurrentOneLineBlock = true; } } // should brackets be removed else if (currentChar == '{' && shouldRemoveBrackets) { bool bracketsRemoved = removeBracketsFromStatement(); if (bracketsRemoved) { shouldRemoveNextClosingBracket = true; if (isBeforeAnyLineEndComment(charNum)) spacePadNum--; else if (shouldBreakOneLineBlocks || (currentLineBeginsWithBracket && currentLine.find_first_not_of(" \t") != string::npos)) shouldBreakLineAtNextChar = true; continue; } } // break 'else-if' if shouldBreakElseIfs is requested if (shouldBreakElseIfs && currentHeader == &AS_ELSE && isOkToBreakBlock(bracketTypeStack->back()) && !isBeforeAnyComment() && (shouldBreakOneLineStatements || !isHeaderInMultiStatementLine)) { string nextText = peekNextText(currentLine.substr(charNum)); if (nextText.length() > 0 && isCharPotentialHeader(nextText, 0) && ASBeautifier::findHeader(nextText, 0, headers) == &AS_IF) { isInLineBreak = true; } } isImmediatelyPostHeader = false; } if (passedSemicolon) // need to break the formattedLine { passedSemicolon = false; if (parenStack->back() == 0 && !isCharImmediatelyPostComment && currentChar != ';') // allow ;; { // does a one-line block have ending comments? if (isBracketType(bracketTypeStack->back(), SINGLE_LINE_TYPE)) { size_t blockEnd = currentLine.rfind(AS_CLOSE_BRACKET); assert(blockEnd != string::npos); // move ending comments to this formattedLine if (isBeforeAnyLineEndComment(blockEnd)) { size_t commentStart = currentLine.find_first_not_of(" \t", blockEnd + 1); assert(commentStart != string::npos); assert((currentLine.compare(commentStart, 2, "//") == 0) || (currentLine.compare(commentStart, 2, "/*") == 0)); size_t commentLength = currentLine.length() - commentStart; formattedLine.append(getIndentLength() - 1, ' '); formattedLine.append(currentLine, commentStart, commentLength); currentLine.erase(commentStart, commentLength); testForTimeToSplitFormattedLine(); } } isInExecSQL = false; shouldReparseCurrentChar = true; if (formattedLine.find_first_not_of(" \t") != string::npos) isInLineBreak = true; if (needHeaderOpeningBracket) { isCharImmediatelyPostCloseBlock = true; needHeaderOpeningBracket = false; } continue; } } if (passedColon) { passedColon = false; if (parenStack->back() == 0 && !isBeforeAnyComment() && (formattedLine.find_first_not_of(" \t") != string::npos)) { shouldReparseCurrentChar = true; isInLineBreak = true; continue; } } // Check if in template declaration, e.g. foo or foo if (!isInTemplate && currentChar == '<') { checkIfTemplateOpener(); } // handle parenthesies if (currentChar == '(' || currentChar == '[' || (isInTemplate && currentChar == '<')) { questionMarkStack->push_back(foundQuestionMark); foundQuestionMark = false; parenStack->back()++; if (currentChar == '[') ++squareBracketCount; } else if (currentChar == ')' || currentChar == ']' || (isInTemplate && currentChar == '>')) { foundPreCommandHeader = false; parenStack->back()--; // this can happen in preprocessor directives if (parenStack->back() < 0) parenStack->back() = 0; if (!questionMarkStack->empty()) { foundQuestionMark = questionMarkStack->back(); questionMarkStack->pop_back(); } if (isInTemplate && currentChar == '>') { templateDepth--; if (templateDepth == 0) { isInTemplate = false; isImmediatelyPostTemplate = true; } } // check if this parenthesis closes a header, e.g. if (...), while (...) if (isInHeader && parenStack->back() == 0) { isInHeader = false; isImmediatelyPostHeader = true; foundQuestionMark = false; } if (currentChar == ']') { --squareBracketCount; if (squareBracketCount < 0) squareBracketCount = 0; } if (currentChar == ')') { foundCastOperator = false; if (parenStack->back() == 0) endOfAsmReached = true; } } // handle brackets if (currentChar == '{' || currentChar == '}') { // if appendOpeningBracket this was already done for the original bracket if (currentChar == '{' && !appendOpeningBracket) { BracketType newBracketType = getBracketType(); foundNamespaceHeader = false; foundClassHeader = false; foundStructHeader = false; foundInterfaceHeader = false; foundPreDefinitionHeader = false; foundPreCommandHeader = false; foundPreCommandMacro = false; isInPotentialCalculation = false; isInObjCMethodDefinition = false; isInObjCInterface = false; isInEnum = false; isJavaStaticConstructor = false; isCharImmediatelyPostNonInStmt = false; needHeaderOpeningBracket = false; shouldKeepLineUnbroken = false; isPreviousBracketBlockRelated = !isBracketType(newBracketType, ARRAY_TYPE); bracketTypeStack->push_back(newBracketType); preBracketHeaderStack->push_back(currentHeader); currentHeader = NULL; structStack->push_back(isInIndentableStruct); if (isBracketType(newBracketType, STRUCT_TYPE) && isCStyle()) isInIndentableStruct = isStructAccessModified(currentLine, charNum); else isInIndentableStruct = false; } // this must be done before the bracketTypeStack is popped BracketType bracketType = bracketTypeStack->back(); bool isOpeningArrayBracket = (isBracketType(bracketType, ARRAY_TYPE) && bracketTypeStack->size() >= 2 && !isBracketType((*bracketTypeStack)[bracketTypeStack->size() - 2], ARRAY_TYPE) ); if (currentChar == '}') { // if a request has been made to append a post block empty line, // but the block exists immediately before a closing bracket, // then there is no need for the post block empty line. isAppendPostBlockEmptyLineRequested = false; breakCurrentOneLineBlock = false; if (isInAsm) endOfAsmReached = true; isInAsmOneLine = isInQuote = false; shouldKeepLineUnbroken = false; squareBracketCount = 0; if (bracketTypeStack->size() > 1) { previousBracketType = bracketTypeStack->back(); bracketTypeStack->pop_back(); isPreviousBracketBlockRelated = !isBracketType(bracketType, ARRAY_TYPE); } else { previousBracketType = NULL_TYPE; isPreviousBracketBlockRelated = false; } if (!preBracketHeaderStack->empty()) { currentHeader = preBracketHeaderStack->back(); preBracketHeaderStack->pop_back(); } else currentHeader = NULL; if (!structStack->empty()) { isInIndentableStruct = structStack->back(); structStack->pop_back(); } else isInIndentableStruct = false; if (isNonInStatementArray && (!isBracketType(bracketTypeStack->back(), ARRAY_TYPE) // check previous bracket || peekNextChar() == ';')) // check for "};" added V2.01 isImmediatelyPostNonInStmt = true; } // format brackets appendOpeningBracket = false; if (isBracketType(bracketType, ARRAY_TYPE)) { formatArrayBrackets(bracketType, isOpeningArrayBracket); } else { if (currentChar == '{') formatOpeningBracket(bracketType); else formatClosingBracket(bracketType); } continue; } if ((((previousCommandChar == '{' && isPreviousBracketBlockRelated) || ((previousCommandChar == '}' && !isImmediatelyPostEmptyBlock && isPreviousBracketBlockRelated && !isPreviousCharPostComment // Fixes wrongly appended newlines after '}' immediately after comments && peekNextChar() != ' ' && !isBracketType(previousBracketType, DEFINITION_TYPE)) && !isBracketType(bracketTypeStack->back(), DEFINITION_TYPE))) && isOkToBreakBlock(bracketTypeStack->back())) // check for array || (previousCommandChar == '{' // added 9/30/2010 && isBracketType(bracketTypeStack->back(), ARRAY_TYPE) && !isBracketType(bracketTypeStack->back(), SINGLE_LINE_TYPE) && isNonInStatementArray)) { isCharImmediatelyPostOpenBlock = (previousCommandChar == '{'); isCharImmediatelyPostCloseBlock = (previousCommandChar == '}'); if (isCharImmediatelyPostOpenBlock && !isCharImmediatelyPostComment && !isCharImmediatelyPostLineComment) { previousCommandChar = ' '; if (bracketFormatMode == NONE_MODE) { if (shouldBreakOneLineBlocks && isBracketType(bracketTypeStack->back(), SINGLE_LINE_TYPE)) isInLineBreak = true; else if (currentLineBeginsWithBracket) formatRunIn(); else breakLine(); } else if (bracketFormatMode == RUN_IN_MODE && currentChar != '#') formatRunIn(); else isInLineBreak = true; } else if (isCharImmediatelyPostCloseBlock && shouldBreakOneLineStatements && (isLegalNameChar(currentChar) && currentChar != '.') && !isCharImmediatelyPostComment) { previousCommandChar = ' '; isInLineBreak = true; } } // reset block handling flags isImmediatelyPostEmptyBlock = false; // look for headers bool isPotentialHeader = isCharPotentialHeader(currentLine, charNum); if (isPotentialHeader && !isInTemplate && !squareBracketCount) { isNonParenHeader = false; foundClosingHeader = false; newHeader = findHeader(headers); if (newHeader != NULL) { const string* previousHeader; // recognize closing headers of do..while, if..else, try..catch..finally if ((newHeader == &AS_ELSE && currentHeader == &AS_IF) || (newHeader == &AS_WHILE && currentHeader == &AS_DO) || (newHeader == &AS_CATCH && currentHeader == &AS_TRY) || (newHeader == &AS_CATCH && currentHeader == &AS_CATCH) || (newHeader == &AS_FINALLY && currentHeader == &AS_TRY) || (newHeader == &AS_FINALLY && currentHeader == &AS_CATCH) || (newHeader == &_AS_FINALLY && currentHeader == &_AS_TRY) || (newHeader == &_AS_EXCEPT && currentHeader == &_AS_TRY) || (newHeader == &AS_SET && currentHeader == &AS_GET) || (newHeader == &AS_REMOVE && currentHeader == &AS_ADD)) foundClosingHeader = true; previousHeader = currentHeader; currentHeader = newHeader; needHeaderOpeningBracket = true; // is the previous statement on the same line? if ((previousNonWSChar == ';' || previousNonWSChar == ':') && !isInLineBreak && isOkToBreakBlock(bracketTypeStack->back())) { // if breaking lines, break the line at the header // except for multiple 'case' statements on a line if (maxCodeLength != string::npos && previousHeader != &AS_CASE) isInLineBreak = true; else isHeaderInMultiStatementLine = true; } if (foundClosingHeader && previousNonWSChar == '}') { if (isOkToBreakBlock(bracketTypeStack->back())) isLineBreakBeforeClosingHeader(); // get the adjustment for a comment following the closing header if (isInLineBreak) nextLineSpacePadNum = getNextLineCommentAdjustment(); else spacePadNum = getCurrentLineCommentAdjustment(); } // check if the found header is non-paren header isNonParenHeader = findHeader(nonParenHeaders) != NULL; // join 'else if' statements if (currentHeader == &AS_IF && previousHeader == &AS_ELSE && isInLineBreak && !shouldBreakElseIfs && !isCharImmediatelyPostLineComment) { // 'else' must be last thing on the line size_t start = formattedLine.length() >= 6 ? formattedLine.length() - 6 : 0; if (formattedLine.find(AS_ELSE, start) != string::npos) { appendSpacePad(); isInLineBreak = false; } } appendSequence(*currentHeader); goForward(currentHeader->length() - 1); // if a paren-header is found add a space after it, if needed // this checks currentLine, appendSpacePad() checks formattedLine // in 'case' and C# 'catch' can be either a paren or non-paren header if (shouldPadHeader && (!isNonParenHeader || (currentHeader == &AS_CASE && peekNextChar() == '(') || (currentHeader == &AS_CATCH && peekNextChar() == '(')) && charNum < (int) currentLine.length() - 1 && !isWhiteSpace(currentLine[charNum + 1])) appendSpacePad(); // Signal that a header has been reached // *** But treat a closing while() (as in do...while) // as if it were NOT a header since a closing while() // should never have a block after it! if (currentHeader != &AS_CASE && currentHeader != &AS_DEFAULT && !(foundClosingHeader && currentHeader == &AS_WHILE)) { isInHeader = true; // in C# 'catch' and 'delegate' can be a paren or non-paren header if (isNonParenHeader && !isSharpStyleWithParen(currentHeader)) { isImmediatelyPostHeader = true; isInHeader = false; } } if (shouldBreakBlocks && isOkToBreakBlock(bracketTypeStack->back()) && !isHeaderInMultiStatementLine) { if (previousHeader == NULL && !foundClosingHeader && !isCharImmediatelyPostOpenBlock && !isImmediatelyPostCommentOnly) { isPrependPostBlockEmptyLineRequested = true; } if (currentHeader == &AS_ELSE || currentHeader == &AS_CATCH || currentHeader == &AS_FINALLY || foundClosingHeader) { isPrependPostBlockEmptyLineRequested = false; } if (shouldBreakClosingHeaderBlocks && isCharImmediatelyPostCloseBlock && !isImmediatelyPostCommentOnly && currentHeader != &AS_WHILE) // closing do-while block { isPrependPostBlockEmptyLineRequested = true; } } if (currentHeader == &AS_CASE || currentHeader == &AS_DEFAULT) isInCase = true; continue; } else if ((newHeader = findHeader(preDefinitionHeaders)) != NULL && parenStack->back() == 0) { if (newHeader == &AS_NAMESPACE) foundNamespaceHeader = true; if (newHeader == &AS_CLASS) foundClassHeader = true; if (newHeader == &AS_STRUCT) foundStructHeader = true; if (newHeader == &AS_INTERFACE) foundInterfaceHeader = true; foundPreDefinitionHeader = true; appendSequence(*newHeader); goForward(newHeader->length() - 1); continue; } else if ((newHeader = findHeader(preCommandHeaders)) != NULL) { foundPreCommandHeader = true; // fall through here for a 'const' that is not a precommand header } else if ((newHeader = findHeader(castOperators)) != NULL) { foundCastOperator = true; appendSequence(*newHeader); goForward(newHeader->length() - 1); continue; } } // (isPotentialHeader && !isInTemplate) if (isInLineBreak) // OK to break line here { breakLine(); if (isInVirginLine) // adjust for the first line { lineCommentNoBeautify = lineCommentNoIndent; lineCommentNoIndent = false; } } if (previousNonWSChar == '}' || currentChar == ';') { if (currentChar == ';') { squareBracketCount = 0; if (((shouldBreakOneLineStatements || isBracketType(bracketTypeStack->back(), SINGLE_LINE_TYPE)) && isOkToBreakBlock(bracketTypeStack->back())) && !(shouldAttachClosingBracket && peekNextChar() == '}')) { passedSemicolon = true; } // append post block empty line for unbracketed header if (shouldBreakBlocks && currentHeader != NULL && currentHeader != &AS_CASE && currentHeader != &AS_DEFAULT && !isHeaderInMultiStatementLine && parenStack->back() == 0) { isAppendPostBlockEmptyLineRequested = true; } } if (currentChar != ';' || (needHeaderOpeningBracket && parenStack->back() == 0)) currentHeader = NULL; resetEndOfStatement(); } if (currentChar == ':') { if (isInCase && previousChar != ':' // not part of '::' && peekNextChar() != ':') // not part of '::' { isInCase = false; if (shouldBreakOneLineStatements) passedColon = true; } else if (isCStyle() // for C/C++ only && isOkToBreakBlock(bracketTypeStack->back()) && shouldBreakOneLineStatements && !foundQuestionMark // not in a ?: sequence && !foundPreDefinitionHeader // not in a definition block (e.g. class foo : public bar && previousCommandChar != ')' // not immediately after closing paren of a method header, e.g. ASFormatter::ASFormatter(...) : ASBeautifier(...) && previousChar != ':' // not part of '::' && peekNextChar() != ':' // not part of '::' && !squareBracketCount // not in objC method call && !isInObjCMethodDefinition // not objC '-' or '+' method && !isInObjCInterface // not objC @interface && !isInObjCSelector // not objC @selector && !isDigit(peekNextChar()) // not a bit field && !isInEnum // not an enum with a base type && !isInAsm // not in extended assembler && !isInAsmOneLine // not in extended assembler && !isInAsmBlock) // not in extended assembler { passedColon = true; } if (isCStyle() && shouldPadMethodColon && (squareBracketCount > 0 || isInObjCMethodDefinition || isInObjCSelector) && previousChar != ':' // not part of '::' && peekNextChar() != ':' // not part of '::' && !foundQuestionMark) // not in a ?: sequence padObjCMethodColon(); if (isInObjCInterface) { appendSpacePad(); if ((int) currentLine.length() > charNum + 1 && !isWhiteSpace(currentLine[charNum + 1])) currentLine.insert(charNum + 1, " "); } } if (currentChar == '?') foundQuestionMark = true; if (isPotentialHeader && !isInTemplate) { if (findKeyword(currentLine, charNum, AS_NEW)) isInPotentialCalculation = false; if (findKeyword(currentLine, charNum, AS_RETURN)) { isInPotentialCalculation = true; // return is the same as an = sign isImmediatelyPostReturn = true; } if (findKeyword(currentLine, charNum, AS_OPERATOR)) isImmediatelyPostOperator = true; if (isCStyle() && findKeyword(currentLine, charNum, AS_THROW) && previousCommandChar != ')' && !foundPreCommandHeader) // 'const' throw() isImmediatelyPostThrow = true; if (isCStyle() && findKeyword(currentLine, charNum, AS_ENUM)) isInEnum = true; if (isCStyle() && findKeyword(currentLine, charNum, AS_EXTERN) && isExternC()) isInExternC = true; // Objective-C NSException macros are preCommandHeaders if (isCStyle() && findKeyword(currentLine, charNum, AS_NS_DURING)) foundPreCommandMacro = true; if (isCStyle() && findKeyword(currentLine, charNum, AS_NS_HANDLER)) foundPreCommandMacro = true; if (isCStyle() && isExecSQL(currentLine, charNum)) isInExecSQL = true; if (isCStyle()) { if (findKeyword(currentLine, charNum, AS_ASM) || findKeyword(currentLine, charNum, AS__ASM__)) { isInAsm = true; } else if (findKeyword(currentLine, charNum, AS_MS_ASM) // microsoft specific || findKeyword(currentLine, charNum, AS_MS__ASM)) { int index = 4; if (peekNextChar() == '_') // check for __asm index = 5; char peekedChar = ASBase::peekNextChar(currentLine, charNum + index); if (peekedChar == '{' || peekedChar == ' ') isInAsmBlock = true; else isInAsmOneLine = true; } } if (isJavaStyle() && (findKeyword(currentLine, charNum, AS_STATIC) && isNextCharOpeningBracket(charNum + 6))) isJavaStaticConstructor = true; if (isSharpStyle() && (findKeyword(currentLine, charNum, AS_DELEGATE) || findKeyword(currentLine, charNum, AS_UNCHECKED))) isSharpDelegate = true; // append the entire name string name = getCurrentWord(currentLine, charNum); // must pad the 'and' and 'or' operators if required if (name == "and" || name == "or") { if (shouldPadOperators && previousNonWSChar != ':') { appendSpacePad(); appendOperator(name); goForward(name.length() - 1); if (!isBeforeAnyComment() && !(currentLine.compare(charNum + 1, 1, AS_SEMICOLON) == 0) && !(currentLine.compare(charNum + 1, 2, AS_SCOPE_RESOLUTION) == 0)) appendSpaceAfter(); } else { appendOperator(name); goForward(name.length() - 1); } } else { appendSequence(name); goForward(name.length() - 1); } continue; } // (isPotentialHeader && !isInTemplate) // determine if this is an Objective-C statement if (currentChar == '@' && isCharPotentialHeader(currentLine, charNum + 1) && findKeyword(currentLine, charNum + 1, AS_INTERFACE) && bracketTypeStack->back() == NULL_TYPE) { isInObjCInterface = true; string name = '@' + AS_INTERFACE; appendSequence(name); goForward(name.length() - 1); continue; } else if (currentChar == '@' && isCharPotentialHeader(currentLine, charNum + 1) && findKeyword(currentLine, charNum + 1, AS_SELECTOR)) { isInObjCSelector = true; string name = '@' + AS_SELECTOR; appendSequence(name); goForward(name.length() - 1); continue; } else if ((currentChar == '-' || currentChar == '+') && peekNextChar() == '(' && bracketTypeStack->back() == NULL_TYPE && !isInPotentialCalculation) { isInObjCMethodDefinition = true; isInObjCInterface = false; appendCurrentChar(); if (shouldPadMethodPrefix || shouldUnPadMethodPrefix) { size_t i = currentLine.find_first_not_of(" \t", charNum + 1); if (i != string::npos) goForward(i - charNum - 1); if (shouldPadMethodPrefix) appendSpaceAfter(); } continue; } // determine if this is a potential calculation bool isPotentialOperator = isCharPotentialOperator(currentChar); newHeader = NULL; if (isPotentialOperator) { newHeader = findOperator(operators); // check for Java ? wildcard if (newHeader == &AS_GCC_MIN_ASSIGN && isJavaStyle() && isInTemplate) newHeader = NULL; if (newHeader != NULL) { if (newHeader == &AS_LAMBDA) foundPreCommandHeader = true; // correct mistake of two >> closing a template if (isInTemplate && (newHeader == &AS_GR_GR || newHeader == &AS_GR_GR_GR)) newHeader = &AS_GR; if (!isInPotentialCalculation) { // must determine if newHeader is an assignment operator // do NOT use findOperator!!! if (find(assignmentOperators->begin(), assignmentOperators->end(), newHeader) != assignmentOperators->end()) { foundPreCommandHeader = false; char peekedChar = peekNextChar(); isInPotentialCalculation = (!(newHeader == &AS_EQUAL && peekedChar == '*') && !(newHeader == &AS_EQUAL && peekedChar == '&')); } } } } // process pointers and references // check newHeader to eliminate things like '&&' sequence if (!isJavaStyle() && (newHeader == &AS_MULT || newHeader == &AS_BIT_AND || newHeader == &AS_BIT_XOR || newHeader == &AS_AND) && isPointerOrReference()) { if (!isDereferenceOrAddressOf()) formatPointerOrReference(); else { appendOperator(*newHeader); goForward(newHeader->length() - 1); } isImmediatelyPostPointerOrReference = true; continue; } if (shouldPadOperators && newHeader != NULL) { padOperators(newHeader); continue; } // pad commas and semi-colons if (currentChar == ';' || (currentChar == ',' && shouldPadOperators)) { char nextChar = ' '; if (charNum + 1 < (int) currentLine.length()) nextChar = currentLine[charNum + 1]; if (!isWhiteSpace(nextChar) && nextChar != '}' && nextChar != ')' && nextChar != ']' && nextChar != '>' && nextChar != ';' && !isBeforeAnyComment() /* && !(isBracketType(bracketTypeStack->back(), ARRAY_TYPE)) */ ) { appendCurrentChar(); appendSpaceAfter(); continue; } } // do NOT use 'continue' after this, it must do padParens if necessary if (currentChar == '(' && shouldPadHeader && (isCharImmediatelyPostReturn || isCharImmediatelyPostThrow)) appendSpacePad(); if ((currentChar == '(' || currentChar == ')') && (shouldPadParensOutside || shouldPadParensInside || shouldUnPadParens || shouldPadFirstParen)) { padParens(); continue; } // bypass the entire operator if (newHeader != NULL) { appendOperator(*newHeader); goForward(newHeader->length() - 1); continue; } appendCurrentChar(); } // end of while loop * end of while loop * end of while loop * end of while loop // return a beautified (i.e. correctly indented) line. string beautifiedLine; size_t readyFormattedLineLength = trim(readyFormattedLine).length(); if (prependEmptyLine // prepend a blank line before this formatted line && readyFormattedLineLength > 0 && previousReadyFormattedLineLength > 0) { isLineReady = true; // signal a waiting readyFormattedLine beautifiedLine = beautify(""); previousReadyFormattedLineLength = 0; // call the enhancer for new empty lines enhancer->enhance(beautifiedLine, isInPreprocessorBeautify, isInBeautifySQL); } else // format the current formatted line { isLineReady = false; horstmannIndentInStatement = horstmannIndentChars; beautifiedLine = beautify(readyFormattedLine); previousReadyFormattedLineLength = readyFormattedLineLength; // the enhancer is not called for no-indent line comments if (!lineCommentNoBeautify) enhancer->enhance(beautifiedLine, isInPreprocessorBeautify, isInBeautifySQL); horstmannIndentChars = 0; lineCommentNoBeautify = lineCommentNoIndent; lineCommentNoIndent = false; isElseHeaderIndent = elseHeaderFollowsComments; isCaseHeaderCommentIndent = caseHeaderFollowsComments; if (isCharImmediatelyPostNonInStmt) { isNonInStatementArray = false; isCharImmediatelyPostNonInStmt = false; } isInPreprocessorBeautify = isInPreprocessor; // used by ASEnhancer isInBeautifySQL = isInExecSQL; // used by ASEnhancer } prependEmptyLine = false; assert(computeChecksumOut(beautifiedLine)); return beautifiedLine; } /** * check if there are any indented lines ready to be read by nextLine() * * @return are there any indented lines ready? */ bool ASFormatter::hasMoreLines() const { return !endOfCodeReached; } /** * comparison function for BracketType enum */ bool ASFormatter::isBracketType(BracketType a, BracketType b) const { return ((a & b) == b); } /** * set the formatting style. * * @param mode the formatting style. */ void ASFormatter::setFormattingStyle(FormatStyle style) { formattingStyle = style; } /** * set the add brackets mode. * options: * true brackets added to headers for single line statements. * false brackets NOT added to headers for single line statements. * * @param mode the add brackets mode. */ void ASFormatter::setAddBracketsMode(bool state) { shouldAddBrackets = state; } /** * set the add one line brackets mode. * options: * true one line brackets added to headers for single line statements. * false one line brackets NOT added to headers for single line statements. * * @param mode the add one line brackets mode. */ void ASFormatter::setAddOneLineBracketsMode(bool state) { shouldAddBrackets = state; shouldAddOneLineBrackets = state; } /** * set the remove brackets mode. * options: * true brackets removed from headers for single line statements. * false brackets NOT removed from headers for single line statements. * * @param mode the remove brackets mode. */ void ASFormatter::setRemoveBracketsMode(bool state) { shouldRemoveBrackets = state; } /** * set the bracket formatting mode. * options: * * @param mode the bracket formatting mode. */ void ASFormatter::setBracketFormatMode(BracketMode mode) { bracketFormatMode = mode; } /** * set 'break after' mode for maximum code length * * @param state the 'break after' mode. */ void ASFormatter::setBreakAfterMode(bool state) { shouldBreakLineAfterLogical = state; } /** * set closing header bracket breaking mode * options: * true brackets just before closing headers (e.g. 'else', 'catch') * will be broken, even if standard brackets are attached. * false closing header brackets will be treated as standard brackets. * * @param state the closing header bracket breaking mode. */ void ASFormatter::setBreakClosingHeaderBracketsMode(bool state) { shouldBreakClosingHeaderBrackets = state; } /** * set 'else if()' breaking mode * options: * true 'else' headers will be broken from their succeeding 'if' headers. * false 'else' headers will be attached to their succeeding 'if' headers. * * @param state the 'else if()' breaking mode. */ void ASFormatter::setBreakElseIfsMode(bool state) { shouldBreakElseIfs = state; } /** * set maximum code length * * @param max the maximum code length. */ void ASFormatter::setMaxCodeLength(int max) { maxCodeLength = max; } /** * set operator padding mode. * options: * true statement operators will be padded with spaces around them. * false statement operators will not be padded. * * @param state the padding mode. */ void ASFormatter::setOperatorPaddingMode(bool state) { shouldPadOperators = state; } /** * set parenthesis outside padding mode. * options: * true statement parentheses will be padded with spaces around them. * false statement parentheses will not be padded. * * @param state the padding mode. */ void ASFormatter::setParensOutsidePaddingMode(bool state) { shouldPadParensOutside = state; } /** * set parenthesis inside padding mode. * options: * true statement parenthesis will be padded with spaces around them. * false statement parenthesis will not be padded. * * @param state the padding mode. */ void ASFormatter::setParensInsidePaddingMode(bool state) { shouldPadParensInside = state; } /** * set padding mode before one or more open parentheses. * options: * true first open parenthesis will be padded with a space before. * false first open parenthesis will not be padded. * * @param state the padding mode. */ void ASFormatter::setParensFirstPaddingMode(bool state) { shouldPadFirstParen = state; } /** * set header padding mode. * options: * true headers will be padded with spaces around them. * false headers will not be padded. * * @param state the padding mode. */ void ASFormatter::setParensHeaderPaddingMode(bool state) { shouldPadHeader = state; } /** * set parenthesis unpadding mode. * options: * true statement parenthesis will be unpadded with spaces removed around them. * false statement parenthesis will not be unpadded. * * @param state the padding mode. */ void ASFormatter::setParensUnPaddingMode(bool state) { shouldUnPadParens = state; } /** * Set strip comment prefix mode. * options: * true strip leading '*' in a comment. * false leading '*' in a comment will be left unchanged. * * @param state the strip comment prefix mode. */ void ASFormatter::setStripCommentPrefix(bool state) { shouldStripCommentPrefix = state; } /** * set objective-c '-' or '+' class prefix padding mode. * options: * true class prefix will be padded a spaces after them. * false class prefix will be left unchanged. * * @param state the padding mode. */ void ASFormatter::setMethodPrefixPaddingMode(bool state) { shouldPadMethodPrefix = state; } /** * set objective-c '-' or '+' class prefix unpadding mode. * options: * true class prefix will be unpadded with spaces after them removed. * false class prefix will left unchanged. * * @param state the unpadding mode. */ void ASFormatter::setMethodPrefixUnPaddingMode(bool state) { shouldUnPadMethodPrefix = state; } /** * set objective-c method colon padding mode. * * @param mode objective-c colon padding mode. */ void ASFormatter::setObjCColonPaddingMode(ObjCColonPad mode) { shouldPadMethodColon = true; objCColonPadMode = mode; } /** * set option to attach closing brackets * * @param state true = attach, false = don't attach. */ void ASFormatter::setAttachClosingBracket(bool state) { shouldAttachClosingBracket = state; } /** * set option to attach class brackets * * @param state true = attach, false = use style default. */ void ASFormatter::setAttachClass(bool state) { shouldAttachClass = state; } /** * set option to attach extern "C" brackets * * @param state true = attach, false = use style default. */ void ASFormatter::setAttachExternC(bool state) { shouldAttachExternC = state; } /** * set option to attach namespace brackets * * @param state true = attach, false = use style default. */ void ASFormatter::setAttachNamespace(bool state) { shouldAttachNamespace = state; } /** * set option to attach inline brackets * * @param state true = attach, false = use style default. */ void ASFormatter::setAttachInline(bool state) { shouldAttachInline = state; } /** * set option to break/not break one-line blocks * * @param state true = break, false = don't break. */ void ASFormatter::setBreakOneLineBlocksMode(bool state) { shouldBreakOneLineBlocks = state; } void ASFormatter::setCloseTemplatesMode(bool state) { shouldCloseTemplates = state; } /** * set option to break/not break lines consisting of multiple statements. * * @param state true = break, false = don't break. */ void ASFormatter::setSingleStatementsMode(bool state) { shouldBreakOneLineStatements = state; } /** * set option to convert tabs to spaces. * * @param state true = convert, false = don't convert. */ void ASFormatter::setTabSpaceConversionMode(bool state) { shouldConvertTabs = state; } /** * set option to indent comments in column 1. * * @param state true = indent, false = don't indent. */ void ASFormatter::setIndentCol1CommentsMode(bool state) { shouldIndentCol1Comments = state; } /** * set option to force all line ends to a particular style. * * @param fmt format enum value */ void ASFormatter::setLineEndFormat(LineEndFormat fmt) { lineEnd = fmt; } /** * set option to break unrelated blocks of code with empty lines. * * @param state true = convert, false = don't convert. */ void ASFormatter::setBreakBlocksMode(bool state) { shouldBreakBlocks = state; } /** * set option to break closing header blocks of code (such as 'else', 'catch', ...) with empty lines. * * @param state true = convert, false = don't convert. */ void ASFormatter::setBreakClosingHeaderBlocksMode(bool state) { shouldBreakClosingHeaderBlocks = state; } /** * set option to delete empty lines. * * @param state true = delete, false = don't delete. */ void ASFormatter::setDeleteEmptyLinesMode(bool state) { shouldDeleteEmptyLines = state; } /** * set the pointer alignment. * * @param alignment the pointer alignment. */ void ASFormatter::setPointerAlignment(PointerAlign alignment) { pointerAlignment = alignment; } void ASFormatter::setReferenceAlignment(ReferenceAlign alignment) { referenceAlignment = alignment; } /** * jump over several characters. * * @param i the number of characters to jump over. */ void ASFormatter::goForward(int i) { while (--i >= 0) getNextChar(); } /** * peek at the next unread character. * * @return the next unread character. */ char ASFormatter::peekNextChar() const { char ch = ' '; size_t peekNum = currentLine.find_first_not_of(" \t", charNum + 1); if (peekNum == string::npos) return ch; ch = currentLine[peekNum]; return ch; } /** * check if current placement is before a comment * * @return is before a comment. */ bool ASFormatter::isBeforeComment() const { bool foundComment = false; size_t peekNum = currentLine.find_first_not_of(" \t", charNum + 1); if (peekNum == string::npos) return foundComment; foundComment = (currentLine.compare(peekNum, 2, "/*") == 0); return foundComment; } /** * check if current placement is before a comment or line-comment * * @return is before a comment or line-comment. */ bool ASFormatter::isBeforeAnyComment() const { bool foundComment = false; size_t peekNum = currentLine.find_first_not_of(" \t", charNum + 1); if (peekNum == string::npos) return foundComment; foundComment = (currentLine.compare(peekNum, 2, "/*") == 0 || currentLine.compare(peekNum, 2, "//") == 0); return foundComment; } /** * check if current placement is before a comment or line-comment * if a block comment it must be at the end of the line * * @return is before a comment or line-comment. */ bool ASFormatter::isBeforeAnyLineEndComment(int startPos) const { bool foundLineEndComment = false; size_t peekNum = currentLine.find_first_not_of(" \t", startPos + 1); if (peekNum != string::npos) { if (currentLine.compare(peekNum, 2, "//") == 0) foundLineEndComment = true; else if (currentLine.compare(peekNum, 2, "/*") == 0) { // comment must be closed on this line with nothing after it size_t endNum = currentLine.find("*/", peekNum + 2); if (endNum != string::npos) { size_t nextChar = currentLine.find_first_not_of(" \t", endNum + 2); if (nextChar == string::npos) foundLineEndComment = true; } } } return foundLineEndComment; } /** * check if current placement is before a comment followed by a line-comment * * @return is before a multiple line-end comment. */ bool ASFormatter::isBeforeMultipleLineEndComments(int startPos) const { bool foundMultipleLineEndComment = false; size_t peekNum = currentLine.find_first_not_of(" \t", startPos + 1); if (peekNum != string::npos) { if (currentLine.compare(peekNum, 2, "/*") == 0) { // comment must be closed on this line with nothing after it size_t endNum = currentLine.find("*/", peekNum + 2); if (endNum != string::npos) { size_t nextChar = currentLine.find_first_not_of(" \t", endNum + 2); if (nextChar != string::npos && currentLine.compare(nextChar, 2, "//") == 0) foundMultipleLineEndComment = true; } } } return foundMultipleLineEndComment; } /** * get the next character, increasing the current placement in the process. * the new character is inserted into the variable currentChar. * * @return whether succeeded to receive the new character. */ bool ASFormatter::getNextChar() { isInLineBreak = false; previousChar = currentChar; if (!isWhiteSpace(currentChar)) { previousNonWSChar = currentChar; if (!isInComment && !isInLineComment && !isInQuote && !isImmediatelyPostComment && !isImmediatelyPostLineComment && !isInPreprocessor && !isSequenceReached("/*") && !isSequenceReached("//")) previousCommandChar = currentChar; } if (charNum + 1 < (int) currentLine.length() && (!isWhiteSpace(peekNextChar()) || isInComment || isInLineComment)) { currentChar = currentLine[++charNum]; if (currentChar == '\t' && shouldConvertTabs) convertTabToSpaces(); return true; } // end of line has been reached return getNextLine(); } /** * get the next line of input, increasing the current placement in the process. * * @param sequence the sequence to append. * @return whether succeeded in reading the next line. */ bool ASFormatter::getNextLine(bool emptyLineWasDeleted /*false*/) { if (sourceIterator->hasMoreLines()) { if (appendOpeningBracket) currentLine = "{"; // append bracket that was removed from the previous line else { currentLine = sourceIterator->nextLine(emptyLineWasDeleted); assert(computeChecksumIn(currentLine)); } // reset variables for new line inLineNumber++; if (endOfAsmReached) endOfAsmReached = isInAsmBlock = isInAsm = false; shouldKeepLineUnbroken = false; isInCommentStartLine = false; isInCase = false; isInAsmOneLine = false; isHeaderInMultiStatementLine = false; isInQuoteContinuation = isInVerbatimQuote | haveLineContinuationChar; haveLineContinuationChar = false; isImmediatelyPostEmptyLine = lineIsEmpty; previousChar = ' '; if (currentLine.length() == 0) currentLine = string(" "); // a null is inserted if this is not done // unless reading in the first line of the file, break a new line. if (!isVirgin) isInLineBreak = true; else isVirgin = false; // TODO: FIX FOR BROKEN CASE STATEMANTS - RELEASE 2.02.1 // REMOVE AT AN APPROPRIATE TIME if ((currentHeader == &AS_CASE || currentHeader == &AS_DEFAULT) && isInLineBreak && !isImmediatelyPostLineComment) { // check for split line if ((formattedLine.length() >= 4 && formattedLine.substr(formattedLine.length() - 4, 4) == "case") || (formattedLine.length() >= 7 && formattedLine.substr(formattedLine.length() - 7, 7) == "default") || (formattedLine[formattedLine.length() - 1] == '\'' && findNextChar(currentLine, ':') != string::npos) ) { isInLineBreak = false; isInCase = true; if (formattedLine.substr(formattedLine.length() - 4, 4) == "case") appendSpacePad(); } } // END OF FIX if (isImmediatelyPostNonInStmt) { isCharImmediatelyPostNonInStmt = true; isImmediatelyPostNonInStmt = false; } // check if is in preprocessor before line trimming // a blank line after a \ will remove the flag isImmediatelyPostPreprocessor = isInPreprocessor; if (!isInComment && (previousNonWSChar != '\\' || isEmptyLine(currentLine))) isInPreprocessor = false; if (passedSemicolon) isInExecSQL = false; initNewLine(); currentChar = currentLine[charNum]; if (isInHorstmannRunIn && previousNonWSChar == '{' && !isInComment) isInLineBreak = false; isInHorstmannRunIn = false; if (currentChar == '\t' && shouldConvertTabs) convertTabToSpaces(); // check for an empty line inside a command bracket. // if yes then read the next line (calls getNextLine recursively). // must be after initNewLine. if (shouldDeleteEmptyLines && lineIsEmpty && isBracketType((*bracketTypeStack)[bracketTypeStack->size() - 1], COMMAND_TYPE)) { if (!shouldBreakBlocks || previousNonWSChar == '{' || !commentAndHeaderFollows()) { isInPreprocessor = isImmediatelyPostPreprocessor; // restore lineIsEmpty = false; return getNextLine(true); } } return true; } else { endOfCodeReached = true; return false; } } /** * jump over the leading white space in the current line, * IF the line does not begin a comment or is in a preprocessor definition. */ void ASFormatter::initNewLine() { size_t len = currentLine.length(); size_t tabSize = getTabLength(); charNum = 0; // don't trim these if (isInQuoteContinuation || (isInPreprocessor && !getPreprocDefineIndent())) return; // SQL continuation lines must be adjusted so the leading spaces // is equivalent to the opening EXEC SQL if (isInExecSQL) { // replace leading tabs with spaces // so that continuation indent will be spaces size_t tabCount_ = 0; size_t i; for (i = 0; i < currentLine.length(); i++) { if (!isWhiteSpace(currentLine[i])) // stop at first text break; if (currentLine[i] == '\t') { size_t numSpaces = tabSize - ((tabCount_ + i) % tabSize); currentLine.replace(i, 1, numSpaces, ' '); tabCount_++; i += tabSize - 1; } } // this will correct the format if EXEC SQL is not a hanging indent trimContinuationLine(); return; } // comment continuation lines must be adjusted so the leading spaces // is equivalent to the opening comment if (isInComment) { if (noTrimCommentContinuation) leadingSpaces = tabIncrementIn = 0; trimContinuationLine(); return; } // compute leading spaces isImmediatelyPostCommentOnly = lineIsLineCommentOnly || lineEndsInCommentOnly; lineIsLineCommentOnly = false; lineEndsInCommentOnly = false; doesLineStartComment = false; currentLineBeginsWithBracket = false; lineIsEmpty = false; currentLineFirstBracketNum = string::npos; tabIncrementIn = 0; // bypass whitespace at the start of a line // preprocessor tabs are replaced later in the program for (charNum = 0; isWhiteSpace(currentLine[charNum]) && charNum + 1 < (int) len; charNum++) { if (currentLine[charNum] == '\t' && !isInPreprocessor) tabIncrementIn += tabSize - 1 - ((tabIncrementIn + charNum) % tabSize); } leadingSpaces = charNum + tabIncrementIn; if (isSequenceReached("/*")) { doesLineStartComment = true; } else if (isSequenceReached("//")) { lineIsLineCommentOnly = true; } else if (isSequenceReached("{")) { currentLineBeginsWithBracket = true; currentLineFirstBracketNum = charNum; size_t firstText = currentLine.find_first_not_of(" \t", charNum + 1); if (firstText != string::npos) { if (currentLine.compare(firstText, 2, "//") == 0) lineIsLineCommentOnly = true; else if (currentLine.compare(firstText, 2, "/*") == 0 || isExecSQL(currentLine, firstText)) { // get the extra adjustment size_t j; for (j = charNum + 1; j < firstText && isWhiteSpace(currentLine[j]); j++) { if (currentLine[j] == '\t') tabIncrementIn += tabSize - 1 - ((tabIncrementIn + j) % tabSize); } leadingSpaces = j + tabIncrementIn; if (currentLine.compare(firstText, 2, "/*") == 0) doesLineStartComment = true; } } } else if (isWhiteSpace(currentLine[charNum]) && !(charNum + 1 < (int) currentLine.length())) { lineIsEmpty = true; } // do not trim indented preprocessor define (except for comment continuation lines) if (isInPreprocessor) { if (!doesLineStartComment) leadingSpaces = 0; charNum = 0; } } /** * Append a character to the current formatted line. * The formattedLine split points are updated. * * @param char the character to append. * @param canBreakLine if true, a registered line-break */ void ASFormatter::appendChar(char ch, bool canBreakLine) { if (canBreakLine && isInLineBreak) breakLine(); formattedLine.append(1, ch); isImmediatelyPostCommentOnly = false; if (maxCodeLength != string::npos) { // These compares reduce the frequency of function calls. if (isOkToSplitFormattedLine()) updateFormattedLineSplitPoints(ch); if (formattedLine.length() > maxCodeLength) testForTimeToSplitFormattedLine(); } } /** * Append a string sequence to the current formatted line. * The formattedLine split points are NOT updated. * But the formattedLine is checked for time to split. * * @param sequence the sequence to append. * @param canBreakLine if true, a registered line-break */ void ASFormatter::appendSequence(const string &sequence, bool canBreakLine) { if (canBreakLine && isInLineBreak) breakLine(); formattedLine.append(sequence); if (formattedLine.length() > maxCodeLength) testForTimeToSplitFormattedLine(); } /** * Append an operator sequence to the current formatted line. * The formattedLine split points are updated. * * @param sequence the sequence to append. * @param canBreakLine if true, a registered line-break */ void ASFormatter::appendOperator(const string &sequence, bool canBreakLine) { if (canBreakLine && isInLineBreak) breakLine(); formattedLine.append(sequence); if (maxCodeLength != string::npos) { // These compares reduce the frequency of function calls. if (isOkToSplitFormattedLine()) updateFormattedLineSplitPointsOperator(sequence); if (formattedLine.length() > maxCodeLength) testForTimeToSplitFormattedLine(); } } /** * append a space to the current formattedline, UNLESS the * last character is already a white-space character. */ void ASFormatter::appendSpacePad() { int len = formattedLine.length(); if (len > 0 && !isWhiteSpace(formattedLine[len - 1])) { formattedLine.append(1, ' '); spacePadNum++; if (maxCodeLength != string::npos) { // These compares reduce the frequency of function calls. if (isOkToSplitFormattedLine()) updateFormattedLineSplitPoints(' '); if (formattedLine.length() > maxCodeLength) testForTimeToSplitFormattedLine(); } } } /** * append a space to the current formattedline, UNLESS the * next character is already a white-space character. */ void ASFormatter::appendSpaceAfter() { int len = currentLine.length(); if (charNum + 1 < len && !isWhiteSpace(currentLine[charNum + 1])) { formattedLine.append(1, ' '); spacePadNum++; if (maxCodeLength != string::npos) { // These compares reduce the frequency of function calls. if (isOkToSplitFormattedLine()) updateFormattedLineSplitPoints(' '); if (formattedLine.length() > maxCodeLength) testForTimeToSplitFormattedLine(); } } } /** * register a line break for the formatted line. */ void ASFormatter::breakLine(bool isSplitLine /*false*/) { isLineReady = true; isInLineBreak = false; spacePadNum = nextLineSpacePadNum; nextLineSpacePadNum = 0; readyFormattedLine = formattedLine; formattedLine = ""; // queue an empty line prepend request if one exists prependEmptyLine = isPrependPostBlockEmptyLineRequested; if (!isSplitLine) { formattedLineCommentNum = string::npos; clearFormattedLineSplitPoints(); if (isAppendPostBlockEmptyLineRequested) { isAppendPostBlockEmptyLineRequested = false; isPrependPostBlockEmptyLineRequested = true; } else isPrependPostBlockEmptyLineRequested = false; } } /** * check if the currently reached open-bracket (i.e. '{') * opens a: * - a definition type block (such as a class or namespace), * - a command block (such as a method block) * - a static array * this method takes for granted that the current character * is an opening bracket. * * @return the type of the opened block. */ BracketType ASFormatter::getBracketType() { assert(currentChar == '{'); BracketType returnVal; if ((previousNonWSChar == '=' || isBracketType(bracketTypeStack->back(), ARRAY_TYPE)) && previousCommandChar != ')') returnVal = ARRAY_TYPE; else if (foundPreDefinitionHeader && previousCommandChar != ')') { returnVal = DEFINITION_TYPE; if (foundNamespaceHeader) returnVal = (BracketType)(returnVal | NAMESPACE_TYPE); else if (foundClassHeader) returnVal = (BracketType)(returnVal | CLASS_TYPE); else if (foundStructHeader) returnVal = (BracketType)(returnVal | STRUCT_TYPE); else if (foundInterfaceHeader) returnVal = (BracketType)(returnVal | INTERFACE_TYPE); } else { bool isCommandType = (foundPreCommandHeader || foundPreCommandMacro || (currentHeader != NULL && isNonParenHeader) || (previousCommandChar == ')') || (previousCommandChar == ':' && !foundQuestionMark) || (previousCommandChar == ';') || ((previousCommandChar == '{' || previousCommandChar == '}') && isPreviousBracketBlockRelated) || isInObjCMethodDefinition || isInObjCInterface || isJavaStaticConstructor || isSharpDelegate); // C# methods containing 'get', 'set', 'add', and 'remove' do NOT end with parens if (!isCommandType && isSharpStyle() && isNextWordSharpNonParenHeader(charNum + 1)) { isCommandType = true; isSharpAccessor = true; } if (isInExternC) returnVal = (isCommandType ? COMMAND_TYPE : EXTERN_TYPE); else returnVal = (isCommandType ? COMMAND_TYPE : ARRAY_TYPE); } int foundOneLineBlock = isOneLineBlockReached(currentLine, charNum); // this assumes each array definition is on a single line // (foundOneLineBlock == 2) is a one line block followed by a comma if (foundOneLineBlock == 2 && returnVal == COMMAND_TYPE) returnVal = ARRAY_TYPE; if (foundOneLineBlock > 0) // found one line block returnVal = (BracketType)(returnVal | SINGLE_LINE_TYPE); if (isBracketType(returnVal, ARRAY_TYPE) && isNonInStatementArrayBracket()) { returnVal = (BracketType)(returnVal | ARRAY_NIS_TYPE); isNonInStatementArray = true; isImmediatelyPostNonInStmt = false; // in case of "},{" nonInStatementBracket = formattedLine.length() - 1; } return returnVal; } /** * check if a line is empty * * @return whether line is empty */ bool ASFormatter::isEmptyLine(const string &line) const { return line.find_first_not_of(" \t") == string::npos; } /** * Check if the following text is "C" as in extern "C". * * @return whether the statement is extern "C" */ bool ASFormatter::isExternC() const { // charNum should be at 'extern' assert(!isWhiteSpace(currentLine[charNum])); size_t startQuote = currentLine.find_first_of(" \t\"", charNum); if (startQuote == string::npos) return false; startQuote = currentLine.find_first_not_of(" \t", startQuote); if (startQuote == string::npos) return false; if (currentLine.compare(startQuote, 3, "\"C\"") != 0) return false; return true; } /** * Check if the currently reached '*', '&' or '^' character is * a pointer-or-reference symbol, or another operator. * A pointer dereference (*) or an "address of" character (&) * counts as a pointer or reference because it is not an * arithmetic operator. * * @return whether current character is a reference-or-pointer */ bool ASFormatter::isPointerOrReference() const { assert(currentChar == '*' || currentChar == '&' || currentChar == '^'); if (isJavaStyle()) return false; if (isCharImmediatelyPostOperator) return false; // get the last legal word (may be a number) string lastWord = getPreviousWord(currentLine, charNum); if (lastWord.empty()) lastWord = " "; // check for preceding or following numeric values string nextText = peekNextText(currentLine.substr(charNum + 1)); if (nextText.length() == 0) nextText = " "; char nextChar = nextText[0]; if (isDigit(lastWord[0]) || isDigit(nextChar) || nextChar == '!' || nextChar == '~') return false; if (isPointerOrReferenceVariable(lastWord)) return true; //check for rvalue reference if (currentChar == '&' && nextChar == '&') { if (currentHeader != NULL || isInPotentialCalculation) return false; if (parenStack->back() > 0 && isBracketType(bracketTypeStack->back(), COMMAND_TYPE)) return false; return true; } if (nextChar == '*' || previousNonWSChar == '=' || previousNonWSChar == '(' || previousNonWSChar == '[' || isCharImmediatelyPostReturn || isInTemplate || isCharImmediatelyPostTemplate || currentHeader == &AS_CATCH || currentHeader == &AS_FOREACH || currentHeader == &AS_FOREVER || currentHeader == &AS_QFOREACH || currentHeader == &AS_QFOREVER) return true; if (isBracketType(bracketTypeStack->back(), ARRAY_TYPE) && isLegalNameChar(lastWord[0]) && isLegalNameChar(nextChar) && previousNonWSChar != ')') { if (isArrayOperator()) return false; } // checks on operators in parens if (parenStack->back() > 0 && isLegalNameChar(lastWord[0]) && isLegalNameChar(nextChar)) { // if followed by an assignment it is a pointer or reference // if followed by semicolon it is a pointer or reference in range-based for const string* followingOperator = getFollowingOperator(); if (followingOperator && followingOperator != &AS_MULT && followingOperator != &AS_BIT_AND) { if (followingOperator == &AS_ASSIGN || followingOperator == &AS_COLON) return true; else return false; } if (!isBracketType(bracketTypeStack->back(), COMMAND_TYPE)) return true; else return false; } // checks on operators in parens with following '(' if (parenStack->back() > 0 && nextChar == '(' && previousNonWSChar != ',' && previousNonWSChar != '(' && previousNonWSChar != '!' && previousNonWSChar != '&' && previousNonWSChar != '*' && previousNonWSChar != '|') return false; if (nextChar == '-' || nextChar == '+') { size_t nextNum = currentLine.find_first_not_of(" \t", charNum + 1); if (nextNum != string::npos) { if (currentLine.compare(nextNum, 2, "++") != 0 && currentLine.compare(nextNum, 2, "--") != 0) return false; } } bool isPR = (!isInPotentialCalculation || isBracketType(bracketTypeStack->back(), DEFINITION_TYPE) || (!isLegalNameChar(previousNonWSChar) && !(previousNonWSChar == ')' && nextChar == '(') && !(previousNonWSChar == ')' && currentChar == '*' && !isImmediatelyPostCast()) && previousNonWSChar != ']') ); if (!isPR) { isPR |= (!isWhiteSpace(nextChar) && nextChar != '-' && nextChar != '(' && nextChar != '[' && !isLegalNameChar(nextChar)); } return isPR; } /** * Check if the currently reached '*' or '&' character is * a dereferenced pointer or "address of" symbol. * NOTE: this MUST be a pointer or reference as determined by * the function isPointerOrReference(). * * @return whether current character is a dereference or address of */ bool ASFormatter::isDereferenceOrAddressOf() const { assert(currentChar == '*' || currentChar == '&' || currentChar == '^'); if (isCharImmediatelyPostTemplate) return false; if (previousNonWSChar == '=' || previousNonWSChar == ',' || previousNonWSChar == '.' || previousNonWSChar == '{' || previousNonWSChar == '>' || previousNonWSChar == '<' || isCharImmediatelyPostLineComment || isCharImmediatelyPostComment || isCharImmediatelyPostReturn) return true; // check for ** char nextChar = peekNextChar(); if (currentChar == '*' && nextChar == '*') { if (previousNonWSChar == '(') return true; if ((int) currentLine.length() < charNum + 2) return true; return false; } if (currentChar == '&' && nextChar == '&') { if (previousNonWSChar == '(' || templateDepth > 0) return true; if ((int) currentLine.length() < charNum + 2) return true; return false; } // check first char on the line if (charNum == (int) currentLine.find_first_not_of(" \t") && (isBracketType(bracketTypeStack->back(), COMMAND_TYPE) || parenStack->back() != 0)) return true; string nextText = peekNextText(currentLine.substr(charNum + 1)); if (nextText.length() > 0 && (nextText[0] == ')' || nextText[0] == '>' || nextText[0] == ',' || nextText[0] == '=')) return false; // check for reference to a pointer *& (cannot have &*) if ((currentChar == '*' && nextChar == '&') || (previousNonWSChar == '*' && currentChar == '&')) return false; if (!isBracketType(bracketTypeStack->back(), COMMAND_TYPE) && parenStack->back() == 0) return false; string lastWord = getPreviousWord(currentLine, charNum); if (lastWord == "else" || lastWord == "delete") return true; if (isPointerOrReferenceVariable(lastWord)) return false; bool isDA = (!(isLegalNameChar(previousNonWSChar) || previousNonWSChar == '>') || (nextText.length() > 0 && !isLegalNameChar(nextText[0]) && nextText[0] != '/') || (ispunct((unsigned char)previousNonWSChar) && previousNonWSChar != '.') || isCharImmediatelyPostReturn); return isDA; } /** * Check if the currently reached '*' or '&' character is * centered with one space on each side. * Only spaces are checked, not tabs. * If true then a space will be deleted on the output. * * @return whether current character is centered. */ bool ASFormatter::isPointerOrReferenceCentered() const { assert(currentLine[charNum] == '*' || currentLine[charNum] == '&' || currentLine[charNum] == '^'); int prNum = charNum; int lineLength = (int) currentLine.length(); // check for end of line if (peekNextChar() == ' ') return false; // check space before if (prNum < 1 || currentLine[prNum - 1] != ' ') return false; // check no space before that if (prNum < 2 || currentLine[prNum - 2] == ' ') return false; // check for ** if (prNum + 1 < lineLength && currentLine[prNum + 1] == '*') prNum++; // check space after if (prNum + 1 <= lineLength && currentLine[prNum + 1] != ' ') return false; // check no space after that if (prNum + 2 < lineLength && currentLine[prNum + 2] == ' ') return false; return true; } /** * Check if a word is a pointer or reference variable type. * * @return whether word is a pointer or reference variable. */ bool ASFormatter::isPointerOrReferenceVariable(string &word) const { if (word == "char" || word == "int" || word == "void" || (word.length() >= 6 // check end of word for _t && word.compare(word.length() - 2, 2, "_t") == 0) || word == "INT" || word == "VOID") return true; return false; } /** * check if the currently reached '+' or '-' character is a unary operator * this method takes for granted that the current character * is a '+' or '-'. * * @return whether the current '+' or '-' is a unary operator. */ bool ASFormatter::isUnaryOperator() const { assert(currentChar == '+' || currentChar == '-'); return ((isCharImmediatelyPostReturn || !isLegalNameChar(previousCommandChar)) && previousCommandChar != '.' && previousCommandChar != '\"' && previousCommandChar != '\'' && previousCommandChar != ')' && previousCommandChar != ']'); } /** * check if the currently reached comment is in a 'switch' statement * * @return whether the current '+' or '-' is in an exponent. */ bool ASFormatter::isInSwitchStatement() const { assert(isInLineComment || isInComment); if (preBracketHeaderStack->size() > 0) for (size_t i = 1; i < preBracketHeaderStack->size(); i++) if (preBracketHeaderStack->at(i) == &AS_SWITCH) return true; return false; } /** * check if the currently reached '+' or '-' character is * part of an exponent, i.e. 0.2E-5. * * @return whether the current '+' or '-' is in an exponent. */ bool ASFormatter::isInExponent() const { assert(currentChar == '+' || currentChar == '-'); int formattedLineLength = formattedLine.length(); if (formattedLineLength >= 2) { char prevPrevFormattedChar = formattedLine[formattedLineLength - 2]; char prevFormattedChar = formattedLine[formattedLineLength - 1]; return ((prevFormattedChar == 'e' || prevFormattedChar == 'E') && (prevPrevFormattedChar == '.' || isDigit(prevPrevFormattedChar))); } else return false; } /** * check if an array bracket should NOT have an in-statement indent * * @return the array is non in-statement */ bool ASFormatter::isNonInStatementArrayBracket() const { bool returnVal = false; char nextChar = peekNextChar(); // if this opening bracket begins the line there will be no inStatement indent if (currentLineBeginsWithBracket && charNum == (int) currentLineFirstBracketNum && nextChar != '}') returnVal = true; // if an opening bracket ends the line there will be no inStatement indent if (isWhiteSpace(nextChar) || isBeforeAnyLineEndComment(charNum) || nextChar == '{') returnVal = true; // Java "new Type [] {...}" IS an inStatement indent if (isJavaStyle() && previousNonWSChar == ']') returnVal = false; return returnVal; } /** * check if a one-line bracket has been reached, * i.e. if the currently reached '{' character is closed * with a complimentary '}' elsewhere on the current line, *. * @return 0 = one-line bracket has not been reached. * 1 = one-line bracket has been reached. * 2 = one-line bracket has been reached and is followed by a comma. */ int ASFormatter::isOneLineBlockReached(string &line, int startChar) const { assert(line[startChar] == '{'); bool isInComment_ = false; bool isInQuote_ = false; int bracketCount = 1; int lineLength = line.length(); char quoteChar_ = ' '; char ch = ' '; char prevCh = ' '; for (int i = startChar + 1; i < lineLength; ++i) { ch = line[i]; if (isInComment_) { if (line.compare(i, 2, "*/") == 0) { isInComment_ = false; ++i; } continue; } if (ch == '\\') { ++i; continue; } if (isInQuote_) { if (ch == quoteChar_) isInQuote_ = false; continue; } if (ch == '"' || ch == '\'') { isInQuote_ = true; quoteChar_ = ch; continue; } if (line.compare(i, 2, "//") == 0) break; if (line.compare(i, 2, "/*") == 0) { isInComment_ = true; ++i; continue; } if (ch == '{') ++bracketCount; else if (ch == '}') --bracketCount; if (bracketCount == 0) { // is this an array? if (parenStack->back() == 0 && prevCh != '}') { size_t peekNum = line.find_first_not_of(" \t", i + 1); if (peekNum != string::npos && line[peekNum] == ',') return 2; } return 1; } if (!isWhiteSpace(ch)) prevCh = ch; } return 0; } /** * peek at the next word to determine if it is a C# non-paren header. * will look ahead in the input file if necessary. * * @param char position on currentLine to start the search * @return true if the next word is get or set. */ bool ASFormatter::isNextWordSharpNonParenHeader(int startChar) const { // look ahead to find the next non-comment text string nextText = peekNextText(currentLine.substr(startChar)); if (nextText.length() == 0) return false; if (nextText[0] == '[') return true; if (!isCharPotentialHeader(nextText, 0)) return false; if (findKeyword(nextText, 0, AS_GET) || findKeyword(nextText, 0, AS_SET) || findKeyword(nextText, 0, AS_ADD) || findKeyword(nextText, 0, AS_REMOVE)) return true; return false; } /** * peek at the next char to determine if it is an opening bracket. * will look ahead in the input file if necessary. * this determines a java static constructor. * * @param char position on currentLine to start the search * @return true if the next word is an opening bracket. */ bool ASFormatter::isNextCharOpeningBracket(int startChar) const { bool retVal = false; string nextText = peekNextText(currentLine.substr(startChar)); if (nextText.length() > 0 && nextText.compare(0, 1, "{") == 0) retVal = true; return retVal; } /** * get the next non-whitespace substring on following lines, bypassing all comments. * * @param the first line to check * @return the next non-whitespace substring. */ string ASFormatter::peekNextText(const string &firstLine, bool endOnEmptyLine /*false*/, bool shouldReset /*false*/) const { bool isFirstLine = true; bool needReset = shouldReset; string nextLine_ = firstLine; size_t firstChar = string::npos; // find the first non-blank text, bypassing all comments. bool isInComment_ = false; while (sourceIterator->hasMoreLines() || isFirstLine) { if (isFirstLine) isFirstLine = false; else { nextLine_ = sourceIterator->peekNextLine(); needReset = true; } firstChar = nextLine_.find_first_not_of(" \t"); if (firstChar == string::npos) { if (endOnEmptyLine && !isInComment_) break; continue; } if (nextLine_.compare(firstChar, 2, "/*") == 0) { firstChar += 2; isInComment_ = true; } if (isInComment_) { firstChar = nextLine_.find("*/", firstChar); if (firstChar == string::npos) continue; firstChar += 2; isInComment_ = false; firstChar = nextLine_.find_first_not_of(" \t", firstChar); if (firstChar == string::npos) continue; } if (nextLine_.compare(firstChar, 2, "//") == 0) continue; // found the next text break; } if (firstChar == string::npos) nextLine_ = ""; else nextLine_ = nextLine_.substr(firstChar); if (needReset) sourceIterator->peekReset(); return nextLine_; } /** * adjust comment position because of adding or deleting spaces * the spaces are added or deleted to formattedLine * spacePadNum contains the adjustment */ void ASFormatter::adjustComments(void) { assert(spacePadNum != 0); assert(currentLine.compare(charNum, 2, "//") == 0 || currentLine.compare(charNum, 2, "/*") == 0); // block comment must be closed on this line with nothing after it if (currentLine.compare(charNum, 2, "/*") == 0) { size_t endNum = currentLine.find("*/", charNum + 2); if (endNum == string::npos) return; if (currentLine.find_first_not_of(" \t", endNum + 2) != string::npos) return; } size_t len = formattedLine.length(); // don't adjust a tab if (formattedLine[len - 1] == '\t') return; // if spaces were removed, need to add spaces before the comment if (spacePadNum < 0) { int adjust = -spacePadNum; // make the number positive formattedLine.append(adjust, ' '); } // if spaces were added, need to delete extra spaces before the comment // if cannot be done put the comment one space after the last text else if (spacePadNum > 0) { int adjust = spacePadNum; size_t lastText = formattedLine.find_last_not_of(' '); if (lastText != string::npos && lastText < len - adjust - 1) formattedLine.resize(len - adjust); else if (len > lastText + 2) formattedLine.resize(lastText + 2); else if (len < lastText + 2) formattedLine.append(len - lastText, ' '); } } /** * append the current bracket inside the end of line comments * currentChar contains the bracket, it will be appended to formattedLine * formattedLineCommentNum is the comment location on formattedLine */ void ASFormatter::appendCharInsideComments(void) { if (formattedLineCommentNum == string::npos) // does the comment start on the previous line? { appendCurrentChar(); // don't attach return; } assert(formattedLine.compare(formattedLineCommentNum, 2, "//") == 0 || formattedLine.compare(formattedLineCommentNum, 2, "/*") == 0); // find the previous non space char size_t end = formattedLineCommentNum; size_t beg = formattedLine.find_last_not_of(" \t", end - 1); if (beg == string::npos) { appendCurrentChar(); // don't attach return; } beg++; // insert the bracket if (end - beg < 3) // is there room to insert? formattedLine.insert(beg, 3 - end + beg, ' '); if (formattedLine[beg] == '\t') // don't pad with a tab formattedLine.insert(beg, 1, ' '); formattedLine[beg + 1] = currentChar; testForTimeToSplitFormattedLine(); if (isBeforeComment()) breakLine(); else if (isCharImmediatelyPostLineComment) shouldBreakLineAtNextChar = true; return; } /** * add or remove space padding to operators * currentChar contains the paren * the operators and necessary padding will be appended to formattedLine * the calling function should have a continue statement after calling this method * * @param *newOperator the operator to be padded */ void ASFormatter::padOperators(const string* newOperator) { assert(shouldPadOperators); assert(newOperator != NULL); bool shouldPad = (newOperator != &AS_SCOPE_RESOLUTION && newOperator != &AS_PLUS_PLUS && newOperator != &AS_MINUS_MINUS && newOperator != &AS_NOT && newOperator != &AS_BIT_NOT && newOperator != &AS_ARROW && !(newOperator == &AS_COLON && !foundQuestionMark // objC methods && (isInObjCMethodDefinition || isInObjCInterface || isInObjCSelector || squareBracketCount)) && !(newOperator == &AS_MINUS && isInExponent()) && !((newOperator == &AS_PLUS || newOperator == &AS_MINUS) // check for unary plus or minus && (previousNonWSChar == '(' || previousNonWSChar == '[' || previousNonWSChar == '=' || previousNonWSChar == ',')) && !(newOperator == &AS_PLUS && isInExponent()) && !isCharImmediatelyPostOperator && !((newOperator == &AS_MULT || newOperator == &AS_BIT_AND || newOperator == &AS_AND) && isPointerOrReference()) && !(newOperator == &AS_MULT && (previousNonWSChar == '.' || previousNonWSChar == '>')) // check for -> && !((isInTemplate || isImmediatelyPostTemplate) && (newOperator == &AS_LS || newOperator == &AS_GR)) && !(newOperator == &AS_GCC_MIN_ASSIGN && ASBase::peekNextChar(currentLine, charNum + 1) == '>') && !(newOperator == &AS_GR && previousNonWSChar == '?') && !(newOperator == &AS_QUESTION // check for Java wildcard && (previousNonWSChar == '<' || ASBase::peekNextChar(currentLine, charNum) == '>' || ASBase::peekNextChar(currentLine, charNum) == '.')) && !isInCase && !isInAsm && !isInAsmOneLine && !isInAsmBlock ); // pad before operator if (shouldPad && !(newOperator == &AS_COLON && (!foundQuestionMark && !isInEnum) && currentHeader != &AS_FOR) && !(newOperator == &AS_QUESTION && isSharpStyle() // check for C# nullable type (e.g. int?) && currentLine.find(':', charNum + 1) == string::npos) ) appendSpacePad(); appendOperator(*newOperator); goForward(newOperator->length() - 1); currentChar = (*newOperator)[newOperator->length() - 1]; // pad after operator // but do not pad after a '-' that is a unary-minus. if (shouldPad && !isBeforeAnyComment() && !(newOperator == &AS_PLUS && isUnaryOperator()) && !(newOperator == &AS_MINUS && isUnaryOperator()) && !(currentLine.compare(charNum + 1, 1, AS_SEMICOLON) == 0) && !(currentLine.compare(charNum + 1, 2, AS_SCOPE_RESOLUTION) == 0) && !(peekNextChar() == ',') && !(newOperator == &AS_QUESTION && isSharpStyle() // check for C# nullable type (e.g. int?) && peekNextChar() == '[') ) appendSpaceAfter(); previousOperator = newOperator; return; } /** * format pointer or reference * currentChar contains the pointer or reference * the symbol and necessary padding will be appended to formattedLine * the calling function should have a continue statement after calling this method * * NOTE: Do NOT use appendCurrentChar() in this method. The line should not be * broken once the calculation starts. */ void ASFormatter::formatPointerOrReference(void) { assert(currentChar == '*' || currentChar == '&' || currentChar == '^'); assert(!isJavaStyle()); int pa = pointerAlignment; int ra = referenceAlignment; int itemAlignment = (currentChar == '*' || currentChar == '^') ? pa : ((ra == REF_SAME_AS_PTR) ? pa : ra); // check for ** char peekedChar = peekNextChar(); if (currentChar == '*' && peekedChar == '*') { // remove any spaces between * and * (compiles OK if there are) if (currentLine[charNum + 1] != '*') { size_t nextPointer = currentLine.find_first_not_of(" \t", charNum + 1); assert(nextPointer != string::npos && currentLine[nextPointer] == '*'); currentLine.erase(charNum + 1, nextPointer - (charNum + 1)); } size_t nextChar = currentLine.find_first_not_of(" \t", charNum + 2); if (nextChar == string::npos) peekedChar = ' '; else peekedChar = currentLine[nextChar]; } if (currentChar == '&' && peekedChar == '&') { size_t nextChar = currentLine.find_first_not_of(" \t", charNum + 2); if (nextChar == string::npos) peekedChar = ' '; else peekedChar = currentLine[nextChar]; } // check for cast if (peekedChar == ')' || peekedChar == '>' || peekedChar == ',') { formatPointerOrReferenceCast(); return; } // check for a padded space and remove it if (charNum > 0 && !isWhiteSpace(currentLine[charNum - 1]) && formattedLine.length() > 0 && isWhiteSpace(formattedLine[formattedLine.length() - 1])) { formattedLine.erase(formattedLine.length() - 1); spacePadNum--; } if (itemAlignment == PTR_ALIGN_TYPE) { formatPointerOrReferenceToType(); } else if (itemAlignment == PTR_ALIGN_MIDDLE) { formatPointerOrReferenceToMiddle(); } else if (itemAlignment == PTR_ALIGN_NAME) { formatPointerOrReferenceToName(); } else // pointerAlignment == PTR_ALIGN_NONE { formattedLine.append(1, currentChar); } } /** * format pointer or reference with align to type */ void ASFormatter::formatPointerOrReferenceToType() { assert(currentChar == '*' || currentChar == '&' || currentChar == '^'); assert(!isJavaStyle()); // do this before bumping charNum bool isOldPRCentered = isPointerOrReferenceCentered(); size_t prevCh = formattedLine.find_last_not_of(" \t"); if (prevCh == string::npos) prevCh = 0; if (formattedLine.length() == 0 || prevCh == formattedLine.length() - 1) formattedLine.append(1, currentChar); else { // exchange * or & with character following the type // this may not work every time with a tab character string charSave = formattedLine.substr(prevCh + 1, 1); formattedLine[prevCh + 1] = currentChar; formattedLine.append(charSave); } if (isSequenceReached("**") || isSequenceReached("&&")) { if (formattedLine.length() == 1) formattedLine.append(1, currentChar); else formattedLine.insert(prevCh + 2, 1, currentChar); goForward(1); } // if no space after then add one if (charNum < (int) currentLine.length() - 1 && !isWhiteSpace(currentLine[charNum + 1]) && currentLine[charNum + 1] != ')') appendSpacePad(); // if old pointer or reference is centered, remove a space if (isOldPRCentered && isWhiteSpace(formattedLine[formattedLine.length() - 1])) { formattedLine.erase(formattedLine.length() - 1, 1); spacePadNum--; } // update the formattedLine split point if (maxCodeLength != string::npos) { size_t index = formattedLine.length() - 1; if (isWhiteSpace(formattedLine[index])) { updateFormattedLineSplitPointsPointerOrReference(index); testForTimeToSplitFormattedLine(); } } } /** * format pointer or reference with align in the middle */ void ASFormatter::formatPointerOrReferenceToMiddle() { assert(currentChar == '*' || currentChar == '&' || currentChar == '^'); assert(!isJavaStyle()); // compute current whitespace before size_t wsBefore = currentLine.find_last_not_of(" \t", charNum - 1); if (wsBefore == string::npos) wsBefore = 0; else wsBefore = charNum - wsBefore - 1; string sequenceToInsert(1, currentChar); if (isSequenceReached("**")) { sequenceToInsert = "**"; goForward(1); } else if (isSequenceReached("&&")) { sequenceToInsert = "&&"; goForward(1); } // if reference to a pointer check for conflicting alignment else if (currentChar == '*' && peekNextChar() == '&' && (referenceAlignment == REF_ALIGN_TYPE || referenceAlignment == REF_ALIGN_MIDDLE || referenceAlignment == REF_SAME_AS_PTR)) { sequenceToInsert = "*&"; goForward(1); for (size_t i = charNum; i < currentLine.length() - 1 && isWhiteSpace(currentLine[i]); i++) goForward(1); } // if a comment follows don't align, just space pad if (isBeforeAnyComment()) { appendSpacePad(); formattedLine.append(sequenceToInsert); appendSpaceAfter(); return; } // do this before goForward() bool isAfterScopeResolution = previousNonWSChar == ':'; size_t charNumSave = charNum; // if this is the last thing on the line if (currentLine.find_first_not_of(" \t", charNum + 1) == string::npos) { if (wsBefore == 0 && !isAfterScopeResolution) { wsBefore = 1; formattedLine.append(1, ' '); } formattedLine.append(sequenceToInsert); return; } // goForward() to convert tabs to spaces, if necessary, // and move following characters to preceding characters // this may not work every time with tab characters for (size_t i = charNum + 1; i < currentLine.length() && isWhiteSpace(currentLine[i]); i++) { goForward(1); if (formattedLine.length() > 0) formattedLine.append(1, currentLine[i]); else spacePadNum--; } // find space padding after size_t wsAfter = currentLine.find_first_not_of(" \t", charNumSave + 1); if (wsAfter == string::npos || isBeforeAnyComment()) wsAfter = 0; else wsAfter = wsAfter - charNumSave - 1; // don't pad before scope resolution operator, but pad after if (isAfterScopeResolution) { size_t lastText = formattedLine.find_last_not_of(" \t"); formattedLine.insert(lastText + 1, sequenceToInsert); appendSpacePad(); } else if (formattedLine.length() > 0) { // whitespace should be at least 2 chars to center if (wsBefore + wsAfter < 2) { size_t charsToAppend = (2 - (wsBefore + wsAfter)); formattedLine.append(charsToAppend, ' '); spacePadNum += charsToAppend; if (wsBefore == 0) wsBefore++; if (wsAfter == 0) wsAfter++; } // insert the pointer or reference char size_t padAfter = (wsBefore + wsAfter) / 2; size_t index = formattedLine.length() - padAfter; formattedLine.insert(index, sequenceToInsert); } else // formattedLine.length() == 0 { formattedLine.append(sequenceToInsert); if (wsAfter == 0) wsAfter++; formattedLine.append(wsAfter, ' '); spacePadNum += wsAfter; } // update the formattedLine split point after the pointer if (maxCodeLength != string::npos && formattedLine.length() > 0) { size_t index = formattedLine.find_last_not_of(" \t"); if (index != string::npos && (index < formattedLine.length() - 1)) { index++; updateFormattedLineSplitPointsPointerOrReference(index); testForTimeToSplitFormattedLine(); } } } /** * format pointer or reference with align to name */ void ASFormatter::formatPointerOrReferenceToName() { assert(currentChar == '*' || currentChar == '&' || currentChar == '^'); assert(!isJavaStyle()); // do this before bumping charNum bool isOldPRCentered = isPointerOrReferenceCentered(); size_t startNum = formattedLine.find_last_not_of(" \t"); if (startNum == string::npos) startNum = 0; string sequenceToInsert(1, currentChar); if (isSequenceReached("**")) { sequenceToInsert = "**"; goForward(1); } else if (isSequenceReached("&&")) { sequenceToInsert = "&&"; goForward(1); } // if reference to a pointer align both to name else if (currentChar == '*' && peekNextChar() == '&') { sequenceToInsert = "*&"; goForward(1); for (size_t i = charNum; i < currentLine.length() - 1 && isWhiteSpace(currentLine[i]); i++) goForward(1); } char peekedChar = peekNextChar(); bool isAfterScopeResolution = previousNonWSChar == ':'; // check for :: // if this is not the last thing on the line if (!isBeforeAnyComment() && (int) currentLine.find_first_not_of(" \t", charNum + 1) > charNum) { // goForward() to convert tabs to spaces, if necessary, // and move following characters to preceding characters // this may not work every time with tab characters for (size_t i = charNum + 1; i < currentLine.length() && isWhiteSpace(currentLine[i]); i++) { // if a padded paren follows don't move if (shouldPadParensOutside && peekedChar == '(' && !isOldPRCentered) { // empty parens don't count size_t start = currentLine.find_first_not_of("( \t", charNum + 1); if (start != string::npos && currentLine[start] != ')') break; } goForward(1); if (formattedLine.length() > 0) formattedLine.append(1, currentLine[i]); else spacePadNum--; } } // don't pad before scope resolution operator if (isAfterScopeResolution) { size_t lastText = formattedLine.find_last_not_of(" \t"); if (lastText != string::npos && lastText + 1 < formattedLine.length()) formattedLine.erase(lastText + 1); } // if no space before * then add one else if (formattedLine.length() > 0 && (formattedLine.length() <= startNum + 1 || !isWhiteSpace(formattedLine[startNum + 1]))) { formattedLine.insert(startNum + 1 , 1, ' '); spacePadNum++; } appendSequence(sequenceToInsert, false); // if old pointer or reference is centered, remove a space if (isOldPRCentered && formattedLine.length() > startNum + 1 && isWhiteSpace(formattedLine[startNum + 1]) && !isBeforeAnyComment()) { formattedLine.erase(startNum + 1, 1); spacePadNum--; } // don't convert to *= or &= if (peekedChar == '=') { appendSpaceAfter(); // if more than one space before, delete one if (formattedLine.length() > startNum && isWhiteSpace(formattedLine[startNum + 1]) && isWhiteSpace(formattedLine[startNum + 2])) { formattedLine.erase(startNum + 1, 1); spacePadNum--; } } // update the formattedLine split point if (maxCodeLength != string::npos) { size_t index = formattedLine.find_last_of(" \t"); if (index != string::npos && index < formattedLine.length() - 1 && (formattedLine[index + 1] == '*' || formattedLine[index + 1] == '&' || formattedLine[index + 1] == '^')) { updateFormattedLineSplitPointsPointerOrReference(index); testForTimeToSplitFormattedLine(); } } } /** * format pointer or reference cast * currentChar contains the pointer or reference * NOTE: the pointers and references in function definitions * are processed as a cast (e.g. void foo(void*, void*)) * is processed here. */ void ASFormatter::formatPointerOrReferenceCast(void) { assert(currentChar == '*' || currentChar == '&' || currentChar == '^'); assert(!isJavaStyle()); int pa = pointerAlignment; int ra = referenceAlignment; int itemAlignment = (currentChar == '*' || currentChar == '^') ? pa : ((ra == REF_SAME_AS_PTR) ? pa : ra); string sequenceToInsert(1, currentChar); if (isSequenceReached("**") || isSequenceReached("&&")) { goForward(1); sequenceToInsert.append(1, currentLine[charNum]); } if (itemAlignment == PTR_ALIGN_NONE) { appendSequence(sequenceToInsert, false); return; } // remove preceding whitespace char prevCh = ' '; size_t prevNum = formattedLine.find_last_not_of(" \t"); if (prevNum != string::npos) { prevCh = formattedLine[prevNum]; if (prevNum + 1 < formattedLine.length() && isWhiteSpace(formattedLine[prevNum + 1]) && prevCh != '(') { spacePadNum -= (formattedLine.length() - 1 - prevNum); formattedLine.erase(prevNum + 1); } } bool isAfterScopeResolution = previousNonWSChar == ':'; if ((itemAlignment == PTR_ALIGN_MIDDLE || itemAlignment == PTR_ALIGN_NAME) && !isAfterScopeResolution && prevCh != '(') { appendSpacePad(); // in this case appendSpacePad may or may not update the split point if (maxCodeLength != string::npos && formattedLine.length() > 0) updateFormattedLineSplitPointsPointerOrReference(formattedLine.length() - 1); appendSequence(sequenceToInsert, false); } else appendSequence(sequenceToInsert, false); // remove trailing whitespace if paren or comma follow char nextChar = peekNextChar(); if (nextChar == ')' || nextChar == ',') { while (isWhiteSpace(currentLine[charNum + 1])) { goForward(1); spacePadNum--; } } } /** * add or remove space padding to parens * currentChar contains the paren * the parens and necessary padding will be appended to formattedLine * the calling function should have a continue statement after calling this method */ void ASFormatter::padParens(void) { assert(shouldPadParensOutside || shouldPadParensInside || shouldUnPadParens || shouldPadFirstParen); assert(currentChar == '(' || currentChar == ')'); int spacesOutsideToDelete = 0; int spacesInsideToDelete = 0; if (currentChar == '(') { spacesOutsideToDelete = formattedLine.length() - 1; spacesInsideToDelete = 0; // compute spaces outside the opening paren to delete if (shouldUnPadParens) { char lastChar = ' '; bool prevIsParenHeader = false; size_t i = formattedLine.find_last_not_of(" \t"); if (i != string::npos) { // if last char is a bracket the previous whitespace is an indent if (formattedLine[i] == '{') spacesOutsideToDelete = 0; else if (isCharImmediatelyPostPointerOrReference) spacesOutsideToDelete = 0; else { spacesOutsideToDelete -= i; lastChar = formattedLine[i]; // if previous word is a header, it will be a paren header string prevWord = getPreviousWord(formattedLine, formattedLine.length()); const string* prevWordH = NULL; if (shouldPadHeader && prevWord.length() > 0 && isCharPotentialHeader(prevWord, 0)) prevWordH = ASBeautifier::findHeader(prevWord, 0, headers); if (prevWordH != NULL) prevIsParenHeader = true; else if (prevWord == "return") // don't unpad prevIsParenHeader = true; else if (isCStyle() && prevWord == "throw" && shouldPadHeader) // don't unpad prevIsParenHeader = true; else if (prevWord == "and" || prevWord == "or") // don't unpad prevIsParenHeader = true; // don't unpad variables else if (prevWord == "bool" || prevWord == "int" || prevWord == "void" || prevWord == "void*" || (prevWord.length() >= 6 // check end of word for _t && prevWord.compare(prevWord.length() - 2, 2, "_t") == 0) || prevWord == "BOOL" || prevWord == "DWORD" || prevWord == "HWND" || prevWord == "INT" || prevWord == "LPSTR" || prevWord == "VOID" || prevWord == "LPVOID" ) { prevIsParenHeader = true; } } } // do not unpad operators, but leave them if already padded if (shouldPadParensOutside || prevIsParenHeader) spacesOutsideToDelete--; else if (lastChar == '|' // check for || || lastChar == '&' // check for && || lastChar == ',' || (lastChar == '(' && shouldPadParensInside) || (lastChar == '>' && !foundCastOperator) || lastChar == '<' || lastChar == '?' || lastChar == ':' || lastChar == ';' || lastChar == '=' || lastChar == '+' || lastChar == '-' || lastChar == '*' || lastChar == '/' || lastChar == '%' || lastChar == '^' ) spacesOutsideToDelete--; if (spacesOutsideToDelete > 0) { formattedLine.erase(i + 1, spacesOutsideToDelete); spacePadNum -= spacesOutsideToDelete; } } // pad open paren outside char peekedCharOutside = peekNextChar(); if (shouldPadFirstParen && previousChar != '(' && peekedCharOutside != ')') appendSpacePad(); else if (shouldPadParensOutside) { if (!(currentChar == '(' && peekedCharOutside == ')')) appendSpacePad(); } appendCurrentChar(); // unpad open paren inside if (shouldUnPadParens) { size_t j = currentLine.find_first_not_of(" \t", charNum + 1); if (j != string::npos) spacesInsideToDelete = j - charNum - 1; if (shouldPadParensInside) spacesInsideToDelete--; if (spacesInsideToDelete > 0) { currentLine.erase(charNum + 1, spacesInsideToDelete); spacePadNum -= spacesInsideToDelete; } // convert tab to space if requested if (shouldConvertTabs && (int)currentLine.length() > charNum + 1 && currentLine[charNum + 1] == '\t') currentLine[charNum + 1] = ' '; } // pad open paren inside char peekedCharInside = peekNextChar(); if (shouldPadParensInside) if (!(currentChar == '(' && peekedCharInside == ')')) appendSpaceAfter(); } else if (currentChar == ')') { spacesOutsideToDelete = 0; spacesInsideToDelete = formattedLine.length(); // unpad close paren inside if (shouldUnPadParens) { size_t i = formattedLine.find_last_not_of(" \t"); if (i != string::npos) spacesInsideToDelete = formattedLine.length() - 1 - i; if (shouldPadParensInside) spacesInsideToDelete--; if (spacesInsideToDelete > 0) { formattedLine.erase(i + 1, spacesInsideToDelete); spacePadNum -= spacesInsideToDelete; } } // pad close paren inside if (shouldPadParensInside) if (!(previousChar == '(' && currentChar == ')')) appendSpacePad(); appendCurrentChar(); // unpad close paren outside // close parens outside are left unchanged if (shouldUnPadParens) { //size_t j = currentLine.find_first_not_of(" \t", charNum + 1); //if (j != string::npos) // spacesOutsideToDelete = j - charNum - 1; //if (shouldPadParensOutside) // spacesOutsideToDelete--; //if (spacesOutsideToDelete > 0) //{ // currentLine.erase(charNum + 1, spacesOutsideToDelete); // spacePadNum -= spacesOutsideToDelete; //} } // pad close paren outside char peekedCharOutside = peekNextChar(); if (shouldPadParensOutside) if (peekedCharOutside != ';' && peekedCharOutside != ',' && peekedCharOutside != '.' && peekedCharOutside != '+' // check for ++ && peekedCharOutside != '-' // check for -- && peekedCharOutside != ']') appendSpaceAfter(); } return; } /** * format opening bracket as attached or broken * currentChar contains the bracket * the brackets will be appended to the current formattedLine or a new formattedLine as necessary * the calling function should have a continue statement after calling this method * * @param bracketType the type of bracket to be formatted. */ void ASFormatter::formatOpeningBracket(BracketType bracketType) { assert(!isBracketType(bracketType, ARRAY_TYPE)); assert(currentChar == '{'); parenStack->push_back(0); bool breakBracket = isCurrentBracketBroken(); if (breakBracket) { if (isBeforeAnyComment() && isOkToBreakBlock(bracketType)) { // if comment is at line end leave the comment on this line if (isBeforeAnyLineEndComment(charNum) && !currentLineBeginsWithBracket) { currentChar = ' '; // remove bracket from current line if (parenStack->size() > 1) parenStack->pop_back(); currentLine[charNum] = currentChar; appendOpeningBracket = true; // append bracket to following line } // else put comment after the bracket else if (!isBeforeMultipleLineEndComments(charNum)) breakLine(); } else if (!isBracketType(bracketType, SINGLE_LINE_TYPE)) breakLine(); else if (shouldBreakOneLineBlocks && peekNextChar() != '}') breakLine(); else if (!isInLineBreak) appendSpacePad(); appendCurrentChar(); // should a following comment break from the bracket? // must break the line AFTER the bracket if (isBeforeComment() && formattedLine.length() > 0 && formattedLine[0] == '{' && isOkToBreakBlock(bracketType) && (bracketFormatMode == BREAK_MODE || bracketFormatMode == LINUX_MODE || bracketFormatMode == STROUSTRUP_MODE)) { shouldBreakLineAtNextChar = true; } } else // attach bracket { // are there comments before the bracket? if (isCharImmediatelyPostComment || isCharImmediatelyPostLineComment) { if (isOkToBreakBlock(bracketType) && !(isCharImmediatelyPostComment && isCharImmediatelyPostLineComment) // don't attach if two comments on the line && !isImmediatelyPostPreprocessor // && peekNextChar() != '}' // don't attach { } // removed release 2.03 && previousCommandChar != '{' // don't attach { { && previousCommandChar != '}' // don't attach } { && previousCommandChar != ';') // don't attach ; { { appendCharInsideComments(); } else { appendCurrentChar(); // don't attach } } else if (previousCommandChar == '{' || previousCommandChar == '}' || previousCommandChar == ';') // '}' , ';' chars added for proper handling of '{' immediately after a '}' or ';' { appendCurrentChar(); // don't attach } else { // if a blank line precedes this don't attach if (isEmptyLine(formattedLine)) appendCurrentChar(); // don't attach else if (isOkToBreakBlock(bracketType) && !(isImmediatelyPostPreprocessor && currentLineBeginsWithBracket)) { if (peekNextChar() != '}') { appendSpacePad(); appendCurrentChar(false); // OK to attach testForTimeToSplitFormattedLine(); // line length will have changed // should a following comment attach with the bracket? // insert spaces to reposition the comment if (isBeforeComment() && !isBeforeMultipleLineEndComments(charNum) && (!isBeforeAnyLineEndComment(charNum) || currentLineBeginsWithBracket)) { shouldBreakLineAtNextChar = true; currentLine.insert(charNum + 1, charNum + 1, ' '); } else if (!isBeforeAnyComment()) // added in release 2.03 { shouldBreakLineAtNextChar = true; } } else { if (currentLineBeginsWithBracket && charNum == (int) currentLineFirstBracketNum) { appendSpacePad(); appendCurrentChar(false); // attach shouldBreakLineAtNextChar = true; } else { appendSpacePad(); appendCurrentChar(); // don't attach } } } else { if (!isInLineBreak) appendSpacePad(); appendCurrentChar(); // don't attach } } } } /** * format closing bracket * currentChar contains the bracket * the calling function should have a continue statement after calling this method * * @param bracketType the type of the opening bracket for this closing bracket. */ void ASFormatter::formatClosingBracket(BracketType bracketType) { assert(!isBracketType(bracketType, ARRAY_TYPE)); assert(currentChar == '}'); // parenStack must contain one entry if (parenStack->size() > 1) parenStack->pop_back(); // mark state of immediately after empty block // this state will be used for locating brackets that appear immediately AFTER an empty block (e.g. '{} \n}'). if (previousCommandChar == '{') isImmediatelyPostEmptyBlock = true; if (shouldAttachClosingBracket) { // for now, namespaces and classes will be attached. Uncomment the lines below to break. if ((isEmptyLine(formattedLine) // if a blank line precedes this || isCharImmediatelyPostLineComment || isCharImmediatelyPostComment || (isImmediatelyPostPreprocessor && (int) currentLine.find_first_not_of(" \t") == charNum) // || (isBracketType(bracketType, CLASS_TYPE) && isOkToBreakBlock(bracketType) && previousNonWSChar != '{') // || (isBracketType(bracketType, NAMESPACE_TYPE) && isOkToBreakBlock(bracketType) && previousNonWSChar != '{') ) && (!isBracketType(bracketType, SINGLE_LINE_TYPE) || isOkToBreakBlock(bracketType))) { breakLine(); appendCurrentChar(); // don't attach } else { if (previousNonWSChar != '{' && (!isBracketType(bracketType, SINGLE_LINE_TYPE) || isOkToBreakBlock(bracketType))) appendSpacePad(); appendCurrentChar(false); // attach } } else if ((!(previousCommandChar == '{' && isPreviousBracketBlockRelated)) // this '}' does not close an empty block && isOkToBreakBlock(bracketType)) // astyle is allowed to break one line blocks { breakLine(); appendCurrentChar(); } else { appendCurrentChar(); } // if a declaration follows a definition, space pad if (isLegalNameChar(peekNextChar())) appendSpaceAfter(); if (shouldBreakBlocks && currentHeader != NULL && !isHeaderInMultiStatementLine && parenStack->back() == 0) { if (currentHeader == &AS_CASE || currentHeader == &AS_DEFAULT) { // do not yet insert a line if "break" statement is outside the brackets string nextText = peekNextText(currentLine.substr(charNum + 1)); if (nextText.length() > 0 && nextText.substr(0, 5) != "break") isAppendPostBlockEmptyLineRequested = true; } else isAppendPostBlockEmptyLineRequested = true; } } /** * format array brackets as attached or broken * determine if the brackets can have an inStatement indent * currentChar contains the bracket * the brackets will be appended to the current formattedLine or a new formattedLine as necessary * the calling function should have a continue statement after calling this method * * @param bracketType the type of bracket to be formatted, must be an ARRAY_TYPE. * @param isOpeningArrayBracket indicates if this is the opening bracket for the array block. */ void ASFormatter::formatArrayBrackets(BracketType bracketType, bool isOpeningArrayBracket) { assert(isBracketType(bracketType, ARRAY_TYPE)); assert(currentChar == '{' || currentChar == '}'); if (currentChar == '{') { // is this the first opening bracket in the array? if (isOpeningArrayBracket) { if (bracketFormatMode == ATTACH_MODE || bracketFormatMode == LINUX_MODE || bracketFormatMode == STROUSTRUP_MODE) { // don't attach to a preprocessor directive or '\' line if ((isImmediatelyPostPreprocessor || (formattedLine.length() > 0 && formattedLine[formattedLine.length() - 1] == '\\')) && currentLineBeginsWithBracket) { isInLineBreak = true; appendCurrentChar(); // don't attach } else if (isCharImmediatelyPostComment) { // TODO: attach bracket to line-end comment appendCurrentChar(); // don't attach } else if (isCharImmediatelyPostLineComment && !isBracketType(bracketType, SINGLE_LINE_TYPE)) { appendCharInsideComments(); } else { // if a blank line precedes this don't attach if (isEmptyLine(formattedLine)) appendCurrentChar(); // don't attach else { // if bracket is broken or not an assignment if (currentLineBeginsWithBracket && !isBracketType(bracketType, SINGLE_LINE_TYPE)) { appendSpacePad(); appendCurrentChar(false); // OK to attach // TODO: debug the following line testForTimeToSplitFormattedLine(); // line length will have changed if (currentLineBeginsWithBracket && (int)currentLineFirstBracketNum == charNum) shouldBreakLineAtNextChar = true; } else { if (previousNonWSChar != '(') appendSpacePad(); appendCurrentChar(); } } } } else if (bracketFormatMode == BREAK_MODE) { if (isWhiteSpace(peekNextChar())) breakLine(); else if (isBeforeAnyComment()) { // do not break unless comment is at line end if (isBeforeAnyLineEndComment(charNum) && !currentLineBeginsWithBracket) { currentChar = ' '; // remove bracket from current line appendOpeningBracket = true; // append bracket to following line } } if (!isInLineBreak && previousNonWSChar != '(') appendSpacePad(); appendCurrentChar(); if (currentLineBeginsWithBracket && (int)currentLineFirstBracketNum == charNum && !isBracketType(bracketType, SINGLE_LINE_TYPE)) shouldBreakLineAtNextChar = true; } else if (bracketFormatMode == RUN_IN_MODE) { if (isWhiteSpace(peekNextChar())) breakLine(); else if (isBeforeAnyComment()) { // do not break unless comment is at line end if (isBeforeAnyLineEndComment(charNum) && !currentLineBeginsWithBracket) { currentChar = ' '; // remove bracket from current line appendOpeningBracket = true; // append bracket to following line } } if (!isInLineBreak && previousNonWSChar != '(') appendSpacePad(); appendCurrentChar(); } else if (bracketFormatMode == NONE_MODE) { if (currentLineBeginsWithBracket && charNum == (int) currentLineFirstBracketNum) { appendCurrentChar(); // don't attach } else { if (previousNonWSChar != '(') appendSpacePad(); appendCurrentChar(false); // OK to attach } } } else // not the first opening bracket { if (bracketFormatMode == RUN_IN_MODE) { if (previousNonWSChar == '{' && bracketTypeStack->size() > 2 && !isBracketType((*bracketTypeStack)[bracketTypeStack->size() - 2], SINGLE_LINE_TYPE)) formatArrayRunIn(); } else if (!isInLineBreak && !isWhiteSpace(peekNextChar()) && previousNonWSChar == '{' && bracketTypeStack->size() > 2 && !isBracketType((*bracketTypeStack)[bracketTypeStack->size() - 2], SINGLE_LINE_TYPE)) formatArrayRunIn(); appendCurrentChar(); } } else if (currentChar == '}') { if (shouldAttachClosingBracket) { if (isEmptyLine(formattedLine) // if a blank line precedes this || isImmediatelyPostPreprocessor || isCharImmediatelyPostLineComment || isCharImmediatelyPostComment) appendCurrentChar(); // don't attach else { appendSpacePad(); appendCurrentChar(false); // attach } } else { // does this close the first opening bracket in the array? // must check if the block is still a single line because of anonymous statements if (!isBracketType(bracketType, SINGLE_LINE_TYPE) || formattedLine.find('{') == string::npos) breakLine(); appendCurrentChar(); } // if a declaration follows an enum definition, space pad char peekedChar = peekNextChar(); if (isLegalNameChar(peekedChar) || peekedChar == '[') appendSpaceAfter(); } } /** * determine if a run-in can be attached. * if it can insert the indents in formattedLine and reset the current line break. */ void ASFormatter::formatRunIn() { assert(bracketFormatMode == RUN_IN_MODE || bracketFormatMode == NONE_MODE); // keep one line blocks returns true without indenting the run-in if (!isOkToBreakBlock(bracketTypeStack->back())) return; // true; // make sure the line begins with a bracket size_t lastText = formattedLine.find_last_not_of(" \t"); if (lastText == string::npos || formattedLine[lastText] != '{') return; // false; // make sure the bracket is broken if (formattedLine.find_first_not_of(" \t{") != string::npos) return; // false; if (isBracketType(bracketTypeStack->back(), NAMESPACE_TYPE)) return; // false; bool extraIndent = false; isInLineBreak = true; // cannot attach a class modifier without indent-classes if (isCStyle() && isCharPotentialHeader(currentLine, charNum) && (isBracketType(bracketTypeStack->back(), CLASS_TYPE) || (isBracketType(bracketTypeStack->back(), STRUCT_TYPE) && isInIndentableStruct))) { if (findKeyword(currentLine, charNum, AS_PUBLIC) || findKeyword(currentLine, charNum, AS_PRIVATE) || findKeyword(currentLine, charNum, AS_PROTECTED)) { if (!getClassIndent()) return; // false; } else if (getClassIndent()) extraIndent = true; } // cannot attach a 'case' statement without indent-switches if (!getSwitchIndent() && isCharPotentialHeader(currentLine, charNum) && (findKeyword(currentLine, charNum, AS_CASE) || findKeyword(currentLine, charNum, AS_DEFAULT))) return; // false; // extra indent for switch statements if (getSwitchIndent() && !preBracketHeaderStack->empty() && preBracketHeaderStack->back() == &AS_SWITCH && ((isLegalNameChar(currentChar) && !findKeyword(currentLine, charNum, AS_CASE)))) extraIndent = true; isInLineBreak = false; // remove for extra whitespace if (formattedLine.length() > lastText + 1 && formattedLine.find_first_not_of(" \t", lastText + 1) == string::npos) formattedLine.erase(lastText + 1); if (getForceTabIndentation() && getIndentLength() != getTabLength()) { // insert the space indents string indent; int indentLength_ = getIndentLength(); int tabLength_ = getTabLength(); indent.append(indentLength_, ' '); if (extraIndent) indent.append(indentLength_, ' '); // replace spaces indents with tab indents size_t tabCount = indent.length() / tabLength_; // truncate extra spaces indent.erase(0U, tabCount * tabLength_); indent.insert(0U, tabCount, '\t'); horstmannIndentChars = indentLength_; if (indent[0] == ' ') // allow for bracket indent.erase(0, 1); formattedLine.append(indent); } else if (getIndentString() == "\t") { appendChar('\t', false); horstmannIndentChars = 2; // one for { and one for tab if (extraIndent) { appendChar('\t', false); horstmannIndentChars++; } } else // spaces { int indentLength_ = getIndentLength(); formattedLine.append(indentLength_ - 1, ' '); horstmannIndentChars = indentLength_; if (extraIndent) { formattedLine.append(indentLength_, ' '); horstmannIndentChars += indentLength_; } } isInHorstmannRunIn = true; } /** * remove whitepace and add indentation for an array run-in. */ void ASFormatter::formatArrayRunIn() { assert(isBracketType(bracketTypeStack->back(), ARRAY_TYPE)); // make sure the bracket is broken if (formattedLine.find_first_not_of(" \t{") != string::npos) return; size_t lastText = formattedLine.find_last_not_of(" \t"); if (lastText == string::npos || formattedLine[lastText] != '{') return; // check for extra whitespace if (formattedLine.length() > lastText + 1 && formattedLine.find_first_not_of(" \t", lastText + 1) == string::npos) formattedLine.erase(lastText + 1); if (getIndentString() == "\t") { appendChar('\t', false); horstmannIndentChars = 2; // one for { and one for tab } else { int indent = getIndentLength(); formattedLine.append(indent - 1, ' '); horstmannIndentChars = indent; } isInHorstmannRunIn = true; isInLineBreak = false; } /** * delete a bracketTypeStack vector object * BracketTypeStack did not work with the DeleteContainer template */ void ASFormatter::deleteContainer(vector* &container) { if (container != NULL) { container->clear(); delete (container); container = NULL; } } /** * delete a vector object * T is the type of vector * used for all vectors except bracketTypeStack */ template void ASFormatter::deleteContainer(T &container) { if (container != NULL) { container->clear(); delete (container); container = NULL; } } /** * initialize a BracketType vector object * BracketType did not work with the DeleteContainer template */ void ASFormatter::initContainer(vector* &container, vector* value) { if (container != NULL) deleteContainer(container); container = value; } /** * initialize a vector object * T is the type of vector * used for all vectors except bracketTypeStack */ template void ASFormatter::initContainer(T &container, T value) { // since the ASFormatter object is never deleted, // the existing vectors must be deleted before creating new ones if (container != NULL) deleteContainer(container); container = value; } /** * convert a tab to spaces. * charNum points to the current character to convert to spaces. * tabIncrementIn is the increment that must be added for tab indent characters * to get the correct column for the current tab. * replaces the tab in currentLine with the required number of spaces. * replaces the value of currentChar. */ void ASFormatter::convertTabToSpaces() { assert(currentLine[charNum] == '\t'); // do NOT replace if in quotes if (isInQuote || isInQuoteContinuation) return; size_t tabSize = getTabLength(); size_t numSpaces = tabSize - ((tabIncrementIn + charNum) % tabSize); currentLine.replace(charNum, 1, numSpaces, ' '); currentChar = currentLine[charNum]; } /** * is it ok to break this block? */ bool ASFormatter::isOkToBreakBlock(BracketType bracketType) const { // Actually, there should not be an ARRAY_TYPE bracket here. // But this will avoid breaking a one line block when there is. // Otherwise they will be formatted differently on consecutive runs. if (isBracketType(bracketType, ARRAY_TYPE) && isBracketType(bracketType, SINGLE_LINE_TYPE)) return false; if (!isBracketType(bracketType, SINGLE_LINE_TYPE) || shouldBreakOneLineBlocks || breakCurrentOneLineBlock) return true; return false; } /** * check if a sharp header is a paren or nonparen header */ bool ASFormatter::isSharpStyleWithParen(const string* header) const { if (isSharpStyle() && peekNextChar() == '(' && (header == &AS_CATCH || header == &AS_DELEGATE)) return true; return false; } /** * Check for a following header when a comment is reached. * firstLine must contain the start of the comment. * return value is a pointer to the header or NULL. */ const string* ASFormatter::checkForHeaderFollowingComment(const string &firstLine) const { assert(isInComment || isInLineComment); assert(shouldBreakElseIfs || shouldBreakBlocks || isInSwitchStatement()); // look ahead to find the next non-comment text bool endOnEmptyLine = (currentHeader == NULL); if (isInSwitchStatement()) endOnEmptyLine = false; string nextText = peekNextText(firstLine, endOnEmptyLine); if (nextText.length() == 0 || !isCharPotentialHeader(nextText, 0)) return NULL; return ASBeautifier::findHeader(nextText, 0, headers); } /** * process preprocessor statements. * charNum should be the index of the #. * * delete bracketTypeStack entries added by #if if a #else is found. * prevents double entries in the bracketTypeStack. */ void ASFormatter::processPreprocessor() { assert(currentChar == '#'); const size_t preproc = currentLine.find_first_not_of(" \t", charNum + 1); if (preproc == string::npos) return; if (currentLine.compare(preproc, 2, "if") == 0) { preprocBracketTypeStackSize = bracketTypeStack->size(); } else if (currentLine.compare(preproc, 4, "else") == 0) { // delete stack entries added in #if // should be replaced by #else if (preprocBracketTypeStackSize > 0) { int addedPreproc = bracketTypeStack->size() - preprocBracketTypeStackSize; for (int i = 0; i < addedPreproc; i++) bracketTypeStack->pop_back(); } } } /** * determine if the next line starts a comment * and a header follows the comment or comments. */ bool ASFormatter::commentAndHeaderFollows() { // called ONLY IF shouldDeleteEmptyLines and shouldBreakBlocks are TRUE. assert(shouldDeleteEmptyLines && shouldBreakBlocks); // is the next line a comment if (!sourceIterator->hasMoreLines()) return false; string nextLine_ = sourceIterator->peekNextLine(); size_t firstChar = nextLine_.find_first_not_of(" \t"); if (firstChar == string::npos || !(nextLine_.compare(firstChar, 2, "//") == 0 || nextLine_.compare(firstChar, 2, "/*") == 0)) { sourceIterator->peekReset(); return false; } // find the next non-comment text, and reset string nextText = peekNextText(nextLine_, false, true); if (nextText.length() == 0 || !isCharPotentialHeader(nextText, 0)) return false; const string* newHeader = ASBeautifier::findHeader(nextText, 0, headers); if (newHeader == NULL) return false; // if a closing header, reset break unless break is requested if (isClosingHeader(newHeader) && !shouldBreakClosingHeaderBlocks) { isAppendPostBlockEmptyLineRequested = false; return false; } return true; } /** * determine if a bracket should be attached or broken * uses brackets in the bracketTypeStack * the last bracket in the bracketTypeStack is the one being formatted * returns true if the bracket should be broken */ bool ASFormatter::isCurrentBracketBroken() const { assert(bracketTypeStack->size() > 1); bool breakBracket = false; size_t stackEnd = bracketTypeStack->size() - 1; // check bracket modifiers if (shouldAttachExternC && isBracketType((*bracketTypeStack)[stackEnd], EXTERN_TYPE)) { return false; } if (shouldAttachNamespace && isBracketType((*bracketTypeStack)[stackEnd], NAMESPACE_TYPE)) { return false; } else if (shouldAttachClass && (isBracketType((*bracketTypeStack)[stackEnd], CLASS_TYPE) || isBracketType((*bracketTypeStack)[stackEnd], INTERFACE_TYPE))) { return false; } else if (shouldAttachInline && isCStyle() // for C++ only && bracketFormatMode != RUN_IN_MODE && isBracketType((*bracketTypeStack)[stackEnd], COMMAND_TYPE)) { size_t i; for (i = 1; i < bracketTypeStack->size(); i++) if (isBracketType((*bracketTypeStack)[i], CLASS_TYPE) || isBracketType((*bracketTypeStack)[i], STRUCT_TYPE)) return false; } // check brackets if (isBracketType((*bracketTypeStack)[stackEnd], EXTERN_TYPE)) { if (currentLineBeginsWithBracket || bracketFormatMode == RUN_IN_MODE) breakBracket = true; } else if (bracketFormatMode == NONE_MODE) { if (currentLineBeginsWithBracket && (int)currentLineFirstBracketNum == charNum) breakBracket = true; } else if (bracketFormatMode == BREAK_MODE || bracketFormatMode == RUN_IN_MODE) { breakBracket = true; } else if (bracketFormatMode == LINUX_MODE || bracketFormatMode == STROUSTRUP_MODE) { // break a namespace, class, or interface if Linux if (isBracketType((*bracketTypeStack)[stackEnd], NAMESPACE_TYPE) || isBracketType((*bracketTypeStack)[stackEnd], CLASS_TYPE) || isBracketType((*bracketTypeStack)[stackEnd], INTERFACE_TYPE)) { if (bracketFormatMode == LINUX_MODE) breakBracket = true; } // break the first bracket if a function else if (isBracketType((*bracketTypeStack)[stackEnd], COMMAND_TYPE)) { if (stackEnd == 1) { breakBracket = true; } else if (stackEnd > 1) { // break the first bracket after these if a function if (isBracketType((*bracketTypeStack)[stackEnd - 1], NAMESPACE_TYPE) || isBracketType((*bracketTypeStack)[stackEnd - 1], CLASS_TYPE) || isBracketType((*bracketTypeStack)[stackEnd - 1], ARRAY_TYPE) || isBracketType((*bracketTypeStack)[stackEnd - 1], STRUCT_TYPE) || isBracketType((*bracketTypeStack)[stackEnd - 1], EXTERN_TYPE)) { breakBracket = true; } } } } return breakBracket; } /** * format comment body * the calling function should have a continue statement after calling this method */ void ASFormatter::formatCommentBody() { assert(isInComment); // append the comment while (charNum < (int) currentLine.length()) { currentChar = currentLine[charNum]; if (currentLine.compare(charNum, 2, "*/") == 0) { formatCommentCloser(); break; } if (currentChar == '\t' && shouldConvertTabs) convertTabToSpaces(); appendCurrentChar(); ++charNum; } if (shouldStripCommentPrefix) stripCommentPrefix(); } /** * format a comment opener * the comment opener will be appended to the current formattedLine or a new formattedLine as necessary * the calling function should have a continue statement after calling this method */ void ASFormatter::formatCommentOpener() { assert(isSequenceReached("/*")); isInComment = isInCommentStartLine = true; isImmediatelyPostLineComment = false; if (previousNonWSChar == '}') resetEndOfStatement(); // Check for a following header. // For speed do not check multiple comment lines more than once. // For speed do not check shouldBreakBlocks if previous line is empty, a comment, or a '{'. const string* followingHeader = NULL; if ((doesLineStartComment && !isImmediatelyPostCommentOnly && isBracketType(bracketTypeStack->back(), COMMAND_TYPE)) && (shouldBreakElseIfs || isInSwitchStatement() || (shouldBreakBlocks && !isImmediatelyPostEmptyLine && previousCommandChar != '{'))) followingHeader = checkForHeaderFollowingComment(currentLine.substr(charNum)); if (spacePadNum != 0 && !isInLineBreak) adjustComments(); formattedLineCommentNum = formattedLine.length(); // must be done BEFORE appendSequence if (previousCommandChar == '{' && !isImmediatelyPostComment && !isImmediatelyPostLineComment) { if (bracketFormatMode == NONE_MODE) { // should a run-in statement be attached? if (currentLineBeginsWithBracket) formatRunIn(); } else if (bracketFormatMode == ATTACH_MODE) { // if the bracket was not attached? if (formattedLine.length() > 0 && formattedLine[0] == '{' && !isBracketType(bracketTypeStack->back(), SINGLE_LINE_TYPE)) isInLineBreak = true; } else if (bracketFormatMode == RUN_IN_MODE) { // should a run-in statement be attached? if (formattedLine.length() > 0 && formattedLine[0] == '{') formatRunIn(); } } else if (!doesLineStartComment) noTrimCommentContinuation = true; // ASBeautifier needs to know the following statements if (shouldBreakElseIfs && followingHeader == &AS_ELSE) elseHeaderFollowsComments = true; if (followingHeader == &AS_CASE || followingHeader == &AS_DEFAULT) caseHeaderFollowsComments = true; // appendSequence will write the previous line appendSequence(AS_OPEN_COMMENT); goForward(1); // must be done AFTER appendSequence // Break before the comment if a header follows the line comment. // But not break if previous line is empty, a comment, or a '{'. if (shouldBreakBlocks && followingHeader != NULL && !isImmediatelyPostEmptyLine && previousCommandChar != '{') { if (isClosingHeader(followingHeader)) { if (!shouldBreakClosingHeaderBlocks) isPrependPostBlockEmptyLineRequested = false; } // if an opening header, break before the comment else isPrependPostBlockEmptyLineRequested = true; } if (previousCommandChar == '}') currentHeader = NULL; } /** * format a comment closer * the comment closer will be appended to the current formattedLine */ void ASFormatter::formatCommentCloser() { isInComment = false; noTrimCommentContinuation = false; isImmediatelyPostComment = true; appendSequence(AS_CLOSE_COMMENT); goForward(1); if (doesLineStartComment && (currentLine.find_first_not_of(" \t", charNum + 1) == string::npos)) lineEndsInCommentOnly = true; if (peekNextChar() == '}' && previousCommandChar != ';' && !isBracketType(bracketTypeStack->back(), ARRAY_TYPE) && !isInPreprocessor && isOkToBreakBlock(bracketTypeStack->back())) { isInLineBreak = true; shouldBreakLineAtNextChar = true; } } /** * format a line comment body * the calling function should have a continue statement after calling this method */ void ASFormatter::formatLineCommentBody() { assert(isInLineComment); // append the comment while (charNum < (int) currentLine.length()) // && !isLineReady // commented out in release 2.04, unnecessary { currentChar = currentLine[charNum]; if (currentChar == '\t' && shouldConvertTabs) convertTabToSpaces(); appendCurrentChar(); ++charNum; } // explicitly break a line when a line comment's end is found. if (charNum == (int) currentLine.length()) { isInLineBreak = true; isInLineComment = false; isImmediatelyPostLineComment = true; currentChar = 0; //make sure it is a neutral char. } } /** * format a line comment opener * the line comment opener will be appended to the current formattedLine or a new formattedLine as necessary * the calling function should have a continue statement after calling this method */ void ASFormatter::formatLineCommentOpener() { assert(isSequenceReached("//")); if ((int)currentLine.length() > charNum + 2 && currentLine[charNum + 2] == '\xf2') // check for windows line marker isAppendPostBlockEmptyLineRequested = false; isInLineComment = true; isCharImmediatelyPostComment = false; if (previousNonWSChar == '}') resetEndOfStatement(); // Check for a following header. // For speed do not check multiple comment lines more than once. // For speed do not check shouldBreakBlocks if previous line is empty, a comment, or a '{'. const string* followingHeader = NULL; if ((lineIsLineCommentOnly && !isImmediatelyPostCommentOnly && isBracketType(bracketTypeStack->back(), COMMAND_TYPE)) && (shouldBreakElseIfs || isInSwitchStatement() || (shouldBreakBlocks && !isImmediatelyPostEmptyLine && previousCommandChar != '{'))) followingHeader = checkForHeaderFollowingComment(currentLine.substr(charNum)); // do not indent if in column 1 or 2 if (!shouldIndentCol1Comments && !lineCommentNoIndent) { if (charNum == 0) lineCommentNoIndent = true; else if (charNum == 1 && currentLine[0] == ' ') lineCommentNoIndent = true; } // move comment if spaces were added or deleted if (lineCommentNoIndent == false && spacePadNum != 0 && !isInLineBreak) adjustComments(); formattedLineCommentNum = formattedLine.length(); // must be done BEFORE appendSequence // check for run-in statement if (previousCommandChar == '{' && !isImmediatelyPostComment && !isImmediatelyPostLineComment) { if (bracketFormatMode == NONE_MODE) { if (currentLineBeginsWithBracket) formatRunIn(); } else if (bracketFormatMode == RUN_IN_MODE) { if (!lineCommentNoIndent) formatRunIn(); else isInLineBreak = true; } else if (bracketFormatMode == BREAK_MODE) { if (formattedLine.length() > 0 && formattedLine[0] == '{') isInLineBreak = true; } else { if (currentLineBeginsWithBracket) isInLineBreak = true; } } // ASBeautifier needs to know the following statements if (shouldBreakElseIfs && followingHeader == &AS_ELSE) elseHeaderFollowsComments = true; if (followingHeader == &AS_CASE || followingHeader == &AS_DEFAULT) caseHeaderFollowsComments = true; // appendSequence will write the previous line appendSequence(AS_OPEN_LINE_COMMENT); goForward(1); // must be done AFTER appendSequence // Break before the comment if a header follows the line comment. // But do not break if previous line is empty, a comment, or a '{'. if (shouldBreakBlocks && followingHeader != NULL && !isImmediatelyPostEmptyLine && previousCommandChar != '{') { if (isClosingHeader(followingHeader)) { if (!shouldBreakClosingHeaderBlocks) isPrependPostBlockEmptyLineRequested = false; } // if an opening header, break before the comment else isPrependPostBlockEmptyLineRequested = true; } if (previousCommandChar == '}') currentHeader = NULL; // if tabbed input don't convert the immediately following tabs to spaces if (getIndentString() == "\t" && lineCommentNoIndent) { while (charNum + 1 < (int) currentLine.length() && currentLine[charNum + 1] == '\t') { currentChar = currentLine[++charNum]; appendCurrentChar(); } } // explicitly break a line when a line comment's end is found. if (charNum + 1 == (int) currentLine.length()) { isInLineBreak = true; isInLineComment = false; isImmediatelyPostLineComment = true; currentChar = 0; //make sure it is a neutral char. } } /** * format quote body * the calling function should have a continue statement after calling this method */ void ASFormatter::formatQuoteBody() { assert(isInQuote); if (isSpecialChar) { isSpecialChar = false; } else if (currentChar == '\\' && !isInVerbatimQuote) { if (peekNextChar() == ' ') // is this '\' at end of line haveLineContinuationChar = true; else isSpecialChar = true; } else if (isInVerbatimQuote && currentChar == '"') { if (peekNextChar() == '"') // check consecutive quotes { appendSequence("\"\""); goForward(1); return; } else { isInQuote = false; isInVerbatimQuote = false; } } else if (quoteChar == currentChar) { isInQuote = false; } appendCurrentChar(); // append the text to the ending quoteChar or an escape sequence // tabs in quotes are NOT changed by convert-tabs if (isInQuote && currentChar != '\\') { while (charNum + 1 < (int) currentLine.length() && currentLine[charNum + 1] != quoteChar && currentLine[charNum + 1] != '\\') { currentChar = currentLine[++charNum]; appendCurrentChar(); } } } /** * format a quote opener * the quote opener will be appended to the current formattedLine or a new formattedLine as necessary * the calling function should have a continue statement after calling this method */ void ASFormatter::formatQuoteOpener() { assert(currentChar == '"' || currentChar == '\''); isInQuote = true; quoteChar = currentChar; if (isSharpStyle() && previousChar == '@') isInVerbatimQuote = true; // a quote following a bracket is an array if (previousCommandChar == '{' && !isImmediatelyPostComment && !isImmediatelyPostLineComment && isNonInStatementArray && !isBracketType(bracketTypeStack->back(), SINGLE_LINE_TYPE) && !isWhiteSpace(peekNextChar())) { if (bracketFormatMode == NONE_MODE) { if (currentLineBeginsWithBracket) formatRunIn(); } else if (bracketFormatMode == RUN_IN_MODE) { formatRunIn(); } else if (bracketFormatMode == BREAK_MODE) { if (formattedLine.length() > 0 && formattedLine[0] == '{') isInLineBreak = true; } else { if (currentLineBeginsWithBracket) isInLineBreak = true; } } previousCommandChar = ' '; appendCurrentChar(); } /** * get the next line comment adjustment that results from breaking a closing bracket. * the bracket must be on the same line as the closing header. * i.e "} else" changed to "} \n else". */ int ASFormatter::getNextLineCommentAdjustment() { assert(foundClosingHeader && previousNonWSChar == '}'); if (charNum < 1) // "else" is in column 1 return 0; size_t lastBracket = currentLine.rfind('}', charNum - 1); if (lastBracket != string::npos) return (lastBracket - charNum); // return a negative number return 0; } // for console build only LineEndFormat ASFormatter::getLineEndFormat() const { return lineEnd; } /** * get the current line comment adjustment that results from attaching * a closing header to a closing bracket. * the bracket must be on the line previous to the closing header. * the adjustment is 2 chars, one for the bracket and one for the space. * i.e "} \n else" changed to "} else". */ int ASFormatter::getCurrentLineCommentAdjustment() { assert(foundClosingHeader && previousNonWSChar == '}'); if (charNum < 1) return 2; size_t lastBracket = currentLine.rfind('}', charNum - 1); if (lastBracket == string::npos) return 2; return 0; } /** * get the previous word on a line * the argument 'currPos' must point to the current position. * * @return is the previous word or an empty string if none found. */ string ASFormatter::getPreviousWord(const string &line, int currPos) const { // get the last legal word (may be a number) if (currPos == 0) return string(); size_t end = line.find_last_not_of(" \t", currPos - 1); if (end == string::npos || !isLegalNameChar(line[end])) return string(); int start; // start of the previous word for (start = end; start > -1; start--) { if (!isLegalNameChar(line[start]) || line[start] == '.') break; } start++; return (line.substr(start, end - start + 1)); } /** * check if a line break is needed when a closing bracket * is followed by a closing header. * the break depends on the bracketFormatMode and other factors. */ void ASFormatter::isLineBreakBeforeClosingHeader() { assert(foundClosingHeader && previousNonWSChar == '}'); if (bracketFormatMode == BREAK_MODE || bracketFormatMode == RUN_IN_MODE || shouldAttachClosingBracket) { isInLineBreak = true; } else if (bracketFormatMode == NONE_MODE) { if (shouldBreakClosingHeaderBrackets || getBracketIndent() || getBlockIndent()) { isInLineBreak = true; } else { appendSpacePad(); // is closing bracket broken? size_t i = currentLine.find_first_not_of(" \t"); if (i != string::npos && currentLine[i] == '}') isInLineBreak = false; if (shouldBreakBlocks) isAppendPostBlockEmptyLineRequested = false; } } // bracketFormatMode == ATTACH_MODE, LINUX_MODE, STROUSTRUP_MODE else { if (shouldBreakClosingHeaderBrackets || getBracketIndent() || getBlockIndent()) { isInLineBreak = true; } else { // if a blank line does not precede this // or last line is not a one line block, attach header bool previousLineIsEmpty = isEmptyLine(formattedLine); int previousLineIsOneLineBlock = 0; size_t firstBracket = findNextChar(formattedLine, '{'); if (firstBracket != string::npos) previousLineIsOneLineBlock = isOneLineBlockReached(formattedLine, firstBracket); if (!previousLineIsEmpty && previousLineIsOneLineBlock == 0) { isInLineBreak = false; appendSpacePad(); spacePadNum = 0; // don't count as comment padding } if (shouldBreakBlocks) isAppendPostBlockEmptyLineRequested = false; } } } /** * Add brackets to a single line statement following a header. * Brackets are not added if the proper conditions are not met. * Brackets are added to the currentLine. */ bool ASFormatter::addBracketsToStatement() { assert(isImmediatelyPostHeader); if (currentHeader != &AS_IF && currentHeader != &AS_ELSE && currentHeader != &AS_FOR && currentHeader != &AS_WHILE && currentHeader != &AS_DO && currentHeader != &AS_QFOREACH && currentHeader != &AS_QFOREVER && currentHeader != &AS_FOREVER && currentHeader != &AS_FOREACH) return false; if (currentHeader == &AS_WHILE && foundClosingHeader) // do-while return false; // do not bracket an empty statement if (currentChar == ';') return false; // do not add if a header follows if (isCharPotentialHeader(currentLine, charNum)) if (findHeader(headers) != NULL) return false; // find the next semi-colon size_t nextSemiColon = charNum; if (currentChar != ';') nextSemiColon = findNextChar(currentLine, ';', charNum + 1); if (nextSemiColon == string::npos) return false; // add closing bracket before changing the line length if (nextSemiColon == currentLine.length() - 1) currentLine.append(" }"); else currentLine.insert(nextSemiColon + 1, " }"); // add opening bracket currentLine.insert(charNum, "{ "); assert(computeChecksumIn("{}")); currentChar = '{'; // remove extra spaces if (!shouldAddOneLineBrackets) { size_t lastText = formattedLine.find_last_not_of(" \t"); if ((formattedLine.length() - 1) - lastText > 1) formattedLine.erase(lastText + 1); } return true; } /** * Remove brackets from a single line statement following a header. * Brackets are not removed if the proper conditions are not met. * The first bracket is replaced by a space. */ bool ASFormatter::removeBracketsFromStatement() { assert(isImmediatelyPostHeader); assert(currentChar == '{'); if (currentHeader != &AS_IF && currentHeader != &AS_ELSE && currentHeader != &AS_FOR && currentHeader != &AS_WHILE && currentHeader != &AS_FOREACH) return false; if (currentHeader == &AS_WHILE && foundClosingHeader) // do-while return false; bool isFirstLine = true; bool needReset = false; string nextLine_; // leave nextLine_ empty if end of line comment follows if (!isBeforeAnyLineEndComment(charNum) || currentLineBeginsWithBracket) nextLine_ = currentLine.substr(charNum + 1); size_t nextChar = 0; // find the first non-blank text while (sourceIterator->hasMoreLines() || isFirstLine) { if (isFirstLine) isFirstLine = false; else { nextLine_ = sourceIterator->peekNextLine(); nextChar = 0; needReset = true; } nextChar = nextLine_.find_first_not_of(" \t", nextChar); if (nextChar != string::npos) break; } // don't remove if comments or a header follow the bracket if ((nextLine_.compare(nextChar, 2, "/*") == 0) || (nextLine_.compare(nextChar, 2, "//") == 0) || (isCharPotentialHeader(nextLine_, nextChar) && ASBeautifier::findHeader(nextLine_, nextChar, headers) != NULL)) { if (needReset) sourceIterator->peekReset(); return false; } // find the next semi-colon size_t nextSemiColon = nextChar; if (nextLine_[nextChar] != ';') nextSemiColon = findNextChar(nextLine_, ';', nextChar + 1); if (nextSemiColon == string::npos) { if (needReset) sourceIterator->peekReset(); return false; } // find the closing bracket isFirstLine = true; nextChar = nextSemiColon + 1; while (sourceIterator->hasMoreLines() || isFirstLine) { if (isFirstLine) isFirstLine = false; else { nextLine_ = sourceIterator->peekNextLine(); nextChar = 0; needReset = true; } nextChar = nextLine_.find_first_not_of(" \t", nextChar); if (nextChar != string::npos) break; } if (nextLine_.length() == 0 || nextLine_[nextChar] != '}') { if (needReset) sourceIterator->peekReset(); return false; } // remove opening bracket currentLine[charNum] = currentChar = ' '; assert(adjustChecksumIn(-'{')); if (needReset) sourceIterator->peekReset(); return true; } /** * Find the next character that is not in quotes or a comment. * * @param line the line to be searched. * @param searchChar the char to find. * @param searchStart the start position on the line (default is 0). * @return the position on the line or string::npos if not found. */ size_t ASFormatter::findNextChar(string &line, char searchChar, int searchStart /*0*/) { // find the next searchChar size_t i; for (i = searchStart; i < line.length(); i++) { if (line.compare(i, 2, "//") == 0) return string::npos; if (line.compare(i, 2, "/*") == 0) { size_t endComment = line.find("*/", i + 2); if (endComment == string::npos) return string::npos; i = endComment + 2; if (i >= line.length()) return string::npos; } if (line[i] == '\'' || line[i] == '\"') { char quote = line[i]; while (i < line.length()) { size_t endQuote = line.find(quote, i + 1); if (endQuote == string::npos) return string::npos; i = endQuote; if (line[endQuote - 1] != '\\') // check for '\"' break; if (line[endQuote - 2] == '\\') // check for '\\' break; } } if (line[i] == searchChar) break; // for now don't process C# 'delegate' brackets // do this last in case the search char is a '{' if (line[i] == '{') return string::npos; } if (i >= line.length()) // didn't find searchChar return string::npos; return i; } /** * Look ahead in the file to see if a struct has access modifiers. * * @param line a reference to the line to indent. * @param index the current line index. * @return true if the struct has access modifiers. */ bool ASFormatter::isStructAccessModified(string &firstLine, size_t index) const { assert(firstLine[index] == '{'); assert(isCStyle()); bool isFirstLine = true; bool needReset = false; size_t bracketCount = 1; string nextLine_ = firstLine.substr(index + 1); // find the first non-blank text, bypassing all comments and quotes. bool isInComment_ = false; bool isInQuote_ = false; char quoteChar_ = ' '; while (sourceIterator->hasMoreLines() || isFirstLine) { if (isFirstLine) isFirstLine = false; else { nextLine_ = sourceIterator->peekNextLine(); needReset = true; } // parse the line for (size_t i = 0; i < nextLine_.length(); i++) { if (isWhiteSpace(nextLine_[i])) continue; if (nextLine_.compare(i, 2, "/*") == 0) isInComment_ = true; if (isInComment_) { if (nextLine_.compare(i, 2, "*/") == 0) { isInComment_ = false; ++i; } continue; } if (nextLine_[i] == '\\') { ++i; continue; } if (isInQuote_) { if (nextLine_[i] == quoteChar_) isInQuote_ = false; continue; } if (nextLine_[i] == '"' || nextLine_[i] == '\'') { isInQuote_ = true; quoteChar_ = nextLine_[i]; continue; } if (nextLine_.compare(i, 2, "//") == 0) { i = nextLine_.length(); continue; } // handle brackets if (nextLine_[i] == '{') ++bracketCount; if (nextLine_[i] == '}') --bracketCount; if (bracketCount == 0) { if (needReset) sourceIterator->peekReset(); return false; } // check for access modifiers if (isCharPotentialHeader(nextLine_, i)) { if (findKeyword(nextLine_, i, AS_PUBLIC) || findKeyword(nextLine_, i, AS_PRIVATE) || findKeyword(nextLine_, i, AS_PROTECTED)) { if (needReset) sourceIterator->peekReset(); return true; } string name = getCurrentWord(nextLine_, i); i += name.length() - 1; } } // end of for loop } // end of while loop if (needReset) sourceIterator->peekReset(); return false; } /** * Check to see if this is an EXEC SQL statement. * * @param line a reference to the line to indent. * @param index the current line index. * @return true if the statement is EXEC SQL. */ bool ASFormatter::isExecSQL(string &line, size_t index) const { if (line[index] != 'e' && line[index] != 'E') // quick check to reject most return false; string word; if (isCharPotentialHeader(line, index)) word = getCurrentWord(line, index); for (size_t i = 0; i < word.length(); i++) word[i] = (char) toupper(word[i]); if (word != "EXEC") return false; size_t index2 = index + word.length(); index2 = line.find_first_not_of(" \t", index2); if (index2 == string::npos) return false; word.erase(); if (isCharPotentialHeader(line, index2)) word = getCurrentWord(line, index2); for (size_t i = 0; i < word.length(); i++) word[i] = (char) toupper(word[i]); if (word != "SQL") return false; return true; } /** * The continuation lines must be adjusted so the leading spaces * is equivalent to the text on the opening line. * * Updates currentLine and charNum. */ void ASFormatter::trimContinuationLine() { size_t len = currentLine.length(); size_t tabSize = getTabLength(); charNum = 0; if (leadingSpaces > 0 && len > 0) { size_t i; size_t continuationIncrementIn = 0; for (i = 0; (i < len) && (i + continuationIncrementIn < leadingSpaces); i++) { if (!isWhiteSpace(currentLine[i])) // don't delete any text { if (i < continuationIncrementIn) leadingSpaces = i + tabIncrementIn; continuationIncrementIn = tabIncrementIn; break; } if (currentLine[i] == '\t') continuationIncrementIn += tabSize - 1 - ((continuationIncrementIn + i) % tabSize); } if ((int) continuationIncrementIn == tabIncrementIn) charNum = i; else { // build a new line with the equivalent leading chars string newLine; int leadingChars = 0; if ((int) leadingSpaces > tabIncrementIn) leadingChars = leadingSpaces - tabIncrementIn; newLine.append(leadingChars, ' '); newLine.append(currentLine, i, len - i); currentLine = newLine; charNum = leadingChars; if (currentLine.length() == 0) currentLine = string(" "); // a null is inserted if this is not done } if (i >= len) charNum = 0; } return; } /** * Determine if a header is a closing header * * @return true if the header is a closing header. */ bool ASFormatter::isClosingHeader(const string* header) const { return (header == &AS_ELSE || header == &AS_CATCH || header == &AS_FINALLY); } /** * Determine if a * following a closing paren is immediately. * after a cast. If so it is a deference and not a multiply. * e.g. "(int*) *ptr" is a deference. */ bool ASFormatter::isImmediatelyPostCast() const { assert(previousNonWSChar == ')' && currentChar == '*'); // find preceding closing paren on currentLine or readyFormattedLine string line; // currentLine or readyFormattedLine size_t paren = currentLine.rfind(")", charNum); if (paren != string::npos) line = currentLine; // if not on currentLine it must be on the previous line else { line = readyFormattedLine; paren = line.rfind(")"); if (paren == string::npos) return false; } if (paren == 0) return false; // find character preceding the closing paren size_t lastChar = line.find_last_not_of(" \t", paren - 1); if (lastChar == string::npos) return false; // check for pointer cast if (line[lastChar] == '*') return true; return false; } /** * Determine if a < is a template definition or instantiation. * Sets the class variables isInTemplate and templateDepth. */ void ASFormatter::checkIfTemplateOpener() { assert(!isInTemplate && currentChar == '<'); // find first char after the '<' operators size_t firstChar = currentLine.find_first_not_of("< \t", charNum); if (firstChar == string::npos || currentLine[firstChar] == '=') { // this is not a template -> leave... isInTemplate = false; return; } bool isFirstLine = true; bool needReset = false; int parenDepth_ = 0; int maxTemplateDepth = 0; templateDepth = 0; string nextLine_ = currentLine.substr(charNum); // find the angle brackets, bypassing all comments and quotes. bool isInComment_ = false; bool isInQuote_ = false; char quoteChar_ = ' '; while (sourceIterator->hasMoreLines() || isFirstLine) { if (isFirstLine) isFirstLine = false; else { nextLine_ = sourceIterator->peekNextLine(); needReset = true; } // parse the line for (size_t i = 0; i < nextLine_.length(); i++) { char currentChar_ = nextLine_[i]; if (isWhiteSpace(currentChar_)) continue; if (nextLine_.compare(i, 2, "/*") == 0) isInComment_ = true; if (isInComment_) { if (nextLine_.compare(i, 2, "*/") == 0) { isInComment_ = false; ++i; } continue; } if (currentChar_ == '\\') { ++i; continue; } if (isInQuote_) { if (currentChar_ == quoteChar_) isInQuote_ = false; continue; } if (currentChar_ == '"' || currentChar_ == '\'') { isInQuote_ = true; quoteChar_ = currentChar_; continue; } if (nextLine_.compare(i, 2, "//") == 0) { i = nextLine_.length(); continue; } // not in a comment or quote if (currentChar_ == '<') { ++templateDepth; ++maxTemplateDepth; continue; } else if (currentChar_ == '>') { --templateDepth; if (templateDepth == 0) { if (parenDepth_ == 0) { // this is a template! isInTemplate = true; templateDepth = maxTemplateDepth; } goto exitFromSearch; } continue; } else if (currentChar_ == '(' || currentChar_ == ')') { if (currentChar_ == '(') ++parenDepth_; else --parenDepth_; if (parenDepth_ >= 0) continue; // this is not a template -> leave... isInTemplate = false; goto exitFromSearch; } else if (nextLine_.compare(i, 2, AS_AND) == 0 || nextLine_.compare(i, 2, AS_OR) == 0) { // this is not a template -> leave... isInTemplate = false; goto exitFromSearch; } else if (currentChar_ == ',' // comma, e.g. A || currentChar_ == '&' // reference, e.g. A || currentChar_ == '*' // pointer, e.g. A || currentChar_ == '^' // C++/CLI managed pointer, e.g. A || currentChar_ == ':' // ::, e.g. std::string || currentChar_ == '=' // assign e.g. default parameter || currentChar_ == '[' // [] e.g. string[] || currentChar_ == ']' // [] e.g. string[] || currentChar_ == '(' // (...) e.g. function definition || currentChar_ == ')' // (...) e.g. function definition || (isJavaStyle() && currentChar_ == '?') // Java wildcard ) { continue; } else if (!isLegalNameChar(currentChar_)) { // this is not a template -> leave... isInTemplate = false; goto exitFromSearch; } string name = getCurrentWord(nextLine_, i); i += name.length() - 1; } // end of for loop } // end of while loop // goto needed to exit from two loops exitFromSearch: if (needReset) sourceIterator->peekReset(); } void ASFormatter::updateFormattedLineSplitPoints(char appendedChar) { assert(maxCodeLength != string::npos); assert(formattedLine.length() > 0); if (!isOkToSplitFormattedLine()) return; char nextChar = peekNextChar(); // don't split before an end of line comment if (nextChar == '/') return; // don't split before or after a bracket if (appendedChar == '{' || appendedChar == '}' || previousNonWSChar == '{' || previousNonWSChar == '}' || nextChar == '{' || nextChar == '}' || currentChar == '{' || currentChar == '}') // currentChar tests for an appended bracket return; // don't split before or after a block paren if (appendedChar == '[' || appendedChar == ']' || previousNonWSChar == '[' || nextChar == '[' || nextChar == ']') return; if (isWhiteSpace(appendedChar)) { if (nextChar != ')' // space before a closing paren && nextChar != '(' // space before an opening paren && nextChar != '/' // space before a comment && nextChar != ':' // space before a colon && currentChar != ')' // appended space before and after a closing paren && currentChar != '(' // appended space before and after a opening paren && previousNonWSChar != '(' // decided at the '(' // don't break before a pointer or reference aligned to type && !(nextChar == '*' && !isCharPotentialOperator(previousNonWSChar) && pointerAlignment == PTR_ALIGN_TYPE) && !(nextChar == '&' && !isCharPotentialOperator(previousNonWSChar) && (referenceAlignment == REF_ALIGN_TYPE || (referenceAlignment == REF_SAME_AS_PTR && pointerAlignment == PTR_ALIGN_TYPE))) ) { if (formattedLine.length() - 1 <= maxCodeLength) maxWhiteSpace = formattedLine.length() - 1; else maxWhiteSpacePending = formattedLine.length() - 1; } } // unpadded closing parens may split after the paren (counts as whitespace) else if (appendedChar == ')') { if (nextChar != ')' && nextChar != ' ' && nextChar != ';' && nextChar != ',' && nextChar != '.' && !(nextChar == '-' && pointerSymbolFollows())) // check for -> { if (formattedLine.length() <= maxCodeLength) maxWhiteSpace = formattedLine.length(); else maxWhiteSpacePending = formattedLine.length(); } } // unpadded commas may split after the comma else if (appendedChar == ',') { if (formattedLine.length() <= maxCodeLength) maxComma = formattedLine.length(); else maxCommaPending = formattedLine.length(); } else if (appendedChar == '(') { if (nextChar != ')' && nextChar != '(' && nextChar != '"' && nextChar != '\'') { // if follows an operator break before size_t parenNum; if (isCharPotentialOperator(previousNonWSChar)) parenNum = formattedLine.length() - 1 ; else parenNum = formattedLine.length(); if (formattedLine.length() <= maxCodeLength) maxParen = parenNum; else maxParenPending = parenNum; } } else if (appendedChar == ';') { if (nextChar != ' ' && nextChar != '}' && nextChar != '/') // check for following comment { if (formattedLine.length() <= maxCodeLength) maxSemi = formattedLine.length(); else maxSemiPending = formattedLine.length(); } } } void ASFormatter::updateFormattedLineSplitPointsOperator(const string &sequence) { assert(maxCodeLength != string::npos); assert(formattedLine.length() > 0); if (!isOkToSplitFormattedLine()) return; char nextChar = peekNextChar(); // don't split before an end of line comment if (nextChar == '/') return; // check for logical conditional if (sequence == "||" || sequence == "&&" || sequence == "or" || sequence == "and") { if (shouldBreakLineAfterLogical) { if (formattedLine.length() <= maxCodeLength) maxAndOr = formattedLine.length(); else maxAndOrPending = formattedLine.length(); } else { // adjust for leading space in the sequence size_t sequenceLength = sequence.length(); if (formattedLine.length() > sequenceLength && isWhiteSpace(formattedLine[formattedLine.length() - sequenceLength - 1])) sequenceLength++; if (formattedLine.length() - sequenceLength <= maxCodeLength) maxAndOr = formattedLine.length() - sequenceLength; else maxAndOrPending = formattedLine.length() - sequenceLength; } } // comparison operators will split after the operator (counts as whitespace) else if (sequence == "==" || sequence == "!=" || sequence == ">=" || sequence == "<=") { if (formattedLine.length() <= maxCodeLength) maxWhiteSpace = formattedLine.length(); else maxWhiteSpacePending = formattedLine.length(); } // unpadded operators that will split BEFORE the operator (counts as whitespace) else if (sequence == "+" || sequence == "-" || sequence == "?") { if (charNum > 0 && (isLegalNameChar(currentLine[charNum - 1]) || currentLine[charNum - 1] == ')' || currentLine[charNum - 1] == ']' || currentLine[charNum - 1] == '\"')) { if (formattedLine.length() - 1 <= maxCodeLength) maxWhiteSpace = formattedLine.length() - 1; else maxWhiteSpacePending = formattedLine.length() - 1; } } // unpadded operators that will USUALLY split AFTER the operator (counts as whitespace) else if (sequence == "=" || sequence == ":") { // split BEFORE if the line is too long // do NOT use <= here, must allow for a bracket attached to an array size_t splitPoint = 0; if (formattedLine.length() < maxCodeLength) splitPoint = formattedLine.length(); else splitPoint = formattedLine.length() - 1; // padded or unpadded arrays if (previousNonWSChar == ']') { if (formattedLine.length() - 1 <= maxCodeLength) maxWhiteSpace = splitPoint; else maxWhiteSpacePending = splitPoint; } else if (charNum > 0 && (isLegalNameChar(currentLine[charNum - 1]) || currentLine[charNum - 1] == ')' || currentLine[charNum - 1] == ']')) { if (formattedLine.length() <= maxCodeLength) maxWhiteSpace = splitPoint; else maxWhiteSpacePending = splitPoint; } } } /** * Update the split point when a pointer or reference is formatted. * The argument is the maximum index of the last whitespace character. */ void ASFormatter::updateFormattedLineSplitPointsPointerOrReference(size_t index) { assert(maxCodeLength != string::npos); assert(formattedLine.length() > 0); assert(index < formattedLine.length()); if (!isOkToSplitFormattedLine()) return; if (index < maxWhiteSpace) // just in case return; if (index <= maxCodeLength) maxWhiteSpace = index; else maxWhiteSpacePending = index; } bool ASFormatter::isOkToSplitFormattedLine() { assert(maxCodeLength != string::npos); // Is it OK to split the line? if (shouldKeepLineUnbroken || isInLineComment || isInComment || isInQuote || isInCase || isInPreprocessor || isInExecSQL || isInAsm || isInAsmOneLine || isInAsmBlock || isInTemplate) return false; if (!isOkToBreakBlock(bracketTypeStack->back()) && currentChar != '{') { shouldKeepLineUnbroken = true; clearFormattedLineSplitPoints(); return false; } else if (isBracketType(bracketTypeStack->back(), ARRAY_TYPE)) { shouldKeepLineUnbroken = true; if (!isBracketType(bracketTypeStack->back(), ARRAY_NIS_TYPE)) clearFormattedLineSplitPoints(); return false; } return true; } /* This is called if the option maxCodeLength is set. */ void ASFormatter::testForTimeToSplitFormattedLine() { // DO NOT ASSERT maxCodeLength HERE // should the line be split if (formattedLine.length() > maxCodeLength && !isLineReady) { size_t splitPoint = findFormattedLineSplitPoint(); if (splitPoint > 0 && splitPoint < formattedLine.length()) { string splitLine = formattedLine.substr(splitPoint); formattedLine = formattedLine.substr(0, splitPoint); breakLine(true); formattedLine = splitLine; // if break-blocks is requested and this is a one-line statement string nextWord = ASBeautifier::getNextWord(currentLine, charNum - 1); if (isAppendPostBlockEmptyLineRequested && (nextWord == "break" || nextWord == "continue")) { isAppendPostBlockEmptyLineRequested = false; isPrependPostBlockEmptyLineRequested = true; } else isPrependPostBlockEmptyLineRequested = false; // adjust max split points maxAndOr = (maxAndOr > splitPoint) ? (maxAndOr - splitPoint) : 0; maxSemi = (maxSemi > splitPoint) ? (maxSemi - splitPoint) : 0; maxComma = (maxComma > splitPoint) ? (maxComma - splitPoint) : 0; maxParen = (maxParen > splitPoint) ? (maxParen - splitPoint) : 0; maxWhiteSpace = (maxWhiteSpace > splitPoint) ? (maxWhiteSpace - splitPoint) : 0; if (maxSemiPending > 0) { maxSemi = (maxSemiPending > splitPoint) ? (maxSemiPending - splitPoint) : 0; maxSemiPending = 0; } if (maxAndOrPending > 0) { maxAndOr = (maxAndOrPending > splitPoint) ? (maxAndOrPending - splitPoint) : 0; maxAndOrPending = 0; } if (maxCommaPending > 0) { maxComma = (maxCommaPending > splitPoint) ? (maxCommaPending - splitPoint) : 0; maxCommaPending = 0; } if (maxParenPending > 0) { maxParen = (maxParenPending > splitPoint) ? (maxParenPending - splitPoint) : 0; maxParenPending = 0; } if (maxWhiteSpacePending > 0) { maxWhiteSpace = (maxWhiteSpacePending > splitPoint) ? (maxWhiteSpacePending - splitPoint) : 0; maxWhiteSpacePending = 0; } // don't allow an empty formatted line size_t firstText = formattedLine.find_first_not_of(" \t"); if (firstText == string::npos && formattedLine.length() > 0) { formattedLine.erase(); clearFormattedLineSplitPoints(); if (isWhiteSpace(currentChar)) for (size_t i = charNum + 1; i < currentLine.length() && isWhiteSpace(currentLine[i]); i++) goForward(1); } else if (firstText > 0) { formattedLine.erase(0, firstText); maxSemi = (maxSemi > firstText) ? (maxSemi - firstText) : 0; maxAndOr = (maxAndOr > firstText) ? (maxAndOr - firstText) : 0; maxComma = (maxComma > firstText) ? (maxComma - firstText) : 0; maxParen = (maxParen > firstText) ? (maxParen - firstText) : 0; maxWhiteSpace = (maxWhiteSpace > firstText) ? (maxWhiteSpace - firstText) : 0; } // reset formattedLineCommentNum if (formattedLineCommentNum != string::npos) { formattedLineCommentNum = formattedLine.find("//"); if (formattedLineCommentNum == string::npos) formattedLineCommentNum = formattedLine.find("/*"); } } } } size_t ASFormatter::findFormattedLineSplitPoint() const { assert(maxCodeLength != string::npos); // determine where to split size_t minCodeLength = 10; size_t splitPoint = 0; splitPoint = maxSemi; if (maxAndOr >= minCodeLength) splitPoint = maxAndOr; if (splitPoint < minCodeLength) { splitPoint = maxWhiteSpace; // use maxParen instead if it is long enough if (maxParen > splitPoint || maxParen >= maxCodeLength * .7) splitPoint = maxParen; // use maxComma instead if it is long enough // increasing the multiplier causes more splits at whitespace if (maxComma > splitPoint || maxComma >= maxCodeLength * .3) splitPoint = maxComma; } // replace split point with first available break point if (splitPoint < minCodeLength) { splitPoint = string::npos; if (maxSemiPending > 0 && maxSemiPending < splitPoint) splitPoint = maxSemiPending; if (maxAndOrPending > 0 && maxAndOrPending < splitPoint) splitPoint = maxAndOrPending; if (maxCommaPending > 0 && maxCommaPending < splitPoint) splitPoint = maxCommaPending; if (maxParenPending > 0 && maxParenPending < splitPoint) splitPoint = maxParenPending; if (maxWhiteSpacePending > 0 && maxWhiteSpacePending < splitPoint) splitPoint = maxWhiteSpacePending; if (splitPoint == string::npos) splitPoint = 0; } // if remaining line after split is too long else if (formattedLine.length() - splitPoint > maxCodeLength) { // if end of the currentLine, find a new split point size_t newCharNum; if (isCharPotentialHeader(currentLine, charNum)) newCharNum = getCurrentWord(currentLine, charNum).length() + charNum; else newCharNum = charNum + 2; if (newCharNum + 1 > currentLine.length()) { // don't move splitPoint from before a conditional to after if (maxWhiteSpace > splitPoint + 3) splitPoint = maxWhiteSpace; if (maxParen > splitPoint) splitPoint = maxParen; } } return splitPoint; } void ASFormatter::clearFormattedLineSplitPoints() { maxSemi = 0; maxAndOr = 0; maxComma = 0; maxParen = 0; maxWhiteSpace = 0; maxSemiPending = 0; maxAndOrPending = 0; maxCommaPending = 0; maxParenPending = 0; maxWhiteSpacePending = 0; } /** * Check if a pointer symbol (->) follows on the currentLine. */ bool ASFormatter::pointerSymbolFollows() const { size_t peekNum = currentLine.find_first_not_of(" \t", charNum + 1); if (peekNum == string::npos || currentLine.compare(peekNum, 2, "->") != 0) return false; return true; } /** * Compute the input checksum. * This is called as an assert so it for is debug config only */ bool ASFormatter::computeChecksumIn(const string ¤tLine_) { for (size_t i = 0; i < currentLine_.length(); i++) if (!isWhiteSpace(currentLine_[i])) checksumIn += currentLine_[i]; return true; } /** * Adjust the input checksum for deleted chars. * This is called as an assert so it for is debug config only */ bool ASFormatter::adjustChecksumIn(int adjustment) { checksumIn += adjustment; return true; } /** * get the value of checksumIn for unit testing * * @return checksumIn. */ size_t ASFormatter::getChecksumIn() const { return checksumIn; } /** * Compute the output checksum. * This is called as an assert so it is for debug config only */ bool ASFormatter::computeChecksumOut(const string &beautifiedLine) { for (size_t i = 0; i < beautifiedLine.length(); i++) if (!isWhiteSpace(beautifiedLine[i])) checksumOut += beautifiedLine[i]; return true; } /** * Return isLineReady for the final check at end of file. */ bool ASFormatter::getIsLineReady() const { return isLineReady; } /** * get the value of checksumOut for unit testing * * @return checksumOut. */ size_t ASFormatter::getChecksumOut() const { return checksumOut; } /** * Return the difference in checksums. * If zero all is okay. */ int ASFormatter::getChecksumDiff() const { return checksumOut - checksumIn; } // for unit testing int ASFormatter::getFormatterFileType() const { return formatterFileType; } // Check if an operator follows the next word. // The next word must be a legal name. const string* ASFormatter::getFollowingOperator() const { // find next word size_t nextNum = currentLine.find_first_not_of(" \t", charNum + 1); if (nextNum == string::npos) return NULL; if (!isLegalNameChar(currentLine[nextNum])) return NULL; // bypass next word and following spaces while (nextNum < currentLine.length()) { if (!isLegalNameChar(currentLine[nextNum]) && !isWhiteSpace(currentLine[nextNum])) break; nextNum++; } if (nextNum >= currentLine.length() || !isCharPotentialOperator(currentLine[nextNum]) || currentLine[nextNum] == '/') // comment return NULL; const string* newOperator = ASBeautifier::findOperator(currentLine, nextNum, operators); return newOperator; } // Check following data to determine if the current character is an array operator. bool ASFormatter::isArrayOperator() const { assert(currentChar == '*' || currentChar == '&' || currentChar == '^'); assert(isBracketType(bracketTypeStack->back(), ARRAY_TYPE)); // find next word size_t nextNum = currentLine.find_first_not_of(" \t", charNum + 1); if (nextNum == string::npos) - return NULL; + return false; if (!isLegalNameChar(currentLine[nextNum])) - return NULL; + return false; // bypass next word and following spaces while (nextNum < currentLine.length()) { if (!isLegalNameChar(currentLine[nextNum]) && !isWhiteSpace(currentLine[nextNum])) break; nextNum++; } // check for characters that indicate an operator if (currentLine[nextNum] == ',' || currentLine[nextNum] == '}' || currentLine[nextNum] == ')' || currentLine[nextNum] == '(') return true; return false; } // Reset the flags that indicate various statement information. void ASFormatter::resetEndOfStatement() { foundQuestionMark = false; foundNamespaceHeader = false; foundClassHeader = false; foundStructHeader = false; foundInterfaceHeader = false; foundPreDefinitionHeader = false; foundPreCommandHeader = false; foundPreCommandMacro = false; foundCastOperator = false; isInPotentialCalculation = false; isSharpAccessor = false; isSharpDelegate = false; isInObjCMethodDefinition = false; isInObjCInterface = false; isInObjCSelector = false; isInEnum = false; isInExternC = false; elseHeaderFollowsComments = false; nonInStatementBracket = 0; while (!questionMarkStack->empty()) questionMarkStack->pop_back(); } // pad an Objective-C method colon void ASFormatter::padObjCMethodColon() { assert(currentChar == ':'); char nextChar = peekNextChar(); if (objCColonPadMode == COLON_PAD_NONE || objCColonPadMode == COLON_PAD_AFTER || nextChar == ')') { // remove spaces before for (int i = formattedLine.length() - 1; (i > -1) && isWhiteSpace(formattedLine[i]); i--) formattedLine.erase(i); } else { // pad space before for (int i = formattedLine.length() - 1; (i > 0) && isWhiteSpace(formattedLine[i]); i--) if (isWhiteSpace(formattedLine[i - 1])) formattedLine.erase(i); appendSpacePad(); } if (objCColonPadMode == COLON_PAD_NONE || objCColonPadMode == COLON_PAD_BEFORE || nextChar == ')') { // remove spaces after // do not need to bump i since a char is erased size_t i = charNum + 1; while ((i < currentLine.length()) && isWhiteSpace(currentLine[i])) currentLine.erase(i, 1); } else { // pad space after // do not need to bump i since a char is erased size_t i = charNum + 1; while ((i + 1 < currentLine.length()) && isWhiteSpace(currentLine[i])) currentLine.erase(i, 1); if (((int) currentLine.length() > charNum + 1) && !isWhiteSpace(currentLine[charNum + 1])) currentLine.insert(charNum + 1, " "); } } // Remove the leading '*' from a comment line and indent to the next tab. void ASFormatter::stripCommentPrefix() { int firstChar = formattedLine.find_first_not_of(" \t"); if (firstChar < 0) return; if (isInCommentStartLine) { // comment opener must begin the line if (formattedLine.compare(firstChar, 2, "/*") != 0) return; int commentOpener = firstChar; // ignore single line comments int commentEnd = formattedLine.find("*/", firstChar + 2); if (commentEnd != -1) return; // first char after the comment opener must be at least one indent int followingText = formattedLine.find_first_not_of(" \t", commentOpener + 2); if (followingText < 0) return; if (formattedLine[followingText] == '*' || formattedLine[followingText] == '!') followingText = formattedLine.find_first_not_of(" \t", followingText + 1); if (followingText < 0) return; if (formattedLine[followingText] == '*') return; int indentLen = getIndentLength(); int followingTextIndent = followingText - commentOpener; if (followingTextIndent < indentLen) { string stringToInsert(indentLen - followingTextIndent, ' '); formattedLine.insert(followingText, stringToInsert); } return; } // comment body including the closer else if (formattedLine[firstChar] == '*') { if (formattedLine.compare(firstChar, 2, "*/") == 0) { // line starts with an end comment formattedLine = "*/"; } else { // build a new line with one indent string newLine; int secondChar = formattedLine.find_first_not_of(" \t", firstChar + 1); if (secondChar < 0) { adjustChecksumIn(-'*'); formattedLine = newLine; return; } if (formattedLine[secondChar] == '*') return; // replace the leading '*' int indentLen = getIndentLength(); adjustChecksumIn(-'*'); // second char must be at least one indent if (formattedLine.substr(0, secondChar).find('\t') != string::npos) { formattedLine.erase(firstChar, 1); } else { int spacesToInsert = 0; if (secondChar >= indentLen) spacesToInsert = secondChar; else spacesToInsert = indentLen; formattedLine = string(spacesToInsert, ' ') + formattedLine.substr(secondChar); } // remove a trailing '*' int lastChar = formattedLine.find_last_not_of(" \t"); if (lastChar > -1 && formattedLine[lastChar] == '*') { adjustChecksumIn(-'*'); formattedLine[lastChar] = ' '; } } } else { // first char not a '*' // first char must be at least one indent if (formattedLine.substr(0, firstChar).find('\t') == string::npos) { int indentLen = getIndentLength(); if (firstChar < indentLen) { string stringToInsert(indentLen, ' '); formattedLine = stringToInsert + formattedLine.substr(firstChar); } } } } } // end namespace astyle diff --git a/languages/clang/codecompletion/context.cpp b/languages/clang/codecompletion/context.cpp index 97d1c678ee..32fd38ed79 100644 --- a/languages/clang/codecompletion/context.cpp +++ b/languages/clang/codecompletion/context.cpp @@ -1,1217 +1,1217 @@ /* * This file is part of KDevelop * Copyright 2014 Milian Wolff * Copyright 2015 Sergey Kalinichev * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "context.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../util/clangdebug.h" #include "../util/clangtypes.h" #include "../duchain/clangdiagnosticevaluator.h" #include "../duchain/parsesession.h" #include "../duchain/navigationwidget.h" #include "../clangsettings/clangsettingsmanager.h" #include #include #include #include using namespace KDevelop; namespace { /// Maximum return-type string length in completion items const int MAX_RETURN_TYPE_STRING_LENGTH = 20; /// Priority of code-completion results. NOTE: Keep in sync with Clang code base. enum CodeCompletionPriority { /// Priority for the next initialization in a constructor initializer list. CCP_NextInitializer = 7, /// Priority for an enumeration constant inside a switch whose condition is of the enumeration type. CCP_EnumInCase = 7, CCP_LocalDeclarationMatch = 8, CCP_DeclarationMatch = 12, CCP_LocalDeclarationSimiliar = 17, /// Priority for a send-to-super completion. CCP_SuperCompletion = 20, CCP_DeclarationSimiliar = 25, /// Priority for a declaration that is in the local scope. CCP_LocalDeclaration = 34, /// Priority for a member declaration found from the current method or member function. CCP_MemberDeclaration = 35, /// Priority for a language keyword (that isn't any of the other categories). CCP_Keyword = 40, /// Priority for a code pattern. CCP_CodePattern = 40, /// Priority for a non-type declaration. CCP_Declaration = 50, /// Priority for a type. CCP_Type = CCP_Declaration, /// Priority for a constant value (e.g., enumerator). CCP_Constant = 65, /// Priority for a preprocessor macro. CCP_Macro = 70, /// Priority for a nested-name-specifier. CCP_NestedNameSpecifier = 75, /// Priority for a result that isn't likely to be what the user wants, but is included for completeness. CCP_Unlikely = 80 }; /** * Common base class for Clang code completion items. */ template class CompletionItem : public Base { public: CompletionItem(const QString& display, const QString& prefix) : Base() , m_display(display) , m_prefix(prefix) { } ~CompletionItem() override = default; QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* /*model*/) const override { if (role == Qt::DisplayRole) { if (index.column() == CodeCompletionModel::Prefix) { return m_prefix; } else if (index.column() == CodeCompletionModel::Name) { return m_display; } } return {}; } protected: QString m_display; QString m_prefix; }; class OverrideItem : public CompletionItem { public: OverrideItem(const QString& nameAndParams, const QString& returnType) : CompletionItem( nameAndParams, i18n("Override %1", returnType) ) , m_returnType(returnType) { } QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* model) const override { if (role == Qt::DecorationRole) { if (index.column() == KTextEditor::CodeCompletionModel::Icon) { static const QIcon icon = QIcon::fromTheme(QStringLiteral("CTparents")); return icon; } } return CompletionItem::data(index, role, model); } void execute(KTextEditor::View* view, const KTextEditor::Range& word) override { view->document()->replaceText(word, m_returnType + QLatin1Char(' ') + m_display.replace(QRegularExpression(QStringLiteral("\\s*=\\s*0")), QString()) + QLatin1String(" override;")); } private: QString m_returnType; }; /** * Specialized completion item class for items which are represented by a Declaration */ class DeclarationItem : public CompletionItem { public: DeclarationItem(Declaration* dec, const QString& display, const QString& prefix, const QString& replacement) : CompletionItem(display, prefix) , m_replacement(replacement) { m_declaration = dec; } QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* model) const override { if (role == CodeCompletionModel::MatchQuality && m_matchQuality) { return m_matchQuality; } auto ret = CompletionItem::data(index, role, model); if (ret.isValid()) { return ret; } return NormalDeclarationCompletionItem::data(index, role, model); } void execute(KTextEditor::View* view, const KTextEditor::Range& word) override { QString repl = m_replacement; DUChainReadLocker lock; if(!m_declaration){ return; } if(m_declaration->isFunctionDeclaration()) { auto doc = view->document(); // Function pointer? bool funcptr = false; const auto line = doc->line(word.start().line()); auto pos = word.end().column() - 1; while ( pos > 0 && (line.at(pos).isLetterOrNumber() || line.at(pos) == QLatin1Char(':')) ) { pos--; if ( line.at(pos) == QLatin1Char('&') ) { funcptr = true; break; } } if ( !funcptr && doc->characterAt(word.end()) != QLatin1Char('(') ) { repl += QLatin1String("()"); } view->document()->replaceText(word, repl); auto f = m_declaration->type(); if (f && f->indexedArgumentsSize()) { view->setCursorPosition(word.start() + KTextEditor::Cursor(0, repl.size() - 1)); } } else { view->document()->replaceText(word, repl); } } bool createsExpandingWidget() const override { return true; } QWidget* createExpandingWidget(const CodeCompletionModel* /*model*/) const override { return new ClangNavigationWidget(m_declaration, KDevelop::AbstractNavigationWidget::EmbeddableWidget); } int matchQuality() const { return m_matchQuality; } ///Sets match quality from 0 to 10. 10 is the best fit. void setMatchQuality(int value) { m_matchQuality = value; } void setInheritanceDepth(int depth) { m_inheritanceDepth = depth; } int argumentHintDepth() const override { return m_depth; } void setArgumentHintDepth(int depth) { m_depth = depth; } protected: int m_matchQuality = 0; int m_depth = 0; QString m_replacement; }; class ImplementsItem : public DeclarationItem { public: static QString replacement(const FuncImplementInfo& info) { QString replacement = info.templatePrefix; if (!info.isDestructor && !info.isConstructor) { replacement += info.returnType + QLatin1Char(' '); } replacement += info.prototype + QLatin1String("\n{\n}\n"); return replacement; } explicit ImplementsItem(const FuncImplementInfo& item) : DeclarationItem(item.declaration.data(), item.prototype, i18n("Implement %1", item.isConstructor ? QStringLiteral("") : item.isDestructor ? QStringLiteral("") : item.returnType), replacement(item) ) { } QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* model) const override { if (index.column() == CodeCompletionModel::Arguments) { // our display string already contains the arguments return {}; } return DeclarationItem::data(index, role, model); } void execute(KTextEditor::View* view, const KTextEditor::Range& word) override { view->document()->replaceText(word, m_replacement); } }; class ArgumentHintItem : public DeclarationItem { public: struct CurrentArgumentRange { int start; int end; }; ArgumentHintItem(Declaration* decl, const QString& prefix, const QString& name, const QString& arguments, const CurrentArgumentRange& range) : DeclarationItem(decl, name, prefix, {}) , m_range(range) , m_arguments(arguments) {} QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* model) const override { if (role == CodeCompletionModel::CustomHighlight && index.column() == CodeCompletionModel::Arguments && argumentHintDepth()) { QList highlighting; highlighting << QVariant(m_range.start); highlighting << QVariant(m_range.end); QTextCharFormat boldFormat; boldFormat.setFontWeight(QFont::Bold); highlighting << boldFormat; return highlighting; } if (role == CodeCompletionModel::HighlightingMethod && index.column() == CodeCompletionModel::Arguments && argumentHintDepth()) { return QVariant(CodeCompletionModel::CustomHighlighting); } if (index.column() == CodeCompletionModel::Arguments && !m_declaration) { return m_arguments; } return DeclarationItem::data(index, role, model); } private: CurrentArgumentRange m_range; QString m_arguments; }; /** * A minimalistic completion item for macros and such */ class SimpleItem : public CompletionItem { public: SimpleItem(const QString& display, const QString& prefix, const QString& replacement, const QIcon& icon = QIcon()) : CompletionItem(display, prefix) , m_replacement(replacement) , m_icon(icon) { } void execute(KTextEditor::View* view, const KTextEditor::Range& word) override { view->document()->replaceText(word, m_replacement); } QVariant data(const QModelIndex& index, int role, const CodeCompletionModel* model) const override { if (role == Qt::DecorationRole && index.column() == KTextEditor::CodeCompletionModel::Icon) { return m_icon; } return CompletionItem::data(index, role, model); } private: QString m_replacement; QIcon m_icon; }; /** * Return true in case position @p position represents a cursor inside a comment */ bool isInsideComment(CXTranslationUnit unit, CXFile file, const KTextEditor::Cursor& position) { if (!position.isValid()) { return false; } // TODO: This may get very slow for a large TU, investigate if we can improve this function auto begin = clang_getLocation(unit, file, 1, 1); auto end = clang_getLocation(unit, file, position.line() + 1, position.column() + 1); CXSourceRange range = clang_getRange(begin, end); // tokenize the whole range from the start until 'position' // if we detect a comment token at this position, return true const ClangTokens tokens(unit, range); for (CXToken token : tokens) { CXTokenKind tokenKind = clang_getTokenKind(token); if (tokenKind != CXToken_Comment) { continue; } auto range = ClangRange(clang_getTokenExtent(unit, token)); if (range.toRange().contains(position)) { return true; } } return false; } QString& elideStringRight(QString& str, int length) { if (str.size() > length + 3) { - return str.replace(length, str.size() - length, QLatin1String("...")); + return str.replace(length, str.size() - length, QStringLiteral("...")); } return str; } /** * @return Value suited for @ref CodeCompletionModel::MatchQuality in the range [0.0, 10.0] (the higher the better) * * See http://clang.llvm.org/doxygen/CodeCompleteConsumer_8h_source.html for list of priorities * They (currently) have a range from [-3, 80] (the lower, the better) */ int codeCompletionPriorityToMatchQuality(unsigned int completionPriority) { return 10u - qBound(0u, completionPriority, 80u) / 8; } int adjustPriorityForType(const AbstractType::Ptr& type, int completionPriority) { const auto modifier = 4; if (type) { const auto whichType = type->whichType(); if (whichType == AbstractType::TypePointer || whichType == AbstractType::TypeReference) { // Clang considers all pointers as similar, this is not what we want. completionPriority += modifier; } else if (whichType == AbstractType::TypeStructure) { // Clang considers all classes as similar too... completionPriority += modifier; } else if (whichType == AbstractType::TypeDelayed) { completionPriority += modifier; } else if (whichType == AbstractType::TypeAlias) { auto aliasedType = type.cast(); return adjustPriorityForType(aliasedType ? aliasedType->type() : AbstractType::Ptr(), completionPriority); } else if (whichType == AbstractType::TypeFunction) { auto functionType = type.cast(); return adjustPriorityForType(functionType ? functionType->returnType() : AbstractType::Ptr(), completionPriority); } } else { completionPriority += modifier; } return completionPriority; } /// Adjusts priority for the @p decl int adjustPriorityForDeclaration(Declaration* decl, unsigned int completionPriority) { if(completionPriority < CCP_LocalDeclarationSimiliar || completionPriority > CCP_SuperCompletion){ return completionPriority; } return adjustPriorityForType(decl->abstractType(), completionPriority); } /** * @return Whether the declaration represented by identifier @p identifier qualifies as completion result * * For example, we don't want to offer SomeClass::SomeClass as completion item to the user * (otherwise we'd end up generating code such as 's.SomeClass();') */ bool isValidCompletionIdentifier(const QualifiedIdentifier& identifier) { const int count = identifier.count(); if (identifier.count() < 2) { return true; } const Identifier scope = identifier.at(count-2); const Identifier id = identifier.last(); if (scope == id) { return false; // is constructor } const QString idString = id.toString(); if (idString.startsWith(QLatin1Char('~')) && scope.toString() == idString.midRef(1)) { return false; // is destructor } return true; } /** * @return Whether the declaration represented by identifier @p identifier qualifies as "special" completion result * * "Special" completion results are items that are likely not regularly used. * * Examples: * - 'SomeClass::operator=(const SomeClass&)' */ bool isValidSpecialCompletionIdentifier(const QualifiedIdentifier& identifier) { if (identifier.count() < 2) { return false; } const Identifier id = identifier.last(); const QString idString = id.toString(); if (idString.startsWith(QLatin1String("operator="))) { return true; // is assignment operator } return false; } Declaration* findDeclaration(const QualifiedIdentifier& qid, const DUContextPointer& ctx, const CursorInRevision& position, QSet& handled) { PersistentSymbolTable::Declarations decl = PersistentSymbolTable::self().getDeclarations(qid); const auto top = ctx->topContext(); const auto& importedContexts = top->importedParentContexts(); for (auto it = decl.iterator(); it; ++it) { // if the context is not included, then this match is not correct for our consideration // this fixes issues where we used to include matches from files that did not have // anything to do with the current TU, e.g. the main from a different file or stuff like that // it also reduces the chance of us picking up a function of the same name from somewhere else // also, this makes sure the context has the correct language and we don't get confused by stuff // from other language plugins if (std::none_of(importedContexts.begin(), importedContexts.end(), [it] (const DUContext::Import& import) { return import.topContextIndex() == it->indexedTopContext().index(); })) { continue; } auto declaration = it->declaration(); if (!declaration) { // Mitigate problems such as: Cannot load a top-context from file "/home/kfunk/.cache/kdevduchain/kdevelop-{foo}/topcontexts/6085" // - the required language-support for handling ID 55 is probably not loaded qCWarning(KDEV_CLANG) << "Detected an invalid declaration for" << qid; continue; } if (declaration->kind() == Declaration::Instance && !declaration->isFunctionDeclaration()) { break; } if (!handled.contains(declaration)) { handled.insert(declaration); return declaration; } } const auto foundDeclarations = ctx->findDeclarations(qid, position); for (auto dec : foundDeclarations) { if (!handled.contains(dec)) { handled.insert(dec); return dec; } } return nullptr; } /// If any parent of this context is a class, the closest class declaration is returned, nullptr otherwise Declaration* classDeclarationForContext(const DUContextPointer& context, const CursorInRevision& position) { auto parent = context; while (parent) { if (parent->type() == DUContext::Class) { break; } if (auto owner = parent->owner()) { // Work-around for out-of-line methods. They have Helper context instead of Class context if (owner->context() && owner->context()->type() == DUContext::Helper) { auto qid = owner->qualifiedIdentifier(); qid.pop(); QSet tmp; auto decl = findDeclaration(qid, context, position, tmp); if (decl && decl->internalContext() && decl->internalContext()->type() == DUContext::Class) { parent = decl->internalContext(); break; } } } parent = parent->parentContext(); } return parent ? parent->owner() : nullptr; } class LookAheadItemMatcher { public: explicit LookAheadItemMatcher(const TopDUContextPointer& ctx) : m_topContext(ctx) , m_enabled(ClangSettingsManager::self()->codeCompletionSettings().lookAhead) {} /// Adds all local declarations for @p declaration into possible look-ahead items. void addDeclarations(Declaration* declaration) { if (!m_enabled) { return; } if (declaration->kind() != Declaration::Instance) { return; } auto type = typeForDeclaration(declaration); auto identifiedType = dynamic_cast(type.data()); if (!identifiedType) { return; } addDeclarationsForType(identifiedType, declaration); } /// Add type for matching. This type'll be used for filtering look-ahead items /// Only items with @p type will be returned through @sa matchedItems void addMatchedType(const IndexedType& type) { matchedTypes.insert(type); } /// @return look-ahead items that math given types. @sa addMatchedType QList matchedItems() { QList lookAheadItems; for (const auto& pair: possibleLookAheadDeclarations) { auto decl = pair.first; if (matchedTypes.contains(decl->indexedType())) { auto parent = pair.second; const QString access = parent->abstractType()->whichType() == AbstractType::TypePointer ? QStringLiteral("->") : QStringLiteral("."); const QString text = parent->identifier().toString() + access + decl->identifier().toString(); auto item = new DeclarationItem(decl, text, {}, text); item->setMatchQuality(8); lookAheadItems.append(CompletionTreeItemPointer(item)); } } return lookAheadItems; } private: AbstractType::Ptr typeForDeclaration(const Declaration* decl) { return TypeUtils::targetType(decl->abstractType(), m_topContext.data()); } void addDeclarationsForType(const IdentifiedType* identifiedType, Declaration* declaration) { if (auto typeDecl = identifiedType->declaration(m_topContext.data())) { if (dynamic_cast(typeDecl->logicalDeclaration(m_topContext.data()))) { if (!typeDecl->internalContext()) { return; } for (auto localDecl : typeDecl->internalContext()->localDeclarations()) { if(localDecl->identifier().isEmpty()){ continue; } if(auto classMember = dynamic_cast(localDecl)){ // TODO: Also add protected/private members if completion is inside this class context. if(classMember->accessPolicy() != Declaration::Public){ continue; } } if(!declaration->abstractType()){ continue; } if (declaration->abstractType()->whichType() == AbstractType::TypeIntegral) { if (auto integralType = declaration->abstractType().cast()) { if (integralType->dataType() == IntegralType::TypeVoid) { continue; } } } possibleLookAheadDeclarations.insert({localDecl, declaration}); } } } } // Declaration and it's context typedef QPair DeclarationContext; /// Types of declarations that look-ahead completion items can have QSet matchedTypes; // List of declarations that can be added to the Look Ahead group // Second declaration represents context QSet possibleLookAheadDeclarations; TopDUContextPointer m_topContext; bool m_enabled; }; struct MemberAccessReplacer : public QObject { Q_OBJECT public: enum Type { None, DotToArrow, ArrowToDot }; public slots: void replaceCurrentAccess(MemberAccessReplacer::Type type) { if (auto document = ICore::self()->documentController()->activeDocument()) { if (auto textDocument = document->textDocument()) { auto activeView = document->activeTextView(); if (!activeView) { return; } auto cursor = activeView->cursorPosition(); QString oldAccess, newAccess; if (type == ArrowToDot) { oldAccess = QStringLiteral("->"); newAccess = QStringLiteral("."); } else { oldAccess = QStringLiteral("."); newAccess = QStringLiteral("->"); } auto oldRange = KTextEditor::Range(cursor - KTextEditor::Cursor(0, oldAccess.length()), cursor); // This code needed for testReplaceMemberAccess test // Maybe we should do a similar thing for '->' to '.' direction, but this is not so important while (textDocument->text(oldRange) == QLatin1String(" ") && oldRange.start().column() >= 0) { oldRange = KTextEditor::Range({oldRange.start().line(), oldRange.start().column() - 1}, {oldRange.end().line(), oldRange.end().column() - 1}); } if (oldRange.start().column() >= 0 && textDocument->text(oldRange) == oldAccess) { textDocument->replaceText(oldRange, newAccess); } } } } }; static MemberAccessReplacer s_memberAccessReplacer; } Q_DECLARE_METATYPE(MemberAccessReplacer::Type) ClangCodeCompletionContext::ClangCodeCompletionContext(const DUContextPointer& context, const ParseSessionData::Ptr& sessionData, const QUrl& url, const KTextEditor::Cursor& position, const QString& text, const QString& followingText ) : CodeCompletionContext(context, text + followingText, CursorInRevision::castFromSimpleCursor(position), 0) , m_results(nullptr, clang_disposeCodeCompleteResults) , m_parseSessionData(sessionData) { qRegisterMetaType(); const QByteArray file = url.toLocalFile().toUtf8(); ParseSession session(m_parseSessionData); { const unsigned int completeOptions = clang_defaultCodeCompleteOptions(); CXUnsavedFile unsaved; unsaved.Filename = file.constData(); const QByteArray content = m_text.toUtf8(); unsaved.Contents = content.constData(); unsaved.Length = content.size() + 1; // + \0-byte m_results.reset(clang_codeCompleteAt(session.unit(), file.constData(), position.line() + 1, position.column() + 1, content.isEmpty() ? nullptr : &unsaved, content.isEmpty() ? 0 : 1, completeOptions)); if (!m_results) { qCWarning(KDEV_CLANG) << "Something went wrong during 'clang_codeCompleteAt' for file" << file; return; } auto numDiagnostics = clang_codeCompleteGetNumDiagnostics(m_results.get()); for (uint i = 0; i < numDiagnostics; i++) { auto diagnostic = clang_codeCompleteGetDiagnostic(m_results.get(), i); auto diagnosticType = ClangDiagnosticEvaluator::diagnosticType(diagnostic); clang_disposeDiagnostic(diagnostic); if (diagnosticType == ClangDiagnosticEvaluator::ReplaceWithArrowProblem || diagnosticType == ClangDiagnosticEvaluator::ReplaceWithDotProblem) { MemberAccessReplacer::Type replacementType; if (diagnosticType == ClangDiagnosticEvaluator::ReplaceWithDotProblem) { replacementType = MemberAccessReplacer::ArrowToDot; } else { replacementType = MemberAccessReplacer::DotToArrow; } QMetaObject::invokeMethod(&s_memberAccessReplacer, "replaceCurrentAccess", Qt::QueuedConnection, Q_ARG(MemberAccessReplacer::Type, replacementType)); m_valid = false; return; } } auto addMacros = ClangSettingsManager::self()->codeCompletionSettings().macros; if (!addMacros) { m_filters |= NoMacros; } } if (!m_results->NumResults) { const auto trimmedText = text.trimmed(); if (trimmedText.endsWith(QLatin1Char('.'))) { // TODO: This shouldn't be needed if Clang provided diagnostic. // But it doesn't always do it, so let's try to manually determine whether '.' is used instead of '->' m_text = trimmedText.left(trimmedText.size() - 1); m_text += QStringLiteral("->"); CXUnsavedFile unsaved; unsaved.Filename = file.constData(); const QByteArray content = m_text.toUtf8(); unsaved.Contents = content.constData(); unsaved.Length = content.size() + 1; m_results.reset(clang_codeCompleteAt(session.unit(), file.constData(), position.line() + 1, position.column() + 1 + 1, &unsaved, 1, clang_defaultCodeCompleteOptions())); if (m_results && m_results->NumResults) { QMetaObject::invokeMethod(&s_memberAccessReplacer, "replaceCurrentAccess", Qt::QueuedConnection, Q_ARG(MemberAccessReplacer::Type, MemberAccessReplacer::DotToArrow)); } m_valid = false; return; } } // check 'isValidPosition' after parsing the new content auto clangFile = session.file(file); if (!isValidPosition(session.unit(), clangFile)) { m_valid = false; return; } m_completionHelper.computeCompletions(session, clangFile, position); } ClangCodeCompletionContext::~ClangCodeCompletionContext() { } bool ClangCodeCompletionContext::isValidPosition(CXTranslationUnit unit, CXFile file) const { if (isInsideComment(unit, file, m_position.castToSimpleCursor())) { clangDebug() << "Invalid completion context: Inside comment"; return false; } return true; } QList ClangCodeCompletionContext::completionItems(bool& abort, bool /*fullCompletion*/) { if (!m_valid || !m_duContext || !m_results) { return {}; } const auto ctx = DUContextPointer(m_duContext->findContextAt(m_position)); /// Normal completion items, such as 'void Foo::foo()' QList items; /// Stuff like 'Foo& Foo::operator=(const Foo&)', etc. Not regularly used by our users. QList specialItems; /// Macros from the current context QList macros; /// Builtins reported by Clang QList builtin; QSet handled; LookAheadItemMatcher lookAheadMatcher(TopDUContextPointer(ctx->topContext())); // If ctx is/inside the Class context, this represents that context. const auto currentClassContext = classDeclarationForContext(ctx, m_position); clangDebug() << "Clang found" << m_results->NumResults << "completion results"; for (uint i = 0; i < m_results->NumResults; ++i) { if (abort) { return {}; } auto result = m_results->Results[i]; const auto availability = clang_getCompletionAvailability(result.CompletionString); if (availability == CXAvailability_NotAvailable) { continue; } const bool isMacroDefinition = result.CursorKind == CXCursor_MacroDefinition; if (isMacroDefinition && m_filters & NoMacros) { continue; } const bool isBuiltin = (result.CursorKind == CXCursor_NotImplemented); if (isBuiltin && m_filters & NoBuiltins) { continue; } const bool isDeclaration = !isMacroDefinition && !isBuiltin; if (isDeclaration && m_filters & NoDeclarations) { continue; } if (availability == CXAvailability_NotAccessible && (!isDeclaration || !currentClassContext)) { continue; } // the string that would be needed to type, usually the identifier of something. Also we use it as name for code completion declaration items. QString typed; // the return type of a function e.g. QString resultType; // the replacement text when an item gets executed QString replacement; QString arguments; ArgumentHintItem::CurrentArgumentRange argumentRange; //BEGIN function signature parsing // nesting depth of parentheses int parenDepth = 0; enum FunctionSignatureState { // not yet inside the function signature Before, // any token is part of the function signature now Inside, // finished parsing the function signature After }; // current state FunctionSignatureState signatureState = Before; //END function signature parsing std::function processChunks = [&] (CXCompletionString completionString) { const uint chunks = clang_getNumCompletionChunks(completionString); for (uint j = 0; j < chunks; ++j) { const auto kind = clang_getCompletionChunkKind(completionString, j); if (kind == CXCompletionChunk_Optional) { completionString = clang_getCompletionChunkCompletionString(completionString, j); if (completionString) { processChunks(completionString); } continue; } // We don't need function signature for declaration items, we can get it directly from the declaration. Also adding the function signature to the "display" would break the "Detailed completion" option. if (isDeclaration && !typed.isEmpty()) { #if CINDEX_VERSION_MINOR >= 30 // TODO: When parent context for CXCursor_OverloadCandidate is fixed remove this check if (result.CursorKind != CXCursor_OverloadCandidate) { break; } #else break; #endif } const QString string = ClangString(clang_getCompletionChunkText(completionString, j)).toString(); switch (kind) { case CXCompletionChunk_TypedText: typed = string; replacement = string; break; case CXCompletionChunk_ResultType: resultType = string; continue; case CXCompletionChunk_Placeholder: if (signatureState == Inside) { arguments += string; } continue; case CXCompletionChunk_LeftParen: if (signatureState == Before && !parenDepth) { signatureState = Inside; } parenDepth++; break; case CXCompletionChunk_RightParen: --parenDepth; if (signatureState == Inside && !parenDepth) { arguments += QLatin1Char(')'); signatureState = After; } break; case CXCompletionChunk_Text: #if CINDEX_VERSION_MINOR >= 30 if (result.CursorKind == CXCursor_OverloadCandidate) { typed += string; } #endif break; case CXCompletionChunk_CurrentParameter: argumentRange.start = arguments.size(); argumentRange.end = string.size(); break; default: break; } if (signatureState == Inside) { arguments += string; } } }; processChunks(result.CompletionString); #if CINDEX_VERSION_MINOR >= 30 // TODO: No closing paren if default parameters present if (result.CursorKind == CXCursor_OverloadCandidate && !arguments.endsWith(QLatin1Char(')'))) { arguments += QLatin1Char(')'); } #endif // ellide text to the right for overly long result types (templates especially) elideStringRight(resultType, MAX_RETURN_TYPE_STRING_LENGTH); if (isDeclaration) { const Identifier id(typed); QualifiedIdentifier qid; ClangString parent(clang_getCompletionParent(result.CompletionString, nullptr)); if (parent.c_str() != nullptr) { qid = QualifiedIdentifier(parent.toString()); } qid.push(id); if (!isValidCompletionIdentifier(qid)) { continue; } auto found = findDeclaration(qid, ctx, m_position, handled); CompletionTreeItemPointer item; if (found) { // TODO: Bug in Clang: protected members from base classes not accessible in derived classes. if (availability == CXAvailability_NotAccessible) { if (auto cl = dynamic_cast(found)) { if (cl->accessPolicy() != Declaration::Protected) { continue; } auto declarationClassContext = classDeclarationForContext(DUContextPointer(found->context()), m_position); uint steps = 10; auto inheriters = DUChainUtils::getInheriters(declarationClassContext, steps); if(!inheriters.contains(currentClassContext)){ continue; } } else { continue; } } auto declarationItem = new DeclarationItem(found, typed, resultType, replacement); const unsigned int completionPriority = adjustPriorityForDeclaration(found, clang_getCompletionPriority(result.CompletionString)); const bool bestMatch = completionPriority <= CCP_SuperCompletion; //don't set best match property for internal identifiers, also prefer declarations from current file if (bestMatch && !found->indexedIdentifier().identifier().toString().startsWith(QLatin1String("__")) ) { const int matchQuality = codeCompletionPriorityToMatchQuality(completionPriority); declarationItem->setMatchQuality(matchQuality); // TODO: LibClang missing API to determine expected code completion type. lookAheadMatcher.addMatchedType(found->indexedType()); } else { declarationItem->setInheritanceDepth(completionPriority); lookAheadMatcher.addDeclarations(found); } #if CINDEX_VERSION_MINOR >= 30 if (result.CursorKind == CXCursor_OverloadCandidate) { declarationItem->setArgumentHintDepth(1); } #endif item = declarationItem; } else { #if CINDEX_VERSION_MINOR >= 30 if (result.CursorKind == CXCursor_OverloadCandidate) { // TODO: No parent context for CXCursor_OverloadCandidate items, hence qid is broken -> no declaration found auto ahi = new ArgumentHintItem({}, resultType, typed, arguments, argumentRange); ahi->setArgumentHintDepth(1); item = ahi; } else { #endif // still, let's trust that Clang found something useful and put it into the completion result list clangDebug() << "Could not find declaration for" << qid; item = CompletionTreeItemPointer(new SimpleItem(typed + arguments, resultType, replacement)); #if CINDEX_VERSION_MINOR >= 30 } #endif } if (isValidSpecialCompletionIdentifier(qid)) { // If it's a special completion identifier e.g. "operator=(const&)" and we don't have a declaration for it, don't add it into completion list, as this item is completely useless and pollutes the test case. // This happens e.g. for "class A{}; a.|". At | we have "operator=(const A&)" as a special completion identifier without a declaration. if(item->declaration()){ specialItems.append(item); } } else { items.append(item); } continue; } if (result.CursorKind == CXCursor_MacroDefinition) { // TODO: grouping of macros and built-in stuff static const QIcon icon = QIcon::fromTheme(QStringLiteral("code-macro")); auto item = CompletionTreeItemPointer(new SimpleItem(typed + arguments, resultType, replacement, icon)); macros.append(item); } else if (result.CursorKind == CXCursor_NotImplemented) { auto item = CompletionTreeItemPointer(new SimpleItem(typed, resultType, replacement)); builtin.append(item); } } if (abort) { return {}; } addImplementationHelperItems(); addOverwritableItems(); eventuallyAddGroup(i18n("Special"), 700, specialItems); eventuallyAddGroup(i18n("Look-ahead Matches"), 800, lookAheadMatcher.matchedItems()); eventuallyAddGroup(i18n("Builtin"), 900, builtin); eventuallyAddGroup(i18n("Macros"), 1000, macros); return items; } void ClangCodeCompletionContext::eventuallyAddGroup(const QString& name, int priority, const QList& items) { if (items.isEmpty()) { return; } KDevelop::CompletionCustomGroupNode* node = new KDevelop::CompletionCustomGroupNode(name, priority); node->appendChildren(items); m_ungrouped << CompletionTreeElementPointer(node); } void ClangCodeCompletionContext::addOverwritableItems() { auto overrideList = m_completionHelper.overrides(); if (overrideList.isEmpty()) { return; } QList overrides; for (int i = 0; i < overrideList.count(); i++) { FuncOverrideInfo info = overrideList.at(i); - QString nameAndParams = info.name + QLatin1Char('(') + info.params.join(QLatin1String(", ")) + QLatin1Char(')'); + QString nameAndParams = info.name + QLatin1Char('(') + info.params.join(QStringLiteral(", ")) + QLatin1Char(')'); if(info.isConst) nameAndParams = nameAndParams + QLatin1String(" const"); if(info.isVirtual) nameAndParams = nameAndParams + QLatin1String(" = 0"); overrides << CompletionTreeItemPointer(new OverrideItem(nameAndParams, info.returnType)); } eventuallyAddGroup(i18n("Virtual Override"), 0, overrides); } void ClangCodeCompletionContext::addImplementationHelperItems() { auto implementsList = m_completionHelper.implements(); if (implementsList.isEmpty()) { return; } QList implements; foreach(const auto& info, implementsList) { implements << CompletionTreeItemPointer(new ImplementsItem(info)); } eventuallyAddGroup(i18n("Implement Function"), 0, implements); } QList ClangCodeCompletionContext::ungroupedElements() { return m_ungrouped; } ClangCodeCompletionContext::ContextFilters ClangCodeCompletionContext::filters() const { return m_filters; } void ClangCodeCompletionContext::setFilters(const ClangCodeCompletionContext::ContextFilters& filters) { m_filters = filters; } #include "context.moc" diff --git a/languages/clang/codegen/codegenhelper.cpp b/languages/clang/codegen/codegenhelper.cpp index 25f89ae151..1f988ef72d 100644 --- a/languages/clang/codegen/codegenhelper.cpp +++ b/languages/clang/codegen/codegenhelper.cpp @@ -1,469 +1,469 @@ /* 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 "codegenhelper.h" #include "adaptsignatureaction.h" #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { IndexedTypeIdentifier stripPrefixIdentifiers(const IndexedTypeIdentifier& id, const QualifiedIdentifier& strip); Identifier stripPrefixIdentifiers(const Identifier& id, const QualifiedIdentifier& strip) { Identifier ret(id); ret.clearTemplateIdentifiers(); for (unsigned int a = 0; a < id.templateIdentifiersCount(); ++a) { ret.appendTemplateIdentifier(stripPrefixIdentifiers(id.templateIdentifier(a), strip)); } return ret; } IndexedTypeIdentifier stripPrefixIdentifiers(const IndexedTypeIdentifier& id, const QualifiedIdentifier& strip) { QualifiedIdentifier oldId(id.identifier().identifier()); QualifiedIdentifier qid; int commonPrefix = 0; for (; commonPrefix < oldId.count() - 1 && commonPrefix < strip.count(); ++commonPrefix) { if (strip.at(commonPrefix).toString() != oldId.at(commonPrefix).toString()) { break; } } for (int a = commonPrefix; a < oldId.count(); ++a) { qid.push(stripPrefixIdentifiers(oldId.at(a), strip)); } IndexedTypeIdentifier ret(id); ret.setIdentifier(qid); return ret; } int reservedIdentifierCount(const QString &name) { - QStringList l = name.split(QLatin1String("::")); + QStringList l = name.split(QStringLiteral("::")); int ret = 0; foreach(const QString &s, l) if (s.startsWith(QLatin1Char('_'))) { ++ret; } return ret; } uint buildIdentifierForType(const AbstractType::Ptr& type, IndexedTypeIdentifier& id, uint pointerLevel, TopDUContext* top) { if (!type) { return pointerLevel; } TypePtr refType = type.cast(); if (refType) { id.setIsReference(true); if (refType->modifiers() & AbstractType::ConstModifier) { id.setIsConstant(true); } return buildIdentifierForType(refType->baseType(), id, pointerLevel, top); } TypePtr pointerType = type.cast(); if (pointerType) { ++pointerLevel; uint maxPointerLevel = buildIdentifierForType(pointerType->baseType(), id, pointerLevel, top); if (type->modifiers() & AbstractType::ConstModifier) { id.setIsConstPointer(maxPointerLevel - pointerLevel, true); } if (static_cast(id.pointerDepth()) < pointerLevel) { id.setPointerDepth(pointerLevel); } return maxPointerLevel; } AbstractType::Ptr useTypeText = type; if (type->modifiers() & AbstractType::ConstModifier) { //Remove the 'const' modifier, as it will be added to the type-identifier below useTypeText = IndexedType(type).abstractType(); useTypeText->setModifiers(useTypeText->modifiers() & (~AbstractType::ConstModifier)); } id.setIdentifier(QualifiedIdentifier(useTypeText->toString(), true)); if (type->modifiers() & AbstractType::ConstModifier) { id.setIsConstant(true); } if (type->modifiers() & AbstractType::VolatileModifier) { id.setIsVolatile(true); } return pointerLevel; } IndexedTypeIdentifier identifierForType(const AbstractType::Ptr& type, TopDUContext* top) { IndexedTypeIdentifier ret; buildIdentifierForType(type, ret, 0, top); return ret; } IndexedTypeIdentifier removeTemplateParameters(const IndexedTypeIdentifier& identifier, int behindPosition); Identifier removeTemplateParameters(const Identifier& id, int behindPosition) { Identifier ret(id); ret.clearTemplateIdentifiers(); for (unsigned int a = 0; a < id.templateIdentifiersCount(); ++a) { IndexedTypeIdentifier replacement = removeTemplateParameters(id.templateIdentifier(a), behindPosition); if (( int ) a < behindPosition) { ret.appendTemplateIdentifier(replacement); } else { ret.appendTemplateIdentifier(IndexedTypeIdentifier(QualifiedIdentifier(QStringLiteral("...")))); break; } } return ret; } IndexedTypeIdentifier removeTemplateParameters(const IndexedTypeIdentifier& identifier, int behindPosition) { IndexedTypeIdentifier ret(identifier); QualifiedIdentifier oldId(identifier.identifier().identifier()); QualifiedIdentifier qid; for (int a = 0; a < oldId.count(); ++a) { qid.push(removeTemplateParameters(oldId.at(a), behindPosition)); } ret.setIdentifier(qid); return ret; } IndexedType removeConstModifier(const IndexedType& indexedType) { AbstractType::Ptr type = indexedType.abstractType(); type->setModifiers(type->modifiers() & (~AbstractType::ConstModifier)); return type->indexed(); } AbstractType::Ptr shortenTypeForViewing(const AbstractType::Ptr& type) { struct ShortenAliasExchanger : public TypeExchanger { AbstractType::Ptr exchange(const AbstractType::Ptr& type) override { if (!type) { return type; } AbstractType::Ptr newType(type->clone()); TypeAliasType::Ptr alias = type.cast(); if (alias) { //If the aliased type has less involved template arguments, prefer it AbstractType::Ptr shortenedTarget = exchange(alias->type()); if (shortenedTarget && shortenedTarget->toString().count(QLatin1Char('<')) < alias->toString().count(QLatin1Char('<')) && reservedIdentifierCount(shortenedTarget->toString()) <= reservedIdentifierCount(alias->toString())) { shortenedTarget->setModifiers(shortenedTarget->modifiers() | alias->modifiers()); return shortenedTarget; } } newType->exchangeTypes(this); return newType; } }; ShortenAliasExchanger exchanger; return exchanger.exchange(type); } ///Returns a type that has all template types replaced with DelayedType's that have their template default parameters stripped away, ///and all scope prefixes removed that are redundant within the given context ///The returned type should not actively be used in the type-system, but rather only for displaying. AbstractType::Ptr stripType(const AbstractType::Ptr& type, DUContext* ctx) { if (!type) { return AbstractType::Ptr(); } struct ShortenTemplateDefaultParameter : public TypeExchanger { DUContext* ctx; explicit ShortenTemplateDefaultParameter(DUContext* _ctx) : ctx(_ctx) { Q_ASSERT(ctx); } AbstractType::Ptr exchange(const AbstractType::Ptr& type) override { if (!type) { return type; } AbstractType::Ptr newType(type->clone()); if (const IdentifiedType * idType = dynamic_cast(type.data())) { Declaration* decl = idType->declaration(ctx->topContext()); if (!decl) { return type; } QualifiedIdentifier newTypeName; #if 0 // from oldcpp if (TemplateDeclaration * tempDecl = dynamic_cast(decl)) { if (decl->context()->type() == DUContext::Class && decl->context()->owner()) { //Strip template default-parameters from the parent class AbstractType::Ptr parentType = stripType(decl->context()->owner()->abstractType(), ctx); if (parentType) { newTypeName = QualifiedIdentifier(parentType->toString(), true); } } if (newTypeName.isEmpty()) { newTypeName = decl->context()->scopeIdentifier(true); } Identifier currentId; if (!idType->qualifiedIdentifier().isEmpty()) { currentId = idType->qualifiedIdentifier().last(); } currentId.clearTemplateIdentifiers(); InstantiationInformation instantiationInfo = tempDecl->instantiatedWith().information(); InstantiationInformation newInformation(instantiationInfo); newInformation.templateParametersList().clear(); for (uint neededParameters = 0; neededParameters < instantiationInfo.templateParametersSize(); ++neededParameters) { newInformation.templateParametersList().append(instantiationInfo.templateParameters()[neededParameters]); AbstractType::Ptr niceParam = stripType(instantiationInfo.templateParameters()[neededParameters].abstractType(), ctx); if (niceParam) { currentId.appendTemplateIdentifier(IndexedTypeIdentifier(niceParam->toString(), true)); // debug() << "testing param" << niceParam->toString(); } if (tempDecl->instantiate(newInformation, ctx->topContext()) == decl) { // debug() << "got full instantiation"; break; } } newTypeName.push(currentId); } else { newTypeName = decl->qualifiedIdentifier(); } #endif newTypeName = decl->qualifiedIdentifier(); //Strip unneded prefixes of the scope QualifiedIdentifier candidate = newTypeName; while (candidate.count() > 1) { candidate = candidate.mid(1); QList decls = ctx->findDeclarations(candidate); if (decls.isEmpty()) { continue; // type aliases might be available for nested sub scopes, hence we must not break early } if (decls[0]->kind() != Declaration::Type || removeConstModifier(decls[0]->indexedType()) != removeConstModifier(IndexedType(type))) { break; } newTypeName = candidate; } if (newTypeName == decl->qualifiedIdentifier()) { return type; } DelayedType::Ptr ret(new DelayedType); IndexedTypeIdentifier ti(newTypeName); ti.setIsConstant(type->modifiers() & AbstractType::ConstModifier); ret->setIdentifier(ti); return ret.cast(); } newType->exchangeTypes(this); return newType; } }; ShortenTemplateDefaultParameter exchanger(ctx); return exchanger.exchange(type); } } namespace CodegenHelper { AbstractType::Ptr typeForShortenedString(Declaration* decl) { AbstractType::Ptr type = decl->abstractType(); if (decl->isTypeAlias()) { if (type.cast()) { type = type.cast()->type(); } } if (decl->isFunctionDeclaration()) { FunctionType::Ptr funType = decl->type(); if (!funType) { return AbstractType::Ptr(); } type = funType->returnType(); } return type; } QString shortenedTypeString(Declaration* decl, DUContext* ctx, int desiredLength, const QualifiedIdentifier& stripPrefix) { return shortenedTypeString(typeForShortenedString(decl), ctx, desiredLength, stripPrefix); } QString simplifiedTypeString(const AbstractType::Ptr& type, DUContext* visibilityFrom) { return shortenedTypeString(type, visibilityFrom, 100000); } QString shortenedTypeString(const AbstractType::Ptr& type, DUContext* ctx, int desiredLength, const QualifiedIdentifier& stripPrefix) { return shortenedTypeIdentifier(type, ctx, desiredLength, stripPrefix).toString(); } IndexedTypeIdentifier shortenedTypeIdentifier(const AbstractType::Ptr& type_, DUContext* ctx, int desiredLength, const QualifiedIdentifier& stripPrefix) { bool isReference = false; bool isRValue = false; auto type = type_; if (const auto& refType = type.cast()) { isReference = true; type = refType->baseType(); isRValue = refType->isRValue(); } type = shortenTypeForViewing(type); if (ctx) { type = stripType(type, ctx); } if (!type) { return IndexedTypeIdentifier(); } IndexedTypeIdentifier identifier = identifierForType(type, ctx ? ctx->topContext() : nullptr); identifier = stripPrefixIdentifiers(identifier, stripPrefix); if (isReference) { identifier.setIsReference(true); } if (isRValue) { identifier.setIsRValue(true); } int removeTemplateParametersFrom = 10; while (identifier.toString().length() > desiredLength * 3 && removeTemplateParametersFrom >= 0) { --removeTemplateParametersFrom; identifier = removeTemplateParameters(identifier, removeTemplateParametersFrom); } return identifier; } QString makeSignatureString(const KDevelop::Declaration* functionDecl, const Signature& signature, const bool editingDefinition) { if (!functionDecl || !functionDecl->internalContext()) { return {}; } const auto visibilityFrom = functionDecl->internalContext()->parentContext(); if (!visibilityFrom) { return {}; } QString ret; if (!editingDefinition) { auto classMember = dynamic_cast(functionDecl); if (classMember && classMember->isStatic()) { ret += QLatin1String("static "); } } // constructors don't have a return type if (signature.returnType.isValid()) { ret += CodegenHelper::simplifiedTypeString(signature.returnType.abstractType(), visibilityFrom); ret += QLatin1Char(' '); } ret += editingDefinition ? functionDecl->qualifiedIdentifier().toString() : functionDecl->identifier().toString(); ret += QLatin1Char('('); int pos = 0; foreach(const ParameterItem &item, signature.parameters) { if (pos != 0) { ret += QLatin1String(", "); } AbstractType::Ptr type = item.first.abstractType(); QString arrayAppendix; ArrayType::Ptr arrayType; while ((arrayType = type.cast())) { type = arrayType->elementType(); //note: we have to prepend since we iterate from outside, i.e. from right to left. if (arrayType->dimension()) { arrayAppendix.prepend(QStringLiteral("[%1]").arg(arrayType->dimension())); } else { // dimensionless arrayAppendix.prepend(QLatin1String("[]")); } } ret += CodegenHelper::simplifiedTypeString(type, visibilityFrom); if (!item.second.isEmpty()) { ret += QLatin1Char(' ') + item.second; } ret += arrayAppendix; if (signature.defaultParams.size() > pos && !signature.defaultParams[pos].isEmpty()) { ret += QLatin1String(" = ") + signature.defaultParams[pos]; } ++pos; } ret += QLatin1Char(')'); if (signature.isConst) { ret += QLatin1String(" const"); } return ret; } } diff --git a/languages/clang/duchain/macronavigationcontext.cpp b/languages/clang/duchain/macronavigationcontext.cpp index b18ca5fae1..2e1db995d2 100644 --- a/languages/clang/duchain/macronavigationcontext.cpp +++ b/languages/clang/duchain/macronavigationcontext.cpp @@ -1,99 +1,99 @@ /* Copyright 2007 David Nolden Copyright 2014 Kevin Funk 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 "macronavigationcontext.h" #include "util/clangdebug.h" #include "util/clangutils.h" #include #include #include #include #include #include using namespace KDevelop; MacroNavigationContext::MacroNavigationContext(const MacroDefinition::Ptr& macro, const KDevelop::DocumentCursor& /* expansionLocation */) : m_macro(macro) { } MacroNavigationContext::~MacroNavigationContext() { } QString MacroNavigationContext::name() const { return m_macro->identifier().toString(); } QString MacroNavigationContext::html(bool shorten) { clear(); modifyHtml() += QLatin1String("

") + fontSizePrefix(shorten); addExternalHtml(prefix()); QStringList parameterList; FOREACH_FUNCTION(const auto& parameter, m_macro->parameters) { parameterList << parameter.str(); } const QString parameters = (!parameterList.isEmpty() ? - QStringLiteral("(%1)").arg(parameterList.join(QLatin1String(", "))) : + QStringLiteral("(%1)").arg(parameterList.join(QStringLiteral(", "))) : QString()); const QUrl url = m_macro->url().toUrl(); const QString path = url.toLocalFile(); KTextEditor::Cursor cursor(m_macro->rangeInCurrentRevision().start()); NavigationAction action(url, cursor); modifyHtml() += i18nc("%1: macro type, i.e.: 'Function macro' or just 'Macro'" "%2: the macro name and arguments", "%1: %2", (m_macro->isFunctionLike() ? i18n("Function macro") : i18n("Macro")), importantHighlight(name()) + parameters); modifyHtml() += QStringLiteral("
"); modifyHtml() += i18nc("%1: the link to the definition", "Defined in: %1", createLink(QStringLiteral("%1 :%2").arg(url.fileName()).arg(cursor.line()+1), path, action)); modifyHtml() += QStringLiteral(" "); //The action name _must_ stay "show_uses", since that is also used from outside makeLink(i18n("Show uses"), QStringLiteral("show_uses"), NavigationAction(m_macro.dynamicCast(), NavigationAction::NavigateUses)); auto code = m_macro->definition().str(); modifyHtml() += QLatin1String("

") + i18n("Body: "); modifyHtml() += QLatin1String("") + code.toHtmlEscaped().replace(QStringLiteral("\n"), QStringLiteral("
")) + QLatin1String("
"); - modifyHtml() += QLatin1String("

"); + modifyHtml() += QStringLiteral("

"); modifyHtml() += fontSizeSuffix(shorten) + QLatin1String("

"); return currentHtml(); } QString MacroNavigationContext::retrievePreprocessedBody(const DocumentCursor& /*expansionLocation*/) const { const TopDUContext* topContext = m_macro->topContext(); if (!topContext) { return QString(); } // TODO: Implement me. Still not exactly sure what do to here... return QString(); } diff --git a/languages/plugins/custom-definesandincludes/compilerprovider/msvccompiler.cpp b/languages/plugins/custom-definesandincludes/compilerprovider/msvccompiler.cpp index 844173938c..5a4f38d8b5 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/msvccompiler.cpp +++ b/languages/plugins/custom-definesandincludes/compilerprovider/msvccompiler.cpp @@ -1,130 +1,130 @@ /* * This file is part of KDevelop * * Copyright 2010 Patrick Spendrin * Copyright 2013 Kevin Funk * Copyright 2014 Sergey Kalinichev * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "msvccompiler.h" #include #include #include #include "../debugarea.h" using namespace KDevelop; Defines MsvcCompiler::defines(const QString&) const { Defines ret; //Get standard macros from kdevmsvcdefinehelpers KProcess proc; proc.setOutputChannelMode( KProcess::MergedChannels ); proc.setTextModeEnabled( true ); // we want to use kdevmsvcdefinehelper as a pseudo compiler backend which // returns the defines used in msvc. there is no such thing as -dM with cl.exe proc << path() << QStringLiteral("/nologo") << QStringLiteral("/Bxkdevmsvcdefinehelper") << QStringLiteral("empty.cpp"); // this will fail, so check on that as well if ( proc.execute( 5000 ) == 2 ) { QString line; proc.readLine(); // read the filename while ( proc.canReadLine() ) { QByteArray buff = proc.readLine(); definesAndIncludesDebug() << "msvcstandardmacros:" << buff; if ( !buff.isEmpty() ) { line = buff; if ( line.startsWith( QLatin1String("#define ") ) ) { line = line.right( line.length() - 8 ).trimmed(); int pos = line.indexOf( ' ' ); if ( pos != -1 ) { ret[line.left( pos )] = line.right( line.length() - pos - 1 ).toUtf8(); } else { ret[line] = QLatin1String(""); } } } } } else { definesAndIncludesDebug() << "Unable to read standard c++ macro definitions from " + path(); while ( proc.canReadLine() ){ definesAndIncludesDebug() << proc.readLine(); } definesAndIncludesDebug() << proc.exitCode(); } // MSVC builtin attributes { ret[QStringLiteral("__cdecl")] = QLatin1String(""); ret[QStringLiteral("__fastcall")] = QLatin1String(""); ret[QStringLiteral("__stdcall")] = QLatin1String(""); ret[QStringLiteral("__thiscall")] = QLatin1String(""); } // MSVC builtin types // see http://msdn.microsoft.com/en-us/library/cc953fe1.aspx { - ret[QStringLiteral("__int8")] = QLatin1String("char"); - ret[QStringLiteral("__int16")] = QLatin1String("short"); - ret[QStringLiteral("__int32")] = QLatin1String("int"); - ret[QStringLiteral("__int64")] = QLatin1String("long long"); - ret[QStringLiteral("__int16")] = QLatin1String("short"); + ret[QStringLiteral("__int8")] = QStringLiteral("char"); + ret[QStringLiteral("__int16")] = QStringLiteral("short"); + ret[QStringLiteral("__int32")] = QStringLiteral("int"); + ret[QStringLiteral("__int64")] = QStringLiteral("long long"); + ret[QStringLiteral("__int16")] = QStringLiteral("short"); ret[QStringLiteral("__ptr32")] = QLatin1String(""); ret[QStringLiteral("__ptr64")] = QLatin1String(""); } // MSVC specific modifiers // see http://msdn.microsoft.com/en-us/library/vstudio/s04b5w00.aspx { ret[QStringLiteral("__sptr")] = QLatin1String(""); ret[QStringLiteral("__uptr")] = QLatin1String(""); ret[QStringLiteral("__unaligned")] = QLatin1String(""); ret[QStringLiteral("__w64")] = QLatin1String(""); } // MSVC function specifiers // see http://msdn.microsoft.com/de-de/library/z8y1yy88.aspx { ret[QStringLiteral("__inline")] = QLatin1String(""); ret[QStringLiteral("__forceinline")] = QLatin1String(""); } return ret; } Path::List MsvcCompiler::includes(const QString&) const { QStringList _includePaths = QProcessEnvironment::systemEnvironment().value( QStringLiteral("INCLUDE") ).split( QStringLiteral(";"), QString::SkipEmptyParts ); Path::List includePaths; foreach( const QString &include, _includePaths ) { includePaths.append( Path( QDir::fromNativeSeparators( include ) ) ); } return includePaths; } MsvcCompiler::MsvcCompiler(const QString& name, const QString& path, bool editable, const QString& factoryName): ICompiler(name, path, factoryName, editable) {} diff --git a/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.cpp b/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.cpp index 295609e863..af3a8c662a 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.cpp +++ b/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.cpp @@ -1,428 +1,428 @@ /* * This file is part of KDevelop * * Copyright 2010 Andreas Pakulat * Copyright 2014 Sergey Kalinichev * * 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 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 "settingsmanager.h" #include #include #include #include #include #include #include #include #include #include #include "compilerprovider.h" using namespace KDevelop; namespace { namespace ConfigConstants { -const QString configKey = QLatin1String( "CustomDefinesAndIncludes" ); -const QString definesKey = QLatin1String( "Defines" ); -const QString includesKey = QLatin1String( "Includes" ); -const QString projectPathPrefix = QLatin1String( "ProjectPath" ); -const QString projectPathKey = QLatin1String( "Path" ); - -const QString customBuildSystemGroup = QLatin1String( "CustomBuildSystem" ); -const QString definesAndIncludesGroup = QLatin1String( "Defines And Includes" ); - -const QString compilersGroup = QLatin1String( "Compilers" ); -const QString compilerNameKey = QLatin1String( "Name" ); -const QString compilerPathKey = QLatin1String( "Path" ); -const QString compilerTypeKey = QLatin1String( "Type" ); +const QString configKey = QStringLiteral( "CustomDefinesAndIncludes" ); +const QString definesKey = QStringLiteral( "Defines" ); +const QString includesKey = QStringLiteral( "Includes" ); +const QString projectPathPrefix = QStringLiteral( "ProjectPath" ); +const QString projectPathKey = QStringLiteral( "Path" ); + +const QString customBuildSystemGroup = QStringLiteral( "CustomBuildSystem" ); +const QString definesAndIncludesGroup = QStringLiteral( "Defines And Includes" ); + +const QString compilersGroup = QStringLiteral( "Compilers" ); +const QString compilerNameKey = QStringLiteral( "Name" ); +const QString compilerPathKey = QStringLiteral( "Path" ); +const QString compilerTypeKey = QStringLiteral( "Type" ); QString parserArgumentsCPP() { return QStringLiteral("parserArguments"); } QString parserArgumentsC() { return QStringLiteral("parserArgumentsC"); } QString parseAmbiguousAsCPP() { return QStringLiteral("parseAmbiguousAsCPP"); } } // the grouplist is randomly sorted b/c it uses QSet internally // we sort the keys here, as the structure is properly defined for us QStringList sorted(QStringList list) { std::sort(list.begin(), list.end()); return list; } ParserArguments defaultArguments() { const static ParserArguments arguments{ QStringLiteral("-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c99"), QStringLiteral("-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c++11"), QStringLiteral("-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c++11"), QStringLiteral("-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c++11"), true }; return arguments; } CompilerPointer createCompilerFromConfig(KConfigGroup& cfg) { auto grp = cfg.group("Compiler"); auto name = grp.readEntry( ConfigConstants::compilerNameKey, QString() ); if (name.isEmpty()) { return SettingsManager::globalInstance()->provider()->checkCompilerExists({}); } for (auto c : SettingsManager::globalInstance()->provider()->compilers()) { if (c->name() == name) { return c; } } // Otherwise we have no such compiler registered (broken config file), return default one return SettingsManager::globalInstance()->provider()->checkCompilerExists({}); } void writeCompilerToConfig(KConfigGroup& cfg, const CompilerPointer& compiler) { Q_ASSERT(compiler); auto grp = cfg.group("Compiler"); // Store only compiler name, path and type retrieved from registered compilers grp.writeEntry(ConfigConstants::compilerNameKey, compiler->name()); } void doWriteSettings( KConfigGroup grp, const QVector& paths ) { int pathIndex = 0; for ( const auto& path : paths ) { KConfigGroup pathgrp = grp.group( ConfigConstants::projectPathPrefix + QString::number( pathIndex++ ) ); pathgrp.writeEntry(ConfigConstants::projectPathKey, path.path); pathgrp.writeEntry(ConfigConstants::parserArgumentsCPP(), path.parserArguments.cppArguments); pathgrp.writeEntry(ConfigConstants::parserArgumentsC(), path.parserArguments.cArguments); pathgrp.writeEntry(ConfigConstants::parseAmbiguousAsCPP(), path.parserArguments.parseAmbiguousAsCPP); { int index = 0; KConfigGroup includes(pathgrp.group(ConfigConstants::includesKey)); for( auto it = path.includes.begin() ; it != path.includes.end(); ++it){ includes.writeEntry(QString::number(++index), *it); } } { KConfigGroup defines(pathgrp.group(ConfigConstants::definesKey)); for (auto it = path.defines.begin(); it != path.defines.end(); ++it) { defines.writeEntry(it.key(), it.value()); } } writeCompilerToConfig(pathgrp, path.compiler); } } /// @param remove if true all read entries will be removed from the config file QVector doReadSettings( KConfigGroup grp, bool remove = false ) { QVector paths; for( const QString &grpName : sorted(grp.groupList()) ) { if ( !grpName.startsWith( ConfigConstants::projectPathPrefix ) ) { continue; } KConfigGroup pathgrp = grp.group( grpName ); ConfigEntry path; path.path = pathgrp.readEntry( ConfigConstants::projectPathKey, "" ); path.parserArguments.cppArguments = pathgrp.readEntry(ConfigConstants::parserArgumentsCPP(), defaultArguments().cppArguments); path.parserArguments.cArguments = pathgrp.readEntry(ConfigConstants::parserArgumentsC(), defaultArguments().cArguments); path.parserArguments.parseAmbiguousAsCPP = pathgrp.readEntry(ConfigConstants::parseAmbiguousAsCPP(), defaultArguments().parseAmbiguousAsCPP); if (path.parserArguments.cppArguments.isEmpty()) { path.parserArguments.cppArguments = defaultArguments().cppArguments; } if (path.parserArguments.cArguments.isEmpty()) { path.parserArguments.cArguments = defaultArguments().cArguments; } { // defines // Backwards compatibility with old config style if(pathgrp.hasKey(ConfigConstants::definesKey)) { QByteArray tmp = pathgrp.readEntry( ConfigConstants::definesKey, QByteArray() ); QDataStream s( tmp ); s.setVersion( QDataStream::Qt_4_5 ); // backwards compatible reading QHash defines; s >> defines; path.setDefines(defines); } else { KConfigGroup defines(pathgrp.group(ConfigConstants::definesKey)); QMap defMap = defines.entryMap(); path.defines.reserve(defMap.size()); for(auto it = defMap.constBegin(); it != defMap.constEnd(); ++it) { QString key = it.key(); if(key.isEmpty()) { // Toss out the invalid key and value since a valid // value needs a valid key continue; } else { path.defines.insert(key, it.value()); } } } } { // includes // Backwards compatibility with old config style if(pathgrp.hasKey(ConfigConstants::includesKey)){ QByteArray tmp = pathgrp.readEntry( ConfigConstants::includesKey, QByteArray() ); QDataStream s( tmp ); s.setVersion( QDataStream::Qt_4_5 ); s >> path.includes; } else { KConfigGroup includes(pathgrp.group(ConfigConstants::includesKey)); QMap incMap = includes.entryMap(); for(auto it = incMap.begin(); it != incMap.end(); ++it){ QString value = it.value(); if(value.isEmpty()){ continue; } path.includes += value; } } } path.compiler = createCompilerFromConfig(pathgrp); if ( remove ) { pathgrp.deleteGroup(); } Q_ASSERT(!path.parserArguments.cppArguments.isEmpty()); Q_ASSERT(!path.parserArguments.cArguments.isEmpty()); paths << path; } return paths; } /** * Reads and converts paths from old (Custom Build System's) format to the current one. * @return all converted paths (if any) */ QVector convertedPaths( KConfig* cfg ) { KConfigGroup group = cfg->group( ConfigConstants::customBuildSystemGroup ); if ( !group.isValid() ) return {}; QVector paths; foreach( const QString &grpName, sorted(group.groupList()) ) { KConfigGroup subgroup = group.group( grpName ); if ( !subgroup.isValid() ) continue; paths += doReadSettings( subgroup, true ); } return paths; } } void ConfigEntry::setDefines(const QHash& newDefines) { defines.clear(); defines.reserve(newDefines.size()); for (auto it = newDefines.begin(); it != newDefines.end(); ++it) { defines[it.key()] = it.value().toString(); } } SettingsManager::SettingsManager() : m_provider(this) {} SettingsManager::~SettingsManager() {} SettingsManager* SettingsManager::globalInstance() { Q_ASSERT(QThread::currentThread() == qApp->thread()); static SettingsManager s_globalInstance; return &s_globalInstance; } CompilerProvider* SettingsManager::provider() { return &m_provider; } const CompilerProvider* SettingsManager::provider() const { return &m_provider; } void SettingsManager::writePaths( KConfig* cfg, const QVector< ConfigEntry >& paths ) { Q_ASSERT(QThread::currentThread() == qApp->thread()); KConfigGroup grp = cfg->group( ConfigConstants::configKey ); if ( !grp.isValid() ) return; grp.deleteGroup(); doWriteSettings( grp, paths ); } QVector SettingsManager::readPaths( KConfig* cfg ) const { Q_ASSERT(QThread::currentThread() == qApp->thread()); auto converted = convertedPaths( cfg ); if ( !converted.isEmpty() ) { const_cast(this)->writePaths( cfg, converted ); return converted; } KConfigGroup grp = cfg->group( ConfigConstants::configKey ); if ( !grp.isValid() ) { return {}; } return doReadSettings( grp ); } bool SettingsManager::needToReparseCurrentProject( KConfig* cfg ) const { auto grp = cfg->group( ConfigConstants::definesAndIncludesGroup ); return grp.readEntry( "reparse", true ); } void SettingsManager::writeUserDefinedCompilers(const QVector< CompilerPointer >& compilers) { QVector< CompilerPointer > editableCompilers; for (const auto& compiler : compilers) { if (!compiler->editable()) { continue; } editableCompilers.append(compiler); } KConfigGroup config = KSharedConfig::openConfig()->group(ConfigConstants::compilersGroup); config.deleteGroup(); config.writeEntry("number", editableCompilers.count()); int i = 0; for (const auto& compiler : editableCompilers) { KConfigGroup grp = config.group(QString::number(i)); ++i; grp.writeEntry(ConfigConstants::compilerNameKey, compiler->name()); grp.writeEntry(ConfigConstants::compilerPathKey, compiler->path()); grp.writeEntry(ConfigConstants::compilerTypeKey, compiler->factoryName()); } config.sync(); } QVector< CompilerPointer > SettingsManager::userDefinedCompilers() const { QVector< CompilerPointer > compilers; KConfigGroup config = KSharedConfig::openConfig()->group(ConfigConstants::compilersGroup); int count = config.readEntry("number", 0); for (int i = 0; i < count; i++) { KConfigGroup grp = config.group(QString::number(i)); auto name = grp.readEntry(ConfigConstants::compilerNameKey, QString()); auto path = grp.readEntry(ConfigConstants::compilerPathKey, QString()); auto type = grp.readEntry(ConfigConstants::compilerTypeKey, QString()); auto cf = m_provider.compilerFactories(); for (auto f : cf) { if (f->name() == type) { auto compiler = f->createCompiler(name, path); compilers.append(compiler); } } } return compilers; } ParserArguments SettingsManager::defaultParserArguments() const { return defaultArguments(); } ConfigEntry::ConfigEntry(const QString& path) : path(path) , compiler(SettingsManager::globalInstance()->provider()->checkCompilerExists({})) , parserArguments(defaultArguments()) {} namespace Utils { LanguageType languageType(const KDevelop::Path& path, bool treatAmbiguousAsCPP) { QMimeDatabase db; const auto mimeType = db.mimeTypeForFile(path.path()).name(); if (mimeType == QStringLiteral("text/x-csrc") || mimeType == QStringLiteral("text/x-chdr") ) { if (treatAmbiguousAsCPP) { if (path.lastPathSegment().endsWith(QLatin1String(".h"), Qt::CaseInsensitive)) { return Cpp; } } // TODO: No proper mime type detection possible yet // cf. https://bugs.freedesktop.org/show_bug.cgi?id=26913 if (path.lastPathSegment().endsWith(QLatin1String(".cl"), Qt::CaseInsensitive)) { return OpenCl; } // TODO: No proper mime type detection possible yet // cf. https://bugs.freedesktop.org/show_bug.cgi?id=23700 if (path.lastPathSegment().endsWith(QLatin1String(".cu"), Qt::CaseInsensitive)) { return Cuda; } return C; } if (mimeType == QStringLiteral("text/x-c++src") || mimeType == QStringLiteral("text/x-c++hdr") ) { return Cpp; } if (mimeType == QStringLiteral("text/x-objcsrc")) { return ObjC; } return Other; } } diff --git a/languages/plugins/custom-definesandincludes/compilerprovider/tests/test_compilerprovider.cpp b/languages/plugins/custom-definesandincludes/compilerprovider/tests/test_compilerprovider.cpp index 8a82e8a2fe..8b9fdb728f 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/tests/test_compilerprovider.cpp +++ b/languages/plugins/custom-definesandincludes/compilerprovider/tests/test_compilerprovider.cpp @@ -1,254 +1,254 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "test_compilerprovider.h" #include #include #include #include #include #include #include #include #include #include "../compilerprovider.h" #include "../settingsmanager.h" #include "../tests/projectsgenerator.h" using namespace KDevelop; namespace { void testCompilerEntry(SettingsManager* settings, KConfig* config){ auto entries = settings->readPaths(config); auto entry = entries.first(); auto compilers = settings->provider()->compilers(); Q_ASSERT(!compilers.isEmpty()); bool gccCompilerInstalled = std::any_of(compilers.begin(), compilers.end(), [](const CompilerPointer& compiler){return compiler->name().contains(QLatin1String("gcc"), Qt::CaseInsensitive);}); if (gccCompilerInstalled) { QCOMPARE(entry.compiler->name(), QStringLiteral("GCC")); } } void testAddingEntry(SettingsManager* settings, KConfig* config){ auto entries = settings->readPaths(config); auto entry = entries.first(); auto compilers = settings->provider()->compilers(); ConfigEntry otherEntry; - otherEntry.defines[QStringLiteral("TEST")] = QLatin1String("lalal"); + otherEntry.defines[QStringLiteral("TEST")] = QStringLiteral("lalal"); otherEntry.includes = QStringList() << QStringLiteral("/foo"); - otherEntry.path = QLatin1String("test"); + otherEntry.path = QStringLiteral("test"); otherEntry.compiler = compilers.first(); entries << otherEntry; settings->writePaths(config, entries); auto readWriteEntries = settings->readPaths(config); QCOMPARE(readWriteEntries.size(), 2); QCOMPARE(readWriteEntries.at(0).path, entry.path); QCOMPARE(readWriteEntries.at(0).defines, entry.defines); QCOMPARE(readWriteEntries.at(0).includes, entry.includes); QCOMPARE(readWriteEntries.at(0).compiler->name(), entry.compiler->name()); QCOMPARE(readWriteEntries.at(1).path, otherEntry.path); QCOMPARE(readWriteEntries.at(1).defines, otherEntry.defines); QCOMPARE(readWriteEntries.at(1).includes, otherEntry.includes); QCOMPARE(readWriteEntries.at(1).compiler->name(), otherEntry.compiler->name()); } } void TestCompilerProvider::initTestCase() { AutoTestShell::init(); TestCore::initialize(); } void TestCompilerProvider::cleanupTestCase() { TestCore::shutdown(); } void TestCompilerProvider::testRegisterCompiler() { auto settings = SettingsManager::globalInstance(); auto provider = settings->provider(); auto cf = provider->compilerFactories(); for (int i = 0 ; i < cf.size(); ++i) { auto compiler = cf[i]->createCompiler(QString::number(i), QString::number(i)); QVERIFY(provider->registerCompiler(compiler)); QVERIFY(!provider->registerCompiler(compiler)); QVERIFY(provider->compilers().contains(compiler)); } QVERIFY(!provider->registerCompiler({})); } void TestCompilerProvider::testCompilerIncludesAndDefines() { auto settings = SettingsManager::globalInstance(); auto provider = settings->provider(); for (auto c : provider->compilers()) { if (!c->editable() && !c->path().isEmpty()) { QVERIFY(!c->defines({}).isEmpty()); QVERIFY(!c->includes({}).isEmpty()); } } QVERIFY(!provider->defines(nullptr).isEmpty()); QVERIFY(!provider->includes(nullptr).isEmpty()); auto compiler = provider->compilerForItem(nullptr); QVERIFY(compiler); QVERIFY(!compiler->defines(QStringLiteral("--std=c++11")).isEmpty()); QVERIFY(!compiler->includes(QStringLiteral("--std=c++11")).isEmpty()); } void TestCompilerProvider::testStorageBackwardsCompatible() { auto settings = SettingsManager::globalInstance(); QTemporaryFile file; QVERIFY(file.open()); QTextStream stream(&file); stream << "[Buildset]\n" << "BuildItems=@Variant(\\x00\\x00\\x00\\t\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x0b\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x1a\\x00S\\x00i\\x00m\\x00p\\x00l\\x00e\\x00P\\x00r\\x00o\\x00j\\x00e\\x00c\\x00t)\n" << "[CustomBuildSystem]\n" << "CurrentConfiguration=BuildConfig0\n" << "[CustomDefinesAndIncludes][ProjectPath0]\n" << "Defines=\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x0c\\x00_\\x00D\\x00E\\x00B\\x00U\\x00G\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x10\\x00V\\x00A\\x00R\\x00I\\x00A\\x00B\\x00L\\x00E\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\n\\x00V\\x00A\\x00L\\x00U\\x00E\n" << "Includes=\\x00\\x00\\x00\\x01\\x00\\x00\\x00$\\x00/\\x00u\\x00s\\x00r\\x00/\\x00i\\x00n\\x00c\\x00l\\x00u\\x00d\\x00e\\x00/\\x00m\\x00y\\x00d\\x00i\\x00r\n" << "Path=/\n" << "[CustomDefinesAndIncludes][ProjectPath0][Compiler]\nName=GCC\nPath=gcc\nType=GCC\n"; file.close(); KConfig config(file.fileName()); auto entries = settings->readPaths(&config); QCOMPARE(entries.size(), 1); auto entry = entries.first(); Defines defines; - defines[QStringLiteral("VARIABLE")] = QLatin1String("VALUE"); + defines[QStringLiteral("VARIABLE")] = QStringLiteral("VALUE"); defines[QStringLiteral("_DEBUG")] = QString(); QCOMPARE(entry.defines, defines); QStringList includes = QStringList() << QStringLiteral("/usr/include/mydir"); QCOMPARE(entry.includes, includes); QCOMPARE(entry.path, QString("/")); QVERIFY(entry.compiler); testCompilerEntry(settings, &config); testAddingEntry(settings, &config); } void TestCompilerProvider::testStorageNewSystem() { auto settings = SettingsManager::globalInstance(); QTemporaryFile file; QVERIFY(file.open()); QTextStream stream(&file); stream << "[Buildset]\n" << "BuildItems=@Variant(\\x00\\x00\\x00\\t\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x0b\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x1a\\x00S\\x00i\\x00m\\x00p\\x00l\\x00e\\x00P\\x00r\\x00o\\x00j\\x00e\\x00c\\x00t)\n\n" << "[CustomBuildSystem]\n" << "CurrentConfiguration=BuildConfig0\n\n" << "[CustomDefinesAndIncludes][ProjectPath0]\n" << "Path=/\n\n" << "[CustomDefinesAndIncludes][ProjectPath0][Defines]\n" << "_DEBUG=\n" << "VARIABLE=VALUE\n" << "[CustomDefinesAndIncludes][ProjectPath0][Includes]\n" << "1=/usr/include/mydir\n" << "2=/usr/local/include/mydir\n" << "[CustomDefinesAndIncludes][ProjectPath0][Compiler]\nName=GCC\nPath=gcc\nType=GCC\n"; file.close(); KConfig config(file.fileName()); auto entries = settings->readPaths(&config); QCOMPARE(entries.size(), 1); auto entry = entries.first(); QCOMPARE(entry.path, QString("/")); Defines defines; - defines[QStringLiteral("VARIABLE")] = QLatin1String("VALUE"); + defines[QStringLiteral("VARIABLE")] = QStringLiteral("VALUE"); defines[QStringLiteral("_DEBUG")] = QString(); QCOMPARE(entry.defines, defines); QMap includeMap; - includeMap[QStringLiteral("1")] = QLatin1String("/usr/include/mydir"); - includeMap[QStringLiteral("2")] = QLatin1String("/usr/local/include/mydir"); + includeMap[QStringLiteral("1")] = QStringLiteral("/usr/include/mydir"); + includeMap[QStringLiteral("2")] = QStringLiteral("/usr/local/include/mydir"); int i = 0; for(auto it = includeMap.begin(); it != includeMap.end(); it++) { QCOMPARE(entry.includes.at(i++), it.value()); } testCompilerEntry(settings, &config); testAddingEntry(settings, &config); } void TestCompilerProvider::testCompilerIncludesAndDefinesForProject() { auto project = ProjectsGenerator::GenerateMultiPathProject(); Q_ASSERT(project); auto settings = SettingsManager::globalInstance(); auto provider = settings->provider(); Q_ASSERT(!provider->compilerFactories().isEmpty()); auto compiler = provider->compilerFactories().first()->createCompiler(QStringLiteral("name"), QStringLiteral("path")); QVERIFY(provider->registerCompiler(compiler)); QVERIFY(provider->compilers().contains(compiler)); auto projectCompiler = provider->compilerForItem(project->projectItem()); QVERIFY(projectCompiler); QVERIFY(projectCompiler != compiler); ProjectBaseItem* mainfile = nullptr; for (const auto& file: project->fileSet() ) { for (auto i: project->filesForPath(file)) { if( i->text() == QLatin1String("main.cpp") ) { mainfile = i; break; } } } QVERIFY(mainfile); auto mainCompiler = provider->compilerForItem(mainfile); QVERIFY(mainCompiler); QVERIFY(mainCompiler->name() == projectCompiler->name()); ConfigEntry entry; - entry.path = QLatin1String("src/main.cpp"); + entry.path = QStringLiteral("src/main.cpp"); entry.compiler = compiler; auto entries = settings->readPaths(project->projectConfiguration().data()); entries.append(entry); settings->writePaths(project->projectConfiguration().data(), entries); QVERIFY(provider->compilers().contains(compiler)); mainCompiler = provider->compilerForItem(mainfile); QVERIFY(mainCompiler); QVERIFY(mainCompiler->name() == compiler->name()); ICore::self()->projectController()->closeProject(project); } QTEST_MAIN(TestCompilerProvider) diff --git a/languages/plugins/custom-definesandincludes/tests/projectsgenerator.cpp b/languages/plugins/custom-definesandincludes/tests/projectsgenerator.cpp index 2ce96c3b8d..d15847c992 100644 --- a/languages/plugins/custom-definesandincludes/tests/projectsgenerator.cpp +++ b/languages/plugins/custom-definesandincludes/tests/projectsgenerator.cpp @@ -1,204 +1,204 @@ /* * Copyright 2014 Sergey Kalinichev * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "projectsgenerator.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { /// @param projectFile projectName.kdev4 file IProject* loadProject( const QString& projectFile, const QString& projectName ) { KDevSignalSpy* projectSpy = new KDevSignalSpy( ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*)) ); ICore::self()->projectController()->openProject( QUrl::fromLocalFile(projectFile) ); if( !projectSpy->wait( 5000 ) ) { qFatal("Expected project to be loaded within 5 seconds, but this didn't happen"); } IProject* project = ICore::self()->projectController()->findProjectByName( projectName ); return project; } void createFile( QFile& file ) { file.remove(); if ( !file.open( QIODevice::ReadWrite ) ) { qFatal("Cannot create the file %s", file.fileName().toUtf8().constData()); } } } IProject* ProjectsGenerator::GenerateSimpleProject() { // directory structure: // ./simpleproject.kdev4 // ./src/main.cpp // ./.kdev4/simpleproject.kdev4 - const QString sp = QLatin1String( "simpleproject" ); + const QString sp = QStringLiteral( "simpleproject" ); auto rootFolder = QDir::temp(); QDir(rootFolder.absolutePath() + "/" + sp).removeRecursively(); rootFolder.mkdir( sp ); rootFolder.cd( sp ); rootFolder.mkdir( QStringLiteral("src") ); rootFolder.mkdir( QStringLiteral(".kdev4") ); { QFile file( rootFolder.filePath( QStringLiteral("simpleproject.kdev4") ) ); createFile( file ); QTextStream stream1( &file ); stream1 << "[Project]\nName=SimpleProject\nManager=KDevCustomBuildSystem"; } { QFile file( rootFolder.filePath( QStringLiteral("src/main.cpp") ) ); createFile( file ); } { QFile file( rootFolder.filePath( QStringLiteral(".kdev4/simpleproject.kdev4") ) ); createFile( file ); QTextStream stream( &file ); stream << "[Buildset]\n" << "BuildItems=@Variant(\\x00\\x00\\x00\\t\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x0b\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x1a\\x00S\\x00i\\x00m\\x00p\\x00l\\x00e\\x00P\\x00r\\x00o\\x00j\\x00e\\x00c\\x00t)\n" << "[CustomBuildSystem]\n" << "CurrentConfiguration=BuildConfig0\n" << "[CustomDefinesAndIncludes][ProjectPath0]\n" << "Defines=\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x0c\\x00_\\x00D\\x00E\\x00B\\x00U\\x00G\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x10\\x00V\\x00A\\x00R\\x00I\\x00A\\x00B\\x00L\\x00E\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\n\\x00V\\x00A\\x00L\\x00U\\x00E\n" << "Includes=\\x00\\x00\\x00\\x01\\x00\\x00\\x00$\\x00/\\x00u\\x00s\\x00r\\x00/\\x00i\\x00n\\x00c\\x00l\\x00u\\x00d\\x00e\\x00/\\x00m\\x00y\\x00d\\x00i\\x00r\n" << "Path=/\n" << "[Project]\n" << "VersionControlSupport=\n"; } return loadProject( QDir::tempPath() + "/simpleproject/simpleproject.kdev4", QStringLiteral("SimpleProject") ); } IProject* ProjectsGenerator::GenerateMultiPathProject() { // directory structure: // ./multipathproject.kdev4 // ./src/main.cpp // ./anotherFolder/tst.h // ./.kdev4/multipathproject.kdev4 - const QString mp = QLatin1String( "multipathproject" ); + const QString mp = QStringLiteral( "multipathproject" ); auto rootFolder = QDir::temp(); QDir(rootFolder.absolutePath() + "/" + mp).removeRecursively(); rootFolder.mkdir( mp ); rootFolder.cd( mp ); rootFolder.mkdir( QStringLiteral("src") ); rootFolder.mkdir( QStringLiteral(".kdev4") ); rootFolder.mkdir( QStringLiteral("anotherFolder") ); { QFile file( rootFolder.filePath( QStringLiteral("multipathproject.kdev4") ) ); createFile( file ); QTextStream stream1( &file ); stream1 << "[Project]\nName=MultiPathProject\nManager=KDevCustomBuildSystem"; ; } { QFile file1( rootFolder.filePath( QStringLiteral("src/main.cpp") ) ); createFile( file1 ); QFile file2( rootFolder.filePath( QStringLiteral("anotherFolder/tst.h") ) ); createFile( file2 ); } { QFile file( rootFolder.filePath( QStringLiteral(".kdev4/multipathproject.kdev4") ) ); createFile( file ); QTextStream stream( &file ); stream << "[Buildset]\n" << "BuildItems=@Variant(\\x00\\x00\\x00\\t\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x0b\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00 \\x00M\\x00u\\x00l\\x00t\\x00i\\x00P\\x00a\\x00t\\x00h\\x00P\\x00r\\x00o\\x00j\\x00e\\x00c\\x00t)\n" << "[CustomBuildSystem]\n" << "CurrentConfiguration=BuildConfig0\n" << "[CustomDefinesAndIncludes][ProjectPath0]\n" << "Defines=\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\n\\x00_\\x00C\\x00O\\x00P\\x00Y\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x0c\\x00S\\x00O\\x00U\\x00R\\x00C\\x00E\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\x0e\\x00C\\x00O\\x00N\\x00T\\x00E\\x00N\\x00T\n" << "Includes=\\x00\\x00\\x00\\x01\\x00\\x00\\x00*\\x00/\\x00u\\x00s\\x00r\\x00/\\x00i\\x00n\\x00c\\x00l\\x00u\\x00d\\x00e\\x00/\\x00o\\x00t\\x00h\\x00e\\x00r\\x00d\\x00i\\x00r\n" << "Path=.\n" << "[CustomDefinesAndIncludes][ProjectPath1]\n" << "Defines=\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\n\\x00B\\x00U\\x00I\\x00L\\x00D\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\n\\x00d\\x00e\\x00b\\x00u\\x00g\n" << "Includes=\\x00\\x00\\x00\\x01\\x00\\x00\\x000\\x00/\\x00u\\x00s\\x00r\\x00/\\x00l\\x00o\\x00c\\x00a\\x00l\\x00/\\x00i\\x00n\\x00c\\x00l\\x00u\\x00d\\x00e\\x00/\\x00m\\x00y\\x00d\\x00i\\x00r\n" << "Path=src\n" << "[CustomDefinesAndIncludes][ProjectPath2]\n" << "Defines=\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x0c\\x00H\\x00I\\x00D\\x00D\\x00E\\x00N\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\x00\n" << "Path=anotherFolder\n" << "[Project]\n" << "VersionControlSupport=\n"; } return loadProject( QDir::tempPath() + "/multipathproject/multipathproject.kdev4", QStringLiteral("MultiPathProject") ); } IProject* ProjectsGenerator::GenerateSimpleProjectWithOutOfProjectFiles() { auto project = GenerateSimpleProject(); Q_ASSERT(project); auto rootFolder = QDir(project->path().path()); const QString includePaths = QStringLiteral(".kdev_include_paths"); QFile file(rootFolder.filePath(includePaths)); createFile(file); QTextStream stream( &file ); stream << "." + QDir::separator() + "include1.h" << endl << rootFolder.canonicalPath() + QDir::separator() + "include2.h"; return project; } IProject* ProjectsGenerator::GenerateEmptyProject() { // directory structure: // ./emptyproject.kdev4 // ./.kdev4/emptyproject.kdev4 - const QString ep = QLatin1String("emptyproject"); + const QString ep = QStringLiteral("emptyproject"); auto rootFolder = QDir::temp(); QDir(rootFolder.absolutePath() + "/" + ep).removeRecursively(); rootFolder.mkdir(ep); rootFolder.cd(ep); rootFolder.mkdir(QStringLiteral(".kdev4")); { QFile file(rootFolder.filePath(QStringLiteral("emptyproject.kdev4"))); createFile(file); QTextStream stream(&file); stream << "[Project]\nName=EmptyProject\nManager=KDevCustomBuildSystem"; } { QFile file(rootFolder.filePath(QStringLiteral(".kdev4/emptyproject.kdev4"))); createFile(file); QTextStream stream(&file); stream << "[Project]\n" << "VersionControlSupport=\n"; } return loadProject(QDir::tempPath() + "/emptyproject/emptyproject.kdev4", QStringLiteral("EmptyProject")); } diff --git a/languages/qmljs/codecompletion/context.cpp b/languages/qmljs/codecompletion/context.cpp index 0ef3dbc052..360dab3f1f 100644 --- a/languages/qmljs/codecompletion/context.cpp +++ b/languages/qmljs/codecompletion/context.cpp @@ -1,501 +1,501 @@ /* * This file is part of qmljs, the QML/JS language support plugin for KDevelop * Copyright (c) 2013 Sven Brauch * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "context.h" #include "items/modulecompletionitem.h" #include "items/functioncalltipcompletionitem.h" #include #include #include #include #include #include #include #include #include #include "../duchain/expressionvisitor.h" #include "../duchain/helper.h" #include "../duchain/cache.h" #include "../duchain/frameworks/nodejs.h" #include #include using namespace KDevelop; typedef QPair DeclarationDepthPair; namespace QmlJS { CodeCompletionContext::CodeCompletionContext(const DUContextPointer& context, const QString& text, const CursorInRevision& position, int depth) : KDevelop::CodeCompletionContext(context, extractLastLine(text), position, depth), m_completionKind(NormalCompletion) { // Detect "import ..." and provide import completions if (m_text.startsWith(QLatin1String("import "))) { m_completionKind = ImportCompletion; } // Node.js module completions if (m_text.endsWith(QLatin1String("require("))) { m_completionKind = NodeModulesCompletion; } // Detect whether the cursor is in a comment bool isLastLine = true; bool inString = false; for (int index = text.size()-1; index > 0; --index) { const QChar c = text.at(index); const QChar prev = text.at(index - 1); if (c == QLatin1Char('\n')) { isLastLine = false; } else if (isLastLine && prev == QLatin1Char('/') && c == QLatin1Char('/')) { // Single-line comment on the current line, we are in a comment m_completionKind = CommentCompletion; break; } else if (prev == QLatin1Char('/') && c == QLatin1Char('*')) { // Start of a multi-line comment encountered m_completionKind = CommentCompletion; break; } else if (prev == QLatin1Char('*') && c == QLatin1Char('/')) { // End of a multi-line comment. Because /* and */ cannot be nested, // encountering a */ is enough to know that the cursor is outside a // comment break; } else if (prev != QLatin1Char('\\') && (c == QLatin1Char('"') || c == QLatin1Char('\''))) { // Toggle whether we are in a string or not inString = !inString; } } if (inString) { m_completionKind = StringCompletion; } // Some specific constructs don't need any code-completion at all (mainly // because the user will declare new things, not use ones) if (m_text.contains(QRegExp(QLatin1String("(var|function)\\s+$"))) || // "var |" or "function |" m_text.contains(QRegExp(QLatin1String("property\\s+[a-zA-Z0-9_]+\\s+$"))) || // "property |" m_text.contains(QRegExp(QLatin1String("function(\\s+[a-zA-Z0-9_]+)?\\s*\\($"))) || // "function (|" or "function (|" m_text.contains(QRegExp(QLatin1String("id:\\s*"))) // "id: |" ) { m_completionKind = NoCompletion; } } QList CodeCompletionContext::completionItems(bool& abort, bool fullCompletion) { Q_UNUSED (fullCompletion); if (abort) { return QList(); } switch (m_completionKind) { case NormalCompletion: return normalCompletion(); case CommentCompletion: return commentCompletion(); case ImportCompletion: return importCompletion(); case NodeModulesCompletion: return nodeModuleCompletions(); case StringCompletion: case NoCompletion: break; } return QList(); } AbstractType::Ptr CodeCompletionContext::typeToMatch() const { return m_typeToMatch; } QList CodeCompletionContext::normalCompletion() { QList items; QChar lastChar = m_text.size() > 0 ? m_text.at(m_text.size() - 1) : QLatin1Char('\0'); bool inQmlObjectScope = (m_duContext->type() == DUContext::Class); // Start with the function call-tips, because functionCallTips is also responsible // for setting m_declarationForTypeMatch items << functionCallTips(); if (lastChar == QLatin1Char('.') || lastChar == QLatin1Char('[')) { // Offer completions for object members and array subscripts items << fieldCompletions( m_text.left(m_text.size() - 1), lastChar == QLatin1Char('[') ? CompletionItem::QuotesAndBracket : CompletionItem::NoDecoration ); } // "object." must only display the members of object, the declarations // available in the current context. if (lastChar != QLatin1Char('.')) { if (inQmlObjectScope) { DUChainReadLocker lock; // The cursor is in a QML object and there is nothing before it. Display // a list of properties and signals that can be used in a script binding. // Note that the properties/signals of parent QML objects are not displayed here items << completionsInContext(m_duContext, CompletionOnlyLocal | CompletionHideWrappers, CompletionItem::ColonOrBracket); items << completionsFromImports(CompletionHideWrappers); items << completionsInContext(DUContextPointer(m_duContext->topContext()), CompletionHideWrappers, CompletionItem::NoDecoration); } else { items << completionsInContext(m_duContext, nullptr, CompletionItem::NoDecoration); items << completionsFromImports(nullptr); - items << completionsFromNodeModule(nullptr, QLatin1String("__builtin_ecmascript")); + items << completionsFromNodeModule(nullptr, QStringLiteral("__builtin_ecmascript")); if (!QmlJS::isQmlFile(m_duContext.data())) { - items << completionsFromNodeModule(nullptr, QLatin1String("__builtin_dom")); + items << completionsFromNodeModule(nullptr, QStringLiteral("__builtin_dom")); } } } return items; } QList CodeCompletionContext::commentCompletion() { return QList(); } QList CodeCompletionContext::importCompletion() { QList items; QString fragment = m_text.section(QLatin1Char(' '), -1, -1); // Use the cache to find the directory corresponding to the fragment // (org.kde is, for instance, /usr/lib64/kde4/imports/org/kde), and list // its subdirectories QString dataDir = Cache::instance().modulePath(m_duContext->url(), fragment); QDir dir; if (!dataDir.isEmpty()) { dir.setPath(dataDir); for (const QString& entry : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name)) { items.append(CompletionTreeItemPointer(new ModuleCompletionItem( fragment + entry.section(QLatin1Char('.'), 0, 0), ModuleCompletionItem::Import ))); } } return items; } QList CodeCompletionContext::nodeModuleCompletions() { QList items; QDir dir; for (auto path : NodeJS::instance().moduleDirectories(m_duContext->url().str())) { dir.setPath(path.toLocalFile()); for (QString entry : dir.entryList(QDir::Files, QDir::Name)) { entry.replace(QLatin1String(".js"), QString()); if (entry.startsWith(QLatin1String("__"))) { // Internal module, don't show continue; } items.append(CompletionTreeItemPointer( new ModuleCompletionItem(entry, ModuleCompletionItem::Quotes) )); } } return items; } QList CodeCompletionContext::functionCallTips() { Stack stack = expressionStack(m_text); QList items; int argumentHintDepth = 1; bool isTopOfStack = true; DUChainReadLocker lock; while (!stack.isEmpty()) { ExpressionStackEntry entry = stack.pop(); if (isTopOfStack && entry.operatorStart > entry.startPosition) { // Deduce the declaration for type matching using operatorStart: // // table[document.base + // [ ^ // // ^ = operatorStart. Just before operatorStart is a code snippet that ends // with the declaration whose type should be used. DeclarationPointer decl = declarationAtEndOfString(m_text.left(entry.operatorStart)); if (decl) { m_typeToMatch = decl->abstractType(); } } if (entry.startPosition > 0 && m_text.at(entry.startPosition - 1) == QLatin1Char('(')) { // The current entry represents a function call, create a call-tip for it DeclarationPointer functionDecl = declarationAtEndOfString(m_text.left(entry.startPosition - 1)); if (functionDecl) { auto item = new FunctionCalltipCompletionItem( functionDecl, argumentHintDepth, entry.commas ); items << CompletionTreeItemPointer(item); argumentHintDepth++; if (isTopOfStack && !m_typeToMatch) { m_typeToMatch = item->currentArgumentType(); } } } isTopOfStack = false; } return items; } QList CodeCompletionContext::completionsFromImports(CompletionInContextFlags flags) { QList items; // Iterate over all the imported namespaces and add their definitions DUChainReadLocker lock; QList imports = m_duContext->findDeclarations(globalImportIdentifier()); QList realImports; foreach (Declaration* import, imports) { if (import->kind() != Declaration::NamespaceAlias) { continue; } NamespaceAliasDeclaration* decl = static_cast(import); realImports << m_duContext->findDeclarations(decl->importIdentifier()); } foreach (Declaration* import, realImports) { items << completionsInContext( DUContextPointer(import->internalContext()), flags, CompletionItem::NoDecoration ); } return items; } QList CodeCompletionContext::completionsFromNodeModule(CompletionInContextFlags flags, const QString& module) { return completionsInContext( DUContextPointer(QmlJS::getInternalContext( QmlJS::NodeJS::instance().moduleExports(module, m_duContext->url()) )), flags | CompletionOnlyLocal, CompletionItem::NoDecoration ); } QList CodeCompletionContext::completionsInContext(const DUContextPointer& context, CompletionInContextFlags flags, CompletionItem::Decoration decoration) { QList items; DUChainReadLocker lock; if (context) { const QList& declarations = context->allDeclarations( CursorInRevision::invalid(), context->topContext(), !flags.testFlag(CompletionOnlyLocal) ); foreach (const DeclarationDepthPair& decl, declarations) { DeclarationPointer declaration(decl.first); CompletionItem::Decoration decorationOfThisItem = decoration; if (declaration->identifier() == globalImportIdentifier()) { continue; } if (declaration->qualifiedIdentifier().isEmpty()) { continue; } else if (context->owner() && ( context->owner()->kind() == Declaration::Namespace || context->owner()->kind() == Declaration::NamespaceAlias ) && decl.second != 0 && decl.second != 1001) { // Only show the local declarations of modules, or the declarations // immediately in its imported parent contexts (that are global // contexts, hence the distance of 1001). This prevens "String()", // "QtQuick1.0" and "builtins" from being listed when the user // types "PlasmaCore.". continue; } else if (decorationOfThisItem == CompletionItem::NoDecoration && declaration->abstractType() && declaration->abstractType()->whichType() == AbstractType::TypeFunction) { // Decorate function calls with brackets decorationOfThisItem = CompletionItem::Brackets; } else if (flags.testFlag(CompletionHideWrappers)) { ClassDeclaration* classDecl = dynamic_cast(declaration.data()); if (classDecl && classDecl->classType() == ClassDeclarationData::Interface) { continue; } } items << CompletionTreeItemPointer(new CompletionItem(declaration, decl.second, decorationOfThisItem)); } } return items; } QList CodeCompletionContext::fieldCompletions(const QString& expression, CompletionItem::Decoration decoration) { // The statement given to this method ends with an expression that may identify // a declaration ("foo" in "test(1, 2, foo"). List the declarations of this // inner context DUContext* context = getInternalContext(declarationAtEndOfString(expression)); if (context) { return completionsInContext(DUContextPointer(context), CompletionOnlyLocal, decoration); } else { return QList(); } } Stack CodeCompletionContext::expressionStack(const QString& expression) { Stack stack; ExpressionStackEntry entry; QmlJS::Lexer lexer(nullptr); bool atEnd = false; lexer.setCode(expression, 1, false); entry.startPosition = 0; entry.operatorStart = 0; entry.operatorEnd = 0; entry.commas = 0; stack.push(entry); // NOTE: KDevelop uses 0-indexed columns while QMLJS uses 1-indexed columns while (!atEnd) { switch (lexer.lex()) { case QmlJSGrammar::EOF_SYMBOL: atEnd = true; break; case QmlJSGrammar::T_LBRACE: case QmlJSGrammar::T_LBRACKET: case QmlJSGrammar::T_LPAREN: entry.startPosition = lexer.tokenEndColumn() - 1; entry.operatorStart = entry.startPosition; entry.operatorEnd = entry.startPosition; entry.commas = 0; stack.push(entry); break; case QmlJSGrammar::T_RBRACE: case QmlJSGrammar::T_RBRACKET: case QmlJSGrammar::T_RPAREN: if (stack.count() > 1) { stack.pop(); } break; case QmlJSGrammar::T_IDENTIFIER: case QmlJSGrammar::T_DOT: case QmlJSGrammar::T_THIS: break; case QmlJSGrammar::T_COMMA: stack.top().commas++; default: // The last operator of every sub-expression is stored on the stack // so that "A = foo." can know that attributes of foo having the same // type as A should be highlighted. stack.top().operatorStart = lexer.tokenStartColumn() - 1; stack.top().operatorEnd = lexer.tokenEndColumn() - 1; } } return stack; } DeclarationPointer CodeCompletionContext::declarationAtEndOfString(const QString& expression) { // Build the expression stack of expression and use the valid portion of the // top sub-expression to find the right-most declaration that can be found // in expression. QmlJS::Document::MutablePtr doc = QmlJS::Document::create(QStringLiteral("inline"), Dialect::JavaScript); ExpressionStackEntry topEntry = expressionStack(expression).top(); doc->setSource(expression.mid(topEntry.operatorEnd)); doc->parseExpression(); if (!doc || !doc->isParsedCorrectly()) { return DeclarationPointer(); } // Use ExpressionVisitor to find the type (and associated declaration) of // the snippet that has been parsed. The inner context of the declaration // can be used to get the list of completions ExpressionVisitor visitor(m_duContext.data()); doc->ast()->accept(&visitor); return visitor.lastDeclaration(); } bool CodeCompletionContext::containsOnlySpaces(const QString& str) { for (int i=0; i * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "completionitem.h" #include "context.h" #include #include #include #include #include #include #include #include #include #include #include "../../duchain/functiontype.h" using namespace QmlJS; using namespace KDevelop; CompletionItem::CompletionItem(DeclarationPointer decl, int inheritanceDepth, Decoration decoration) : NormalDeclarationCompletionItem(decl, QExplicitlySharedDataPointer(), inheritanceDepth), m_decoration(decoration) { } QVariant CompletionItem::data(const QModelIndex& index, int role, const CodeCompletionModel* model) const { DUChainReadLocker lock; Declaration* decl = declaration().data(); if (!decl) { return QVariant(); } ClassDeclaration* classDecl = dynamic_cast(decl); StructureType::Ptr declType = StructureType::Ptr::dynamicCast(decl->abstractType()); auto funcType = QmlJS::FunctionType::Ptr::dynamicCast(decl->abstractType()); if (role == CodeCompletionModel::BestMatchesCount) { return 5; } else if (role == CodeCompletionModel::MatchQuality) { AbstractType::Ptr referenceType = static_cast(model->completionContext().data())->typeToMatch(); if (!referenceType) { return QVariant(); } AbstractType::Ptr declType = decl->abstractType(); if (!declType) { return QVariant(); } QmlJS::FunctionType::Ptr declFunc = QmlJS::FunctionType::Ptr::dynamicCast(declType); if (declType->equals(referenceType.constData())) { // Perfect type match return QVariant(10); } else if (declFunc && declFunc->returnType() && declFunc->returnType()->equals(referenceType.constData())) { // Also very nice: a function returning the proper type return QVariant(9); } else { // Completely different types, no luck return QVariant(); } } else if (role == Qt::DisplayRole && funcType) { // Functions are displayed using the "type funcName(arg, arg, arg...)" format Declaration* funcDecl = funcType->declaration(decl->topContext()); if (funcDecl) { switch (index.column()) { case CodeCompletionModel::Prefix: return funcType->returnType()->toString(); case CodeCompletionModel::Name: // Return the identifier of the declaration being listed, not of its // function declaration (because the function may have been declared // anonymously, even if it has been assigned to a variable) return decl->identifier().toString(); case CodeCompletionModel::Arguments: { QStringList args; for (auto arg : funcDecl->internalContext()->localDeclarations()) { args.append(arg->toString()); } - return QStringLiteral("(%1)").arg(args.join(QLatin1String(", "))); + return QStringLiteral("(%1)").arg(args.join(QStringLiteral(", "))); } } } } else if (role == Qt::DisplayRole && index.column() == CodeCompletionModel::Prefix) { if (classDecl) { if (classDecl->classType() == ClassDeclarationData::Class) { // QML component return QStringLiteral("component"); } else if (classDecl->classType() == ClassDeclarationData::Interface) { // C++-ish QML component return QStringLiteral("wrapper"); } } if (decl && ( decl->kind() == Declaration::NamespaceAlias || decl->kind() == Declaration::Namespace )) { // Display namespaces and namespace aliases as modules return QStringLiteral("module"); } if (decl && decl->abstractType() && decl->kind() == Declaration::Type && decl->abstractType()->whichType() == AbstractType::TypeEnumeration) { // Enum return QStringLiteral("enum"); } if (declType && decl->kind() == Declaration::Instance && declType->declarationId().qualifiedIdentifier().isEmpty()) { // QML component instance. The type that should be displayed is the // base class of its anonymous class ClassDeclaration* anonymousClass = dynamic_cast(declType->declaration(decl->topContext())); if (anonymousClass && anonymousClass->baseClassesSize() > 0) { return anonymousClass->baseClasses()[0].baseClass.abstractType()->toString(); } } } return NormalDeclarationCompletionItem::data(index, role, model); } QString CompletionItem::declarationName() const { ClassFunctionDeclaration* classFuncDecl = dynamic_cast(declaration().data()); if (classFuncDecl && classFuncDecl->isSignal() && m_decoration == QmlJS::CompletionItem::ColonOrBracket) { // Signals, when completed in a QML component context, are transformed into slots QString signal = classFuncDecl->identifier().toString(); if (signal.size() > 0) { return QLatin1String("on") + signal.at(0).toUpper() + signal.mid(1); } } return NormalDeclarationCompletionItem::declarationName(); } CodeCompletionModel::CompletionProperties CompletionItem::completionProperties() const { DUChainReadLocker lock; // Variables having a function type should have a function icon. FunctionDeclarations // are skipped here because they are already handled properly by completionProperties() if (declaration() && declaration()->abstractType() && !declaration()->isFunctionDeclaration() && declaration()->abstractType()->whichType() == AbstractType::TypeFunction) { return CodeCompletionModel::Function; } // Put declarations in a context owned by a namespace in the namespace scope auto properties = NormalDeclarationCompletionItem::completionProperties(); if (declaration() && declaration()->context() && declaration()->context()->owner() && ( declaration()->context()->owner()->kind() == Declaration::Namespace || declaration()->context()->type() == DUContext::Enum )) { properties &= ~(CodeCompletionModel::LocalScope | CodeCompletionModel::GlobalScope | CodeCompletionModel::Public); properties |= CodeCompletionModel::NamespaceScope; } return properties; } void CompletionItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { KTextEditor::Document* document = view->document(); QString base = declarationName(); switch (m_decoration) { case QmlJS::CompletionItem::NoDecoration: document->replaceText(word, base); break; case QmlJS::CompletionItem::Quotes: document->replaceText(word, "\"" + base + "\""); break; case QmlJS::CompletionItem::QuotesAndBracket: document->replaceText(word, "\"" + base + "\"]"); break; case QmlJS::CompletionItem::ColonOrBracket: if (declaration() && declaration()->abstractType() && declaration()->abstractType()->whichType() == AbstractType::TypeStructure) { document->replaceText(word, base + " {}"); } else { document->replaceText(word, base + ": "); } break; case QmlJS::CompletionItem::Brackets: document->replaceText(word, base + "()"); } } diff --git a/languages/qmljs/codecompletion/items/functioncalltipcompletionitem.cpp b/languages/qmljs/codecompletion/items/functioncalltipcompletionitem.cpp index 7b01a650dd..124f7ce056 100644 --- a/languages/qmljs/codecompletion/items/functioncalltipcompletionitem.cpp +++ b/languages/qmljs/codecompletion/items/functioncalltipcompletionitem.cpp @@ -1,178 +1,178 @@ /* * This file is part of qmljs, the QML/JS language support plugin for KDevelop * Copyright (c) 2014 Denis Steckelmacher * * 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "functioncalltipcompletionitem.h" #include "../../duchain/helper.h" #include "../../duchain/functiontype.h" #include #include #include #include using namespace KDevelop; using namespace QmlJS; FunctionCalltipCompletionItem::FunctionCalltipCompletionItem(const DeclarationPointer& decl, int depth, int argumentIndex) : m_declaration(decl), m_depth(depth) { // Ensure that decl has a function type if (!decl) { return; } QmlJS::FunctionType::Ptr func = QmlJS::FunctionType::Ptr::dynamicCast(decl->abstractType()); if (!func) { return; } // Arguments can be fetch from the function declaration (if available), or // from its function type Declaration* funcDecl = func->declaration(decl->topContext()); DUContext* argsContext = (funcDecl ? funcDecl->internalContext() : nullptr); QStringList arguments; if (argsContext) { auto args = argsContext->allDeclarations(CursorInRevision::invalid(), decl->topContext(), false); for (auto pair : args) { arguments.append(pair.first->toString()); } if (argumentIndex < args.count()) { m_currentArgumentType = args.at(argumentIndex).first->abstractType(); } } else { for (auto type : func->arguments()) { arguments.append(type->toString()); } if (argumentIndex < func->arguments().count()) { m_currentArgumentType = func->arguments().at(argumentIndex); } } // [type] functionName if (func->returnType()) { m_prefix = func->returnType()->toString() + QLatin1String(" "); } m_prefix += decl->identifier().toString(); // (arg1, arg2, [currentArgument in m_currentArgument], arg4, arg5) - m_arguments = QLatin1String("("); + m_arguments = QStringLiteral("("); for (int i=0; i 0) { m_arguments += QLatin1String(", "); } if (i == argumentIndex) { m_currentArgumentStart = m_arguments.length(); m_currentArgumentLength = arguments.at(i).length(); } m_arguments += arguments.at(i); } m_arguments += QLatin1String(")"); } AbstractType::Ptr FunctionCalltipCompletionItem::currentArgumentType() const { return m_currentArgumentType; } QVariant FunctionCalltipCompletionItem::data(const QModelIndex& index, int role, const CodeCompletionModel* model) const { Q_UNUSED(model) switch (role) { case Qt::DisplayRole: switch (index.column()) { case CodeCompletionModel::Prefix: return m_prefix; case CodeCompletionModel::Arguments: return m_arguments; } break; case CodeCompletionModel::ArgumentHintDepth: return argumentHintDepth(); case CodeCompletionModel::CompletionRole: return (int)completionProperties(); case CodeCompletionModel::HighlightingMethod: if (index.column() == CodeCompletionModel::Arguments) { return (int)CodeCompletionModel::CustomHighlighting; } break; case CodeCompletionModel::CustomHighlight: if (index.column() == CodeCompletionModel::Arguments) { QTextFormat format; format.setBackground(QBrush(QColor::fromRgb(142, 186, 255))); // Same color as kdev-python format.setProperty(QTextFormat::FontWeight, 99); return QVariantList() << m_currentArgumentStart << m_currentArgumentLength << format; } break; case Qt::DecorationRole: if (index.column() == CodeCompletionModel::Prefix) { return DUChainUtils::iconForProperties(completionProperties()); } break; } return QVariant(); } DeclarationPointer FunctionCalltipCompletionItem::declaration() const { return m_declaration; } int FunctionCalltipCompletionItem::argumentHintDepth() const { return m_depth; } int FunctionCalltipCompletionItem::inheritanceDepth() const { return 0; } CodeCompletionModel::CompletionProperties FunctionCalltipCompletionItem::completionProperties() const { return CodeCompletionModel::Function; } diff --git a/languages/qmljs/duchain/declarationbuilder.cpp b/languages/qmljs/duchain/declarationbuilder.cpp index fcc1e5e307..2519139164 100644 --- a/languages/qmljs/duchain/declarationbuilder.cpp +++ b/languages/qmljs/duchain/declarationbuilder.cpp @@ -1,1541 +1,1541 @@ /************************************************************************************* * Copyright (C) 2012 by Aleix Pol * * Copyright (C) 2012 by Milian Wolff * * * * 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 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 "declarationbuilder.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include "expressionvisitor.h" #include "parsesession.h" #include "functiondeclaration.h" #include "functiontype.h" #include "helper.h" #include "cache.h" #include "frameworks/nodejs.h" #include #include #include using namespace KDevelop; DeclarationBuilder::DeclarationBuilder(ParseSession* session) : m_prebuilding(false) { m_session = session; } ReferencedTopDUContext DeclarationBuilder::build(const IndexedString& url, QmlJS::AST::Node* node, ReferencedTopDUContext updateContext) { Q_ASSERT(m_session->url() == url); // The declaration builder needs to run twice, so it can resolve uses of e.g. functions // which are called before they are defined (which is easily possible, due to JS's dynamic nature). if (!m_prebuilding) { qCDebug(KDEV_QMLJS_DUCHAIN) << "building, but running pre-builder first"; auto prebuilder = new DeclarationBuilder(m_session); prebuilder->m_prebuilding = true; updateContext = prebuilder->build(url, node, updateContext); qCDebug(KDEV_QMLJS_DUCHAIN) << "pre-builder finished"; delete prebuilder; if (!m_session->allDependenciesSatisfied()) { qCDebug(KDEV_QMLJS_DUCHAIN) << "dependencies were missing, don't perform the second parsing pass"; return updateContext; } } else { qCDebug(KDEV_QMLJS_DUCHAIN) << "prebuilding"; } return DeclarationBuilderBase::build(url, node, updateContext); } void DeclarationBuilder::startVisiting(QmlJS::AST::Node* node) { DUContext* builtinQmlContext = nullptr; if (QmlJS::isQmlFile(currentContext()) && !currentContext()->url().str().contains(QLatin1String("__builtin_qml.qml"))) { builtinQmlContext = m_session->contextOfFile( - QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kdevqmljssupport/nodejsmodules/__builtin_qml.qml")) + QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevqmljssupport/nodejsmodules/__builtin_qml.qml")) ); } { DUChainWriteLocker lock; // Remove all the imported parent contexts: imports may have been edited // and there musn't be any leftover parent context currentContext()->topContext()->clearImportedParentContexts(); // Initialize Node.js QmlJS::NodeJS::instance().initialize(this); // Built-in QML types (color, rect, etc) if (builtinQmlContext) { topContext()->addImportedParentContext(builtinQmlContext); } } DeclarationBuilderBase::startVisiting(node); } /* * Functions */ template void DeclarationBuilder::declareFunction(QmlJS::AST::Node* node, bool newPrototypeContext, const QualifiedIdentifier& name, const RangeInRevision& nameRange, QmlJS::AST::Node* parameters, const RangeInRevision& parametersRange, QmlJS::AST::Node* body, const RangeInRevision& bodyRange) { setComment(node); // Declare the function QmlJS::FunctionType::Ptr func(new QmlJS::FunctionType); Decl* decl; { DUChainWriteLocker lock; decl = openDeclaration(name, nameRange); decl->setKind(Declaration::Type); func->setDeclaration(decl); decl->setType(func); } openType(func); // Parameters, if any (a function must always have an internal function context, // so always open a context here even if there are no parameters) DUContext* parametersContext = openContext( node + 1, // Don't call setContextOnNode on node, only the body context can be associated with node RangeInRevision(parametersRange.start, bodyRange.end), // Ensure that this context contains both the parameters and the body DUContext::Function, name ); if (parameters) { QmlJS::AST::Node::accept(parameters, this); } // The internal context of the function is its parameter context { DUChainWriteLocker lock; decl->setInternalContext(parametersContext); } // Open the prototype context, if any. This has to be done before the body // because this context is needed for "this" to be properly resolved // in it. if (newPrototypeContext) { DUChainWriteLocker lock; QmlJS::FunctionDeclaration* d = reinterpret_cast(decl); d->setPrototypeContext(openContext( node + 2, // Don't call setContextOnNode on node, only the body context can be associated with node RangeInRevision(parametersRange.start, parametersRange.start), DUContext::Function, // This allows QmlJS::getOwnerOfContext to know that the parent of this context is the function declaration QualifiedIdentifier(name) )); - if (name.last() != Identifier(QLatin1String("Object"))) { + if (name.last() != Identifier(QStringLiteral("Object"))) { // Every class inherit from Object QmlJS::importObjectContext(currentContext(), topContext()); } closeContext(); } // Body, if any (it is a child context of the parameters) openContext( node, bodyRange, DUContext::Other, name ); if (body) { QmlJS::AST::Node::accept(body, this); } // Close the body context and then the parameters context closeContext(); closeContext(); } template void DeclarationBuilder::declareParameters(Node* node, QStringRef Node::*typeAttribute) { for (Node *plist = node; plist; plist = plist->next) { const QualifiedIdentifier name(plist->name.toString()); const RangeInRevision range = m_session->locationToRange(plist->identifierToken); AbstractType::Ptr type = (typeAttribute ? typeFromName((plist->*typeAttribute).toString()) : // The typeAttribute attribute of plist contains the type name of the argument AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)) // No type information, use mixed ); { DUChainWriteLocker lock; openDeclaration(name, range); } openType(type); closeAndAssignType(); if (QmlJS::FunctionType::Ptr funType = currentType()) { funType->addArgument(type); } } } bool DeclarationBuilder::visit(QmlJS::AST::FunctionDeclaration* node) { declareFunction( node, true, // A function declaration always has its own prototype context QualifiedIdentifier(node->name.toString()), m_session->locationToRange(node->identifierToken), node->formals, m_session->locationsToRange(node->lparenToken, node->rparenToken), node->body, m_session->locationsToRange(node->lbraceToken, node->rbraceToken) ); return false; } bool DeclarationBuilder::visit(QmlJS::AST::FunctionExpression* node) { declareFunction( node, false, QualifiedIdentifier(), QmlJS::emptyRangeOnLine(node->functionToken), node->formals, m_session->locationsToRange(node->lparenToken, node->rparenToken), node->body, m_session->locationsToRange(node->lbraceToken, node->rbraceToken) ); return false; } bool DeclarationBuilder::visit(QmlJS::AST::FormalParameterList* node) { declareParameters(node, (QStringRef QmlJS::AST::FormalParameterList::*)nullptr); return DeclarationBuilderBase::visit(node); } bool DeclarationBuilder::visit(QmlJS::AST::UiParameterList* node) { declareParameters(node, &QmlJS::AST::UiParameterList::type); return DeclarationBuilderBase::visit(node); } bool DeclarationBuilder::visit(QmlJS::AST::ReturnStatement* node) { if (QmlJS::FunctionType::Ptr func = currentType()) { AbstractType::Ptr returnType; if (node->expression) { returnType = findType(node->expression).type; } else { returnType = new IntegralType(IntegralType::TypeVoid); } DUChainWriteLocker lock; func->setReturnType(QmlJS::mergeTypes(func->returnType(), returnType)); } return false; // findType has already explored node } void DeclarationBuilder::endVisitFunction() { QmlJS::FunctionType::Ptr func = currentType(); if (func && !func->returnType()) { // A function that returns nothing returns void DUChainWriteLocker lock; func->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); } closeAndAssignType(); } void DeclarationBuilder::endVisit(QmlJS::AST::FunctionDeclaration* node) { DeclarationBuilderBase::endVisit(node); endVisitFunction(); } void DeclarationBuilder::endVisit(QmlJS::AST::FunctionExpression* node) { DeclarationBuilderBase::endVisit(node); endVisitFunction(); } /* * Variables */ void DeclarationBuilder::inferArgumentsFromCall(QmlJS::AST::Node* base, QmlJS::AST::ArgumentList* arguments) { ContextBuilder::ExpressionType expr = findType(base); QmlJS::FunctionType::Ptr func_type = QmlJS::FunctionType::Ptr::dynamicCast(expr.type); DUChainWriteLocker lock; if (!func_type) { return; } auto func_declaration = dynamic_cast(func_type->declaration(topContext())); if (!func_declaration || !func_declaration->internalContext()) { return; } // Put the argument nodes in a list that has a definite size QVector argumentDecls = func_declaration->internalContext()->localDeclarations(); QVector args; for (auto argument = arguments; argument; argument = argument->next) { args.append(argument); } // Don't update a function when it is called with the wrong number // of arguments if (args.size() != argumentDecls.count()) { return; } // Update the types of the function arguments QmlJS::FunctionType::Ptr new_func_type(new QmlJS::FunctionType); for (int i=0; iabstractType(); // Merge the current type of the argument with its type in the call expression AbstractType::Ptr call_type = findType(argument->expression).type; AbstractType::Ptr new_type = QmlJS::mergeTypes(current_type, call_type); // Update the declaration of the argument and its type in the function type if (func_declaration->topContext() == topContext()) { new_func_type->addArgument(new_type); argumentDecls.at(i)->setAbstractType(new_type); } // Add a warning if it is possible that the argument types don't match if (!m_prebuilding && !areTypesEqual(current_type, call_type)) { m_session->addProblem(argument, i18n( "Possible type mismatch between the argument type (%1) and the value passed as argument (%2)", current_type->toString(), call_type->toString() ), IProblem::Hint); } } // Replace the function's type with the new type having updated arguments if (func_declaration->topContext() == topContext()) { new_func_type->setReturnType(func_type->returnType()); new_func_type->setDeclaration(func_declaration); func_declaration->setAbstractType(new_func_type.cast()); if (expr.declaration) { // expr.declaration is the variable that contains the function, while // func_declaration is the declaration of the function. They can be // different and both need to be updated expr.declaration->setAbstractType(new_func_type.cast()); } } return; } bool DeclarationBuilder::visit(QmlJS::AST::VariableDeclaration* node) { setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8()); const QualifiedIdentifier name(node->name.toString()); const RangeInRevision range = m_session->locationToRange(node->identifierToken); const AbstractType::Ptr type = findType(node->expression).type; { DUChainWriteLocker lock; openDeclaration(name, range); } openType(type); return false; // findType has already explored node } void DeclarationBuilder::endVisit(QmlJS::AST::VariableDeclaration* node) { DeclarationBuilderBase::endVisit(node); closeAndAssignType(); } bool DeclarationBuilder::visit(QmlJS::AST::BinaryExpression* node) { if (node->op == QSOperator::Assign) { ExpressionType leftType = findType(node->left); ExpressionType rightType = findType(node->right); DUChainWriteLocker lock; if (leftType.declaration) { DUContext* leftCtx = leftType.declaration->context(); DUContext* leftInternalCtx = QmlJS::getInternalContext(leftType.declaration); // object.prototype.method = function(){} : when assigning a function // to a variable living in a Class context, set the prototype // context of the function to the context of the variable if (rightType.declaration && leftCtx->type() == DUContext::Class) { auto func = rightType.declaration.dynamicCast(); if (!QmlJS::getOwnerOfContext(leftCtx) && !leftCtx->importers().isEmpty()) { // MyClass.prototype.myfunc declares "myfunc" in a small context // that is imported by MyClass. The prototype of myfunc should // be the context of MyClass, not the small context in which // it has been declared leftCtx = leftCtx->importers().at(0); } if (func && !func->prototypeContext()) { func->setPrototypeContext(leftCtx); } } if (leftType.declaration->topContext() != topContext()) { // Do not modify a declaration of another file } else if (leftType.isPrototype && leftInternalCtx) { // Assigning something to a prototype is equivalent to making it // inherit from a class: "Class.prototype = ClassOrObject;" leftInternalCtx->clearImportedParentContexts(); QmlJS::importDeclarationInContext( leftInternalCtx, rightType.declaration ); } else { // Merge the already-known type of the variable with the new one leftType.declaration->setAbstractType(QmlJS::mergeTypes(leftType.type, rightType.type)); } } return false; // findType has already explored node } return DeclarationBuilderBase::visit(node); } bool DeclarationBuilder::visit(QmlJS::AST::CallExpression* node) { inferArgumentsFromCall(node->base, node->arguments); return false; } bool DeclarationBuilder::visit(QmlJS::AST::NewMemberExpression* node) { inferArgumentsFromCall(node->base, node->arguments); return false; } /* * Arrays */ void DeclarationBuilder::declareFieldMember(const KDevelop::DeclarationPointer& declaration, const QString& member, QmlJS::AST::Node* node, const QmlJS::AST::SourceLocation& location) { if (QmlJS::isPrototypeIdentifier(member)) { // Don't declare "prototype", this is a special member return; } if (!m_session->allDependenciesSatisfied()) { // Don't declare anything automatically if dependencies are missing: the // checks hereafter may pass now but fail later, thus causing disappearing // declarations return; } DUChainWriteLocker lock; QualifiedIdentifier identifier(member); // Declaration must have an internal context so that the member can be added // into it. DUContext* ctx = QmlJS::getInternalContext(declaration); if (!ctx || ctx->topContext() != topContext()) { return; } // No need to re-declare a field if it already exists if (QmlJS::getDeclaration(identifier, ctx, false)) { return; } // The internal context of declaration is already closed and does not contain // location. This can be worked around by opening a new context, declaring the // new field in it, and then adding the context as a parent of // declaration->internalContext(). RangeInRevision range = m_session->locationToRange(location); IntegralType::Ptr type = IntegralType::Ptr(new IntegralType(IntegralType::TypeMixed)); DUContext* importedContext = openContext(node, range, DUContext::Class); Declaration* decl = openDeclaration(identifier, range); decl->setInSymbolTable(false); // This declaration is in an anonymous context, and the symbol table acts as if the declaration was in the global context openType(type); closeAndAssignType(); closeContext(); ctx->addImportedParentContext(importedContext); } bool DeclarationBuilder::visit(QmlJS::AST::FieldMemberExpression* node) { setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8()); ExpressionType type = findType(node->base); if (type.declaration) { declareFieldMember( type.declaration, node->name.toString(), node, node->identifierToken ); } return false; // findType has already visited node->base } bool DeclarationBuilder::visit(QmlJS::AST::ArrayMemberExpression* node) { setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8()); // When the user types array["new_key"], declare "new_key" as a new field of // array. auto stringLiteral = QmlJS::AST::cast(node->expression); if (!stringLiteral) { return DeclarationBuilderBase::visit(node); } ExpressionType type = findType(node->base); if (type.declaration) { declareFieldMember( type.declaration, stringLiteral->value.toString(), node, stringLiteral->literalToken ); } node->expression->accept(this); return false; // findType has already visited node->base, and we have just visited node->expression } bool DeclarationBuilder::visit(QmlJS::AST::ObjectLiteral* node) { setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8()); // Object literals can appear in the "values" property of enumerations. Their // keys must be declared in the enumeration, not in an anonymous class if (currentContext()->type() == DUContext::Enum) { return DeclarationBuilderBase::visit(node); } // Open an anonymous class declaration, with its internal context StructureType::Ptr type(new StructureType); { DUChainWriteLocker lock; ClassDeclaration* decl = openDeclaration( QualifiedIdentifier(), QmlJS::emptyRangeOnLine(node->lbraceToken) ); decl->setKind(Declaration::Type); decl->setInternalContext(openContext( node, m_session->locationsToRange(node->lbraceToken, node->rbraceToken), DUContext::Class )); type->setDeclaration(decl); // Every object literal inherits from Object QmlJS::importObjectContext(currentContext(), topContext()); } openType(type); return DeclarationBuilderBase::visit(node); } bool DeclarationBuilder::visit(QmlJS::AST::PropertyNameAndValue* node) { setComment(node); if (!node->name || !node->value) { return DeclarationBuilderBase::visit(node); } RangeInRevision range(m_session->locationToRange(node->name->propertyNameToken)); QualifiedIdentifier name(QmlJS::getNodeValue(node->name)); // The type of the declaration can either be an enumeration value or the type // of its expression ExpressionType type; bool inSymbolTable = false; if (currentContext()->type() == DUContext::Enum) { // This is an enumeration value auto value = QmlJS::AST::cast(node->value); EnumeratorType::Ptr enumerator(new EnumeratorType); enumerator->setDataType(IntegralType::TypeInt); if (value) { enumerator->setValue((int)value->value); } type.type = AbstractType::Ptr::staticCast(enumerator); type.declaration = nullptr; inSymbolTable = true; } else { // Normal value type = findType(node->value); } // If a function is assigned to an object member, set the prototype context // of the function to the object containing the member if (type.declaration) { DUChainWriteLocker lock; auto func = type.declaration.dynamicCast(); if (func && !func->prototypeContext()) { func->setPrototypeContext(currentContext()); } } // Open the declaration { DUChainWriteLocker lock; ClassMemberDeclaration* decl = openDeclaration(name, range); decl->setInSymbolTable(inSymbolTable); } openType(type.type); return false; // findType has already explored node->expression } void DeclarationBuilder::endVisit(QmlJS::AST::PropertyNameAndValue* node) { DeclarationBuilderBase::endVisit(node); closeAndAssignType(); } void DeclarationBuilder::endVisit(QmlJS::AST::ObjectLiteral* node) { DeclarationBuilderBase::endVisit(node); if (currentContext()->type() != DUContext::Enum) { // Enums are special-cased in visit(ObjectLiteral) closeContext(); closeAndAssignType(); } } /* * plugins.qmltypes files */ void DeclarationBuilder::declareComponent(QmlJS::AST::UiObjectInitializer* node, const RangeInRevision &range, const QualifiedIdentifier &name) { QString baseClass = QmlJS::getQMLAttributeValue(node->members, QStringLiteral("prototype")).value.section('/', -1, -1); // Declare the component itself StructureType::Ptr type(new StructureType); ClassDeclaration* decl; { DUChainWriteLocker lock; decl = openDeclaration(name, range); decl->setKind(Declaration::Type); decl->setClassType(ClassDeclarationData::Interface); decl->clearBaseClasses(); if (!baseClass.isNull()) { addBaseClass(decl, baseClass); } type->setDeclaration(decl); decl->setType(type); // declareExports needs to know the type of decl } openType(type); } void DeclarationBuilder::declareMethod(QmlJS::AST::UiObjectInitializer* node, const RangeInRevision &range, const QualifiedIdentifier &name, bool isSlot, bool isSignal) { QString type_name = QmlJS::getQMLAttributeValue(node->members, QStringLiteral("type")).value; QmlJS::FunctionType::Ptr type(new QmlJS::FunctionType); if (type_name.isNull()) { type->setReturnType(typeFromName(QStringLiteral("void"))); } else { type->setReturnType(typeFromName(type_name)); } { DUChainWriteLocker lock; ClassFunctionDeclaration* decl = openDeclaration(name, range); decl->setIsSlot(isSlot); decl->setIsSignal(isSignal); type->setDeclaration(decl); } openType(type); } void DeclarationBuilder::declareProperty(QmlJS::AST::UiObjectInitializer* node, const RangeInRevision &range, const QualifiedIdentifier &name) { AbstractType::Ptr type = typeFromName(QmlJS::getQMLAttributeValue(node->members, QStringLiteral("type")).value); { DUChainWriteLocker lock; ClassMemberDeclaration* decl = openDeclaration(name, range); decl->setAbstractType(type); } openType(type); } void DeclarationBuilder::declareParameter(QmlJS::AST::UiObjectInitializer* node, const RangeInRevision &range, const QualifiedIdentifier &name) { QmlJS::FunctionType::Ptr function = currentType(); AbstractType::Ptr type = typeFromName(QmlJS::getQMLAttributeValue(node->members, QStringLiteral("type")).value); Q_ASSERT(function); function->addArgument(type); { DUChainWriteLocker lock; openDeclaration(name, range); } openType(type); } void DeclarationBuilder::declareEnum(const RangeInRevision &range, const QualifiedIdentifier &name) { EnumerationType::Ptr type(new EnumerationType); { DUChainWriteLocker lock; ClassMemberDeclaration* decl = openDeclaration(name, range); decl->setKind(Declaration::Type); decl->setType(type); // The type needs to be set here because closeContext is called before closeAndAssignType and needs to know the type of decl type->setDataType(IntegralType::TypeEnumeration); type->setDeclaration(decl); } openType(type); } void DeclarationBuilder::declareComponentSubclass(QmlJS::AST::UiObjectInitializer* node, const KDevelop::RangeInRevision& range, const QString& baseclass, QmlJS::AST::UiQualifiedId* qualifiedId) { QualifiedIdentifier name( QmlJS::getQMLAttributeValue(node->members, QStringLiteral("name")).value.section('/', -1, -1) ); DUContext::ContextType contextType = DUContext::Class; if (baseclass == QLatin1String("Component")) { // QML component, equivalent to a QML class declareComponent(node, range, name); } else if (baseclass == QLatin1String("Method") || baseclass == QLatin1String("Signal") || baseclass == QLatin1String("Slot")) { // Method (that can also be a signal or a slot) declareMethod(node, range, name, baseclass == QLatin1String("Slot"), baseclass == QLatin1String("Signal")); contextType = DUContext::Function; } else if (baseclass == QLatin1String("Property")) { // A property declareProperty(node, range, name); } else if (baseclass == QLatin1String("Parameter") && currentType()) { // One parameter of a signal/slot/method declareParameter(node, range, name); } else if (baseclass == QLatin1String("Enum")) { // Enumeration. The "values" key contains a dictionary of name -> number entries. declareEnum(range, name); contextType = DUContext::Enum; name = QualifiedIdentifier(); // Enum contexts should have no name so that their members have the correct scope } else { // Define an anonymous subclass of the baseclass. This subclass will // be instantiated when "id:" is encountered name = QualifiedIdentifier(); // Use ExpressionVisitor to find the declaration of the base class DeclarationPointer baseClass = findType(qualifiedId).declaration; StructureType::Ptr type(new StructureType); { DUChainWriteLocker lock; ClassDeclaration* decl = openDeclaration( currentContext()->type() == DUContext::Global ? QualifiedIdentifier(m_session->moduleName()) : name, QmlJS::emptyRangeOnLine(node->lbraceToken) ); decl->clearBaseClasses(); decl->setKind(Declaration::Type); decl->setType(type); // The class needs to know its type early because it contains definitions that depend on that type type->setDeclaration(decl); if (baseClass) { addBaseClass(decl, baseClass->indexedType()); } } openType(type); } // Open a context of the proper type and identifier openContext( node, m_session->locationsToInnerRange(node->lbraceToken, node->rbraceToken), contextType, name ); DUContext* ctx = currentContext(); Declaration* decl = currentDeclaration(); { // Set the inner context of the current declaration, because nested classes // need to know the inner context of their parents DUChainWriteLocker lock; decl->setInternalContext(ctx); if (contextType == DUContext::Enum) { ctx->setPropagateDeclarations(true); } } // If we have have declared a class, import the context of its base classes registerBaseClasses(); } void DeclarationBuilder::declareComponentInstance(QmlJS::AST::ExpressionStatement* expression) { if (!expression) { return; } auto identifier = QmlJS::AST::cast(expression->expression); if (!identifier) { return; } { DUChainWriteLocker lock; injectContext(topContext()); Declaration* decl = openDeclaration( QualifiedIdentifier(identifier->name.toString()), m_session->locationToRange(identifier->identifierToken) ); closeInjectedContext(); // Put the declaration in the global scope decl->setKind(Declaration::Instance); decl->setType(currentAbstractType()); } closeDeclaration(); } DeclarationBuilder::ExportLiteralsAndNames DeclarationBuilder::exportedNames(QmlJS::AST::ExpressionStatement* exports) { ExportLiteralsAndNames res; if (!exports) { return res; } auto exportslist = QmlJS::AST::cast(exports->expression); if (!exportslist) { return res; } // Explore all the exported symbols for this component and keep only those // having a version compatible with the one of this module QSet knownNames; for (auto it = exportslist->elements; it && it->expression; it = it->next) { auto stringliteral = QmlJS::AST::cast(it->expression); if (!stringliteral) { continue; } // String literal like "Namespace/Class version". QStringList nameAndVersion = stringliteral->value.toString().section('/', -1, -1).split(' '); QString name = nameAndVersion.at(0); - QString version = (nameAndVersion.count() > 1 ? nameAndVersion.at(1) : QLatin1String("1.0")); + QString version = (nameAndVersion.count() > 1 ? nameAndVersion.at(1) : QStringLiteral("1.0")); if (!knownNames.contains(name)) { knownNames.insert(name); res.append(qMakePair(stringliteral, name)); } } return res; } void DeclarationBuilder::declareExports(const ExportLiteralsAndNames& exports, ClassDeclaration* classdecl) { DUChainWriteLocker lock; // Create the exported versions of the component for (auto exp : exports) { QmlJS::AST::StringLiteral* literal = exp.first; QString name = exp.second; StructureType::Ptr type(new StructureType); injectContext(currentContext()->parentContext()); // Don't declare the export in its C++-ish component, but in the scope above ClassDeclaration* decl = openDeclaration( QualifiedIdentifier(name), m_session->locationToRange(literal->literalToken) ); closeInjectedContext(); // The exported version inherits from the C++ component decl->setKind(Declaration::Type); decl->setClassType(ClassDeclarationData::Class); decl->clearBaseClasses(); type->setDeclaration(decl); addBaseClass(decl, classdecl->indexedType()); // Open a context for the exported class, and register its base class in it decl->setInternalContext(openContext( literal, DUContext::Class, QualifiedIdentifier(name) )); registerBaseClasses(); closeContext(); openType(type); closeAndAssignType(); } } /* * UI */ void DeclarationBuilder::importDirectory(const QString& directory, QmlJS::AST::UiImport* node) { DUChainWriteLocker lock; QString currentFilePath = currentContext()->topContext()->url().str(); QFileInfo dir(directory); QFileInfoList entries; if (dir.isDir()) { // Import all the files in the given directory entries = QDir(directory).entryInfoList( QStringList() << (QLatin1String("*.") + currentFilePath.section(QLatin1Char('.'), -1, -1)) - << QLatin1String("*.qmltypes") - << QLatin1String("*.so"), + << QStringLiteral("*.qmltypes") + << QStringLiteral("*.so"), QDir::Files ); } else if (dir.isFile()) { // Import the specific file given in the import statement entries.append(dir); } else if (!m_prebuilding) { m_session->addProblem(node, i18n("Module not found, some types or properties may not be recognized")); return; } // Translate the QFileInfos into QStrings (and replace .so files with // qmlplugindump dumps) lock.unlock(); QStringList filePaths = QmlJS::Cache::instance().getFileNames(entries); lock.lock(); if (node && !node->importId.isEmpty()) { // Open a namespace that will contain the declarations QualifiedIdentifier identifier(node->importId.toString()); RangeInRevision range = m_session->locationToRange(node->importIdToken); Declaration* decl = openDeclaration(identifier, range); decl->setKind(Declaration::Namespace); decl->setInternalContext(openContext(node, range, DUContext::Class, identifier)); } for (const QString& filePath : filePaths) { if (filePath == currentFilePath) { continue; } ReferencedTopDUContext context = m_session->contextOfFile(filePath); if (context) { currentContext()->addImportedParentContext(context.data()); } } if (node && !node->importId.isEmpty()) { // Close the namespace containing the declarations closeContext(); closeDeclaration(); } } void DeclarationBuilder::importModule(QmlJS::AST::UiImport* node) { QmlJS::AST::UiQualifiedId *part = node->importUri; QString uri; while (part) { if (!uri.isEmpty()) { uri.append('.'); } uri.append(part->name.toString()); part = part->next; } // Version of the import QString version = m_session->symbolAt(node->versionToken); // Import the directory containing the module QString modulePath = QmlJS::Cache::instance().modulePath(m_session->url(), uri, version); importDirectory(modulePath, node); } bool DeclarationBuilder::visit(QmlJS::AST::UiImport* node) { if (node->importUri) { importModule(node); } else if (!node->fileName.isNull() && node->fileName != QLatin1String(".")) { QUrl currentFileUrl = currentContext()->topContext()->url().toUrl(); QUrl importUrl = QUrl(node->fileName.toString()); importDirectory(currentFileUrl.resolved(importUrl).toLocalFile(), node); } return DeclarationBuilderBase::visit(node); } bool DeclarationBuilder::visit(QmlJS::AST::UiObjectDefinition* node) { setComment(node); // Do not crash if the user has typed an empty object definition if (!node->initializer || !node->initializer->members) { m_skipEndVisit.push(true); return DeclarationBuilderBase::visit(node); } RangeInRevision range(m_session->locationToRange(node->qualifiedTypeNameId->identifierToken)); QString baseclass = node->qualifiedTypeNameId->name.toString(); // "Component" needs special care: a component that appears only in a future // version of this module, or that already appeared in a former version, must // be skipped because it is useless ExportLiteralsAndNames exports; if (baseclass == QLatin1String("Component")) { - QmlJS::AST::Statement* statement = QmlJS::getQMLAttribute(node->initializer->members, QLatin1String("exports")); + QmlJS::AST::Statement* statement = QmlJS::getQMLAttribute(node->initializer->members, QStringLiteral("exports")); exports = exportedNames(QmlJS::AST::cast(statement)); if (statement && exports.count() == 0) { // This component has an "exports:" member but no export matched // the version of this module. Skip the component m_skipEndVisit.push(true); return false; } } else if (baseclass == QLatin1String("Module")) { // "Module" is disabled. This allows the declarations of a module // dump to appear in the same namespace as the .qml files in the same // directory. m_skipEndVisit.push(true); return true; } // Declare the component subclass declareComponentSubclass(node->initializer, range, baseclass, node->qualifiedTypeNameId); // If we had a component with exported names, declare these exports if (baseclass == QLatin1String("Component")) { ClassDeclaration* classDecl = currentDeclaration(); if (classDecl) { declareExports(exports, classDecl); } } m_skipEndVisit.push(false); return DeclarationBuilderBase::visit(node); } void DeclarationBuilder::endVisit(QmlJS::AST::UiObjectDefinition* node) { DeclarationBuilderBase::endVisit(node); // Do not crash if the user has typed an empty object definition if (!m_skipEndVisit.pop()) { closeContext(); closeAndAssignType(); } } bool DeclarationBuilder::visit(QmlJS::AST::UiScriptBinding* node) { setComment(node); if (!node->qualifiedId) { return DeclarationBuilderBase::visit(node); } // Special-case some binding names QString bindingName = node->qualifiedId->name.toString(); if (bindingName == QLatin1String("id")) { // Instantiate a QML component: its type is the current type (the anonymous // QML class that surrounds the declaration) declareComponentInstance(QmlJS::AST::cast(node->statement)); } // Use ExpressionVisitor to find the signal/property bound DeclarationPointer bindingDecl = findType(node->qualifiedId).declaration; DUChainPointer signal; // If a Javascript block is used as expression or if the script binding is a // slot, open a subcontext so that variables declared in the binding are kept // local, and the signal parameters can be visible to the slot if (( bindingDecl && (signal = bindingDecl.dynamicCast()) && signal->isSignal() ) || node->statement->kind == QmlJS::AST::Node::Kind_Block) { openContext( node->statement, m_session->locationsToInnerRange( node->statement->firstSourceLocation(), node->statement->lastSourceLocation() ), DUContext::Other ); // If this script binding is a slot, import the parameters of its signal if (signal && signal->isSignal() && signal->internalContext()) { DUChainWriteLocker lock; currentContext()->addIndirectImport(DUContext::Import( signal->internalContext(), nullptr )); } } else { // Check that the type of the value matches the type of the property AbstractType::Ptr expressionType = findType(node->statement).type; DUChainReadLocker lock; if (!m_prebuilding && bindingDecl && !areTypesEqual(bindingDecl->abstractType(), expressionType)) { m_session->addProblem(node->qualifiedId, i18n( "Mismatch between the value type (%1) and the property type (%2)", expressionType->toString(), bindingDecl->abstractType()->toString() ), IProblem::Error); } } return DeclarationBuilderBase::visit(node); } void DeclarationBuilder::endVisit(QmlJS::AST::UiScriptBinding* node) { QmlJS::AST::Visitor::endVisit(node); // If visit(UiScriptBinding) has opened a context, close it if (currentContext()->type() == DUContext::Other) { closeContext(); } } bool DeclarationBuilder::visit(QmlJS::AST::UiObjectBinding* node) { setComment(node); if (!node->qualifiedId || !node->qualifiedTypeNameId || !node->initializer) { return DeclarationBuilderBase::visit(node); } // Declare the component subclass. "Behavior on ... {}" is treated exactly // like "Behavior {}". RangeInRevision range = m_session->locationToRange(node->qualifiedTypeNameId->identifierToken); QString baseclass = node->qualifiedTypeNameId->name.toString(); declareComponentSubclass(node->initializer, range, baseclass, node->qualifiedTypeNameId); return DeclarationBuilderBase::visit(node); } void DeclarationBuilder::endVisit(QmlJS::AST::UiObjectBinding* node) { DeclarationBuilderBase::endVisit(node); if (node->qualifiedId && node->qualifiedTypeNameId && node->initializer) { closeContext(); closeAndAssignType(); } } bool DeclarationBuilder::visit(QmlJS::AST::UiPublicMember* node) { setComment(node); RangeInRevision range = m_session->locationToRange(node->identifierToken); QualifiedIdentifier id(node->name.toString()); QString typeName = node->memberType.toString(); bool res = DeclarationBuilderBase::visit(node); // Build the type of the public member if (node->type == QmlJS::AST::UiPublicMember::Signal) { // Open a function declaration corresponding to this signal declareFunction( node, false, QualifiedIdentifier(node->name.toString()), m_session->locationToRange(node->identifierToken), node->parameters, m_session->locationToRange(node->identifierToken), // The AST does not provide the location of the parens nullptr, m_session->locationToRange(node->identifierToken) // A body range must be provided ); // This declaration is a signal and its return type is void { DUChainWriteLocker lock; currentDeclaration()->setIsSignal(true); currentType()->setReturnType(typeFromName(QStringLiteral("void"))); } } else { AbstractType::Ptr type; if (typeName == QLatin1String("alias")) { // Property aliases take the type of their aliased property type = findType(node->statement).type; res = false; // findType has already explored node->statement } else { type = typeFromName(typeName); if (node->typeModifier == QLatin1String("list")) { // QML list, noted "list" in the source file ArrayType::Ptr array(new ArrayType); array->setElementType(type); type = array.cast(); } } { DUChainWriteLocker lock; Declaration* decl = openDeclaration(id, range); decl->setInSymbolTable(false); } openType(type); } return res; } void DeclarationBuilder::endVisit(QmlJS::AST::UiPublicMember* node) { DeclarationBuilderBase::endVisit(node); closeAndAssignType(); } /* * Utils */ void DeclarationBuilder::setComment(QmlJS::AST::Node* node) { setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8()); } void DeclarationBuilder::closeAndAssignType() { closeType(); Declaration* dec = currentDeclaration(); Q_ASSERT(dec); if (auto type = lastType()) { DUChainWriteLocker lock; dec->setType(type); } closeDeclaration(); } AbstractType::Ptr DeclarationBuilder::typeFromName(const QString& name) { auto type = IntegralType::TypeNone; QString realName = name; // Built-in types if (name == QLatin1String("string")) { type = IntegralType::TypeString; } else if (name == QLatin1String("bool")) { type = IntegralType::TypeBoolean; } else if (name == QLatin1String("int")) { type = IntegralType::TypeInt; } else if (name == QLatin1String("float")) { type = IntegralType::TypeFloat; } else if (name == QLatin1String("double") || name == QLatin1String("real")) { type = IntegralType::TypeDouble; } else if (name == QLatin1String("void")) { type = IntegralType::TypeVoid; } else if (name == QLatin1String("var") || name == QLatin1String("variant")) { type = IntegralType::TypeMixed; } else if (m_session->language() == QmlJS::Dialect::Qml) { // In QML files, some Qt type names need to be renamed to the QML equivalent if (name == QLatin1String("QFont")) { realName = QStringLiteral("Font"); } else if (name == QLatin1String("QColor")) { realName = QStringLiteral("color"); } else if (name == QLatin1String("QDateTime")) { realName = QStringLiteral("date"); } else if (name == QLatin1String("QDate")) { realName = QStringLiteral("date"); } else if (name == QLatin1String("QTime")) { realName = QStringLiteral("time"); } else if (name == QLatin1String("QRect") || name == QLatin1String("QRectF")) { realName = QStringLiteral("rect"); } else if (name == QLatin1String("QPoint") || name == QLatin1String("QPointF")) { realName = QStringLiteral("point"); } else if (name == QLatin1String("QSize") || name == QLatin1String("QSizeF")) { realName = QStringLiteral("size"); } else if (name == QLatin1String("QUrl")) { realName = QStringLiteral("url"); } else if (name == QLatin1String("QVector3D")) { realName = QStringLiteral("vector3d"); } else if (name.endsWith(QLatin1String("ScriptString"))) { // Q{Declarative,Qml}ScriptString represents a JS snippet auto func = new QmlJS::FunctionType; func->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); return AbstractType::Ptr(func); } } if (type == IntegralType::TypeNone) { // Not a built-in type, but a class return typeFromClassName(realName); } else { return AbstractType::Ptr(new IntegralType(type)); } } AbstractType::Ptr DeclarationBuilder::typeFromClassName(const QString& name) { DeclarationPointer decl = QmlJS::getDeclaration(QualifiedIdentifier(name), currentContext()); if (!decl) { if (name == QLatin1String("QRegExp")) { decl = QmlJS::NodeJS::instance().moduleMember(QStringLiteral("__builtin_ecmascript"), QStringLiteral("RegExp"), currentContext()->url()); } } if (decl) { return decl->abstractType(); } else { DelayedType::Ptr type(new DelayedType); type->setKind(DelayedType::Unresolved); type->setIdentifier(IndexedTypeIdentifier(name)); return type; } } void DeclarationBuilder::addBaseClass(ClassDeclaration* classDecl, const QString& name) { addBaseClass(classDecl, IndexedType(typeFromClassName(name))); } void DeclarationBuilder::addBaseClass(ClassDeclaration* classDecl, const IndexedType& type) { BaseClassInstance baseClass; baseClass.access = Declaration::Public; baseClass.virtualInheritance = false; baseClass.baseClass = type; classDecl->addBaseClass(baseClass); } void DeclarationBuilder::registerBaseClasses() { ClassDeclaration* classdecl = currentDeclaration(); DUContext *ctx = currentContext(); if (classdecl) { DUChainWriteLocker lock; for (uint i=0; ibaseClassesSize(); ++i) { const BaseClassInstance &baseClass = classdecl->baseClasses()[i]; StructureType::Ptr baseType = StructureType::Ptr::dynamicCast(baseClass.baseClass.abstractType()); TopDUContext* topctx = topContext(); if (baseType && baseType->declaration(topctx)) { QmlJS::importDeclarationInContext(ctx, DeclarationPointer(baseType->declaration(topctx))); } } } } static bool enumContainsEnumerator(const AbstractType::Ptr& a, const AbstractType::Ptr& b) { Q_ASSERT(a->whichType() == AbstractType::TypeEnumeration); auto aEnum = EnumerationType::Ptr::staticCast(a); Q_ASSERT(b->whichType() == AbstractType::TypeEnumerator); auto bEnumerator = EnumeratorType::Ptr::staticCast(b); return bEnumerator->qualifiedIdentifier().beginsWith(aEnum->qualifiedIdentifier()); } static bool isNumeric(const IntegralType::Ptr& type) { return type->dataType() == IntegralType::TypeInt || type->dataType() == IntegralType::TypeIntegral || type->dataType() == IntegralType::TypeFloat || type->dataType() == IntegralType::TypeDouble; } bool DeclarationBuilder::areTypesEqual(const AbstractType::Ptr& a, const AbstractType::Ptr& b) { if (!a || !b) { return true; } if (a->whichType() == AbstractType::TypeUnsure || b->whichType() == AbstractType::TypeUnsure) { // Don't try to guess something if one of the types is unsure return true; } const auto bIntegral = IntegralType::Ptr::dynamicCast(b); if (bIntegral && (bIntegral->dataType() == IntegralType::TypeString || bIntegral->dataType() == IntegralType::TypeMixed)) { // In QML/JS, a string can be converted to nearly everything else, similarly ignore mixed types return true; } const auto aIntegral = IntegralType::Ptr::dynamicCast(a); if (aIntegral && (aIntegral->dataType() == IntegralType::TypeString || aIntegral->dataType() == IntegralType::TypeMixed)) { // In QML/JS, nearly everything can be to a string, similarly ignore mixed types return true; } if (aIntegral && bIntegral) { if (isNumeric(aIntegral) && isNumeric(bIntegral)) { // Casts between integral types is possible return true; } } if (a->whichType() == AbstractType::TypeEnumeration && b->whichType() == AbstractType::TypeEnumerator) { return enumContainsEnumerator(a, b); } else if (a->whichType() == AbstractType::TypeEnumerator && b->whichType() == AbstractType::TypeEnumeration) { return enumContainsEnumerator(b, a); } { auto aId = dynamic_cast(a.constData()); auto bId = dynamic_cast(b.constData()); if (aId && bId && aId->qualifiedIdentifier() == bId->qualifiedIdentifier()) return true; } { auto aStruct = StructureType::Ptr::dynamicCast(a); auto bStruct = StructureType::Ptr::dynamicCast(b); if (aStruct && bStruct) { auto top = currentContext()->topContext(); auto aDecl = dynamic_cast(aStruct->declaration(top)); auto bDecl = dynamic_cast(bStruct->declaration(top)); if (aDecl && bDecl) { if (aDecl->isPublicBaseClass(bDecl, top) || bDecl->isPublicBaseClass(aDecl, top)) { return true; } } } } return a->equals(b.constData()); } diff --git a/languages/qmljs/kdevqmljsplugin.cpp b/languages/qmljs/kdevqmljsplugin.cpp index 9e50b688d1..cdc349f0c8 100644 --- a/languages/qmljs/kdevqmljsplugin.cpp +++ b/languages/qmljs/kdevqmljsplugin.cpp @@ -1,204 +1,204 @@ /************************************************************************************* * Copyright (C) 2012 by Aleix Pol * * Copyright (C) 2012 by Milian Wolff * * Copyright (C) 2013 by Sven Brauch * * * * 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 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 "kdevqmljsplugin.h" #include "qmljsparsejob.h" #include "qmljshighlighting.h" #include "codecompletion/model.h" #include "navigation/propertypreviewwidget.h" #include "duchain/helper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KDevQmlJsSupportFactory, "kdevqmljs.json", registerPlugin(); ) using namespace KDevelop; /// TODO: Extend? See qmljsmodelmanager.h in qt-creator.git class ModelManager: public QmlJS::ModelManagerInterface { Q_OBJECT public: explicit ModelManager(QObject *parent = nullptr); ~ModelManager() override; }; ModelManager::ModelManager(QObject* parent) : QmlJS::ModelManagerInterface(parent) {} ModelManager::~ModelManager() {} KDevQmlJsPlugin::KDevQmlJsPlugin(QObject* parent, const QVariantList& ) -: IPlugin(QLatin1String("kdevqmljssupport"), parent ) +: IPlugin(QStringLiteral("kdevqmljssupport"), parent ) , ILanguageSupport() , m_highlighting(new QmlJsHighlighting(this)) , m_refactoring(new BasicRefactoring(this)) , m_modelManager(new ModelManager(this)) { QmlJS::registerDUChainItems(); CodeCompletionModel* codeCompletion = new QmlJS::CodeCompletionModel(this); new KDevelop::CodeCompletion(this, codeCompletion, name()); auto assistantsManager = core()->languageController()->staticAssistantsManager(); assistantsManager->registerAssistant(StaticAssistant::Ptr(new RenameAssistant(this))); } KDevQmlJsPlugin::~KDevQmlJsPlugin() { parseLock()->lockForWrite(); // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state parseLock()->unlock(); QmlJS::unregisterDUChainItems(); } ParseJob* KDevQmlJsPlugin::createParseJob(const IndexedString& url) { return new QmlJsParseJob(url, this); } QString KDevQmlJsPlugin::name() const { return QStringLiteral("qml/js"); } ICodeHighlighting* KDevQmlJsPlugin::codeHighlighting() const { return m_highlighting; } BasicRefactoring* KDevQmlJsPlugin::refactoring() const { return m_refactoring; } ContextMenuExtension KDevQmlJsPlugin::contextMenuExtension(Context* context) { ContextMenuExtension cm; EditorContext *ec = dynamic_cast(context); if (ec && ICore::self()->languageController()->languagesForUrl(ec->url()).contains(this)) { // It's a QML/JS file, let's add our context menu. m_refactoring->fillContextMenu(cm, context); } return cm; } const QString textFromDoc(const IDocument* doc, const KTextEditor::Range& range) { return doc->textDocument()->line(range.start().line()).mid(range.start().column(), range.end().column()-range.start().column()); }; // Finds how many spaces the given string has at one end. // direction=+1 -> left end of the string, -1 for right end. int spacesAtCorner(const QString& string, int direction = +1) { int spaces = 0; QString::const_iterator it; for ( it = direction == 1 ? string.begin() : string.end()-1 ; it != string.end(); it += direction ) { if ( ! it->isSpace() ) break; spaces += 1; } return spaces; } // Take the given QML line and check if it's a line of the form foo.bar: value. // Return ranges for the key and the value. const QPair parseProperty(const QString& line, const KTextEditor::Cursor& position) { QStringList items = line.split(';'); QString matchingItem; int col_offset = -1; // This is to also support FooAnimation { foo: bar; baz: bang; duration: 200 } // or similar foreach ( const QString& item, items ) { col_offset += item.size() + 1; if ( position.column() < col_offset ) { matchingItem = item; break; } } QStringList split = matchingItem.split(':'); if ( split.size() != 2 ) { // The expression is not of the form foo:bar, thus invalid. return qMakePair(KTextEditor::Range::invalid(), KTextEditor::Range::invalid()); } QString key = split.at(0); QString value = split.at(1); // For animations or similar, remove the trailing '}' if there's no semicolon after the last entry if ( value.trimmed().endsWith('}') ) { col_offset -= value.size() - value.lastIndexOf('}') + 1; value = value.left(value.lastIndexOf('}')-1); } return qMakePair( KTextEditor::Range( KTextEditor::Cursor(position.line(), col_offset - value.size() - key.size() + spacesAtCorner(key, +1) - 1), KTextEditor::Cursor(position.line(), col_offset - value.size() - 1 + spacesAtCorner(key, -1)) ), KTextEditor::Range( KTextEditor::Cursor(position.line(), col_offset - value.size() + spacesAtCorner(value, +1)), KTextEditor::Cursor(position.line(), col_offset + spacesAtCorner(value, -1)) )); }; QWidget* KDevQmlJsPlugin::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) { IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if ( doc && doc->textDocument() ) { // Check for a QML property, and construct a property preview widget // if the property key is listed in the supported properties. QPair property = parseProperty(doc->textDocument()->line(position.line()), position); if ( property.first.isValid() && property.second.isValid() ) { Declaration* decl = DUChainUtils::itemUnderCursor(url, property.first.start()).declaration; return PropertyPreviewWidget::constructIfPossible( doc->textDocument(), property.first, property.second, decl, textFromDoc(doc, property.first), textFromDoc(doc, property.second) ); } } // Otherwise, display no special "navigation" widget. return KDevelop::ILanguageSupport::specialLanguageObjectNavigationWidget(url, position); } #include "kdevqmljsplugin.moc" diff --git a/languages/qmljs/libs/languageutils/fakemetaobject.cpp b/languages/qmljs/libs/languageutils/fakemetaobject.cpp index 2c4f669e3a..20e944bf8e 100644 --- a/languages/qmljs/libs/languageutils/fakemetaobject.cpp +++ b/languages/qmljs/libs/languageutils/fakemetaobject.cpp @@ -1,601 +1,601 @@ /**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms and ** conditions see http://www.qt.io/terms-conditions. For further information ** use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "fakemetaobject.h" #include using namespace LanguageUtils; FakeMetaEnum::FakeMetaEnum() {} FakeMetaEnum::FakeMetaEnum(const QString &name) : m_name(name) {} bool FakeMetaEnum::isValid() const { return !m_name.isEmpty(); } QString FakeMetaEnum::name() const { return m_name; } void FakeMetaEnum::setName(const QString &name) { m_name = name; } void FakeMetaEnum::addKey(const QString &key, int value) { m_keys.append(key); m_values.append(value); } QString FakeMetaEnum::key(int index) const { return m_keys.at(index); } int FakeMetaEnum::keyCount() const { return m_keys.size(); } QStringList FakeMetaEnum::keys() const { return m_keys; } bool FakeMetaEnum::hasKey(const QString &key) const { return m_keys.contains(key); } void FakeMetaEnum::addToHash(QCryptographicHash &hash) const { int len = m_name.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(m_name.constData()), len * sizeof(QChar)); len = m_keys.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); foreach (const QString &key, m_keys) { len = key.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(key.constData()), len * sizeof(QChar)); } len = m_values.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); foreach (int value, m_values) hash.addData(reinterpret_cast(&value), sizeof(value)); } QString FakeMetaEnum::describe(int baseIndent) const { QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent); - QString res = QLatin1String("Enum "); + QString res = QStringLiteral("Enum "); res += name(); res += QLatin1String(":{"); for (int i = 0; i < keyCount(); ++i) { res += newLine; res += QLatin1String(" "); res += key(i); res += QLatin1String(": "); res += QString::number(m_values.value(i, -1)); } res += newLine; res += QLatin1Char('}'); return res; } QString FakeMetaEnum::toString() const { return describe(); } FakeMetaMethod::FakeMetaMethod(const QString &name, const QString &returnType) : m_name(name) , m_returnType(returnType) , m_methodTy(FakeMetaMethod::Method) , m_methodAccess(FakeMetaMethod::Public) , m_revision(0) {} FakeMetaMethod::FakeMetaMethod() : m_methodTy(FakeMetaMethod::Method) , m_methodAccess(FakeMetaMethod::Public) , m_revision(0) {} QString FakeMetaMethod::methodName() const { return m_name; } void FakeMetaMethod::setMethodName(const QString &name) { m_name = name; } void FakeMetaMethod::setReturnType(const QString &type) { m_returnType = type; } QStringList FakeMetaMethod::parameterNames() const { return m_paramNames; } QStringList FakeMetaMethod::parameterTypes() const { return m_paramTypes; } void FakeMetaMethod::addParameter(const QString &name, const QString &type) { m_paramNames.append(name); m_paramTypes.append(type); } int FakeMetaMethod::methodType() const { return m_methodTy; } void FakeMetaMethod::setMethodType(int methodType) { m_methodTy = methodType; } int FakeMetaMethod::access() const { return m_methodAccess; } int FakeMetaMethod::revision() const { return m_revision; } void FakeMetaMethod::setRevision(int r) { m_revision = r; } void FakeMetaMethod::addToHash(QCryptographicHash &hash) const { int len = m_name.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(m_name.constData()), len * sizeof(QChar)); hash.addData(reinterpret_cast(&m_methodAccess), sizeof(m_methodAccess)); hash.addData(reinterpret_cast(&m_methodTy), sizeof(m_methodTy)); hash.addData(reinterpret_cast(&m_revision), sizeof(m_revision)); len = m_paramNames.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); foreach (const QString &pName, m_paramNames) { len = pName.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(pName.constData()), len * sizeof(QChar)); } len = m_paramTypes.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); foreach (const QString &pType, m_paramTypes) { len = pType.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(pType.constData()), len * sizeof(QChar)); } len = m_returnType.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(m_returnType.constData()), len * sizeof(QChar)); } QString FakeMetaMethod::describe(int baseIndent) const { QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent); - QString res = QLatin1String("Method {"); + QString res = QStringLiteral("Method {"); res += newLine; res += QLatin1String(" methodName:"); res += methodName(); res += newLine; res += QLatin1String(" methodType:"); res += methodType(); res += newLine; res += QLatin1String(" parameterNames:["); foreach (const QString &pName, parameterNames()) { res += newLine; res += QLatin1String(" "); res += pName; } res += QLatin1Char(']'); res += newLine; res += QLatin1String(" parameterTypes:["); foreach (const QString &pType, parameterTypes()) { res += newLine; res += QLatin1String(" "); res += pType; } res += QLatin1Char(']'); res += newLine; res += QLatin1Char('}'); return res; } QString FakeMetaMethod::toString() const { return describe(); } FakeMetaProperty::FakeMetaProperty(const QString &name, const QString &type, bool isList, bool isWritable, bool isPointer, int revision) : m_propertyName(name) , m_type(type) , m_isList(isList) , m_isWritable(isWritable) , m_isPointer(isPointer) , m_revision(revision) {} QString FakeMetaProperty::name() const { return m_propertyName; } QString FakeMetaProperty::typeName() const { return m_type; } bool FakeMetaProperty::isList() const { return m_isList; } bool FakeMetaProperty::isWritable() const { return m_isWritable; } bool FakeMetaProperty::isPointer() const { return m_isPointer; } int FakeMetaProperty::revision() const { return m_revision; } void FakeMetaProperty::addToHash(QCryptographicHash &hash) const { int len = m_propertyName.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(m_propertyName.constData()), len * sizeof(QChar)); hash.addData(reinterpret_cast(&m_revision), sizeof(m_revision)); int flags = (m_isList ? (1 << 0) : 0) + (m_isPointer ? (1 << 1) : 0) + (m_isWritable ? (1 << 2) : 0); hash.addData(reinterpret_cast(&flags), sizeof(flags)); len = m_type.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(m_type.constData()), len * sizeof(QChar)); } QString FakeMetaProperty::describe(int baseIndent) const { QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent); - QString res = QLatin1String("Property {"); + QString res = QStringLiteral("Property {"); res += newLine; res += QLatin1String(" name:"); res += name(); res += newLine; res += QLatin1String(" typeName:"); res += typeName(); res += newLine; res += QLatin1String(" typeName:"); res += QString::number(revision()); res += newLine; res += QLatin1String(" isList:"); res += isList(); res += newLine; res += QLatin1String(" isPointer:"); res += isPointer(); res += newLine; res += QLatin1String(" isWritable:"); res += isWritable(); res += newLine; res += QLatin1Char('}'); return res; } QString FakeMetaProperty::toString() const { return describe(); } FakeMetaObject::FakeMetaObject() : m_isSingleton(false), m_isCreatable(true), m_isComposite(false) { } QString FakeMetaObject::className() const { return m_className; } void FakeMetaObject::setClassName(const QString &name) { m_className = name; } void FakeMetaObject::addExport(const QString &name, const QString &package, const ComponentVersion& version) { Export exp; exp.type = name; exp.package = package; exp.version = version; m_exports.append(exp); } void FakeMetaObject::setExportMetaObjectRevision(int exportIndex, int metaObjectRevision) { m_exports[exportIndex].metaObjectRevision = metaObjectRevision; } QList FakeMetaObject::exports() const { return m_exports; } FakeMetaObject::Export FakeMetaObject::exportInPackage(const QString &package) const { foreach (const Export &exp, m_exports) { if (exp.package == package) return exp; } return Export(); } void FakeMetaObject::setSuperclassName(const QString &superclass) { m_superName = superclass; } QString FakeMetaObject::superclassName() const { return m_superName; } void FakeMetaObject::addEnum(const FakeMetaEnum &fakeEnum) { m_enumNameToIndex.insert(fakeEnum.name(), m_enums.size()); m_enums.append(fakeEnum); } int FakeMetaObject::enumeratorCount() const { return m_enums.size(); } int FakeMetaObject::enumeratorOffset() const { return 0; } FakeMetaEnum FakeMetaObject::enumerator(int index) const { return m_enums.at(index); } int FakeMetaObject::enumeratorIndex(const QString &name) const { return m_enumNameToIndex.value(name, -1); } void FakeMetaObject::addProperty(const FakeMetaProperty &property) { m_propNameToIdx.insert(property.name(), m_props.size()); m_props.append(property); } int FakeMetaObject::propertyCount() const { return m_props.size(); } int FakeMetaObject::propertyOffset() const { return 0; } FakeMetaProperty FakeMetaObject::property(int index) const { return m_props.at(index); } int FakeMetaObject::propertyIndex(const QString &name) const { return m_propNameToIdx.value(name, -1); } void FakeMetaObject::addMethod(const FakeMetaMethod &method) { m_methods.append(method); } int FakeMetaObject::methodCount() const { return m_methods.size(); } int FakeMetaObject::methodOffset() const { return 0; } FakeMetaMethod FakeMetaObject::method(int index) const { return m_methods.at(index); } int FakeMetaObject::methodIndex(const QString &name) const //If performances becomes an issue, just use a nameToIdx hash { for (int i=0; i(&len), sizeof(len)); hash.addData(reinterpret_cast(m_className.constData()), len * sizeof(QChar)); len = m_attachedTypeName.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(m_attachedTypeName.constData()), len * sizeof(QChar)); len = m_defaultPropertyName.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(m_defaultPropertyName.constData()), len * sizeof(QChar)); len = m_enumNameToIndex.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); { QStringList keys(m_enumNameToIndex.keys()); keys.sort(); foreach (const QString &key, keys) { len = key.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(key.constData()), len * sizeof(QChar)); int value = m_enumNameToIndex.value(key); hash.addData(reinterpret_cast(&value), sizeof(value)); // avoid? this adds order dependency to fingerprint... m_enums.at(value).addToHash(hash); } } len = m_exports.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); foreach (const Export &e, m_exports) e.addToHash(hash); // normalize order? len = m_exports.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); foreach (const FakeMetaMethod &m, m_methods) m.addToHash(hash); // normalize order? { QStringList keys(m_propNameToIdx.keys()); keys.sort(); foreach (const QString &key, keys) { len = key.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(key.constData()), len * sizeof(QChar)); int value = m_propNameToIdx.value(key); hash.addData(reinterpret_cast(&value), sizeof(value)); // avoid? this adds order dependency to fingerprint... m_props.at(value).addToHash(hash); } } len = m_superName.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(m_superName.constData()), len * sizeof(QChar)); QByteArray res = hash.result(); res.append('F'); return res; } void FakeMetaObject::updateFingerprint() { m_fingerprint = calculateFingerprint(); } QByteArray FakeMetaObject::fingerprint() const { return m_fingerprint; } bool FakeMetaObject::isSingleton() const { return m_isSingleton; } bool FakeMetaObject::isCreatable() const { return m_isCreatable; } bool FakeMetaObject::isComposite() const { return m_isComposite; } void FakeMetaObject::setIsSingleton(bool value) { m_isSingleton = value; } void FakeMetaObject::setIsCreatable(bool value) { m_isCreatable = value; } void FakeMetaObject::setIsComposite(bool value) { m_isSingleton = value; } QString FakeMetaObject::toString() const { return describe(); } QString FakeMetaObject::describe(bool printDetails, int baseIndent) const { QString res = QString::fromLatin1("FakeMetaObject@%1") .arg((quintptr)(void *)this, 0, 16); if (!printDetails) return res; QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent); res += QLatin1Char('{'); res += newLine; res += QLatin1String("className:"); res += className(); res += newLine; res += QLatin1String("superClassName:"); res += superclassName(); res += newLine; res += QLatin1String("isSingleton:"); res += isSingleton(); res += newLine; res += QLatin1String("isCreatable:"); res += isCreatable(); res += newLine; res += QLatin1String("isComposite:"); res += isComposite(); res += newLine; res += QLatin1String("defaultPropertyName:"); res += defaultPropertyName(); res += newLine; res += QLatin1String("attachedTypeName:"); res += attachedTypeName(); res += newLine; res += QLatin1String("fingerprint:"); res += QString::fromUtf8(fingerprint()); res += newLine; res += QLatin1String("exports:["); foreach (const Export &e, exports()) { res += newLine; res += QLatin1String(" "); res += e.describe(baseIndent + 2); } res += QLatin1Char(']'); res += newLine; res += QLatin1String("enums:["); for (int iEnum = 0; iEnum < enumeratorCount() ; ++ iEnum) { FakeMetaEnum e = enumerator(enumeratorOffset() + iEnum); res += newLine; res += QLatin1String(" "); res += e.describe(baseIndent + 2); } res += QLatin1Char(']'); res += newLine; res += QLatin1String("properties:["); for (int iProp = 0; iProp < propertyCount() ; ++ iProp) { FakeMetaProperty prop = property(propertyOffset() + iProp); res += newLine; res += QLatin1String(" "); res += prop.describe(baseIndent + 2); } res += QLatin1Char(']'); res += QLatin1String("methods:["); for (int iMethod = 0; iMethod < methodOffset() ; ++ iMethod) { FakeMetaMethod m = method(methodOffset() + iMethod); res += newLine; res += QLatin1String(" "); m.describe(baseIndent + 2); } res += QLatin1Char(']'); res += newLine; res += QLatin1Char('}'); return res; } FakeMetaObject::Export::Export() : metaObjectRevision(0) {} bool FakeMetaObject::Export::isValid() const { return version.isValid() || !package.isEmpty() || !type.isEmpty(); } void FakeMetaObject::Export::addToHash(QCryptographicHash &hash) const { int len = package.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(package.constData()), len * sizeof(QChar)); len = type.size(); hash.addData(reinterpret_cast(&len), sizeof(len)); hash.addData(reinterpret_cast(type.constData()), len * sizeof(QChar)); version.addToHash(hash); hash.addData(reinterpret_cast(&metaObjectRevision), sizeof(metaObjectRevision)); } QString FakeMetaObject::Export::describe(int baseIndent) const { QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent); - QString res = QLatin1String("Export {"); + QString res = QStringLiteral("Export {"); res += newLine; res += QLatin1String(" package:"); res += package; res += newLine; res += QLatin1String(" type:"); res += type; res += newLine; res += QLatin1String(" version:"); res += version.toString(); res += newLine; res += QLatin1String(" metaObjectRevision:"); res += QString::number(metaObjectRevision); res += newLine; res += QLatin1String(" isValid:"); res += QString::number(isValid()); res += newLine; res += QLatin1Char('}'); return res; } QString FakeMetaObject::Export::toString() const { return describe(); } diff --git a/languages/qmljs/libs/utils/environment.cpp b/languages/qmljs/libs/utils/environment.cpp index c691f68e8d..45a67b91b9 100644 --- a/languages/qmljs/libs/utils/environment.cpp +++ b/languages/qmljs/libs/utils/environment.cpp @@ -1,517 +1,517 @@ /**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms and ** conditions see http://www.qt.io/terms-conditions. For further information ** use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "environment.h" #include #include #include #include class SystemEnvironment : public Utils::Environment { public: SystemEnvironment() : Environment(QProcessEnvironment::systemEnvironment().toStringList()) { if (Utils::HostOsInfo::isLinuxHost()) { - QString ldLibraryPath = value(QLatin1String("LD_LIBRARY_PATH")); + QString ldLibraryPath = value(QStringLiteral("LD_LIBRARY_PATH")); QDir lib(QCoreApplication::applicationDirPath()); - lib.cd(QLatin1String("../lib")); + lib.cd(QStringLiteral("../lib")); QString toReplace = lib.path(); - lib.cd(QLatin1String("qtcreator")); + lib.cd(QStringLiteral("qtcreator")); toReplace.append(QLatin1Char(':')); toReplace.append(lib.path()); if (ldLibraryPath.startsWith(toReplace)) - set(QLatin1String("LD_LIBRARY_PATH"), ldLibraryPath.remove(0, toReplace.length())); + set(QStringLiteral("LD_LIBRARY_PATH"), ldLibraryPath.remove(0, toReplace.length())); } } }; Q_GLOBAL_STATIC(SystemEnvironment, staticSystemEnvironment) namespace Utils { static bool sortEnvironmentItem(const EnvironmentItem &a, const EnvironmentItem &b) { return a.name < b.name; } void EnvironmentItem::sort(QList *list) { std::sort(list->begin(), list->end(), &sortEnvironmentItem); } QList EnvironmentItem::fromStringList(const QStringList &list) { QList result; foreach (const QString &string, list) { int pos = string.indexOf(QLatin1Char('='), 1); if (pos == -1) { EnvironmentItem item(string, QString()); item.unset = true; result.append(item); } else { EnvironmentItem item(string.left(pos), string.mid(pos+1)); result.append(item); } } return result; } QStringList EnvironmentItem::toStringList(const QList &list) { QStringList result; foreach (const EnvironmentItem &item, list) { if (item.unset) result << QString(item.name); else result << QString(item.name + QLatin1Char('=') + item.value); } return result; } Environment::Environment(const QStringList &env, OsType osType) : m_osType(osType) { foreach (const QString &s, env) { int i = s.indexOf(QLatin1Char('='), 1); if (i >= 0) { if (m_osType == OsTypeWindows) m_values.insert(s.left(i).toUpper(), s.mid(i+1)); else m_values.insert(s.left(i), s.mid(i+1)); } } } QStringList Environment::toStringList() const { QStringList result; const QMap::const_iterator end = m_values.constEnd(); for (QMap::const_iterator it = m_values.constBegin(); it != end; ++it) { QString entry = it.key(); entry += QLatin1Char('='); entry += it.value(); result.push_back(entry); } return result; } QProcessEnvironment Environment::toProcessEnvironment() const { QProcessEnvironment result; const QMap::const_iterator end = m_values.constEnd(); for (QMap::const_iterator it = m_values.constBegin(); it != end; ++it) result.insert(it.key(), it.value()); return result; } void Environment::set(const QString &key, const QString &value) { m_values.insert(m_osType == OsTypeWindows ? key.toUpper() : key, value); } void Environment::unset(const QString &key) { m_values.remove(m_osType == OsTypeWindows ? key.toUpper() : key); } void Environment::appendOrSet(const QString &key, const QString &value, const QString &sep) { const QString &_key = m_osType == OsTypeWindows ? key.toUpper() : key; QMap::iterator it = m_values.find(_key); if (it == m_values.end()) { m_values.insert(_key, value); } else { // Append unless it is already there const QString toAppend = sep + value; if (!it.value().endsWith(toAppend)) it.value().append(toAppend); } } void Environment::prependOrSet(const QString&key, const QString &value, const QString &sep) { const QString &_key = m_osType == OsTypeWindows ? key.toUpper() : key; QMap::iterator it = m_values.find(_key); if (it == m_values.end()) { m_values.insert(_key, value); } else { // Prepend unless it is already there const QString toPrepend = value + sep; if (!it.value().startsWith(toPrepend)) it.value().prepend(toPrepend); } } void Environment::appendOrSetPath(const QString &value) { - appendOrSet(QLatin1String("PATH"), QDir::toNativeSeparators(value), + appendOrSet(QStringLiteral("PATH"), QDir::toNativeSeparators(value), QString(OsSpecificAspects(m_osType).pathListSeparator())); } void Environment::prependOrSetPath(const QString &value) { - prependOrSet(QLatin1String("PATH"), QDir::toNativeSeparators(value), + prependOrSet(QStringLiteral("PATH"), QDir::toNativeSeparators(value), QString(OsSpecificAspects(m_osType).pathListSeparator())); } void Environment::prependOrSetLibrarySearchPath(const QString &value) { switch (m_osType) { case OsTypeWindows: { const QChar sep = QLatin1Char(';'); const QLatin1String path("PATH"); prependOrSet(path, QDir::toNativeSeparators(value), QString(sep)); break; } case OsTypeMac: { - const QString sep = QLatin1String(":"); + const QString sep = QStringLiteral(":"); const QString nativeValue = QDir::toNativeSeparators(value); - prependOrSet(QLatin1String("DYLD_LIBRARY_PATH"), nativeValue, sep); - prependOrSet(QLatin1String("DYLD_FRAMEWORK_PATH"), nativeValue, sep); + prependOrSet(QStringLiteral("DYLD_LIBRARY_PATH"), nativeValue, sep); + prependOrSet(QStringLiteral("DYLD_FRAMEWORK_PATH"), nativeValue, sep); break; } case OsTypeLinux: case OsTypeOtherUnix: { const QChar sep = QLatin1Char(':'); const QLatin1String path("LD_LIBRARY_PATH"); prependOrSet(path, QDir::toNativeSeparators(value), QString(sep)); break; } default: break; } } Environment Environment::systemEnvironment() { return *staticSystemEnvironment(); } void Environment::clear() { m_values.clear(); } FileName Environment::searchInDirectory(const QStringList &execs, QString directory) const { const QChar slash = QLatin1Char('/'); if (directory.isEmpty()) return FileName(); // Avoid turing / into // on windows which triggers windows to check // for network drives! if (!directory.endsWith(slash)) directory += slash; foreach (const QString &exec, execs) { QFileInfo fi(directory + exec); if (fi.exists() && fi.isFile() && fi.isExecutable()) return FileName::fromString(fi.absoluteFilePath()); } return FileName(); } QStringList Environment::appendExeExtensions(const QString &executable) const { QFileInfo fi(executable); QStringList execs(executable); if (m_osType == OsTypeWindows) { // Check all the executable extensions on windows: // PATHEXT is only used if the executable has no extension if (fi.suffix().isEmpty()) { - QStringList extensions = value(QLatin1String("PATHEXT")).split(QLatin1Char(';')); + QStringList extensions = value(QStringLiteral("PATHEXT")).split(QLatin1Char(';')); foreach (const QString &ext, extensions) execs << executable + ext.toLower(); } } return execs; } FileName Environment::searchInPath(const QString &executable, const QStringList &additionalDirs) const { if (executable.isEmpty()) return FileName(); QString exec = QDir::cleanPath(expandVariables(executable)); QFileInfo fi(exec); QStringList execs = appendExeExtensions(exec); if (fi.isAbsolute()) { foreach (const QString &path, execs) if (QFile::exists(path)) return FileName::fromString(path); return FileName::fromString(exec); } QSet alreadyChecked; foreach (const QString &dir, additionalDirs) { if (alreadyChecked.contains(dir)) continue; alreadyChecked.insert(dir); FileName tmp = searchInDirectory(execs, dir); if (!tmp.isEmpty()) return tmp; } if (executable.indexOf(QLatin1Char('/')) != -1) return FileName(); foreach (const QString &p, path()) { if (alreadyChecked.contains(p)) continue; alreadyChecked.insert(p); FileName tmp = searchInDirectory(execs, QDir::fromNativeSeparators(p)); if (!tmp.isEmpty()) return tmp; } return FileName(); } QStringList Environment::path() const { - return m_values.value(QLatin1String("PATH")) + return m_values.value(QStringLiteral("PATH")) .split(OsSpecificAspects(m_osType).pathListSeparator(), QString::SkipEmptyParts); } QString Environment::value(const QString &key) const { return m_values.value(key); } QString Environment::key(Environment::const_iterator it) const { return it.key(); } QString Environment::value(Environment::const_iterator it) const { return it.value(); } Environment::const_iterator Environment::constBegin() const { return m_values.constBegin(); } Environment::const_iterator Environment::constEnd() const { return m_values.constEnd(); } Environment::const_iterator Environment::constFind(const QString &name) const { QMap::const_iterator it = m_values.constFind(name); if (it == m_values.constEnd()) return constEnd(); else return it; } int Environment::size() const { return m_values.size(); } void Environment::modify(const QList & list) { Environment resultEnvironment = *this; foreach (const EnvironmentItem &item, list) { if (item.unset) { resultEnvironment.unset(item.name); } else { // TODO use variable expansion QString value = item.value; for (int i=0; i < value.size(); ++i) { if (value.at(i) == QLatin1Char('$')) { if ((i + 1) < value.size()) { const QChar &c = value.at(i+1); int end = -1; if (c == QLatin1Char('(')) end = value.indexOf(QLatin1Char(')'), i); else if (c == QLatin1Char('{')) end = value.indexOf(QLatin1Char('}'), i); if (end != -1) { const QString &name = value.mid(i+2, end-i-2); Environment::const_iterator it = constFind(name); if (it != constEnd()) value.replace(i, end-i+1, it.value()); } } } } resultEnvironment.set(item.name, value); } } *this = resultEnvironment; } QList Environment::diff(const Environment &other) const { QMap::const_iterator thisIt = constBegin(); QMap::const_iterator otherIt = other.constBegin(); QList result; while (thisIt != constEnd() || otherIt != other.constEnd()) { if (thisIt == constEnd()) { result.append(EnvironmentItem(otherIt.key(), otherIt.value())); ++otherIt; } else if (otherIt == other.constEnd()) { EnvironmentItem item(thisIt.key(), QString()); item.unset = true; result.append(item); ++thisIt; } else if (thisIt.key() < otherIt.key()) { EnvironmentItem item(thisIt.key(), QString()); item.unset = true; result.append(item); ++thisIt; } else if (thisIt.key() > otherIt.key()) { result.append(EnvironmentItem(otherIt.key(), otherIt.value())); ++otherIt; } else { if (thisIt.value() != otherIt.value()) result.append(EnvironmentItem(otherIt.key(), otherIt.value())); ++otherIt; ++thisIt; } } return result; } bool Environment::hasKey(const QString &key) const { return m_values.contains(key); } QString Environment::userName() const { return value(QLatin1String(m_osType == OsTypeWindows ? "USERNAME" : "USER")); } bool Environment::operator!=(const Environment &other) const { return !(*this == other); } bool Environment::operator==(const Environment &other) const { return m_osType == other.m_osType && m_values == other.m_values; } /** Expand environment variables in a string. * * Environment variables are accepted in the following forms: * $SOMEVAR, ${SOMEVAR} on Unix and %SOMEVAR% on Windows. * No escapes and quoting are supported. * If a variable is not found, it is not substituted. */ QString Environment::expandVariables(const QString &input) const { QString result = input; if (m_osType == OsTypeWindows) { for (int vStart = -1, i = 0; i < result.length(); ) { if (result.at(i++) == QLatin1Char('%')) { if (vStart > 0) { const_iterator it = m_values.constFind(result.mid(vStart, i - vStart - 1).toUpper()); if (it != m_values.constEnd()) { result.replace(vStart - 1, i - vStart + 1, *it); i = vStart - 1 + it->length(); vStart = -1; } else { vStart = i; } } else { vStart = i; } } } } else { enum { BASE, OPTIONALVARIABLEBRACE, VARIABLE, BRACEDVARIABLE } state = BASE; int vStart = -1; for (int i = 0; i < result.length();) { QChar c = result.at(i++); if (state == BASE) { if (c == QLatin1Char('$')) state = OPTIONALVARIABLEBRACE; } else if (state == OPTIONALVARIABLEBRACE) { if (c == QLatin1Char('{')) { state = BRACEDVARIABLE; vStart = i; } else if (c.isLetterOrNumber() || c == QLatin1Char('_')) { state = VARIABLE; vStart = i - 1; } else { state = BASE; } } else if (state == BRACEDVARIABLE) { if (c == QLatin1Char('}')) { const_iterator it = m_values.constFind(result.mid(vStart, i - 1 - vStart)); if (it != constEnd()) { result.replace(vStart - 2, i - vStart + 2, *it); i = vStart - 2 + it->length(); } state = BASE; } } else if (state == VARIABLE) { if (!c.isLetterOrNumber() && c != QLatin1Char('_')) { const_iterator it = m_values.constFind(result.mid(vStart, i - vStart - 1)); if (it != constEnd()) { result.replace(vStart - 1, i - vStart, *it); i = vStart - 1 + it->length(); } state = BASE; } } } if (state == VARIABLE) { const_iterator it = m_values.constFind(result.mid(vStart)); if (it != constEnd()) result.replace(vStart - 1, result.length() - vStart + 1, *it); } } return result; } QStringList Environment::expandVariables(const QStringList &variables) const { QStringList results; foreach (const QString &i, variables) results << expandVariables(i); return results; } } // namespace Utils diff --git a/languages/qmljs/libs/utils/fileutils.cpp b/languages/qmljs/libs/utils/fileutils.cpp index 2443059eaa..5f6ffa023c 100644 --- a/languages/qmljs/libs/utils/fileutils.cpp +++ b/languages/qmljs/libs/utils/fileutils.cpp @@ -1,793 +1,793 @@ /**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms and ** conditions see http://www.qt.io/terms-conditions. For further information ** use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "fileutils.h" #include "savefile.h" #include "algorithm.h" #include "hostosinfo.h" #include "qtcassert.h" #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #include #endif #ifdef Q_OS_OSX #include "fileutils_mac.h" #endif QT_BEGIN_NAMESPACE QDebug operator<<(QDebug dbg, const Utils::FileName &c) { return dbg << c.toString(); } QT_END_NAMESPACE namespace Utils { /*! \class Utils::FileUtils \brief The FileUtils class contains file and directory related convenience functions. */ /*! Removes the directory \a filePath and its subdirectories recursively. \note The \a error parameter is optional. Returns whether the operation succeeded. */ bool FileUtils::removeRecursively(const FileName &filePath, QString *error) { QFileInfo fileInfo = filePath.toFileInfo(); if (!fileInfo.exists() && !fileInfo.isSymLink()) return true; QFile::setPermissions(filePath.toString(), fileInfo.permissions() | QFile::WriteUser); if (fileInfo.isDir()) { QDir dir(filePath.toString()); dir = dir.canonicalPath(); if (dir.isRoot()) { if (error) { *error = QCoreApplication::translate("Utils::FileUtils", "Refusing to remove root directory."); } return false; } if (dir.path() == QDir::home().canonicalPath()) { if (error) { *error = QCoreApplication::translate("Utils::FileUtils", "Refusing to remove your home directory."); } return false; } QStringList fileNames = dir.entryList(QDir::Files | QDir::Hidden | QDir::System | QDir::Dirs | QDir::NoDotAndDotDot); foreach (const QString &fileName, fileNames) { if (!removeRecursively(FileName(filePath).appendPath(fileName), error)) return false; } if (!QDir::root().rmdir(dir.path())) { if (error) { *error = QCoreApplication::translate("Utils::FileUtils", "Failed to remove directory \"%1\".") .arg(filePath.toUserOutput()); } return false; } } else { if (!QFile::remove(filePath.toString())) { if (error) { *error = QCoreApplication::translate("Utils::FileUtils", "Failed to remove file \"%1\".") .arg(filePath.toUserOutput()); } return false; } } return true; } /*! Copies the directory specified by \a srcFilePath recursively to \a tgtFilePath. \a tgtFilePath will contain the target directory, which will be created. Example usage: \code QString error; book ok = Utils::FileUtils::copyRecursively("/foo/bar", "/foo/baz", &error); if (!ok) qDebug() << error; \endcode This will copy the contents of /foo/bar into to the baz directory under /foo, which will be created in the process. \note The \a error parameter is optional. Returns whether the operation succeeded. */ bool FileUtils::copyRecursively(const FileName &srcFilePath, const FileName &tgtFilePath, QString *error, const std::function ©Helper) { QFileInfo srcFileInfo = srcFilePath.toFileInfo(); if (srcFileInfo.isDir()) { if (!tgtFilePath.exists()) { QDir targetDir(tgtFilePath.toString()); targetDir.cdUp(); if (!targetDir.mkdir(tgtFilePath.fileName())) { if (error) { *error = QCoreApplication::translate("Utils::FileUtils", "Failed to create directory \"%1\".") .arg(tgtFilePath.toUserOutput()); } return false; } } QDir sourceDir(srcFilePath.toString()); QStringList fileNames = sourceDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); foreach (const QString &fileName, fileNames) { FileName newSrcFilePath = srcFilePath; newSrcFilePath.appendPath(fileName); FileName newTgtFilePath = tgtFilePath; newTgtFilePath.appendPath(fileName); if (!copyRecursively(newSrcFilePath, newTgtFilePath, error, copyHelper)) return false; } } else { if (copyHelper) { if (!copyHelper(srcFileInfo, tgtFilePath.toFileInfo(), error)) return false; } else { if (!QFile::copy(srcFilePath.toString(), tgtFilePath.toString())) { if (error) { *error = QCoreApplication::translate("Utils::FileUtils", "Could not copy file \"%1\" to \"%2\".") .arg(srcFilePath.toUserOutput(), tgtFilePath.toUserOutput()); } return false; } } } return true; } /*! If \a filePath is a directory, the function will recursively check all files and return true if one of them is newer than \a timeStamp. If \a filePath is a single file, true will be returned if the file is newer than \a timeStamp. Returns whether at least one file in \a filePath has a newer date than \a timeStamp. */ bool FileUtils::isFileNewerThan(const FileName &filePath, const QDateTime &timeStamp) { QFileInfo fileInfo = filePath.toFileInfo(); if (!fileInfo.exists() || fileInfo.lastModified() >= timeStamp) return true; if (fileInfo.isDir()) { const QStringList dirContents = QDir(filePath.toString()) .entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); foreach (const QString &curFileName, dirContents) { if (isFileNewerThan(FileName(filePath).appendPath(curFileName), timeStamp)) return true; } } return false; } /*! Recursively resolves possibly present symlinks in \a filePath. Unlike QFileInfo::canonicalFilePath(), this function will still return the expected target file even if the symlink is dangling. \note Maximum recursion depth == 16. Returns the symlink target file path. */ FileName FileUtils::resolveSymlinks(const FileName &path) { QFileInfo f = path.toFileInfo(); int links = 16; while (links-- && f.isSymLink()) f.setFile(f.symLinkTarget()); if (links <= 0) return FileName(); return FileName::fromString(f.filePath()); } /*! Like QDir::toNativeSeparators(), but use prefix '~' instead of $HOME on unix systems when an absolute path is given. Returns the possibly shortened path with native separators. */ QString FileUtils::shortNativePath(const FileName &path) { if (HostOsInfo::isAnyUnixHost()) { const FileName home = FileName::fromString(QDir::cleanPath(QDir::homePath())); if (path.isChildOf(home)) { return QLatin1Char('~') + QDir::separator() + QDir::toNativeSeparators(path.relativeChildPath(home).toString()); } } return path.toUserOutput(); } QString FileUtils::fileSystemFriendlyName(const QString &name) { QString result = name; - result.replace(QRegExp(QLatin1String("\\W")), QLatin1String("_")); - result.replace(QRegExp(QLatin1String("_+")), QLatin1String("_")); // compact _ + result.replace(QRegExp(QLatin1String("\\W")), QStringLiteral("_")); + result.replace(QRegExp(QLatin1String("_+")), QStringLiteral("_")); // compact _ result.remove(QRegExp(QLatin1String("^_*"))); // remove leading _ result.remove(QRegExp(QLatin1String("_+$"))); // remove trailing _ if (result.isEmpty()) - result = QLatin1String("unknown"); + result = QStringLiteral("unknown"); return result; } int FileUtils::indexOfQmakeUnfriendly(const QString &name, int startpos) { static QRegExp checkRegExp(QLatin1String("[^a-zA-Z0-9_.-]")); return checkRegExp.indexIn(name, startpos); } QString FileUtils::qmakeFriendlyName(const QString &name) { QString result = name; // Remove characters that might trip up a build system (especially qmake): int pos = indexOfQmakeUnfriendly(result); while (pos >= 0) { result[pos] = QLatin1Char('_'); pos = indexOfQmakeUnfriendly(result, pos); } return fileSystemFriendlyName(result); } bool FileUtils::makeWritable(const FileName &path) { const QString fileName = path.toString(); return QFile::setPermissions(fileName, QFile::permissions(fileName) | QFile::WriteUser); } // makes sure that capitalization of directories is canonical on Windows and OS X. // This mimics the logic in QDeclarative_isFileCaseCorrect QString FileUtils::normalizePathName(const QString &name) { #ifdef Q_OS_WIN const QString nativeSeparatorName(QDir::toNativeSeparators(name)); const LPCTSTR nameC = reinterpret_cast(nativeSeparatorName.utf16()); // MinGW PIDLIST_ABSOLUTE file; HRESULT hr = SHParseDisplayName(nameC, NULL, &file, 0, NULL); if (FAILED(hr)) return name; TCHAR buffer[MAX_PATH]; if (!SHGetPathFromIDList(file, buffer)) return name; return QDir::fromNativeSeparators(QString::fromUtf16(reinterpret_cast(buffer))); #elif defined(Q_OS_OSX) return Internal::normalizePathName(name); #else // do not try to handle case-insensitive file systems on Linux return name; #endif } bool FileUtils::isRelativePath(const QString &path) { if (path.startsWith(QLatin1Char('/'))) return false; if (HostOsInfo::isWindowsHost()) { if (path.startsWith(QLatin1Char('\\'))) return false; // Unlike QFileInfo, this won't accept a relative path with a drive letter. // Such paths result in a royal mess anyway ... if (path.length() >= 3 && path.at(1) == QLatin1Char(':') && path.at(0).isLetter() && (path.at(2) == QLatin1Char('/') || path.at(2) == QLatin1Char('\\'))) return false; } return true; } QString FileUtils::resolvePath(const QString &baseDir, const QString &fileName) { if (fileName.isEmpty()) return QString(); if (isAbsolutePath(fileName)) return QDir::cleanPath(fileName); return QDir::cleanPath(baseDir + QLatin1Char('/') + fileName); } QByteArray FileReader::fetchQrc(const QString &fileName) { QTC_ASSERT(fileName.startsWith(QLatin1Char(':')), return QByteArray()); QFile file(fileName); bool ok = file.open(QIODevice::ReadOnly); QTC_ASSERT(ok, qWarning() << fileName << "not there!"; return QByteArray()); return file.readAll(); } bool FileReader::fetch(const QString &fileName, QIODevice::OpenMode mode) { QTC_ASSERT(!(mode & ~(QIODevice::ReadOnly | QIODevice::Text)), return false); QFile file(fileName); if (!file.open(QIODevice::ReadOnly | mode)) { m_errorString = tr("Cannot open %1 for reading: %2").arg( QDir::toNativeSeparators(fileName), file.errorString()); return false; } m_data = file.readAll(); if (file.error() != QFile::NoError) { m_errorString = tr("Cannot read %1: %2").arg( QDir::toNativeSeparators(fileName), file.errorString()); return false; } return true; } bool FileReader::fetch(const QString &fileName, QIODevice::OpenMode mode, QString *errorString) { if (fetch(fileName, mode)) return true; if (errorString) *errorString = m_errorString; return false; } bool FileReader::fetch(const QString &fileName, QIODevice::OpenMode mode, QWidget *parent) { if (fetch(fileName, mode)) return true; if (parent) QMessageBox::critical(parent, tr("File Error"), m_errorString); return false; } FileSaverBase::FileSaverBase() : m_file(nullptr) , m_hasError(false) { } FileSaverBase::~FileSaverBase() { } bool FileSaverBase::finalize() { m_file->close(); setResult(m_file->error() == QFile::NoError); // We delete the object, so it is really closed even if it is a QTemporaryFile. delete m_file; m_file = 0; return !m_hasError; } bool FileSaverBase::finalize(QString *errStr) { if (finalize()) return true; if (errStr) *errStr = errorString(); return false; } bool FileSaverBase::finalize(QWidget *parent) { if (finalize()) return true; QMessageBox::critical(parent, tr("File Error"), errorString()); return false; } bool FileSaverBase::write(const char *data, int len) { if (m_hasError) return false; return setResult(m_file->write(data, len) == len); } bool FileSaverBase::write(const QByteArray &bytes) { if (m_hasError) return false; return setResult(m_file->write(bytes) == bytes.count()); } bool FileSaverBase::setResult(bool ok) { if (!ok && !m_hasError) { m_errorString = tr("Cannot write file %1. Disk full?").arg( QDir::toNativeSeparators(m_fileName)); m_hasError = true; } return ok; } bool FileSaverBase::setResult(QTextStream *stream) { stream->flush(); return setResult(stream->status() == QTextStream::Ok); } bool FileSaverBase::setResult(QDataStream *stream) { return setResult(stream->status() == QDataStream::Ok); } bool FileSaverBase::setResult(QXmlStreamWriter *stream) { return setResult(!stream->hasError()); } FileSaver::FileSaver(const QString &filename, QIODevice::OpenMode mode) { m_fileName = filename; if (mode & (QIODevice::ReadOnly | QIODevice::Append)) { m_file = new QFile(filename); m_isSafe = false; } else { m_file = new SaveFile(filename); m_isSafe = true; } if (!m_file->open(QIODevice::WriteOnly | mode)) { QString err = QFile::exists(filename) ? tr("Cannot overwrite file %1: %2") : tr("Cannot create file %1: %2"); m_errorString = err.arg(QDir::toNativeSeparators(filename), m_file->errorString()); m_hasError = true; } } bool FileSaver::finalize() { if (!m_isSafe) return FileSaverBase::finalize(); SaveFile *sf = static_cast(m_file); if (m_hasError) { if (sf->isOpen()) sf->rollback(); } else { setResult(sf->commit()); } delete sf; m_file = 0; return !m_hasError; } TempFileSaver::TempFileSaver(const QString &templ) : m_autoRemove(true) { QTemporaryFile *tempFile = new QTemporaryFile(); if (!templ.isEmpty()) tempFile->setFileTemplate(templ); tempFile->setAutoRemove(false); if (!tempFile->open()) { m_errorString = tr("Cannot create temporary file in %1: %2").arg( QDir::toNativeSeparators(QFileInfo(tempFile->fileTemplate()).absolutePath()), tempFile->errorString()); m_hasError = true; } m_file = tempFile; m_fileName = tempFile->fileName(); } TempFileSaver::~TempFileSaver() { delete m_file; m_file = 0; if (m_autoRemove) QFile::remove(m_fileName); } /*! \class Utils::FileName \brief The FileName class is a light-weight convenience class for filenames. On windows filenames are compared case insensitively. */ FileName::FileName() : QString() { } /// Constructs a FileName from \a info FileName::FileName(const QFileInfo &info) : QString(info.absoluteFilePath()) { } /// \returns a QFileInfo QFileInfo FileName::toFileInfo() const { return QFileInfo(*this); } /// \returns a QString for passing on to QString based APIs const QString &FileName::toString() const { return *this; } /// \returns a QString to display to the user /// Converts the separators to the native format QString FileName::toUserOutput() const { return QDir::toNativeSeparators(toString()); } QString FileName::fileName(int pathComponents) const { if (pathComponents < 0) return *this; const QChar slash = QLatin1Char('/'); QTC_CHECK(!endsWith(slash)); int i = lastIndexOf(slash); if (pathComponents == 0 || i == -1) return mid(i + 1); int component = i + 1; // skip adjacent slashes while (i > 0 && at(--i) == slash); while (i >= 0 && --pathComponents >= 0) { i = lastIndexOf(slash, i); component = i + 1; while (i > 0 && at(--i) == slash); } // If there are no more slashes before the found one, return the entire string if (i > 0 && lastIndexOf(slash, i) != -1) return mid(component); return *this; } /// \returns a bool indicating whether a file with this /// FileName exists. bool FileName::exists() const { return QFileInfo::exists(*this); } /// Find the parent directory of a given directory. /// Returns an empty FileName if the current directory is already /// a root level directory. /// \returns \a FileName with the last segment removed. FileName FileName::parentDir() const { const QString basePath = toString(); if (basePath.isEmpty()) return FileName(); const QDir base(basePath); if (base.isRoot()) return FileName(); const QString path = basePath + QLatin1String("/.."); const QString parent = QDir::cleanPath(path); return FileName::fromString(parent); } /// Constructs a FileName from \a filename /// \a filename is not checked for validity. FileName FileName::fromString(const QString &filename) { return FileName(filename); } /// Constructs a FileName from \a fileName. The \a defaultExtension is appended /// to \a filename if that does not have an extension already. /// \a fileName is not checked for validity. FileName FileName::fromString(const QString &filename, const QString &defaultExtension) { if (filename.isEmpty() || defaultExtension.isEmpty()) return filename; QString rc = filename; QFileInfo fi(filename); // Add extension unless user specified something else const QChar dot = QLatin1Char('.'); if (!fi.fileName().contains(dot)) { if (!defaultExtension.startsWith(dot)) rc += dot; rc += defaultExtension; } return rc; } /// Constructs a FileName from \a fileName /// \a fileName is not checked for validity. FileName FileName::fromLatin1(const QByteArray &filename) { return FileName(QString::fromLatin1(filename)); } /// Constructs a FileName from \a fileName /// \a fileName is only passed through QDir::cleanPath FileName FileName::fromUserInput(const QString &filename) { QString clean = QDir::cleanPath(filename); if (clean.startsWith(QLatin1String("~/"))) clean = QDir::homePath() + clean.mid(1); return FileName(clean); } /// Constructs a FileName from \a fileName, which is encoded as UTF-8. /// \a fileName is not checked for validity. FileName FileName::fromUtf8(const char *filename, int filenameSize) { return FileName(QString::fromUtf8(filename, filenameSize)); } FileName::FileName(const QString &string) : QString(string) { } bool FileName::operator==(const FileName &other) const { return QString::compare(*this, other, HostOsInfo::fileNameCaseSensitivity()) == 0; } bool FileName::operator!=(const FileName &other) const { return !(*this == other); } bool FileName::operator<(const FileName &other) const { return QString::compare(*this, other, HostOsInfo::fileNameCaseSensitivity()) < 0; } bool FileName::operator<=(const FileName &other) const { return QString::compare(*this, other, HostOsInfo::fileNameCaseSensitivity()) <= 0; } bool FileName::operator>(const FileName &other) const { return other < *this; } bool FileName::operator>=(const FileName &other) const { return other <= *this; } /// \returns whether FileName is a child of \a s bool FileName::isChildOf(const FileName &s) const { if (s.isEmpty()) return false; if (!QString::startsWith(s, HostOsInfo::fileNameCaseSensitivity())) return false; if (size() <= s.size()) return false; // s is root, '/' was already tested in startsWith if (s.QString::endsWith(QLatin1Char('/'))) return true; // s is a directory, next character should be '/' (/tmpdir is NOT a child of /tmp) return at(s.size()) == QLatin1Char('/'); } /// \overload bool FileName::isChildOf(const QDir &dir) const { return isChildOf(FileName::fromString(dir.absolutePath())); } /// \returns whether FileName endsWith \a s bool FileName::endsWith(const QString &s) const { return QString::endsWith(s, HostOsInfo::fileNameCaseSensitivity()); } /// \returns the relativeChildPath of FileName to parent if FileName is a child of parent /// \note returns a empty FileName if FileName is not a child of parent /// That is, this never returns a path starting with "../" FileName FileName::relativeChildPath(const FileName &parent) const { if (!isChildOf(parent)) return FileName(); return FileName(QString::mid(parent.size() + 1, -1)); } /// Appends \a s, ensuring a / between the parts FileName &FileName::appendPath(const QString &s) { if (s.isEmpty()) return *this; if (!isEmpty() && !QString::endsWith(QLatin1Char('/'))) appendString(QLatin1Char('/')); appendString(s); return *this; } FileName &FileName::appendString(const QString &str) { QString::append(str); return *this; } FileName &FileName::appendString(QChar str) { QString::append(str); return *this; } QTextStream &operator<<(QTextStream &s, const FileName &fn) { return s << fn.toString(); } int FileNameList::removeDuplicates() { QSet seen; int removed = 0; for (int i = 0; i < size(); ) { const FileName &fn = at(i); if (seen.contains(fn)) { removeAt(i); ++removed; } else { seen.insert(fn); ++i; } } return removed; } } // namespace Utils QT_BEGIN_NAMESPACE uint qHash(const Utils::FileName &a) { if (Utils::HostOsInfo::fileNameCaseSensitivity() == Qt::CaseInsensitive) return qHash(a.toString().toUpper()); return qHash(a.toString()); } QT_END_NAMESPACE diff --git a/languages/qmljs/libs/utils/json.cpp b/languages/qmljs/libs/utils/json.cpp index 47f1230c44..f06441c4a8 100644 --- a/languages/qmljs/libs/utils/json.cpp +++ b/languages/qmljs/libs/utils/json.cpp @@ -1,752 +1,752 @@ /**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms and ** conditions see http://www.qt.io/terms-conditions. For further information ** use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "json.h" #include #include #include #include #include #include using namespace Utils; JsonMemoryPool::~JsonMemoryPool() { foreach (char *obj, _objs) { reinterpret_cast(obj)->~JsonValue(); delete[] obj; } } JsonValue::JsonValue(Kind kind) : m_kind(kind) {} JsonValue::~JsonValue() {} JsonValue *JsonValue::create(const QString &s, JsonMemoryPool *pool) { const QJsonDocument document = QJsonDocument::fromJson(s.toUtf8()); if (document.isNull()) return 0; return build(document.toVariant(), pool); } void *JsonValue::operator new(size_t size, JsonMemoryPool *pool) { return pool->allocate(size); } void JsonValue::operator delete(void *) { } void JsonValue::operator delete(void *, JsonMemoryPool *) { } QString JsonValue::kindToString(JsonValue::Kind kind) { if (kind == String) - return QLatin1String("string"); + return QStringLiteral("string"); if (kind == Double) - return QLatin1String("number"); + return QStringLiteral("number"); if (kind == Int) - return QLatin1String("integer"); + return QStringLiteral("integer"); if (kind == Object) - return QLatin1String("object"); + return QStringLiteral("object"); if (kind == Array) - return QLatin1String("array"); + return QStringLiteral("array"); if (kind == Boolean) - return QLatin1String("boolean"); + return QStringLiteral("boolean"); if (kind == Null) - return QLatin1String("null"); + return QStringLiteral("null"); - return QLatin1String("unknown"); + return QStringLiteral("unknown"); } JsonValue *JsonValue::build(const QVariant &variant, JsonMemoryPool *pool) { switch (variant.type()) { case QVariant::List: { JsonArrayValue *newValue = new (pool) JsonArrayValue; foreach (const QVariant &element, variant.toList()) newValue->addElement(build(element, pool)); return newValue; } case QVariant::Map: { JsonObjectValue *newValue = new (pool) JsonObjectValue; const QVariantMap variantMap = variant.toMap(); for (QVariantMap::const_iterator it = variantMap.begin(); it != variantMap.end(); ++it) newValue->addMember(it.key(), build(it.value(), pool)); return newValue; } case QVariant::String: return new (pool) JsonStringValue(variant.toString()); case QVariant::Int: return new (pool) JsonIntValue(variant.toInt()); case QVariant::Double: return new (pool) JsonDoubleValue(variant.toDouble()); case QVariant::Bool: return new (pool) JsonBooleanValue(variant.toBool()); case QVariant::Invalid: return new (pool) JsonNullValue; default: break; } return 0; } /////////////////////////////////////////////////////////////////////////////// QString JsonSchema::kType() { return QStringLiteral("type"); } QString JsonSchema::kProperties() { return QStringLiteral("properties"); } QString JsonSchema::kPatternProperties() { return QStringLiteral("patternProperties"); } QString JsonSchema::kAdditionalProperties() { return QStringLiteral("additionalProperties"); } QString JsonSchema::kItems() { return QStringLiteral("items"); } QString JsonSchema::kAdditionalItems() { return QStringLiteral("additionalItems"); } QString JsonSchema::kRequired() { return QStringLiteral("required"); } QString JsonSchema::kDependencies() { return QStringLiteral("dependencies"); } QString JsonSchema::kMinimum() { return QStringLiteral("minimum"); } QString JsonSchema::kMaximum() { return QStringLiteral("maximum"); } QString JsonSchema::kExclusiveMinimum() { return QStringLiteral("exclusiveMinimum"); } QString JsonSchema::kExclusiveMaximum() { return QStringLiteral("exclusiveMaximum"); } QString JsonSchema::kMinItems() { return QStringLiteral("minItems"); } QString JsonSchema::kMaxItems() { return QStringLiteral("maxItems"); } QString JsonSchema::kUniqueItems() { return QStringLiteral("uniqueItems"); } QString JsonSchema::kPattern() { return QStringLiteral("pattern"); } QString JsonSchema::kMinLength() { return QStringLiteral("minLength"); } QString JsonSchema::kMaxLength() { return QStringLiteral("maxLength"); } QString JsonSchema::kTitle() { return QStringLiteral("title"); } QString JsonSchema::kDescription() { return QStringLiteral("description"); } QString JsonSchema::kExtends() { return QStringLiteral("extends"); } QString JsonSchema::kRef() { return QStringLiteral("$ref"); } JsonSchema::JsonSchema(JsonObjectValue *rootObject, const JsonSchemaManager *manager) : m_manager(manager) { enter(rootObject); } bool JsonSchema::isTypeConstrained() const { // Simple types if (JsonStringValue *sv = getStringValue(kType(), currentValue())) return isCheckableType(sv->value()); // Union types if (JsonArrayValue *av = getArrayValue(kType(), currentValue())) { QTC_ASSERT(currentIndex() != -1, return false); QTC_ASSERT(av->elements().at(currentIndex())->kind() == JsonValue::String, return false); JsonStringValue *sv = av->elements().at(currentIndex())->toString(); return isCheckableType(sv->value()); } return false; } bool JsonSchema::acceptsType(const QString &type) const { // Simple types if (JsonStringValue *sv = getStringValue(kType(), currentValue())) return typeMatches(sv->value(), type); // Union types if (JsonArrayValue *av = getArrayValue(kType(), currentValue())) { QTC_ASSERT(currentIndex() != -1, return false); QTC_ASSERT(av->elements().at(currentIndex())->kind() == JsonValue::String, return false); JsonStringValue *sv = av->elements().at(currentIndex())->toString(); return typeMatches(sv->value(), type); } return false; } QStringList JsonSchema::validTypes(JsonObjectValue *v) { QStringList all; if (JsonStringValue *sv = getStringValue(kType(), v)) all.append(sv->value()); if (JsonObjectValue *ov = getObjectValue(kType(), v)) return validTypes(ov); if (JsonArrayValue *av = getArrayValue(kType(), v)) { foreach (JsonValue *v, av->elements()) { if (JsonStringValue *sv = v->toString()) all.append(sv->value()); else if (JsonObjectValue *ov = v->toObject()) all.append(validTypes(ov)); } } return all; } bool JsonSchema::typeMatches(const QString &expected, const QString &actual) { if (expected == QLatin1String("number") && actual == QLatin1String("integer")) return true; return expected == actual; } bool JsonSchema::isCheckableType(const QString &s) { if (s == QLatin1String("string") || s == QLatin1String("number") || s == QLatin1String("integer") || s == QLatin1String("boolean") || s == QLatin1String("object") || s == QLatin1String("array") || s == QLatin1String("null")) { return true; } return false; } QStringList JsonSchema::validTypes() const { return validTypes(currentValue()); } bool JsonSchema::hasTypeSchema() const { return getObjectValue(kType(), currentValue()); } void JsonSchema::enterNestedTypeSchema() { QTC_ASSERT(hasTypeSchema(), return); enter(getObjectValue(kType(), currentValue())); } QStringList JsonSchema::properties(JsonObjectValue *v) const { typedef QHash::ConstIterator MemberConstIterator; QStringList all; if (JsonObjectValue *ov = getObjectValue(kProperties(), v)) { const MemberConstIterator cend = ov->members().constEnd(); for (MemberConstIterator it = ov->members().constBegin(); it != cend; ++it) if (hasPropertySchema(it.key())) all.append(it.key()); } if (JsonObjectValue *base = resolveBase(v)) all.append(properties(base)); return all; } QStringList JsonSchema::properties() const { QTC_ASSERT(acceptsType(JsonValue::kindToString(JsonValue::Object)), return QStringList()); return properties(currentValue()); } JsonObjectValue *JsonSchema::propertySchema(const QString &property, JsonObjectValue *v) const { if (JsonObjectValue *ov = getObjectValue(kProperties(), v)) { JsonValue *member = ov->member(property); if (member && member->kind() == JsonValue::Object) return member->toObject(); } if (JsonObjectValue *base = resolveBase(v)) return propertySchema(property, base); return 0; } bool JsonSchema::hasPropertySchema(const QString &property) const { return propertySchema(property, currentValue()); } void JsonSchema::enterNestedPropertySchema(const QString &property) { QTC_ASSERT(hasPropertySchema(property), return); JsonObjectValue *schema = propertySchema(property, currentValue()); enter(schema); } /*! * An array schema is allowed to have its \e items specification in the form of * another schema * or in the form of an array of schemas [Sec. 5.5]. This functions checks whether this is case * in which the items are a schema. * * Returns whether or not the items from the array are a schema. */ bool JsonSchema::hasItemSchema() const { QTC_ASSERT(acceptsType(JsonValue::kindToString(JsonValue::Array)), return false); return getObjectValue(kItems(), currentValue()); } void JsonSchema::enterNestedItemSchema() { QTC_ASSERT(hasItemSchema(), return); enter(getObjectValue(kItems(), currentValue())); } /*! * An array schema is allowed to have its \e items specification in the form of another schema * or in the form of an array of schemas [Sec. 5.5]. This functions checks whether this is case * in which the items are an array of schemas. * * Returns whether or not the items from the array are a an array of schemas. */ bool JsonSchema::hasItemArraySchema() const { QTC_ASSERT(acceptsType(JsonValue::kindToString(JsonValue::Array)), return false); return getArrayValue(kItems(), currentValue()); } int JsonSchema::itemArraySchemaSize() const { QTC_ASSERT(hasItemArraySchema(), return false); return getArrayValue(kItems(), currentValue())->size(); } /*! * When evaluating the items of an array it might be necessary to \e enter a * particular schema, * since this API assumes that there's always a valid schema in context (the one the user is * interested on). This shall only happen if the item at the supplied array index is of type * object, which is then assumed to be a schema. * * The function also marks the context as being inside an array evaluation. * * Returns whether it was necessary to enter a schema for the supplied * array \a index, false if index is out of bounds. */ bool JsonSchema::maybeEnterNestedArraySchema(int index) { QTC_ASSERT(itemArraySchemaSize(), return false); QTC_ASSERT(index >= 0 && index < itemArraySchemaSize(), return false); JsonValue *v = getArrayValue(kItems(), currentValue())->elements().at(index); return maybeEnter(v, Array, index); } /*! * The type of a schema can be specified in the form of a union type, which is basically an * array of allowed types for the particular instance [Sec. 5.1]. This function checks whether * the current schema is one of such. * * Returns whether or not the current schema specifies a union type. */ bool JsonSchema::hasUnionSchema() const { return getArrayValue(kType(), currentValue()); } int JsonSchema::unionSchemaSize() const { return getArrayValue(kType(), currentValue())->size(); } /*! * When evaluating union types it might be necessary to enter a particular * schema, since this * API assumes that there's always a valid schema in context (the one the user is interested on). * This shall only happen if the item at the supplied union \a index, which is then assumed to be * a schema. * * The function also marks the context as being inside an union evaluation. * * Returns whether or not it was necessary to enter a schema for the * supplied union index. */ bool JsonSchema::maybeEnterNestedUnionSchema(int index) { QTC_ASSERT(unionSchemaSize(), return false); QTC_ASSERT(index >= 0 && index < unionSchemaSize(), return false); JsonValue *v = getArrayValue(kType(), currentValue())->elements().at(index); return maybeEnter(v, Union, index); } void JsonSchema::leaveNestedSchema() { QTC_ASSERT(!m_schemas.isEmpty(), return); leave(); } bool JsonSchema::required() const { if (JsonBooleanValue *bv = getBooleanValue(kRequired(), currentValue())) return bv->value(); return false; } bool JsonSchema::hasMinimum() const { QTC_ASSERT(acceptsType(JsonValue::kindToString(JsonValue::Int)), return false); return getDoubleValue(kMinimum(), currentValue()); } double JsonSchema::minimum() const { QTC_ASSERT(hasMinimum(), return 0); return getDoubleValue(kMinimum(), currentValue())->value(); } bool JsonSchema::hasExclusiveMinimum() { QTC_ASSERT(acceptsType(JsonValue::kindToString(JsonValue::Int)), return false); if (JsonBooleanValue *bv = getBooleanValue(kExclusiveMinimum(), currentValue())) return bv->value(); return false; } bool JsonSchema::hasMaximum() const { QTC_ASSERT(acceptsType(JsonValue::kindToString(JsonValue::Int)), return false); return getDoubleValue(kMaximum(), currentValue()); } double JsonSchema::maximum() const { QTC_ASSERT(hasMaximum(), return 0); return getDoubleValue(kMaximum(), currentValue())->value(); } bool JsonSchema::hasExclusiveMaximum() { QTC_ASSERT(acceptsType(JsonValue::kindToString(JsonValue::Int)), return false); if (JsonBooleanValue *bv = getBooleanValue(kExclusiveMaximum(), currentValue())) return bv->value(); return false; } QString JsonSchema::pattern() const { QTC_ASSERT(acceptsType(JsonValue::kindToString(JsonValue::String)), return QString()); if (JsonStringValue *sv = getStringValue(kPattern(), currentValue())) return sv->value(); return QString(); } int JsonSchema::minimumLength() const { QTC_ASSERT(acceptsType(JsonValue::kindToString(JsonValue::String)), return -1); if (JsonDoubleValue *dv = getDoubleValue(kMinLength(), currentValue())) return dv->value(); return -1; } int JsonSchema::maximumLength() const { QTC_ASSERT(acceptsType(JsonValue::kindToString(JsonValue::String)), return -1); if (JsonDoubleValue *dv = getDoubleValue(kMaxLength(), currentValue())) return dv->value(); return -1; } bool JsonSchema::hasAdditionalItems() const { QTC_ASSERT(acceptsType(JsonValue::kindToString(JsonValue::Array)), return false); return currentValue()->member(kAdditionalItems()); } bool JsonSchema::maybeSchemaName(const QString &s) { if (s.isEmpty() || s == QLatin1String("any")) return false; return !isCheckableType(s); } JsonObjectValue *JsonSchema::rootValue() const { QTC_ASSERT(!m_schemas.isEmpty(), return 0); return m_schemas.first().m_value; } JsonObjectValue *JsonSchema::currentValue() const { QTC_ASSERT(!m_schemas.isEmpty(), return 0); return m_schemas.last().m_value; } int JsonSchema::currentIndex() const { QTC_ASSERT(!m_schemas.isEmpty(), return 0); return m_schemas.last().m_index; } void JsonSchema::evaluate(EvaluationMode eval, int index) { QTC_ASSERT(!m_schemas.isEmpty(), return); m_schemas.last().m_eval = eval; m_schemas.last().m_index = index; } void JsonSchema::enter(JsonObjectValue *ov, EvaluationMode eval, int index) { Context context; context.m_eval = eval; context.m_index = index; context.m_value = resolveReference(ov); m_schemas.push_back(context); } bool JsonSchema::maybeEnter(JsonValue *v, EvaluationMode eval, int index) { evaluate(eval, index); if (v->kind() == JsonValue::Object) { enter(v->toObject()); return true; } if (v->kind() == JsonValue::String) { const QString &s = v->toString()->value(); if (maybeSchemaName(s)) { JsonSchema *schema = m_manager->schemaByName(s); if (schema) { enter(schema->rootValue()); return true; } } } return false; } void JsonSchema::leave() { QTC_ASSERT(!m_schemas.isEmpty(), return); m_schemas.pop_back(); } JsonObjectValue *JsonSchema::resolveReference(JsonObjectValue *ov) const { if (JsonStringValue *sv = getStringValue(kRef(), ov)) { JsonSchema *referenced = m_manager->schemaByName(sv->value()); if (referenced) return referenced->rootValue(); } return ov; } JsonObjectValue *JsonSchema::resolveBase(JsonObjectValue *ov) const { if (JsonValue *v = ov->member(kExtends())) { if (v->kind() == JsonValue::String) { JsonSchema *schema = m_manager->schemaByName(v->toString()->value()); if (schema) return schema->rootValue(); } else if (v->kind() == JsonValue::Object) { return resolveReference(v->toObject()); } } return 0; } JsonStringValue *JsonSchema::getStringValue(const QString &name, JsonObjectValue *value) { JsonValue *v = value->member(name); if (!v) return 0; return v->toString(); } JsonObjectValue *JsonSchema::getObjectValue(const QString &name, JsonObjectValue *value) { JsonValue *v = value->member(name); if (!v) return 0; return v->toObject(); } JsonBooleanValue *JsonSchema::getBooleanValue(const QString &name, JsonObjectValue *value) { JsonValue *v = value->member(name); if (!v) return 0; return v->toBoolean(); } JsonArrayValue *JsonSchema::getArrayValue(const QString &name, JsonObjectValue *value) { JsonValue *v = value->member(name); if (!v) return 0; return v->toArray(); } JsonDoubleValue *JsonSchema::getDoubleValue(const QString &name, JsonObjectValue *value) { JsonValue *v = value->member(name); if (!v) return 0; return v->toDouble(); } /////////////////////////////////////////////////////////////////////////////// JsonSchemaManager::JsonSchemaManager(const QStringList &searchPaths) : m_searchPaths(searchPaths) { foreach (const QString &path, m_searchPaths) { QDir dir(path); if (!dir.exists() && !dir.mkpath(path)) continue; - dir.setNameFilters(QStringList(QLatin1String("*.json"))); + dir.setNameFilters(QStringList(QStringLiteral("*.json"))); foreach (const QFileInfo &fi, dir.entryInfoList()) m_schemas.insert(fi.baseName(), JsonSchemaData(fi.absoluteFilePath())); } } JsonSchemaManager::~JsonSchemaManager() { foreach (const JsonSchemaData &schemaData, m_schemas) delete schemaData.m_schema; } /*! * Tries to find a JSON schema to validate \a fileName against. According * to the specification, how the schema/instance association is done is implementation defined. * Currently we use a quite naive approach which is simply based on file names. Specifically, * if one opens a foo.json file we'll look for a schema named foo.json. We should probably * investigate alternative settings later. * * Returns a valid schema or 0. */ JsonSchema *JsonSchemaManager::schemaForFile(const QString &fileName) const { QString baseName(QFileInfo(fileName).baseName()); return schemaByName(baseName); } JsonSchema *JsonSchemaManager::schemaByName(const QString &baseName) const { QHash::iterator it = m_schemas.find(baseName); if (it == m_schemas.end()) { foreach (const QString &path, m_searchPaths) { QFileInfo candidate(path % baseName % QLatin1String(".json")); if (candidate.exists()) { m_schemas.insert(baseName, candidate.absoluteFilePath()); break; } } } it = m_schemas.find(baseName); if (it == m_schemas.end()) return 0; JsonSchemaData *schemaData = &it.value(); if (!schemaData->m_schema) { // Schemas are built on-demand. QFileInfo currentSchema(schemaData->m_absoluteFileName); Q_ASSERT(currentSchema.exists()); if (schemaData->m_lastParseAttempt.isNull() || schemaData->m_lastParseAttempt < currentSchema.lastModified()) { schemaData->m_schema = parseSchema(currentSchema.absoluteFilePath()); } } return schemaData->m_schema; } JsonSchema *JsonSchemaManager::parseSchema(const QString &schemaFileName) const { FileReader reader; if (reader.fetch(schemaFileName, QIODevice::Text)) { const QString &contents = QString::fromUtf8(reader.data()); JsonValue *json = JsonValue::create(contents, &m_pool); if (json && json->kind() == JsonValue::Object) return new JsonSchema(json->toObject(), this); } return 0; } diff --git a/languages/qmljs/navigation/propertypreviewwidget.cpp b/languages/qmljs/navigation/propertypreviewwidget.cpp index 2640ee9f31..e80630fec2 100644 --- a/languages/qmljs/navigation/propertypreviewwidget.cpp +++ b/languages/qmljs/navigation/propertypreviewwidget.cpp @@ -1,183 +1,183 @@ /************************************************************************************* * Copyright (C) 2013 by Sven Brauch * * * * 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 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 "propertypreviewwidget.h" #include #include #include #include #include #include #include #include #include #include #include // List of supported properties. The string must be the name of the property, // which can contain dots if necessary QHash PropertyPreviewWidget::supportedProperties; QWidget* PropertyPreviewWidget::constructIfPossible(KTextEditor::Document* doc, KTextEditor::Range keyRange, KTextEditor::Range valueRange, Declaration* decl, const QString& key, const QString& value) { #define PROP(key, filename, type, class) \ supportedProperties.insertMulti(key, SupportedProperty(QUrl(base + filename), type, class)); if ( supportedProperties.isEmpty() ) { QString base = QStandardPaths::locate( QStandardPaths::GenericDataLocation, - QLatin1String("kdevqmljssupport/propertywidgets"), + QStringLiteral("kdevqmljssupport/propertywidgets"), QStandardPaths::LocateDirectory ) + '/'; // Positioning PROP("width", "Width.qml", QString(), QString()) PROP("height", "Height.qml", QString(), QString()) PROP("spacing", "Spacing.qml", QString(), QString()) // Margins PROP("margins", "Spacing.qml", QString(), "QQuickAnchors"); // matches anchors.margins and anchors { margins: } PROP("margins", "Spacing.qml", QString(), "QDeclarativeAnchors"); PROP("leftMargin", "Spacing.qml", QString(), "QQuickAnchors"); PROP("leftMargin", "Spacing.qml", QString(), "QDeclarativeAnchors"); PROP("rightMargin", "Spacing.qml", QString(), "QQuickAnchors"); PROP("rightMargin", "Spacing.qml", QString(), "QDeclarativeAnchors"); PROP("topMargin", "Spacing.qml", QString(), "QQuickAnchors"); PROP("topMargin", "Spacing.qml", QString(), "QDeclarativeAnchors"); PROP("bottomMargin", "Spacing.qml", QString(), "QQuickAnchors"); PROP("bottomMargin", "Spacing.qml", QString(), "QDeclarativeAnchors"); // Animations PROP("duration", "Duration.qml", QString(), QString()) // Font QDeclarativeFontValueType, QQuickFontValueType PROP("family", "FontFamily.qml", QString(), "QDeclarativeFontValueType") PROP("family", "FontFamily.qml", QString(), "QQuickFontValueType") PROP("pointSize", "FontSize.qml", QString(), "QDeclarativeFontValueType") PROP("pointSize", "FontSize.qml", QString(), "QQuickFontValueType") // Appearance PROP("opacity", "Opacity.qml", QString(), QString()) // Type-dependent widgets PROP(QString(), "ColorPicker.qml", "color", QString()) } #undef PROP QList properties; properties << supportedProperties.values(key.section(QLatin1Char('.'), -1, -1)); properties << supportedProperties.values(QString()); // Explore each possible supported property and return the first supported widget DUChainReadLocker lock; for (const SupportedProperty& property : properties) { if (!decl || !decl->abstractType() || !decl->context() || !decl->context()->owner()) { continue; } if (!decl->abstractType()->toString().contains(property.typeContains)) { continue; } if (!decl->context()->owner()->toString().contains(property.classContains)) { continue; } return new PropertyPreviewWidget(doc, keyRange, valueRange, property, value); } return nullptr; } void PropertyPreviewWidget::updateValue() { QString newValue = view->rootObject()->property("value").toString(); // set the cursor to the edited range, otherwise the view will jump if we call doc->endEditing() //document->activeView()->setCursorPosition(KTextEditor::Cursor(valueRange.start.line, valueRange.start.column)); if (valueRange.end().column() - valueRange.start().column() == newValue.size()) { document->replaceText(valueRange, newValue); } else { // the length of the text changed so don't replace it but remove the old // and insert the new text. KTextEditor::Document::EditingTransaction transaction(document); document->removeText(valueRange); document->insertText(valueRange.start(), newValue); valueRange.setRange( valueRange.start(), KTextEditor::Cursor(valueRange.start().line(), valueRange.start().column() + newValue.size()) ); } } PropertyPreviewWidget::~PropertyPreviewWidget() { } PropertyPreviewWidget::PropertyPreviewWidget(KTextEditor::Document* doc, KTextEditor::Range keyRange, KTextEditor::Range valueRange, const SupportedProperty& property, const QString& value) : QWidget() , view(new QQuickWidget) , document(doc) , keyRange(keyRange) , valueRange(valueRange) , property(property) { //setup kdeclarative library KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(view->engine()); kdeclarative.setupBindings(); //binds things like kconfig and icons // Configure layout auto l = new QHBoxLayout; l->setContentsMargins(0, 0, 0, 0); setLayout(l); // see docstring for ILanguageSupport::specialLanguageObjectNavigationWidget setProperty("DoNotCloseOnCursorMove", true); view->setSource(property.qmlfile); if (!view->rootObject()) { // don't crash because of a syntax error or missing QML file l->addWidget(new QLabel(i18n("Error loading QML file: %1", property.qmlfile.path()))); delete view; return; } // set the initial value read from the document view->rootObject()->setProperty("initialValue", value); // connect to the slot which has to be emitted from QML when the value changes QObject::connect(view->rootObject(), SIGNAL(valueChanged()), this, SLOT(updateValue())); l->addWidget(view); } diff --git a/projectmanagers/cmake/cmakeserverimportjob.cpp b/projectmanagers/cmake/cmakeserverimportjob.cpp index c7e1023bff..1a4ebf9c93 100644 --- a/projectmanagers/cmake/cmakeserverimportjob.cpp +++ b/projectmanagers/cmake/cmakeserverimportjob.cpp @@ -1,180 +1,180 @@ /* KDevelop CMake Support * * Copyright 2017 Aleix Pol Gonzalez * * 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 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 "cmakeserverimportjob.h" #include "cmakeutils.h" #include "cmakeserver.h" #include #include #include #include #include #include #include "debug.h" template static T kTransform(const Q& list, W func) { T ret; ret.reserve(list.size()); for (auto it = list.constBegin(), itEnd = list.constEnd(); it!=itEnd; ++it) ret += func(*it); return ret; } static QString unescape(const QStringRef& input) { QString output; output.reserve(input.length()); bool isEscaped = false; for (auto it = input.data(), end = it + input.length(); it != end; ++it) { QChar c = *it; if (!isEscaped && c == '\\') { isEscaped = true; } else { output.append(c); isEscaped = false; } } return output; } static QHash processDefines(const QString &compileFlags, const QJsonArray &defines) { QHash ret; const auto& defineRx = MakeFileResolver::defineRegularExpression(); auto it = defineRx.globalMatch(compileFlags); while (it.hasNext()) { const auto match = it.next(); QString value; if (match.lastCapturedIndex() > 1) { value = unescape(match.capturedRef(match.lastCapturedIndex())); } ret[match.captured(1)] = value; } for (const QJsonValue& defineValue: defines) { const QString define = defineValue.toString(); const int eqIdx = define.indexOf(QLatin1Char('=')); if (eqIdx<0) { ret[define] = QString(); } else { ret[define.left(eqIdx)] = define.mid(eqIdx+1); } } return ret; } void CMakeServerImportJob::processFileData(const QJsonObject &response, CMakeProjectData &data) { - const auto configs = response.value(QLatin1String("configurations")).toArray(); + const auto configs = response.value(QStringLiteral("configurations")).toArray(); qCDebug(CMAKE) << "process response" << response; for (const auto &config: configs) { - const auto projects = config.toObject().value(QLatin1String("projects")).toArray(); + const auto projects = config.toObject().value(QStringLiteral("projects")).toArray(); for (const auto &project: projects) { - const auto targets = project.toObject().value(QLatin1String("targets")).toArray(); + const auto targets = project.toObject().value(QStringLiteral("targets")).toArray(); for (const auto &targetObject: targets) { const auto target = targetObject.toObject(); - const KDevelop::Path targetDir(target.value(QLatin1String("sourceDirectory")).toString()); + const KDevelop::Path targetDir(target.value(QStringLiteral("sourceDirectory")).toString()); - data.targets[targetDir] += target.value(QLatin1String("name")).toString(); + data.targets[targetDir] += target.value(QStringLiteral("name")).toString(); - const auto fileGroups = target.value(QLatin1String("fileGroups")).toArray(); + const auto fileGroups = target.value(QStringLiteral("fileGroups")).toArray(); for (const auto &fileGroupValue: fileGroups) { const auto fileGroup = fileGroupValue.toObject(); CMakeFile file; - file.includes = kTransform(fileGroup.value(QLatin1String("includePath")).toArray(), [](const QJsonValue& val) { return KDevelop::Path(val.toObject().value(QLatin1String("path")).toString()); }); - file.defines = processDefines(fileGroup.value(QLatin1String("compileFlags")).toString(), fileGroup.value(QLatin1String("defines")).toArray()); + file.includes = kTransform(fileGroup.value(QStringLiteral("includePath")).toArray(), [](const QJsonValue& val) { return KDevelop::Path(val.toObject().value(QStringLiteral("path")).toString()); }); + file.defines = processDefines(fileGroup.value(QStringLiteral("compileFlags")).toString(), fileGroup.value(QStringLiteral("defines")).toArray()); - const auto sourcesArray = fileGroup.value(QLatin1String("sources")).toArray(); + const auto sourcesArray = fileGroup.value(QStringLiteral("sources")).toArray(); const KDevelop::Path::List sources = kTransform(sourcesArray, [targetDir](const QJsonValue& val) { return KDevelop::Path(targetDir, val.toString()); }); for (const auto& source: sources) { // NOTE: we use the canonical file path to prevent issues with symlinks in the path // leading to lookup failures const auto localFile = source.toLocalFile(); const auto canonicalFile = QFileInfo(source.toLocalFile()).canonicalFilePath(); const auto sourcePath = localFile == canonicalFile ? source : KDevelop::Path(canonicalFile); data.compilationData.files[sourcePath] = file; } qCDebug(CMAKE) << "registering..." << sources << file; } } } } } CMakeServerImportJob::CMakeServerImportJob(KDevelop::IProject* project, CMakeServer* server, QObject* parent) : KJob(parent) , m_server(server) , m_project(project) { connect(m_server.data(), &CMakeServer::disconnected, this, [this]() { setError(UnexpectedDisconnect); emitResult(); }); } void CMakeServerImportJob::start() { if (m_server->isServerAvailable()) doStart(); else connect(m_server.data(), &CMakeServer::connected, this, &CMakeServerImportJob::doStart); } void CMakeServerImportJob::doStart() { connect(m_server.data(), &CMakeServer::response, this, &CMakeServerImportJob::processResponse); m_server->handshake(m_project->path(), CMake::currentBuildDir(m_project)); } void CMakeServerImportJob::processResponse(const QJsonObject& response) { - const auto responseType = response.value(QLatin1String("type")); + const auto responseType = response.value(QStringLiteral("type")); if (responseType == QLatin1String("reply")) { - const auto inReplyTo = response.value(QLatin1String("inReplyTo")); + const auto inReplyTo = response.value(QStringLiteral("inReplyTo")); qCDebug(CMAKE) << "replying..." << inReplyTo; if (inReplyTo == QLatin1String("handshake")) { m_server->configure({}); } else if (inReplyTo == QLatin1String("configure")) { m_server->compute(); } else if (inReplyTo == QLatin1String("compute")) { m_server->codemodel(); } else if(inReplyTo == QLatin1String("codemodel")) { processFileData(response, m_data); m_data.m_server = m_server; emitResult(); } else { qWarning() << "unhandled reply" << response; } } else if(responseType == QLatin1String("error")) { setError(ErrorResponse); - setErrorText(response.value(QLatin1String("errorMessage")).toString()); + setErrorText(response.value(QStringLiteral("errorMessage")).toString()); qWarning() << "error!!" << response; emitResult(); } else { qWarning() << "unhandled message" << response; } } diff --git a/projectmanagers/qmake/parser/tests/functionscopetest.cpp b/projectmanagers/qmake/parser/tests/functionscopetest.cpp index a2ffc4d5ae..0ae83b0584 100644 --- a/projectmanagers/qmake/parser/tests/functionscopetest.cpp +++ b/projectmanagers/qmake/parser/tests/functionscopetest.cpp @@ -1,342 +1,342 @@ /* KDevelop * * Copyright 2007 Andreas Pakulat * * 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 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 "functionscopetest.h" #include "ast.h" #include "qmakedriver.h" #include "testhelpers.h" QTEST_MAIN(FunctionScopeTest) FunctionScopeTest::FunctionScopeTest(QObject* parent) : QObject(parent) , ast(nullptr) { } FunctionScopeTest::~FunctionScopeTest() { } void FunctionScopeTest::init() { ast = new QMake::ProjectAST(); QVERIFY(ast != nullptr); } void FunctionScopeTest::cleanup() { delete ast; ast = nullptr; QVERIFY(ast == nullptr); } BEGINTESTFUNCIMPL(FunctionScopeTest, execBasicFunc, 1) QMake::FunctionCallAST* scope = dynamic_cast(ast->statements.first()); TESTFUNCNAME(scope, "foobar") QVERIFY(scope->body == nullptr); ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, execBasicFunc, "foobar()\n") BEGINTESTFUNCIMPL(FunctionScopeTest, execSimpleFunc, 1) QMake::FunctionCallAST* scope = dynamic_cast(ast->statements.first()); TESTFUNCNAME(scope, "foobar") QVERIFY(scope->body == nullptr); ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, execSimpleFunc, "foobar( arg1, arg2 )\n") BEGINTESTFUNCIMPL(FunctionScopeTest, argWithEqual, 1) QMake::FunctionCallAST* scope = dynamic_cast(ast->statements.first()); TESTFUNCNAME(scope, "foobar") QVERIFY(scope->body == nullptr); ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, argWithEqual, "foobar( arg1 = arg2 )\n") BEGINTESTFUNCIMPL(FunctionScopeTest, evalQMakeSyntax, 1) QMake::FunctionCallAST* scope = dynamic_cast(ast->statements.first()); TESTFUNCNAME(scope, "eval") QVERIFY(scope->body == nullptr); ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, evalQMakeSyntax, "eval($${subdir}.depends = $$basename($${subdir})/$(MAKEFILE) )\n") BEGINTESTFUNCIMPL(FunctionScopeTest, simpleVarArg, 1) QMake::FunctionCallAST* scope = dynamic_cast(ast->statements.first()); TESTFUNCNAME(scope, "eval") QVERIFY(scope->body == nullptr); ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, simpleVarArg, "eval($${subdir}, $$SOMEVAR, $(SHELLVAR))\n") BEGINTESTFUNCIMPL(FunctionScopeTest, partlyQuotedArg, 1) QMake::FunctionCallAST* scope = dynamic_cast(ast->statements.first()); TESTFUNCNAME(scope, "eval") QVERIFY(scope->body == nullptr); ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, partlyQuotedArg, "eval(\"SOMELITERALSTRNIG\" SOMETHINGELSE)\n") BEGINTESTFUNCIMPL(FunctionScopeTest, slashArg, 1) QMake::FunctionCallAST* scope = dynamic_cast(ast->statements.first()); TESTFUNCNAME(scope, "eval") QVERIFY(scope->body == nullptr); ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, slashArg, "eval(SOMELITERALSTRNIG/$$SOMETHINGELSE)\n") BEGINTESTFUNCIMPL(FunctionScopeTest, nestedFunccalls, 1) QMake::FunctionCallAST* scope = dynamic_cast(ast->statements.first()); TESTFUNCNAME(scope, "eval") QVERIFY(scope->body == nullptr); ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, nestedFunccalls, "eval(val, $$contains(QT_PROJECT, $$foobar(some)))\n") BEGINTESTFUNCIMPL(FunctionScopeTest, oneStatementScope, 1) QMake::FunctionCallAST* scope = dynamic_cast(ast->statements.first()); TESTFUNCNAME(scope, "contains") QList teststmts; auto tst = new QMake::AssignmentAST(scope->body); auto val = new QMake::ValueAST(tst); - val->value = QLatin1String("VARIABLE"); + val->value = QStringLiteral("VARIABLE"); tst->identifier = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("="); + val->value = QStringLiteral("="); tst->op = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("val1"); + val->value = QStringLiteral("val1"); tst->values.append(val); val = new QMake::ValueAST(tst); - val->value = QLatin1String("val2"); + val->value = QStringLiteral("val2"); tst->values.append(val); teststmts.append(tst); TESTSCOPEBODY(scope, teststmts, 1) ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, oneStatementScope, "contains(SOMETHINGELSE, foobar) : VARIABLE = val1 val2\n") BEGINTESTFUNCIMPL(FunctionScopeTest, oneStatementSubScope, 1) QMake::FunctionCallAST* scope = dynamic_cast(ast->statements.first()); TESTFUNCNAME(scope, "contains") QList teststmts; auto call = new QMake::FunctionCallAST(scope->body); auto val = new QMake::ValueAST(call); - val->value = QLatin1String("contains"); + val->value = QStringLiteral("contains"); call->identifier = val; val = new QMake::ValueAST(call); - val->value = QLatin1String("foobar"); + val->value = QStringLiteral("foobar"); call->args.insert(0, val); auto body = new QMake::ScopeBodyAST(call); auto tst = new QMake::AssignmentAST(body); val = new QMake::ValueAST(tst); - val->value = QLatin1String("VARIABLE"); + val->value = QStringLiteral("VARIABLE"); tst->identifier = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("="); + val->value = QStringLiteral("="); tst->op = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("val1"); + val->value = QStringLiteral("val1"); tst->values.append(val); val = new QMake::ValueAST(tst); - val->value = QLatin1String("val2"); + val->value = QStringLiteral("val2"); tst->values.append(val); body->statements.insert(0, tst); call->body = body; teststmts.append(call); TESTSCOPEBODY(scope, teststmts, 1) ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, oneStatementSubScope, "contains(SOMETHINGELSE, foobar) : contains( foobar ) : VARIABLE = val1 val2\n") BEGINTESTFUNCIMPL(FunctionScopeTest, multiLineScope, 1) QMake::FunctionCallAST* scope = dynamic_cast(ast->statements.first()); TESTFUNCNAME(scope, "contains") QList teststmts; auto tst = new QMake::AssignmentAST(scope->body); auto val = new QMake::ValueAST(tst); - val->value = QLatin1String("VARIABLE"); + val->value = QStringLiteral("VARIABLE"); tst->identifier = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("="); + val->value = QStringLiteral("="); tst->op = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("val1"); + val->value = QStringLiteral("val1"); tst->values.append(val); val = new QMake::ValueAST(tst); - val->value = QLatin1String("val2"); + val->value = QStringLiteral("val2"); tst->values.append(val); teststmts.append(tst); tst = new QMake::AssignmentAST(scope->body); val = new QMake::ValueAST(tst); - val->value = QLatin1String("VARIABLE2"); + val->value = QStringLiteral("VARIABLE2"); tst->identifier = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("="); + val->value = QStringLiteral("="); tst->op = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("val2"); + val->value = QStringLiteral("val2"); tst->values.append(val); teststmts.append(tst); TESTSCOPEBODY(scope, teststmts, 2) ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, multiLineScope, "contains(SOMETHINGELSE, foobar) {\n VARIABLE = val1 val2\nVARIABLE2 = val2\n}\n") BEGINTESTFUNCIMPL(FunctionScopeTest, multiLineScopeFuncCall, 1) QMake::FunctionCallAST* scope = dynamic_cast(ast->statements.first()); TESTFUNCNAME(scope, "contains") QList teststmts; auto tst = new QMake::AssignmentAST(scope->body); auto val = new QMake::ValueAST(tst); - val->value = QLatin1String("VARIABLE"); + val->value = QStringLiteral("VARIABLE"); tst->identifier = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("="); + val->value = QStringLiteral("="); tst->op = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("val1"); + val->value = QStringLiteral("val1"); tst->values.append(val); val = new QMake::ValueAST(tst); - val->value = QLatin1String("val2"); + val->value = QStringLiteral("val2"); tst->values.append(val); teststmts.append(tst); QMake::FunctionCallAST* call; call = new QMake::FunctionCallAST(scope->body); val = new QMake::ValueAST(call); - val->value = QLatin1String("func2"); + val->value = QStringLiteral("func2"); call->identifier = val; QMake::ScopeBodyAST* body; body = new QMake::ScopeBodyAST(call); tst = new QMake::AssignmentAST(body); val = new QMake::ValueAST(tst); - val->value = QLatin1String("VARIABLE2"); + val->value = QStringLiteral("VARIABLE2"); tst->identifier = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("="); + val->value = QStringLiteral("="); tst->op = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("val2"); + val->value = QStringLiteral("val2"); tst->values.append(val); body->statements.insert(0, tst); call->body = body; teststmts.append(call); TESTSCOPEBODY(scope, teststmts, 2) ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, multiLineScopeFuncCall, "contains(SOMETHINGELSE, foobar) {\n VARIABLE = val1 val2\n func2() {\n VARIABLE2 = val2\n}\n}\n") BEGINTESTFUNCIMPL(FunctionScopeTest, notFunc, 1) QMake::FunctionCallAST* scope = dynamic_cast(ast->statements.first()); TESTFUNCNAME(scope, "!contains") QList teststmts; auto tst = new QMake::AssignmentAST(scope->body); auto val = new QMake::ValueAST(tst); - val->value = QLatin1String("VARIABLE"); + val->value = QStringLiteral("VARIABLE"); tst->identifier = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("="); + val->value = QStringLiteral("="); tst->op = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("val1"); + val->value = QStringLiteral("val1"); tst->values.append(val); val = new QMake::ValueAST(tst); - val->value = QLatin1String("val2"); + val->value = QStringLiteral("val2"); tst->values.append(val); teststmts.append(tst); TESTSCOPEBODY(scope, teststmts, 1) ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, notFunc, "!contains(SOMETHINGELSE, foobar) : VARIABLE = val1 val2\n") BEGINTESTFUNCIMPL(FunctionScopeTest, orOperator, 1) QMake::OrAST* orop = dynamic_cast(ast->statements.first()); QStringList funcs; funcs << QStringLiteral("!contains") << QStringLiteral("contains"); TESTOROP(orop, funcs) QList teststmts; auto tst = new QMake::AssignmentAST(orop->body); auto val = new QMake::ValueAST(tst); - val->value = QLatin1String("VARIABLE"); + val->value = QStringLiteral("VARIABLE"); tst->identifier = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("="); + val->value = QStringLiteral("="); tst->op = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("val1"); + val->value = QStringLiteral("val1"); tst->values.append(val); val = new QMake::ValueAST(tst); - val->value = QLatin1String("val2"); + val->value = QStringLiteral("val2"); tst->values.append(val); teststmts.append(tst); TESTSCOPEBODY(orop, teststmts, 1) ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, orOperator, "!contains(SOMETHINGELSE, foobar) | contains(OTHER,foo) : VARIABLE = val1 val2\n") BEGINTESTFUNCIMPL(FunctionScopeTest, spaceBeforeBrace, 1) QMake::FunctionCallAST* fn = dynamic_cast(ast->statements.first()); TESTFUNCNAME(fn, "func") QStringList testlist; testlist << QStringLiteral("some "); ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, spaceBeforeBrace, "func ( some )\n") BEGINTESTFAILFUNCIMPL(FunctionScopeTest, missingParenthesis, "No closing parenthesis for function call") ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, missingParenthesis, "eval(SOMETHINGELSE\n") BEGINTESTFUNCIMPL(FunctionScopeTest, missingStatement, 1) QMake::FunctionCallAST* fn = dynamic_cast(ast->statements.first()); TESTFUNCNAME(fn, "eval") QStringList testlist; testlist << QStringLiteral("SOMETHINGELSE "); ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, missingStatement, "eval(SOMETHINGELSE):\n") BEGINTESTFAILFUNCIMPL(FunctionScopeTest, missingBrace, "No closing brace for function scope") ENDTESTFUNCIMPL DATAFUNCIMPL(FunctionScopeTest, missingBrace, "eval(SOMETHINGELSE){\n") diff --git a/projectmanagers/qmake/parser/tests/scopetest.cpp b/projectmanagers/qmake/parser/tests/scopetest.cpp index e916c48e7e..47ffe268e9 100644 --- a/projectmanagers/qmake/parser/tests/scopetest.cpp +++ b/projectmanagers/qmake/parser/tests/scopetest.cpp @@ -1,138 +1,138 @@ /* KDevelop * * Copyright 2007 Andreas Pakulat * * 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 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 "scopetest.h" #include "ast.h" #include "qmakedriver.h" #include "testhelpers.h" QTEST_MAIN(ScopeTest) ScopeTest::ScopeTest(QObject* parent) : QObject(parent) , ast(nullptr) { } ScopeTest::~ScopeTest() { } void ScopeTest::init() { ast = new QMake::ProjectAST(); QVERIFY(ast != nullptr); } void ScopeTest::cleanup() { delete ast; ast = nullptr; QVERIFY(ast == nullptr); } BEGINTESTFUNCIMPL(ScopeTest, basicScope, 1) QMake::SimpleScopeAST* scope = dynamic_cast(ast->statements.first()); TESTSCOPENAME(scope, "foobar") QList testlist; auto tst = new QMake::AssignmentAST(scope->body); auto val = new QMake::ValueAST(tst); - val->value = QLatin1String("VARIABLE"); + val->value = QStringLiteral("VARIABLE"); tst->identifier = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("="); + val->value = QStringLiteral("="); tst->op = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("FOO"); + val->value = QStringLiteral("FOO"); tst->values.append(val); testlist.append(tst); TESTSCOPEBODY(scope, testlist, 1) ENDTESTFUNCIMPL DATAFUNCIMPL(ScopeTest, basicScope, "foobar : VARIABLE = FOO\n") BEGINTESTFUNCIMPL(ScopeTest, basicScopeBrace, 1) QMake::SimpleScopeAST* scope = dynamic_cast(ast->statements.first()); TESTSCOPENAME(scope, "foobar") QList testlist; auto tst = new QMake::AssignmentAST(scope->body); auto val = new QMake::ValueAST(tst); - val->value = QLatin1String("VARIABLE"); + val->value = QStringLiteral("VARIABLE"); tst->identifier = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("="); + val->value = QStringLiteral("="); tst->op = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("FOO"); + val->value = QStringLiteral("FOO"); tst->values.append(val); testlist.append(tst); TESTSCOPEBODY(scope, testlist, 1) ENDTESTFUNCIMPL DATAFUNCIMPL(ScopeTest, basicScopeBrace, "foobar {\n VARIABLE = FOO\n}\n") BEGINTESTFUNCIMPL(ScopeTest, nestedScope, 1) QMake::SimpleScopeAST* scope = dynamic_cast(ast->statements.first()); TESTSCOPENAME(scope, "foobar") QList testlist; auto simple = new QMake::SimpleScopeAST(scope->body); auto val = new QMake::ValueAST(simple); - val->value = QLatin1String("barfoo"); + val->value = QStringLiteral("barfoo"); simple->identifier = val; auto body = new QMake::ScopeBodyAST(simple); QList sublist; auto tst = new QMake::AssignmentAST(body); val = new QMake::ValueAST(tst); - val->value = QLatin1String("VARIABLE"); + val->value = QStringLiteral("VARIABLE"); tst->identifier = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("="); + val->value = QStringLiteral("="); tst->op = val; val = new QMake::ValueAST(tst); - val->value = QLatin1String("FOO"); + val->value = QStringLiteral("FOO"); tst->values.append(val); body->statements.append(tst); simple->body = body; testlist.append(simple); TESTSCOPEBODY(scope, testlist, 1) ENDTESTFUNCIMPL DATAFUNCIMPL(ScopeTest, nestedScope, "foobar :barfoo : VARIABLE = FOO\n") BEGINTESTFUNCIMPL(ScopeTest, missingStatement, 1) QMake::SimpleScopeAST* scope = dynamic_cast(ast->statements.first()); TESTSCOPENAME(scope, "eval") ENDTESTFUNCIMPL DATAFUNCIMPL(ScopeTest, missingStatement, "eval :\n") BEGINTESTFAILFUNCIMPL(ScopeTest, missingColon, "No colon") ENDTESTFUNCIMPL DATAFUNCIMPL(ScopeTest, missingColon, "eval \n") void ScopeTest::strangeScopeNames() { QMake::Driver d; d.setContent(QStringLiteral("linux-gcc++-* {\n VARIABLE = FOO\n}\n")); bool ret = d.parse(&ast); QVERIFY(ret); } diff --git a/projectmanagers/qmake/qmakefile.cpp b/projectmanagers/qmake/qmakefile.cpp index 0d80fb412b..f7994fd299 100644 --- a/projectmanagers/qmake/qmakefile.cpp +++ b/projectmanagers/qmake/qmakefile.cpp @@ -1,211 +1,211 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * 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 Library 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 "qmakefile.h" #include #include #include #include "debug.h" #include "parser/ast.h" #include "qmakedriver.h" #define ifDebug(x) //@TODO: Make the globbing stuff work with drives on win32 void resolveShellGlobbingInternal(QStringList& entries, const QStringList& segments, const QFileInfo& match, QDir& dir, int offset); QStringList resolveShellGlobbingInternal(const QStringList& segments, QDir& dir, int offset = 0) { if (offset >= segments.size()) { return QStringList(); } const QString& pathPattern = segments.at(offset); QStringList entries; if (pathPattern.contains('*') || pathPattern.contains('?') || pathPattern.contains('[')) { // pattern contains globbing chars foreach (const QFileInfo& match, dir.entryInfoList(QStringList() << pathPattern, QDir::AllEntries | QDir::NoDotAndDotDot, QDir::Unsorted)) { resolveShellGlobbingInternal(entries, segments, match, dir, offset); } } else { // pattern is "simple" hence be fast, but make sure the file exists QFileInfo info(dir.filePath(pathPattern)); if (info.exists()) { resolveShellGlobbingInternal(entries, segments, info, dir, offset); } } return entries; } void resolveShellGlobbingInternal(QStringList& entries, const QStringList& segments, const QFileInfo& match, QDir& dir, int offset) { if (match.isDir() && offset + 1 < segments.size()) { dir.cd(match.fileName()); entries += resolveShellGlobbingInternal(segments, dir, offset + 1); dir.cdUp(); } else { entries << match.canonicalFilePath(); } } QStringList resolveShellGlobbingInternal(const QString& pattern, const QString& dir) { if (pattern.isEmpty()) { return QStringList(); } - QDir dir_(pattern.startsWith('/') ? QLatin1String("/") : dir); + QDir dir_(pattern.startsWith('/') ? QStringLiteral("/") : dir); // break up pattern into path segments return resolveShellGlobbingInternal(pattern.split(QLatin1Char('/'), QString::SkipEmptyParts), dir_); } QMakeFile::QMakeFile(QString file) : m_ast(nullptr) , m_projectFile(std::move(file)) , m_project(nullptr) { Q_ASSERT(!m_projectFile.isEmpty()); } bool QMakeFile::read() { Q_ASSERT(!m_projectFile.isEmpty()); QFileInfo fi(m_projectFile); ifDebug(qCDebug(KDEV_QMAKE) << "Is" << m_projectFile << "a dir?" << fi.isDir();) if (fi.isDir()) { QDir dir(m_projectFile); QStringList l = dir.entryList(QStringList() << QStringLiteral("*.pro")); QString projectfile; if (!l.count() || (l.count() && l.indexOf(fi.baseName() + ".pro") != -1)) { projectfile = fi.baseName() + ".pro"; } else { projectfile = l.first(); } m_projectFile += '/' + projectfile; } QMake::Driver d; d.readFile(m_projectFile); if (!d.parse(&m_ast)) { qCWarning(KDEV_QMAKE) << "Couldn't parse project:" << m_projectFile; delete m_ast; m_ast = nullptr; m_projectFile = QString(); return false; } else { ifDebug(qCDebug(KDEV_QMAKE) << "found ast:" << m_ast->statements.count();) QMakeFileVisitor visitor(this, this); /// TODO: cleanup, re-use m_variableValues directly in the visitor visitor.setVariables(m_variableValues); m_variableValues = visitor.visitFile(m_ast); ifDebug(qCDebug(KDEV_QMAKE) << "Variables found:" << m_variableValues;) } return true; } QMakeFile::~QMakeFile() { delete m_ast; m_ast = nullptr; } QString QMakeFile::absoluteDir() const { return QFileInfo(m_projectFile).absoluteDir().canonicalPath(); } QString QMakeFile::absoluteFile() const { return m_projectFile; } QMake::ProjectAST* QMakeFile::ast() const { return m_ast; } QStringList QMakeFile::variables() const { return m_variableValues.keys(); } QStringList QMakeFile::variableValues(const QString& variable) const { return m_variableValues.value(variable, QStringList()); } bool QMakeFile::containsVariable(const QString& variable) const { return m_variableValues.contains(variable); } QMakeFile::VariableMap QMakeFile::variableMap() const { return m_variableValues; } QStringList QMakeFile::resolveVariable(const QString& variable, VariableInfo::VariableType type) const { if (type == VariableInfo::QMakeVariable && m_variableValues.contains(variable)) { return m_variableValues.value(variable); } else { qCWarning(KDEV_QMAKE) << "unresolved variable:" << variable << "type:" << type; return QStringList(); } } QStringList QMakeFile::resolveShellGlobbing(const QString& pattern, const QString& base) const { return resolveShellGlobbingInternal(pattern, base.isEmpty() ? absoluteDir() : base); } QString QMakeFile::resolveToSingleFileName(const QString& file, const QString& base) const { QStringList l = resolveFileName(file, base); if (l.isEmpty()) return QString(); else return l.first(); } QStringList QMakeFile::resolveFileName(const QString& file, const QString& base) const { return resolveShellGlobbing(file, base); } void QMakeFile::setProject(KDevelop::IProject* project) { m_project = project; } KDevelop::IProject* QMakeFile::project() const { return m_project; } diff --git a/projectmanagers/qmake/qmakeprojectfile.cpp b/projectmanagers/qmake/qmakeprojectfile.cpp index ab47a19803..05314040ed 100644 --- a/projectmanagers/qmake/qmakeprojectfile.cpp +++ b/projectmanagers/qmake/qmakeprojectfile.cpp @@ -1,426 +1,426 @@ /* KDevelop QMake Support * * Copyright 2006 Andreas Pakulat * * 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 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 "qmakeprojectfile.h" #include #include #include #include "debug.h" #include "parser/ast.h" #include "qmakecache.h" #include "qmakemkspecs.h" #include "qmakeconfig.h" #include #include #define ifDebug(x) QHash> QMakeProjectFile::m_qmakeQueryCache; const QStringList QMakeProjectFile::FileVariables = QStringList() << QStringLiteral("IDLS") << QStringLiteral("RESOURCES") << QStringLiteral("IMAGES") << QStringLiteral("LEXSOURCES") << QStringLiteral("DISTFILES") << QStringLiteral("YACCSOURCES") << QStringLiteral("TRANSLATIONS") << QStringLiteral("HEADERS") << QStringLiteral("SOURCES") << QStringLiteral("INTERFACES") << QStringLiteral("FORMS"); QMakeProjectFile::QMakeProjectFile(const QString& projectfile) : QMakeFile(projectfile) , m_mkspecs(nullptr) , m_cache(nullptr) { } void QMakeProjectFile::setQMakeCache(QMakeCache* cache) { m_cache = cache; } void QMakeProjectFile::setMkSpecs(QMakeMkSpecs* mkspecs) { m_mkspecs = mkspecs; } bool QMakeProjectFile::read() { // default values // NOTE: if we already have such a var, e.g. in an include file, we must not overwrite it here! if (!m_variableValues.contains(QStringLiteral("QT"))) { m_variableValues[QStringLiteral("QT")] = QStringList() << QStringLiteral("core") << QStringLiteral("gui"); } if (!m_variableValues.contains(QStringLiteral("CONFIG"))) { m_variableValues[QStringLiteral("CONFIG")] = QStringList() << QStringLiteral("qt"); } Q_ASSERT(m_mkspecs); foreach (const QString& var, m_mkspecs->variables()) { if (!m_variableValues.contains(var)) { m_variableValues[var] = m_mkspecs->variableValues(var); } } if (m_cache) { foreach (const QString& var, m_cache->variables()) { if (!m_variableValues.contains(var)) { m_variableValues[var] = m_cache->variableValues(var); } } } /// TODO: more special variables m_variableValues[QStringLiteral("PWD")] = QStringList() << pwd(); m_variableValues[QStringLiteral("_PRO_FILE_")] = QStringList() << proFile(); m_variableValues[QStringLiteral("_PRO_FILE_PWD_")] = QStringList() << proFilePwd(); m_variableValues[QStringLiteral("OUT_PWD")] = QStringList() << outPwd(); const QString qtInstallHeaders = QStringLiteral("QT_INSTALL_HEADERS"); const QString qtVersion = QStringLiteral("QT_VERSION"); const QString qtInstallLibs = QStringLiteral("QT_INSTALL_LIBS"); const QString executable = QMakeConfig::qmakeExecutable(project()); if (!m_qmakeQueryCache.contains(executable)) { const auto queryResult = QMakeConfig::queryQMake(executable, {qtInstallHeaders, qtVersion, qtInstallLibs}); if (queryResult.isEmpty()) { qCWarning(KDEV_QMAKE) << "Failed to query qmake - bad qmake executable configured?" << executable; } m_qmakeQueryCache[executable] = queryResult; } const auto cachedQueryResult = m_qmakeQueryCache.value(executable); m_qtIncludeDir = cachedQueryResult.value(qtInstallHeaders); m_qtVersion = cachedQueryResult.value(qtVersion); m_qtLibDir = cachedQueryResult.value(qtInstallLibs); return QMakeFile::read(); } QStringList QMakeProjectFile::subProjects() const { ifDebug(qCDebug(KDEV_QMAKE) << "Fetching subprojects";) QStringList list; foreach (QString subdir, variableValues("SUBDIRS")) { QString fileOrPath; ifDebug(qCDebug(KDEV_QMAKE) << "Found value:" << subdir;) if (containsVariable(subdir + ".file") && !variableValues(subdir + ".file").isEmpty()) { subdir = variableValues(subdir + ".file").first(); } else if (containsVariable(subdir + ".subdir") && !variableValues(subdir + ".subdir").isEmpty()) { subdir = variableValues(subdir + ".subdir").first(); } if (subdir.endsWith(QLatin1String(".pro"))) { fileOrPath = resolveToSingleFileName(subdir.trimmed()); } else { fileOrPath = resolveToSingleFileName(subdir.trimmed()); } if (fileOrPath.isEmpty()) { qCWarning(KDEV_QMAKE) << "could not resolve subdir" << subdir << "to file or path, skipping"; continue; } list << fileOrPath; } ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "subprojects";) return list; } bool QMakeProjectFile::hasSubProject(const QString& file) const { foreach (const QString& sub, subProjects()) { if (sub == file) { return true; } else if (QFileInfo(file).absoluteDir() == sub) { return true; } } return false; } void QMakeProjectFile::addPathsForVariable(const QString& variable, QStringList* list, const QString& base) const { const QStringList values = variableValues(variable); ifDebug(qCDebug(KDEV_QMAKE) << variable << values;) foreach (const QString& val, values) { QString path = resolveToSingleFileName(val, base); if (!path.isEmpty() && !list->contains(val)) { list->append(path); } } } QStringList QMakeProjectFile::includeDirectories() const { ifDebug(qCDebug(KDEV_QMAKE) << "Fetching include dirs" << m_qtIncludeDir;) ifDebug(qCDebug(KDEV_QMAKE) << "CONFIG" << variableValues("CONFIG");) QStringList list; addPathsForVariable(QStringLiteral("INCLUDEPATH"), &list); addPathsForVariable(QStringLiteral("QMAKE_INCDIR"), &list); if (variableValues(QStringLiteral("CONFIG")).contains(QStringLiteral("opengl"))) { addPathsForVariable(QStringLiteral("QMAKE_INCDIR_OPENGL"), &list); } if (variableValues(QStringLiteral("CONFIG")).contains(QStringLiteral("qt"))) { if (!list.contains(m_qtIncludeDir)) list << m_qtIncludeDir; QDir incDir(m_qtIncludeDir); auto modules = variableValues(QStringLiteral("QT")); if (!modules.isEmpty() && !modules.contains(QStringLiteral("core"))) { // TODO: proper dependency tracking of modules // for now, at least include core if we include any other module modules << QStringLiteral("core"); } // TODO: This is all very fragile, should rather read QMake module .pri files (e.g. qt_lib_core_private.pri) foreach (const QString& module, modules) { QString pattern = module; bool isPrivate = false; if (module.endsWith(QLatin1String("-private"))) { pattern.chop(qstrlen("-private")); isPrivate = true; } else if (module.endsWith(QLatin1String("_private"))) { // _private is less common, but still a valid suffix pattern.chop(qstrlen("_private")); isPrivate = true; } if (pattern == QLatin1String("qtestlib") || pattern == QLatin1String("testlib")) { - pattern = QLatin1String("QtTest"); + pattern = QStringLiteral("QtTest"); } else if (pattern == QLatin1String("qaxcontainer")) { - pattern = QLatin1String("ActiveQt"); + pattern = QStringLiteral("ActiveQt"); } else if (pattern == QLatin1String("qaxserver")) { - pattern = QLatin1String("ActiveQt"); + pattern = QStringLiteral("ActiveQt"); } QFileInfoList match = incDir.entryInfoList({QString("Qt%1").arg(pattern)}, QDir::Dirs); if (match.isEmpty()) { // try non-prefixed pattern match = incDir.entryInfoList({pattern}, QDir::Dirs); if (match.isEmpty()) { qCWarning(KDEV_QMAKE) << "unhandled Qt module:" << module << pattern; continue; } } QString path = match.first().canonicalFilePath(); if (isPrivate) { path += '/' + m_qtVersion + '/' + match.first().fileName() + "/private/"; } if (!list.contains(path)) { list << path; } } } if (variableValues(QStringLiteral("CONFIG")).contains(QStringLiteral("thread"))) { addPathsForVariable(QStringLiteral("QMAKE_INCDIR_THREAD"), &list); } if (variableValues(QStringLiteral("CONFIG")).contains(QStringLiteral("x11"))) { addPathsForVariable(QStringLiteral("QMAKE_INCDIR_X11"), &list); } addPathsForVariable(QStringLiteral("MOC_DIR"), &list, outPwd()); addPathsForVariable(QStringLiteral("OBJECTS_DIR"), &list, outPwd()); addPathsForVariable(QStringLiteral("UI_DIR"), &list, outPwd()); ifDebug(qCDebug(KDEV_QMAKE) << "final list:" << list;) return list; } // Scan QMAKE_C*FLAGS for -F and -iframework and QMAKE_LFLAGS for good measure. Time will // tell if we need to scan the release/debug/... specific versions of QMAKE_C*FLAGS. // Also include QT_INSTALL_LIBS which corresponds to Qt's framework directory on OS X. QStringList QMakeProjectFile::frameworkDirectories() const { const auto variablesToCheck = {QStringLiteral("QMAKE_CFLAGS"), QStringLiteral("QMAKE_CXXFLAGS"), QStringLiteral("QMAKE_LFLAGS")}; const QLatin1String fOption("-F"); const QLatin1String iframeworkOption("-iframework"); QStringList fwDirs; foreach (const auto& var, variablesToCheck) { bool storeArg = false; foreach (const auto& arg, variableValues(var)) { if (arg == fOption || arg == iframeworkOption) { // detached -F/-iframework arg; set a warrant to store the next argument storeArg = true; } else { if (arg.startsWith(fOption)) { fwDirs << arg.mid(fOption.size()); } else if (arg.startsWith(iframeworkOption)) { fwDirs << arg.mid(iframeworkOption.size()); } else if (storeArg) { fwDirs << arg; } // cancel any outstanding warrants to store the next argument storeArg = false; } } } #ifdef Q_OS_OSX fwDirs << m_qtLibDir; #endif return fwDirs; } QStringList QMakeProjectFile::files() const { ifDebug(qCDebug(KDEV_QMAKE) << "Fetching files";) QStringList list; foreach (const QString& variable, QMakeProjectFile::FileVariables) { foreach (const QString& value, variableValues(variable)) { list += resolveFileName(value); } } ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "files";) return list; } QStringList QMakeProjectFile::filesForTarget(const QString& s) const { ifDebug(qCDebug(KDEV_QMAKE) << "Fetching files";) QStringList list; if (variableValues(QStringLiteral("INSTALLS")).contains(s)) { const QStringList files = variableValues(s + ".files"); if (!files.isEmpty()) { foreach (const QString& val, files) { list += QStringList(resolveFileName(val)); } } } if (!variableValues(QStringLiteral("INSTALLS")).contains(s) || s == QLatin1String("target")) { foreach (const QString& variable, QMakeProjectFile::FileVariables) { foreach (const QString& value, variableValues(variable)) { list += QStringList(resolveFileName(value)); } } } ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "files";) return list; } QString QMakeProjectFile::getTemplate() const { QString templ = QStringLiteral("app"); if (!variableValues(QStringLiteral("TEMPLATE")).isEmpty()) { templ = variableValues(QStringLiteral("TEMPLATE")).first(); } return templ; } QStringList QMakeProjectFile::targets() const { ifDebug(qCDebug(KDEV_QMAKE) << "Fetching targets";) QStringList list; list += variableValues(QStringLiteral("TARGET")); if (list.isEmpty() && getTemplate() != QLatin1String("subdirs")) { list += QFileInfo(absoluteFile()).baseName(); } foreach (const QString& target, variableValues("INSTALLS")) { if (!target.isEmpty() && target != QLatin1String("target")) list << target; } if (list.removeAll(QString())) { // remove empty targets - which is probably a bug... qCWarning(KDEV_QMAKE) << "got empty entry in TARGET of file" << absoluteFile(); } ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "targets";) return list; } QMakeProjectFile::~QMakeProjectFile() { // TODO: delete cache, specs, ...? } QStringList QMakeProjectFile::resolveVariable(const QString& variable, VariableInfo::VariableType type) const { if (type == VariableInfo::QtConfigVariable) { if (m_mkspecs->isQMakeInternalVariable(variable)) { return QStringList() << m_mkspecs->qmakeInternalVariable(variable); } else { qCWarning(KDEV_QMAKE) << "unknown QtConfig Variable:" << variable; return QStringList(); } } return QMakeFile::resolveVariable(variable, type); } QMakeMkSpecs* QMakeProjectFile::mkSpecs() const { return m_mkspecs; } QMakeCache* QMakeProjectFile::qmakeCache() const { return m_cache; } QList QMakeProjectFile::defines() const { QList d; foreach (QString def, variableMap()["DEFINES"]) { int pos = def.indexOf('='); if (pos >= 0) { // a value is attached to define d.append(DefinePair(def.left(pos), def.right(def.length() - (pos + 1)))); } else { // a value-less define d.append(DefinePair(def, QLatin1String(""))); } } return d; } QString QMakeProjectFile::pwd() const { return absoluteDir(); } QString QMakeProjectFile::outPwd() const { if (!project()) { return absoluteDir(); } else { return QMakeConfig::buildDirFromSrc(project(), KDevelop::Path(absoluteDir())).toLocalFile(); } } QString QMakeProjectFile::proFile() const { return absoluteFile(); } QString QMakeProjectFile::proFilePwd() const { return absoluteDir(); } diff --git a/projectmanagers/qmake/tests/test_qmakefile.cpp b/projectmanagers/qmake/tests/test_qmakefile.cpp index d0147be27a..62bad7e52b 100644 --- a/projectmanagers/qmake/tests/test_qmakefile.cpp +++ b/projectmanagers/qmake/tests/test_qmakefile.cpp @@ -1,651 +1,651 @@ /* KDevelop QMake Support * * Copyright 2010 Milian Wolff * * 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 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 "test_qmakefile.h" #include "qmakefile.h" #include "variablereferenceparser.h" #include "qmakeprojectfile.h" #include "qmakemkspecs.h" #include #include #include #include #include #include #include #include #include #include QTEST_MAIN(TestQMakeFile); typedef QHash DefineHash; Q_DECLARE_METATYPE(QMakeFile::VariableMap) Q_DECLARE_METATYPE(DefineHash) namespace QTest { template <> char* toString(const QStringList& list) { QByteArray ba; if (list.isEmpty()) { ba = "()"; } else { ba = "(\"" + list.join(QStringLiteral("\", \"")).toLocal8Bit() + "\")"; } return qstrdup(ba.data()); } template <> char* toString(const QMakeFile::VariableMap& variables) { QByteArray ba = "VariableMap("; QMakeFile::VariableMap::const_iterator it = variables.constBegin(); while (it != variables.constEnd()) { ba += "["; ba += it.key().toLocal8Bit(); ba += "] = "; ba += toString(it.value()); ++it; if (it != variables.constEnd()) { ba += ", "; } } ba += ")"; return qstrdup(ba.data()); } } QHash setDefaultMKSpec(QMakeProjectFile& file) { static const QHash qmvars = QMakeConfig::queryQMake(QMakeConfig::qmakeExecutable(nullptr)); static const QString specFile = QMakeConfig::findBasicMkSpec(qmvars); Q_ASSERT(QFile::exists(specFile)); QMakeMkSpecs* mkspecs = new QMakeMkSpecs(specFile, qmvars); mkspecs->read(); file.setMkSpecs(mkspecs); return qmvars; } void TestQMakeFile::varResolution() { QFETCH(QString, fileContents); QFETCH(QMakeFile::VariableMap, variables); QTemporaryFile tmpfile; tmpfile.open(); QTextStream stream(&tmpfile); stream << fileContents; stream << flush; tmpfile.close(); QMakeFile file(tmpfile.fileName()); QVERIFY(file.read()); QCOMPARE(file.variableMap(), variables); } void TestQMakeFile::varResolution_data() { QTest::addColumn("fileContents"); QTest::addColumn("variables"); { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR1")] = QStringList() << QStringLiteral("1"); QTest::newRow("simple") << "VAR1 = 1\n" << variables; } { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR1")] = QStringList() << QStringLiteral("1"); variables[QStringLiteral("VAR2")] = QStringList() << QStringLiteral("1"); QTest::newRow("var-in-var") << "VAR1 = 1\nVAR2 = $$VAR1\n" << variables; } { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR1")] = QStringList() << QStringLiteral("foo"); variables[QStringLiteral("VAR2")] = QStringList() << QStringLiteral("foo"); QTest::newRow("curlyvar") << "VAR1 = foo\nVAR2 = $${VAR1}\n" << variables; } { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR1")] = QStringList() << QProcessEnvironment::systemEnvironment().value(QStringLiteral("USER")); QTest::newRow("qmakeshell") << "VAR1 = $$(USER)\n" << variables; } { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR1")] = QStringList() << QStringLiteral("foo"); variables[QStringLiteral("VAR2")] = QStringList() << QStringLiteral("foo/bar"); QTest::newRow("path") << "VAR1 = foo\nVAR2 = $$VAR1/bar\n" << variables; } { QMakeFile::VariableMap variables; variables[QStringLiteral("VAR_1")] = QStringList() << QStringLiteral("foo"); variables[QStringLiteral("VAR_2")] = QStringList() << QStringLiteral("foo/bar"); QTest::newRow("var-underscore") << "VAR_1 = foo\nVAR_2 = $$VAR_1/bar" << variables; } } void TestQMakeFile::referenceParser() { QFETCH(QString, var); VariableReferenceParser parser; parser.setContent(var); QVERIFY(parser.parse()); } void TestQMakeFile::referenceParser_data() { QTest::addColumn("var"); QTest::newRow("dot") << "."; QTest::newRow("dotdot") << ".."; } void TestQMakeFile::libTarget() { QFETCH(QString, target); QFETCH(QString, resolved); QTemporaryFile tmpfile; tmpfile.open(); QTextStream stream(&tmpfile); stream << "TARGET = " << target << "\nTEMPLATE = lib\n"; stream << flush; tmpfile.close(); QMakeProjectFile file(tmpfile.fileName()); setDefaultMKSpec(file); QVERIFY(file.read()); QCOMPARE(file.targets(), QStringList() << resolved); } void TestQMakeFile::libTarget_data() { QTest::addColumn("target"); QTest::addColumn("resolved"); QTest::newRow("simple") << "MyLib" << "MyLib"; QTest::newRow("qtLibraryTarget") << "$$qtLibraryTarget(MyLib)" << "MyLib"; QTest::newRow("qtLibraryTarget-Var") << "MyLib\nTARGET = $$qtLibraryTarget($$TARGET)" << "MyLib"; } void TestQMakeFile::defines() { QFETCH(QString, fileContents); QFETCH(DefineHash, expectedDefines); QTemporaryFile tmpfile; tmpfile.open(); QTextStream stream(&tmpfile); stream << fileContents; stream << flush; tmpfile.close(); QMakeProjectFile file(tmpfile.fileName()); setDefaultMKSpec(file); QVERIFY(file.read()); QList list = file.defines(); QCOMPARE(list.size(), expectedDefines.size()); foreach (QMakeProjectFile::DefinePair define, list) { QVERIFY(expectedDefines.find(define.first) != expectedDefines.end()); QCOMPARE(define.second, expectedDefines[define.first]); } } void TestQMakeFile::defines_data() { QTest::addColumn("fileContents"); QTest::addColumn("expectedDefines"); { DefineHash list; list.insert(QStringLiteral("VAR1"), QLatin1String("")); QTest::newRow("Simple define") << "DEFINES += VAR1" << list; } { DefineHash list; list.insert(QStringLiteral("ANSWER"), QStringLiteral("42")); QTest::newRow("Define with value") << "DEFINES += ANSWER=42" << list; } { DefineHash list; list.insert(QStringLiteral("ANSWER"), QStringLiteral("42")); list.insert(QStringLiteral("ANOTHER_DEFINE"), QLatin1String("")); QTest::newRow("Multiple defines") << "DEFINES += ANSWER=42 ANOTHER_DEFINE" << list; } } void TestQMakeFile::replaceFunctions_data() { QTest::addColumn("fileContents"); QTest::addColumn("definedVariables"); QTest::addColumn("undefinedVariables"); { QString contents = "defineReplace(test) {\n" " FOO = $$1\n" " return($$FOO)\n" "}\n" "BAR = $$test(asdf)\n"; QMakeFile::VariableMap vars; vars[QStringLiteral("BAR")] = QStringList() << QStringLiteral("asdf"); QStringList undefined; undefined << QStringLiteral("FOO") << QStringLiteral("1"); QTest::newRow("defineReplace-1") << contents << vars << undefined; } } void TestQMakeFile::replaceFunctions() { QFETCH(QString, fileContents); QFETCH(QMakeFile::VariableMap, definedVariables); QFETCH(QStringList, undefinedVariables); QTemporaryFile tmpFile; tmpFile.open(); tmpFile.write(fileContents.toUtf8()); tmpFile.close(); QMakeProjectFile file(tmpFile.fileName()); setDefaultMKSpec(file); QVERIFY(file.read()); QMakeFile::VariableMap::const_iterator it = definedVariables.constBegin(); while (it != definedVariables.constEnd()) { QCOMPARE(file.variableValues(it.key()), it.value()); ++it; } foreach (const QString& var, undefinedVariables) { QVERIFY(!file.containsVariable(var)); } } void TestQMakeFile::qtIncludeDirs_data() { QTest::addColumn("fileContents"); QTest::addColumn("modules"); QTest::addColumn("missingModules"); { QStringList list; list << QStringLiteral("core") << QStringLiteral("gui"); QTest::newRow("defaults") << "" << list; } { QStringList list; list << QStringLiteral("core"); QTest::newRow("minimal") << "QT -= gui" << list; } { QStringList modules; modules << QStringLiteral("core") << QStringLiteral("gui") << QStringLiteral("network") << QStringLiteral("opengl") << QStringLiteral("phonon") << QStringLiteral("script") << QStringLiteral("scripttools") << QStringLiteral("sql") << QStringLiteral("svg") << QStringLiteral("webkit") << QStringLiteral("xml") << QStringLiteral("xmlpatterns") << QStringLiteral("qt3support") << QStringLiteral("designer") << QStringLiteral("uitools") << QStringLiteral("help") << QStringLiteral("assistant") << QStringLiteral("qtestlib") << QStringLiteral("testlib") << QStringLiteral("qaxcontainer") << QStringLiteral("qaxserver") << QStringLiteral("dbus") << QStringLiteral("declarative"); foreach (const QString& module, modules) { QStringList expected; expected << module; if (module != QLatin1String("core")) { expected << QStringLiteral("core"); } QTest::newRow(qPrintable(module)) << QStringLiteral("QT = %1").arg(module) << expected; } } } void TestQMakeFile::qtIncludeDirs() { QFETCH(QString, fileContents); QFETCH(QStringList, modules); QMap moduleMap; - moduleMap[QStringLiteral("core")] = QLatin1String("QtCore"); - moduleMap[QStringLiteral("gui")] = QLatin1String("QtGui"); - moduleMap[QStringLiteral("network")] = QLatin1String("QtNetwork"); - moduleMap[QStringLiteral("opengl")] = QLatin1String("QtOpenGL"); - moduleMap[QStringLiteral("phonon")] = QLatin1String("Phonon"); - moduleMap[QStringLiteral("script")] = QLatin1String("QtScript"); - moduleMap[QStringLiteral("scripttools")] = QLatin1String("QtScriptTools"); - moduleMap[QStringLiteral("sql")] = QLatin1String("QtSql"); - moduleMap[QStringLiteral("svg")] = QLatin1String("QtSvg"); - moduleMap[QStringLiteral("webkit")] = QLatin1String("QtWebKit"); - moduleMap[QStringLiteral("xml")] = QLatin1String("QtXml"); - moduleMap[QStringLiteral("xmlpatterns")] = QLatin1String("QtXmlPatterns"); - moduleMap[QStringLiteral("qt3support")] = QLatin1String("Qt3Support"); - moduleMap[QStringLiteral("designer")] = QLatin1String("QtDesigner"); - moduleMap[QStringLiteral("uitools")] = QLatin1String("QtUiTools"); - moduleMap[QStringLiteral("help")] = QLatin1String("QtHelp"); - moduleMap[QStringLiteral("assistant")] = QLatin1String("QtAssistant"); - moduleMap[QStringLiteral("qtestlib")] = QLatin1String("QtTest"); - moduleMap[QStringLiteral("testlib")] = QLatin1String("QtTest"); - moduleMap[QStringLiteral("qaxcontainer")] = QLatin1String("ActiveQt"); - moduleMap[QStringLiteral("qaxserver")] = QLatin1String("ActiveQt"); - moduleMap[QStringLiteral("dbus")] = QLatin1String("QtDBus"); - moduleMap[QStringLiteral("declarative")] = QLatin1String("QtDeclarative"); + moduleMap[QStringLiteral("core")] = QStringLiteral("QtCore"); + moduleMap[QStringLiteral("gui")] = QStringLiteral("QtGui"); + moduleMap[QStringLiteral("network")] = QStringLiteral("QtNetwork"); + moduleMap[QStringLiteral("opengl")] = QStringLiteral("QtOpenGL"); + moduleMap[QStringLiteral("phonon")] = QStringLiteral("Phonon"); + moduleMap[QStringLiteral("script")] = QStringLiteral("QtScript"); + moduleMap[QStringLiteral("scripttools")] = QStringLiteral("QtScriptTools"); + moduleMap[QStringLiteral("sql")] = QStringLiteral("QtSql"); + moduleMap[QStringLiteral("svg")] = QStringLiteral("QtSvg"); + moduleMap[QStringLiteral("webkit")] = QStringLiteral("QtWebKit"); + moduleMap[QStringLiteral("xml")] = QStringLiteral("QtXml"); + moduleMap[QStringLiteral("xmlpatterns")] = QStringLiteral("QtXmlPatterns"); + moduleMap[QStringLiteral("qt3support")] = QStringLiteral("Qt3Support"); + moduleMap[QStringLiteral("designer")] = QStringLiteral("QtDesigner"); + moduleMap[QStringLiteral("uitools")] = QStringLiteral("QtUiTools"); + moduleMap[QStringLiteral("help")] = QStringLiteral("QtHelp"); + moduleMap[QStringLiteral("assistant")] = QStringLiteral("QtAssistant"); + moduleMap[QStringLiteral("qtestlib")] = QStringLiteral("QtTest"); + moduleMap[QStringLiteral("testlib")] = QStringLiteral("QtTest"); + moduleMap[QStringLiteral("qaxcontainer")] = QStringLiteral("ActiveQt"); + moduleMap[QStringLiteral("qaxserver")] = QStringLiteral("ActiveQt"); + moduleMap[QStringLiteral("dbus")] = QStringLiteral("QtDBus"); + moduleMap[QStringLiteral("declarative")] = QStringLiteral("QtDeclarative"); QTemporaryFile tmpFile; tmpFile.open(); tmpFile.write(fileContents.toUtf8()); tmpFile.close(); QMakeProjectFile file(tmpFile.fileName()); QHash qmvars = setDefaultMKSpec(file); QVERIFY(file.read()); const QStringList includes = file.includeDirectories(); // should always be there QVERIFY(includes.contains(qmvars["QT_INSTALL_HEADERS"])); for (QMap::const_iterator it = moduleMap.constBegin(); it != moduleMap.constEnd(); ++it) { QFileInfo include(qmvars[QStringLiteral("QT_INSTALL_HEADERS")] + "/" + it.value()); bool shouldBeIncluded = include.exists(); if (shouldBeIncluded) { shouldBeIncluded = modules.contains(it.key()); if (!shouldBeIncluded) { foreach (const QString& module, modules) { if (module != it.key() && moduleMap.value(module) == it.value()) { shouldBeIncluded = true; break; } } } } QCOMPARE((bool)includes.contains(include.filePath()), shouldBeIncluded); } } void TestQMakeFile::testInclude() { QTemporaryDir tempDir; QVERIFY(tempDir.isValid()); QTemporaryFile includeFile(tempDir.path() + "/qmake-include"); QVERIFY(includeFile.open()); includeFile.write("DEFINES += SOME_INCLUDE_DEF\n" "SOURCES += includedFile.cpp\n" "INCLUDEPATH += $$PWD\n" "QT += webkit\n"); includeFile.close(); QTemporaryFile baseFile; baseFile.open(); baseFile.write("TEMPLATE = app\n" "TARGET = includeTest\n" "QT += network\n" "DEFINES += SOME_DEF\n" "SOURCES += file.cpp\n" /* "CONFIG += console" "# Comment to enable Debug Messages" "DEFINES += QT_NO_DEBUG_OUTPUT" "DESTDIR = ../bin" "RESOURCES = phantomjs.qrc" "HEADERS += csconverter.h \\" " phantom.h \\" " webpage.h \\" " consts.h \\" " utils.h \\" " networkaccessmanager.h \\" " cookiejar.h \\" " filesystem.h \\" " terminal.h \\" " encoding.h \\" " config.h \\" " mimesniffer.cpp \\" " third_party/mongoose/mongoose.h \\" " webserver.h" "SOURCES += phantom.cpp \\" " webpage.cpp \\" " main.cpp \\" " csconverter.cpp \\" " utils.cpp \\" " networkaccessmanager.cpp \\" " cookiejar.cpp \\" " filesystem.cpp \\" " terminal.cpp \\" " encoding.cpp \\" " config.cpp \\" " mimesniffer.cpp \\" " third_party/mongoose/mongoose.c \\" " webserver.cpp" "" "OTHER_FILES += usage.txt \\" " bootstrap.js \\" " configurator.js \\" " modules/fs.js \\" " modules/webpage.js \\" " modules/webserver.js" "" */ "include(" + includeFile.fileName().toLocal8Bit() + ")\n"); baseFile.close(); QMakeProjectFile file(baseFile.fileName()); setDefaultMKSpec(file); QVERIFY(file.read()); QCOMPARE(file.variableValues("DEFINES"), QStringList() << "SOME_DEF" << "SOME_INCLUDE_DEF"); QCOMPARE(file.variableValues("SOURCES"), QStringList() << "file.cpp" << "includedFile.cpp"); QCOMPARE(file.variableValues("QT"), QStringList() << "core" << "gui" << "network" << "webkit"); // verify that include path was properly propagated QVERIFY(file.includeDirectories().contains(tempDir.path())); } void TestQMakeFile::globbing_data() { QTest::addColumn("files"); QTest::addColumn("pattern"); QTest::addColumn("matches"); QTest::newRow("wildcard-simple") << (QStringList() << QStringLiteral("foo.cpp")) << "*.cpp" << (QStringList() << QStringLiteral("foo.cpp")); QTest::newRow("wildcard-extended") << (QStringList() << QStringLiteral("foo.cpp") << QStringLiteral("bar.h") << QStringLiteral("asdf.cpp")) << "*.cpp" << (QStringList() << QStringLiteral("foo.cpp") << QStringLiteral("asdf.cpp")); QTest::newRow("wildcard-multiple") << (QStringList() << QStringLiteral("foo.cpp") << QStringLiteral("bar.h") << QStringLiteral("asdf.cpp")) << "*.cpp *.h" << (QStringList() << QStringLiteral("foo.cpp") << QStringLiteral("bar.h") << QStringLiteral("asdf.cpp")); QTest::newRow("wildcard-subdir") << (QStringList() << QStringLiteral("foo/bar.cpp") << QStringLiteral("fooasdf/bar.cpp") << QStringLiteral("asdf/asdf.cpp")) << "foo*/*.cpp" << (QStringList() << QStringLiteral("foo/bar.cpp") << QStringLiteral("fooasdf/bar.cpp")); QTest::newRow("bracket") << (QStringList() << QStringLiteral("foo1.cpp") << QStringLiteral("foo2.cpp") << QStringLiteral("fooX.cpp")) << "foo[0-9].cpp" << (QStringList() << QStringLiteral("foo1.cpp") << QStringLiteral("foo2.cpp")); QTest::newRow("questionmark") << (QStringList() << QStringLiteral("foo1.cpp") << QStringLiteral("fooX.cpp") << QStringLiteral("foo.cpp") << QStringLiteral("fooXY.cpp")) << "foo?.cpp" << (QStringList() << QStringLiteral("foo1.cpp") << QStringLiteral("fooX.cpp")); QTest::newRow("mixed") << (QStringList() << QStringLiteral("foo/asdf/test.cpp") << QStringLiteral("fooX/asdf1/test.cpp")) << "foo?/asdf[0-9]/*.cpp" << (QStringList() << QStringLiteral("fooX/asdf1/test.cpp")); } void TestQMakeFile::globbing() { QFETCH(QStringList, files); QFETCH(QString, pattern); QFETCH(QStringList, matches); QTemporaryDir tempDir; QDir tempDirDir(tempDir.path()); QVERIFY(tempDir.isValid()); foreach (const QString& file, files) { QVERIFY(tempDirDir.mkpath(QFileInfo(file).path())); QFile f(tempDir.path() + '/' + file); QVERIFY(f.open(QIODevice::WriteOnly)); } QTemporaryFile testFile(tempDir.path() + "/XXXXXX.pro"); QVERIFY(testFile.open()); testFile.write(("SOURCES = " + pattern + "\n").toUtf8()); testFile.close(); QMakeProjectFile pro(testFile.fileName()); setDefaultMKSpec(pro); QVERIFY(pro.read()); QStringList actual; foreach (QString path, pro.files()) { actual << path.remove(tempDir.path() + '/'); } std::sort(actual.begin(), actual.end()); std::sort(matches.begin(), matches.end()); QCOMPARE(actual, matches); } void TestQMakeFile::benchGlobbing() { QTemporaryDir tempDir; QDir dir(tempDir.path()); const int folders = 10; const int files = 100; for (int i = 0; i < folders; ++i) { QString folder = QStringLiteral("folder%1").arg(i); dir.mkdir(folder); for (int j = 0; j < files; ++j) { QFile f1(dir.filePath(folder + QStringLiteral("/file%1.cpp").arg(j))); QVERIFY(f1.open(QIODevice::WriteOnly)); QFile f2(dir.filePath(folder + QStringLiteral("/file%1.h").arg(j))); QVERIFY(f2.open(QIODevice::WriteOnly)); } } QTemporaryFile testFile(tempDir.path() + "/XXXXXX.pro"); QVERIFY(testFile.open()); testFile.write("SOURCES = fo?der[0-9]/*.cpp\n"); testFile.close(); QMakeProjectFile pro(testFile.fileName()); setDefaultMKSpec(pro); QVERIFY(pro.read()); int found = 0; QBENCHMARK { found = pro.files().size(); } QCOMPARE(found, files * folders); } void TestQMakeFile::benchGlobbingNoPattern() { QTemporaryDir tempDir; QDir dir(tempDir.path()); const int folders = 10; const int files = 100; for (int i = 0; i < folders; ++i) { QString folder = QStringLiteral("folder%1").arg(i); dir.mkdir(folder); for (int j = 0; j < files; ++j) { QFile f1(dir.filePath(folder + QStringLiteral("/file%1.cpp").arg(j))); QVERIFY(f1.open(QIODevice::WriteOnly)); QFile f2(dir.filePath(folder + QStringLiteral("/file%1.h").arg(j))); QVERIFY(f2.open(QIODevice::WriteOnly)); } } QTemporaryFile testFile(tempDir.path() + "/XXXXXX.pro"); QVERIFY(testFile.open()); testFile.write("SOURCES = folder0/file1.cpp\n"); testFile.close(); QMakeProjectFile pro(testFile.fileName()); setDefaultMKSpec(pro); QVERIFY(pro.read()); int found = 0; QBENCHMARK { found = pro.files().size(); } QCOMPARE(found, 1); } diff --git a/providers/ghprovider/ghproviderwidget.cpp b/providers/ghprovider/ghproviderwidget.cpp index 0f9409c6ef..0a4bbea769 100644 --- a/providers/ghprovider/ghproviderwidget.cpp +++ b/providers/ghprovider/ghproviderwidget.cpp @@ -1,174 +1,174 @@ /* This file is part of KDevelop * * Copyright (C) 2012-2013 Miquel Sabaté * * 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 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace gh { ProviderWidget::ProviderWidget(QWidget *parent) : IProjectProviderWidget(parent) { setLayout(new QVBoxLayout()); m_projects = new QListView(this); connect(m_projects, &QListView::clicked, this, &ProviderWidget::projectIndexChanged); m_waiting = new QLabel(i18n("Waiting for response"), this); m_waiting->setAlignment(Qt::AlignCenter); m_waiting->hide(); ProviderModel *model = new ProviderModel(this); m_projects->setModel(model); m_projects->setEditTriggers(QAbstractItemView::NoEditTriggers); m_resource = new Resource(this, model); m_account = new Account(m_resource); connect(m_resource, &Resource::reposUpdated, m_waiting, &QLabel::hide); QHBoxLayout *topLayout = new QHBoxLayout(); m_edit = new LineEdit(this); m_edit->setPlaceholderText(i18n("Search")); m_edit->setToolTip(i18n("You can press the Return key if you do not want to wait")); connect(m_edit, &LineEdit::returnPressed, this, &ProviderWidget::searchRepo); topLayout->addWidget(m_edit); m_combo = new QComboBox(this); m_combo->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); connect(m_combo, static_cast(&QComboBox::currentIndexChanged), this, &ProviderWidget::searchRepo); fillCombo(); topLayout->addWidget(m_combo); QPushButton *settings = new QPushButton(QIcon::fromTheme(QStringLiteral("configure")), QLatin1String(""), this); settings->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); settings->setToolTip(i18n("Click this button to configure your GitHub account")); connect(settings, &QPushButton::clicked, this, &ProviderWidget::showSettings); topLayout->addWidget(settings); layout()->addItem(topLayout); layout()->addWidget(m_waiting); layout()->addWidget(m_projects); } KDevelop::VcsJob * ProviderWidget::createWorkingCopy(const QUrl &dest) { QModelIndex pos = m_projects->currentIndex(); if (!pos.isValid()) return nullptr; auto plugin = ICore::self()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IBasicVersionControl"), QStringLiteral("kdevgit")); if (!plugin) { KMessageBox::error(0, i18n("The Git plugin could not be loaded which is required to import a GitHub project."), i18n("GitHub Provider Error")); return nullptr; } QString url = pos.data(ProviderModel::VcsLocationRole).toString(); if (m_account->validAccount()) url = "https://" + m_account->token() + "@" + url.mid(8); QUrl real = QUrl(url); VcsLocation loc(real); auto vc = plugin->extension(); Q_ASSERT(vc); return vc->createWorkingCopy(loc, dest); } void ProviderWidget::fillCombo() { m_combo->clear(); m_combo->addItem(i18n("User"), 1); m_combo->addItem(i18n("Organization"), 3); if (m_account->validAccount()) { m_combo->addItem(m_account->name(), 0); m_combo->setCurrentIndex(2); } const QStringList &orgs = m_account->orgs(); foreach (const QString &org, orgs) m_combo->addItem(org, 2); } bool ProviderWidget::isCorrect() const { return m_projects->currentIndex().isValid(); } void ProviderWidget::projectIndexChanged(const QModelIndex ¤tIndex) { if (currentIndex.isValid()) { QString name = currentIndex.data().toString(); emit changed(name); } } void ProviderWidget::showSettings() { Dialog *dialog = new Dialog(this, m_account); connect(dialog, &Dialog::shouldUpdate, this, &ProviderWidget::fillCombo); dialog->show(); } void ProviderWidget::searchRepo() { bool enabled = true; QString uri, text = m_edit->text(); int idx = m_combo->itemData(m_combo->currentIndex()).toInt(); switch (idx) { case 0: /* Looking for this user's repo */ - uri = QLatin1String("/user/repos"); + uri = QStringLiteral("/user/repos"); enabled = false; break; case 1: /* Looking for some user's repo */ if (text == m_account->name()) - uri = QLatin1String("/user/repos"); + uri = QStringLiteral("/user/repos"); else uri = QStringLiteral("/users/%1/repos").arg(text); break; case 2: /* Known organization */ text = m_combo->currentText(); enabled = false; default:/* Looking for some organization's repo. */ uri = QStringLiteral("/orgs/%1/repos").arg(text); break; } m_edit->setEnabled(enabled); m_waiting->show(); m_resource->searchRepos(uri, m_account->token()); } } // End of namespace gh diff --git a/utils/okteta/oktetawidget.cpp b/utils/okteta/oktetawidget.cpp index 625737a900..d32fca66fa 100644 --- a/utils/okteta/oktetawidget.cpp +++ b/utils/okteta/oktetawidget.cpp @@ -1,156 +1,156 @@ /* This file is part of the KDevelop Okteta module, part of the KDE project. Copyright 2010 Friedrich W. H. Kossebau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "oktetawidget.h" // plugin #include "oktetadocument.h" #include "oktetaplugin.h" // Okteta Kasten #include //#include //#include #include #include #include #include #include #include #include #include #include #include #include #include // Kasten #include // #include // #include #include #include #include #include #include #include #include #include // KDevelop #include // KDE #include #include #include #include // Qt #include namespace KDevelop { OktetaWidget::OktetaWidget( QWidget* parent, Kasten::ByteArrayView* byteArrayView, OktetaPlugin* plugin ) : QWidget( parent ), KXMLGUIClient(), mByteArrayView( byteArrayView ) { setComponentName( QStringLiteral("kdevokteta") , QStringLiteral("KDevelop Okteta")); setXMLFile( QStringLiteral("kdevokteta.rc") ); setupActions(plugin); QVBoxLayout* layout = new QVBoxLayout( this ); layout->setMargin( 0 ); QWidget* widget = mByteArrayView->widget(); layout->addWidget( widget ); setFocusProxy( widget ); } void OktetaWidget::setupActions(OktetaPlugin* plugin) { mControllers.append( new Kasten::VersionController(this) ); mControllers.append( new Kasten::ReadOnlyController(this) ); // TODO: save_as // mControllers.append( new ExportController(mProgram->viewManager(),mProgram->documentManager(),this) ); mControllers.append( new Kasten::ZoomController(this) ); mControllers.append( new Kasten::SelectController(this) ); mControllers.append( new Kasten::ClipboardController(this) ); // if( modus != BrowserViewModus ) // mControllers.append( new Kasten::InsertController(mProgram->viewManager(),mProgram->documentManager(),this) ); // mControllers.append( new Kasten::CopyAsController(mProgram->viewManager(),mProgram->documentManager(),this) ); mControllers.append( new Kasten::OverwriteModeController(this) ); mControllers.append( new Kasten::SearchController(this,this) ); mControllers.append( new Kasten::ReplaceController(this,this) ); // mControllers.append( new Kasten::GotoOffsetController(mGroupedViews,this) ); // mControllers.append( new Kasten::SelectRangeController(mGroupedViews,this) ); mControllers.append( new Kasten::BookmarksController(this) ); mControllers.append( new Kasten::PrintController( this ) ); mControllers.append( new Kasten::ViewConfigController(this) ); mControllers.append( new Kasten::ViewModeController(this) ); Kasten::ByteArrayViewProfileManager* viewProfileManager = plugin->viewProfileManager(); mControllers.append( new Kasten::ViewProfileController(viewProfileManager, mByteArrayView->widget(), this) ); mControllers.append( new Kasten::ViewProfilesManageController(this, viewProfileManager, mByteArrayView->widget()) ); // update the text of the viewprofiles_manage action, to make clear this is just for byte arrays - QAction* viewprofilesManageAction = actionCollection()->action(QLatin1String("settings_viewprofiles_manage")); + QAction* viewprofilesManageAction = actionCollection()->action(QStringLiteral("settings_viewprofiles_manage")); viewprofilesManageAction->setText( i18nc("@action:inmenu", "Manage Byte Array View Profiles...") ); // Kasten::StatusBar* bottomBar = static_cast( statusBar() ); // mControllers.append( new ViewStatusController(bottomBar) ); // mControllers.append( new ReadOnlyBarController(bottomBar) ); // mControllers.append( new ZoomBarController(bottomBar) ); foreach( Kasten::AbstractXmlGuiController* controller, mControllers ) controller->setTargetModel( mByteArrayView ); #if 0 QDesignerFormWindowManagerInterface* manager = mDocument->form()->core()->formWindowManager(); KActionCollection* ac = actionCollection(); KStandardAction::save( this, SLOT(save()), ac); ac->addAction( "adjust_size", manager->actionAdjustSize() ); ac->addAction( "break_layout", manager->actionBreakLayout() ); ac->addAction( "designer_cut", manager->actionCut() ); ac->addAction( "designer_copy", manager->actionCopy() ); ac->addAction( "designer_paste", manager->actionPaste() ); ac->addAction( "designer_delete", manager->actionDelete() ); ac->addAction( "layout_grid", manager->actionGridLayout() ); ac->addAction( "layout_horiz", manager->actionHorizontalLayout() ); ac->addAction( "layout_vertical", manager->actionVerticalLayout() ); ac->addAction( "layout_split_horiz", manager->actionSplitHorizontal() ); ac->addAction( "layout_split_vert", manager->actionSplitVertical() ); ac->addAction( "designer_undo", manager->actionUndo() ); ac->addAction( "designer_redo", manager->actionRedo() ); ac->addAction( "designer_select_all", manager->actionSelectAll() ); #endif } #if 0 void OktetaWidget::save() { mDocument->save(); } #endif OktetaWidget::~OktetaWidget() { qDeleteAll( mControllers ); } }