diff --git a/app/urlinfo.h b/app/urlinfo.h index dbcaf0c26b..787f5f0301 100644 --- a/app/urlinfo.h +++ b/app/urlinfo.h @@ -1,126 +1,126 @@ /* This file is part of the KDE project Copyright (C) 2015 Milian Wolff 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 . */ #ifndef URLINFO_H #define URLINFO_H #include #include #include #include #include #include /** * Represents a file to be opened, consisting of its URL and the cursor to jump to. */ class UrlInfo { public: /** * Parses a file path argument and determines its line number and column and full path * @param path path passed on e.g. command line to parse into an URL */ UrlInfo(QString path = QString()) : cursor(KTextEditor::Cursor::invalid()) { /** * first try: just check if the path is an existing file */ if (QFile::exists(path)) { /** * create absolute file path, we will e.g. pass this over dbus to other processes * and then we are done, no cursor can be detected here! */ url = QUrl::fromLocalFile(QDir::current().absoluteFilePath(path)); return; } /** * ok, the path as is, is no existing file, now, cut away :xx:yy stuff as cursor * this will make test:50 to test with line 50 */ const auto match = QRegularExpression(QStringLiteral(":(\\d+)(?::(\\d+))?:?$")).match(path); if (match.isValid()) { /** * cut away the line/column specification from the path */ path.chop(match.capturedLength()); /** * set right cursor position * don't use an invalid column when the line is valid */ const int line = match.captured(1).toInt() - 1; const int column = qMax(0, match.captured(2).toInt() - 1); cursor.setPosition(line, column); } /** * construct url: * - make relative paths absolute using the current working directory * - prefer local file, if in doubt! */ url = QUrl::fromUserInput(path, QDir::currentPath(), QUrl::AssumeLocalFile); /** * in some cases, this will fail, e.g. if you have line/column specs like test.c:10:1 * => fallback: assume a local file and just convert it to an url */ if (!url.isValid()) { /** * create absolute file path, we will e.g. pass this over dbus to other processes */ url = QUrl::fromLocalFile(QDir::current().absoluteFilePath(path)); } } /** * url computed out of the passed path */ QUrl url; /** - * initial cursor position, if any found inside the path as line/colum specification at the end + * initial cursor position, if any found inside the path as line/column specification at the end */ KTextEditor::Cursor cursor; }; QDataStream& operator<<(QDataStream& stream, const UrlInfo& info) { stream << info.url; stream << info.cursor.line(); stream << info.cursor.column(); return stream; } QDataStream& operator>>(QDataStream& stream, UrlInfo& info) { stream >> info.url; int line, column; stream >> line; stream >> column; info.cursor.setLine(line); info.cursor.setColumn(column); return stream; } #endif // URLINFO_H diff --git a/debuggers/common/mi/micommand.h b/debuggers/common/mi/micommand.h index bf9026373f..7733c111e9 100644 --- a/debuggers/common/mi/micommand.h +++ b/debuggers/common/mi/micommand.h @@ -1,377 +1,377 @@ /*************************************************************************** begin : Sun Aug 8 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org copyright : (C) 2016 by Aetf email : aetf@unlimitedcodeworks.xyz ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef _MICOMMAND_H_ #define _MICOMMAND_H_ #include "mi/mi.h" #include #include #include #include namespace KDevMI { class MIDebugSession; namespace MI { class VarItem; class ValueCallback; enum CommandFlag { /// The command handler also wishes to receive an error responses, overriding the default error handler CmdHandlesError = 1 << 0, /// The command is expected to cause the inferior to run. Controllers that display the /// program's state should refrain from sending commands while a command with this flag /// is currently pending; however, note that a command with this flag needn't be guaranteed /// to lead to a running state. CmdMaybeStartsRunning = 1 << 1, /// The command is a temporary-run type command, meaning that it typically causes the program /// to run, but only for a short time before it stops again (e.g. Step and StepInto-type /// commands). When the program is running due to this type of command, a CmdImmediately /// command will wait before forcing an interrupt of the debugger, and the program is _not_ /// automatically restarted if an interrupt was forced. /// /// TODO: this special handling has not actually been implemented yet CmdTemporaryRun = 1 << 2, /// This command should be executed immediately, even if the program is currently running /// (e.g. breakpoint setting and modification); however, if the program is interrupted, /// it should be resumed after this command has run. CmdImmediately = 1 << 3, /// This is a command that should interrupt a running program, without resuming. CmdInterrupt = 1 << 4, }; Q_DECLARE_FLAGS(CommandFlags, CommandFlag) //base class for handlers class MICommandHandler { public: virtual ~MICommandHandler() {} virtual void handle(const ResultRecord&) = 0; virtual bool handlesError() { return false; } /** * If the handler object should be deleted after the handle() call. */ virtual bool autoDelete() { return true; } }; class FunctionCommandHandler : public MICommandHandler { public: typedef std::function Function; FunctionCommandHandler(const Function& callback, CommandFlags flags = nullptr); virtual void handle(const ResultRecord&) override; virtual bool handlesError() override; private: CommandFlags _flags; Function _callback; }; /** * @author John Birch */ class MICommand { protected: MICommand(CommandType type, const QString& arguments = QString(), CommandFlags flags = nullptr); friend class KDevMI::MIDebugSession; public: virtual ~MICommand(); CommandType type() const; virtual QString miCommand() const; CommandFlags flags() const {return flags_;} /** * Returns the MI token with which the command is sent, allowing the parser to match up * the result message with the command. */ uint32_t token() const {return token_;} /** * Set the MI token. This is done by \ref MICommandQueue. */ void setToken(uint32_t token) {token_ = token;} /** * Returns the thread that needs to be currently selected when this command is executed, * or -1 if there is no requirement. */ int thread() const; /** * Set the thread required to be currently selected when the command is executed. */ void setThread(int thread); /** * Returns the frame that needs to be currently selected when this command is executed, * or -1 if there is no requirement. */ int frame() const; /** * Set the frame required to be currently selected when the command is executed. */ void setFrame(int frame); /** * Sets the handler for results. * * The command object assumes ownership of @p handler. */ void setHandler(MICommandHandler* handler); void setHandler(const FunctionCommandHandler::Function &callback); template void setHandler(Handler* handler_this, void (Handler::* handler_method)(const ResultRecord&)); /* The command that should be sent to debugger. This method is virtual so the command can compute this dynamically, possibly using results of the previous commands. If the empty string is returned, nothing is sent. */ virtual QString cmdToSend(); /* Returns the initial string that was specified in ctor invocation. The actual command will be determined by cmdToSend above and the return value of this method is only used in various diagnostic messages emitted before actually sending the command. */ QString initialString() const; /* Returns true if this is command entered by the user and so should be always shown in the gdb output window. */ virtual bool isUserCommand() const; // If there's a handler for this command, invokes it and returns true. // Otherwise, returns false. bool invokeHandler(const ResultRecord& r); // Returns 'true' if 'invokeHandler' should be invoked even // on MI errors. bool handlesError() const; // Called by debuggercontroller for each new output string // debugger emits for this command. In MI mode, this includes // all "stream" messages, but does not include MI responses. void newOutput(const QString&); const QStringList& allStreamOutput() const; QString command() const; void setStateReloading(bool f); bool stateReloading() const; /// Called when the command has been enqueued in the debug session /// and the command is wait for being submitted to GDB. void markAsEnqueued(); /// Called when the command has been submitted to GDB and the command /// waits for completion by GDB. void markAsSubmitted(); /// Called when the command has been completed and the response has arrived. void markAsCompleted(); /// returns the amount of time (in ms) passed between submission and completion. qint64 gdbProcessingTime() const; /// returns the amount of time (in ms) passed between enqueuing and submission. qint64 queueTime() const; /// returns the amount of time (in ms) passed between enqueuing and completion. qint64 totalProcessingTime() const; protected: CommandType type_; CommandFlags flags_; uint32_t token_ = 0; QString command_; MICommandHandler *commandHandler_; QStringList lines; bool stateReloading_; int m_thread; int m_frame; // remember the timestamps (in ms since start of the epoch) when this command // - was added to the command queue (enqueued) // - was submitted to GDB // - was completed; response from GDB arrived qint64 m_enqueueTimestamp; qint64 m_submitTimestamp; qint64 m_completeTimestamp; }; class UserCommand : public MICommand { public: UserCommand(CommandType type, const QString& s); bool isUserCommand() const override; }; /** This is a class for raw CLI commands. Instead of invoking user provided hook with MI response, it invokes the a hook with lists of strings. */ class CliCommand : public MICommand { public: template CliCommand(CommandType type, const QString& command, Handler* handler_this, void (Handler::* handler_method)(const QStringList&), CommandFlags flags = nullptr); }; /** Command that does nothing and can be just used to invoke - a user provided handler when all preceeding commands are + a user provided handler when all preceding commands are executed. */ class SentinelCommand : public MICommand { public: typedef std::function Function; template SentinelCommand(Handler* handler_this, void (Handler::* handler_method)(), CommandFlags flags = nullptr) : MICommand(NonMI, QString(), flags) { QPointer guarded_this(handler_this); handler = [guarded_this, handler_method]() { if (guarded_this) { (guarded_this.data()->*handler_method)(); } }; } SentinelCommand(const Function& handler, CommandFlags flags = nullptr) : MICommand(NonMI, QString(), flags) , handler(handler) { } using MICommand::invokeHandler; void invokeHandler() { handler(); } QString cmdToSend() override { return ""; } private: Function handler; }; class ExpressionValueCommand : public QObject, public MICommand { public: typedef void (QObject::*handler_method_t)(const QString&); template ExpressionValueCommand( const QString& expression, Handler* handler_this, void (Handler::* handler_method)(const QString&)) : MICommand(DataEvaluateExpression, expression), handler_this(handler_this), handler_method(static_cast(handler_method)) { setHandler(this, &ExpressionValueCommand::handleResponse); } void handleResponse(const ResultRecord& r) { (handler_this.data()->*handler_method)(r["value"].literal()); } private: QPointer handler_this; handler_method_t handler_method; }; template FunctionCommandHandler::Function guarded_callback(Handler *handler_this, void (Handler::* handler_method)(const ResultRecord&)) { QPointer guarded_this(handler_this); return [guarded_this, handler_method](const ResultRecord& r) { if (guarded_this) { (guarded_this.data()->*handler_method)(r); } }; } template void MICommand::setHandler(Handler* handler_this, void (Handler::* handler_method)(const ResultRecord&)) { QPointer guarded_this(handler_this); setHandler(new FunctionCommandHandler([guarded_this, handler_method](const ResultRecord& r) { if (guarded_this) { (guarded_this.data()->*handler_method)(r); } }, flags())); } template CliCommand::CliCommand( CommandType type, const QString& command, Handler* handler_this, void (Handler::* handler_method)(const QStringList&), CommandFlags flags) : MICommand(type, command) { QPointer guarded_this(handler_this); setHandler(new FunctionCommandHandler([this, guarded_this, handler_method](const ResultRecord&) { if (guarded_this) { (guarded_this.data()->*handler_method)(this->allStreamOutput()); } }, flags)); } } // end of namespace MI } // end of namespace KDevMI Q_DECLARE_OPERATORS_FOR_FLAGS(KDevMI::MI::CommandFlags) #endif diff --git a/debuggers/common/midebugsession.cpp b/debuggers/common/midebugsession.cpp index b8be4cf39b..b50a527582 100644 --- a/debuggers/common/midebugsession.cpp +++ b/debuggers/common/midebugsession.cpp @@ -1,1316 +1,1316 @@ /* * 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 #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("[\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 << "--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 == "GdbConsole") { emit raiseDebuggerConsoleViews(); } else if (config_startWith == "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 == "error") { KMessageBox::error( qApp->activeWindow(), i18n("Could not attach debugger:
")+ r["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, "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("(gdb) detach\n"); } // Now try to stop debugger running. addCommand(GdbExit); emit debuggerUserCommandOutput("(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, QString("%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, QString("*%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, QString("tbreak %1:%2").arg(url.toLocalFile()).arg(line)); addCommand(NonMI, QString("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, QString("tbreak *%1").arg(address)); addCommand(NonMI, QString("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 begining, so debugger won't get confused if the + // 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 = "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("reason")) reason = r["reason"].literal(); if (reason == "exited-normally" || reason == "exited") { if (r.hasField("exit-code")) { programNoApp(i18n("Exited with return code: %1", r["exit-code"].literal())); } else { programNoApp(i18n("Exited normally")); } m_stateReloadNeeded = false; return; } if (reason == "exited-signalled") { programNoApp(i18n("Exited on signal %1", r["signal-name"].literal())); m_stateReloadNeeded = false; return; } if (reason == "watchpoint-scope") { QString number = r["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 == "signal-received") { QString name = r["signal-name"].literal(); QString user_name = r["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 == "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("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("frame")) { const MI::Value& frame = r["frame"]; QString file, line, addr; if (frame.hasField("fullname")) file = frame["fullname"].literal();; if (frame.hasField("line")) line = frame["line"].literal(); if (frame.hasField("addr")) addr = frame["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 == "thread-group-started") { setDebuggerStateOff(s_appNotStarted | s_programExited); } else if (async.reason == "thread-group-exited") { setDebuggerStateOn(s_programExited); } else if (async.reason == "library-loaded") { // do nothing } else if (async.reason == "breakpoint-created") { breakpointController()->notifyBreakpointCreated(async); } else if (async.reason == "breakpoint-modified") { breakpointController()->notifyBreakpointModified(async); } else if (async.reason == "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 continiously 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 = QString("*** %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 continiously 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["msg"].literal(); if (msg.contains("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/stringhelpers.cpp b/debuggers/common/stringhelpers.cpp index 604e1952f3..57bbb3f13a 100644 --- a/debuggers/common/stringhelpers.cpp +++ b/debuggers/common/stringhelpers.cpp @@ -1,286 +1,286 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "stringhelpers.h" #include "debuglog.h" #include #include #include #include #include #include namespace Utils { bool parenFits( QChar c1, QChar c2 ) { if( c1 == '<' && c2 == '>' ) return true; else if( c1 == '(' && c2 == ')' ) return true; else if( c1 == '[' && c2 == ']' ) return true; else if( c1 == '{' && c2 == '}' ) return true; else return false; } bool isParen( QChar c1 ) { if( c1 == '<' || c1 == '>' ) return true; else if( c1 == '(' || c1 == ')' ) return true; else if( c1 == '[' || c1 == ']' ) return true; else if( c1 == '{' || c1 == '}' ) return true; else return false; } bool isTypeParen( QChar c1 ) { if( c1 == '<' || c1 == '>' ) return true; else return false; } bool isTypeOpenParen( QChar c1 ) { if( c1 == '<' ) return true; else return false; } bool isTypeCloseParen( QChar c1 ) { if( c1 == '>' ) return true; else return false; } bool isLeftParen( QChar c1 ) { if( c1 == '<' ) return true; else if( c1 == '(' ) return true; else if( c1 == '[' ) return true; else if( c1 == '{' ) return true; else return false; } enum { T_ACCESS, T_PAREN, T_BRACKET, T_IDE, T_UNKNOWN, T_TEMP }; int expressionAt( const QString& text, int index ) { if( index == 0 ) return 0; int last = T_UNKNOWN; int start = index; --index; while ( index > 0 ) { while ( index > 0 && text[ index ].isSpace() ) { --index; } QChar ch = text[ index ]; QString ch2 = text.mid( index - 1, 2 ); if ( ( last != T_IDE ) && ( ch.isLetterOrNumber() || ch == '_' ) ) { while ( index > 0 && ( text[ index ].isLetterOrNumber() || text[ index ] == '_' ) ) { --index; } last = T_IDE; } else if ( last != T_IDE && ch == ')' ) { int count = 0; while ( index > 0 ) { QChar ch = text[ index ]; if ( ch == '(' ) { ++count; } else if ( ch == ')' ) { --count; } --index; if ( count == 0 ) { //index; last = T_PAREN; break; } } } else if ( last != T_IDE && ch == '>' && ch2 != "->" ) { int count = 0; while ( index > 0 ) { QChar ch = text[ index ]; if ( ch == '<' ) { ++count; } else if ( ch == '>' ) { --count; } else if ( count == 0 ) { //--index; last = T_TEMP; break; } --index; } } else if ( ch == ']' ) { int count = 0; while ( index > 0 ) { QChar ch = text[ index ]; if ( ch == '[' ) { ++count; } else if ( ch == ']' ) { --count; } else if ( count == 0 ) { //--index; last = T_BRACKET; break; } --index; } } else if ( ch == '.' ) { --index; last = T_ACCESS; } else if ( ch2 == "::" ) { index -= 2; last = T_ACCESS; } else if ( ch2 == "->" ) { index -= 2; last = T_ACCESS; } else { if ( start > index ) { ++index; } last = T_UNKNOWN; break; } } ///If we're at the first item, the above algorithm cannot be used safely, ///so just determine whether the sign is valid for the beginning of an expression, if it isn't reject it. if ( index == 0 && start > index && !( text[ index ].isLetterOrNumber() || text[ index ] == '_' || text[ index ] == ':' ) ) { ++index; } return index; } QString quoteExpression(QString expr) { return quote(expr, '"'); } QString unquoteExpression(QString expr) { return unquote(expr, false); } QString quote(QString str, char quoteCh) { str.replace("\\", "\\\\").replace(quoteCh, QStringLiteral("\\") + quoteCh); return str.prepend(quoteCh).append(quoteCh); } QString unquote(const QString &str, bool unescapeUnicode, char quoteCh) { if (str.startsWith(quoteCh) && str.endsWith(quoteCh)) { QString res; res.reserve(str.length()); bool esc = false; int type = 0; QString escSeq; escSeq.reserve(4); - // skip begining and ending quoteCh, no need for str = str.mid(1, str.length() - 2) + // skip beginning and ending quoteCh, no need for str = str.mid(1, str.length() - 2) for (int i = 1; i != str.length() - 1; i++) { auto ch = str[i]; if (esc) { switch (ch.unicode()) { case '\\': if (type != 0) { escSeq += ch; qCDebug(DEBUGGERCOMMON) << "Unrecognized escape sequence:" << escSeq; res += '\\'; res += escSeq; escSeq.clear(); esc = false; type = 0; } else { res.append('\\'); // escSeq.clear(); // escSeq must be empty. esc = false; } break; case 'u': case 'x': if (type != 0 || !unescapeUnicode) { escSeq += ch; qCDebug(DEBUGGERCOMMON) << "Unrecognized escape sequence:" << escSeq; res += '\\'; res += escSeq; escSeq.clear(); esc = false; type = 0; } else { type = ch == 'u' ? 1 : 2; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': escSeq += ch; if (type == 0) { qCDebug(DEBUGGERCOMMON) << "Unrecognized escape sequence:" << escSeq; res += '\\'; res += escSeq; escSeq.clear(); esc = false; type = 0; } else { // \uNNNN // \xNN if (escSeq.length() == (type == 1 ? 4 : 2)) { // no need to handle error, we know for sure escSeq is '[0-9a-fA-F]+' auto code = escSeq.toInt(nullptr, 16); res += QChar(code); escSeq.clear(); esc = false; type = 0; } } break; default: if (type == 0 && ch == quoteCh) { res += ch; } else { escSeq += ch; qCDebug(DEBUGGERCOMMON) << "Unrecognized escape sequence:" << escSeq; res += '\\'; res += escSeq; escSeq.clear(); } esc = false; type = 0; break; } } else { if (ch == '\\') { esc = true; continue; } res += ch; } } return res; } else { return str; } } } // end of namespace Utils diff --git a/debuggers/common/stty.cpp b/debuggers/common/stty.cpp index c348cb22e0..01b8227d2d 100644 --- a/debuggers/common/stty.cpp +++ b/debuggers/common/stty.cpp @@ -1,347 +1,347 @@ /*************************************************************************** begin : Mon Sep 13 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org This code was originally written by Judin Maxim, from the KDEStudio project. It was then updated with later code from konsole (KDE). It has also been enhanced with an idea from the code in kdbg written by Johannes Sixt ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #ifdef __osf__ #define _XOPEN_SOURCE_EXTENDED #define O_NDELAY O_NONBLOCK #endif #include #include #include #include #include #include #ifdef HAVE_SYS_STROPTS_H #include #define _NEW_TTY_CTRL #endif #include #include #include #include #include #include #include #include #include #include #if defined (_HPUX_SOURCE) #define _TERMIOS_INCLUDED #include #endif #include #include #include #include #include #include #include #include #include "stty.h" #include "debuglog.h" #define PTY_FILENO 3 #define BASE_CHOWN "konsole_grantpty" #include using namespace KDevMI; static int chownpty(int fd, int grant) // param fd: the fd of a master pty. // param grant: 1 to grant, 0 to revoke // returns 1 on success 0 on fail { void(*tmp)(int) = signal(SIGCHLD,SIG_DFL); pid_t pid = fork(); if (pid < 0) { signal(SIGCHLD,tmp); return 0; } if (pid == 0) { /* We pass the master pseudo terminal as file descriptor PTY_FILENO. */ if (fd != PTY_FILENO && dup2(fd, PTY_FILENO) < 0) ::exit(1); QString path = QStandardPaths::findExecutable(BASE_CHOWN); execle(QFile::encodeName(path), BASE_CHOWN, grant?"--grant":"--revoke", (void *)nullptr, NULL); ::exit(1); // should not be reached } if (pid > 0) { int w; // retry: int rc = waitpid (pid, &w, 0); if (rc != pid) ::exit(1); // { // signal from other child, behave like catchChild. // // guess this gives quite some control chaos... // Shell* sh = shells.indexOf(rc); // if (sh) { shells.remove(rc); sh->doneShell(w); } // goto retry; // } signal(SIGCHLD,tmp); return (rc != -1 && WIFEXITED(w) && WEXITSTATUS(w) == 0); } signal(SIGCHLD,tmp); return 0; //dummy. } // ************************************************************************** STTY::STTY(bool ext, const QString &termAppName) : QObject(), out(nullptr), ttySlave(""), m_externalTerminal(nullptr), external_(ext) { if (ext) { findExternalTTY(termAppName); } else { fout = findTTY(); if (fout >= 0) { ttySlave = QString(tty_slave); out = new QSocketNotifier(fout, QSocketNotifier::Read, this); connect( out, &QSocketNotifier::activated, this, &STTY::OutReceived ); } } } // ************************************************************************** STTY::~STTY() { if (out) { ::close(fout); delete out; } } // ************************************************************************** int STTY::findTTY() { int ptyfd = -1; bool needGrantPty = true; // Find a master pty that we can open //////////////////////////////// #ifdef __sgi__ ptyfd = open("/dev/ptmx",O_RDWR); #elif defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) ptyfd = posix_openpt(O_RDWR); #endif #if defined(__sgi__) || defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) if (ptyfd == -1) { perror("Can't open a pseudo teletype"); return(-1); } else if (ptyfd >= 0) { strncpy(tty_slave, ptsname(ptyfd), 50); grantpt(ptyfd); unlockpt(ptyfd); needGrantPty = false; } #endif // first we try UNIX PTY's #if defined(TIOCGPTN) && !defined(Q_OS_FREEBSD) strcpy(pty_master,"/dev/ptmx"); strcpy(tty_slave,"/dev/pts/"); ptyfd = open(pty_master,O_RDWR); if (ptyfd >= 0) { // got the master pty int ptyno; if (ioctl(ptyfd, TIOCGPTN, &ptyno) == 0) { struct stat sbuf; sprintf(tty_slave,"/dev/pts/%d",ptyno); if (stat(tty_slave,&sbuf) == 0 && S_ISCHR(sbuf.st_mode)) needGrantPty = false; else { close(ptyfd); ptyfd = -1; } } else { close(ptyfd); ptyfd = -1; } } #endif #if defined(_SCO_DS) || defined(__USLC__) /* SCO OSr5 and UnixWare */ if (ptyfd < 0) { for (int idx = 0; idx < 256; idx++) { sprintf(pty_master, "/dev/ptyp%d", idx); sprintf(tty_slave, "/dev/ttyp%d", idx); if (access(tty_slave, F_OK) < 0) { idx = 256; break; } if ((ptyfd = open (pty_master, O_RDWR)) >= 0) { if (access (tty_slave, R_OK|W_OK) == 0) break; close(ptyfd); ptyfd = -1; } } } #endif if (ptyfd < 0) { /// \FIXME Linux, Trouble on other systems? for (const char* s3 = "pqrstuvwxyzabcde"; *s3 != 0; s3++) { for (const char* s4 = "0123456789abcdef"; *s4 != 0; s4++) { sprintf(pty_master,"/dev/pty%c%c",*s3,*s4); sprintf(tty_slave,"/dev/tty%c%c",*s3,*s4); if ((ptyfd = open(pty_master, O_RDWR)) >= 0) { if (geteuid() == 0 || access(tty_slave, R_OK|W_OK) == 0) break; close(ptyfd); ptyfd = -1; } } if (ptyfd >= 0) break; } } if (ptyfd >= 0) { if (needGrantPty && !chownpty(ptyfd, true)) { fprintf(stderr,"kdevelop: chownpty failed for device %s::%s.\n",pty_master,tty_slave); fprintf(stderr," : This means the session can be eavesdroped.\n"); fprintf(stderr," : Make sure konsole_grantpty is installed and setuid root.\n"); } ::fcntl(ptyfd, F_SETFL, O_NDELAY); #ifdef TIOCSPTLCK int flag = 0; ioctl(ptyfd, TIOCSPTLCK, &flag); // unlock pty #endif } if (ptyfd==-1) { m_lastError = i18n("Cannot use the tty* or pty* devices.\n" "Check the settings on /dev/tty* and /dev/pty*\n" "As root you may need to \"chmod ug+rw\" tty* and pty* devices " "and/or add the user to the tty group using " "\"usermod -aG tty username\"."); } return ptyfd; } // ************************************************************************** void STTY::OutReceived(int f) { char buf[1024]; int n; // read until socket is empty. We shouldn't be receiving a continuous // stream of data, so the loop is unlikely to cause problems. while ((n = ::read(f, buf, sizeof(buf)-1)) > 0) { *(buf+n) = 0; // a standard string QByteArray ba(buf); emit OutOutput(ba); } // Note: for some reason, n can be 0 here. // I can understand that non-blocking read returns 0, - // but I don't understand how OutRecieved can be even + // but I don't understand how OutReceived can be even // called when there's no input. if (n == 0 /* eof */ || (n == -1 && errno != EAGAIN)) { // Found eof or error. Disable socket notifier, otherwise Qt // will repeatedly call this method, eating CPU // cycles. out->setEnabled(false); } } void STTY::readRemaining() { if (!external_) OutReceived(fout); } bool STTY::findExternalTTY(const QString& termApp) { QString appName(termApp.isEmpty() ? QString("xterm") : termApp); if (QStandardPaths::findExecutable(appName).isEmpty()) { m_lastError = i18n("%1 is incorrect terminal name", termApp); return false; } QTemporaryFile file; if (!file.open()) { m_lastError = i18n("Can't create a temporary file"); return false; } m_externalTerminal.reset(new QProcess(this)); if (appName == "konsole") { m_externalTerminal->start(appName, QStringList() << "-e" << "sh" << "-c" << "tty>" + file.fileName() + ";exec<&-;exec>&-;while :;do sleep 3600;done"); } else if (appName == "xfce4-terminal") { m_externalTerminal->start(appName, QStringList() << "-e" << " sh -c \"tty>" + file.fileName() + ";\"\"<&\\-\"\">&\\-;\"\"while :;\"\"do sleep 3600;\"\"done\""); } else { m_externalTerminal->start(appName, QStringList() << "-e" << "sh -c \"tty>" + file.fileName() + ";exec<&-;exec>&-;while :;do sleep 3600;done\""); } if (!m_externalTerminal->waitForStarted(500)) { m_lastError = "Can't run terminal: " + appName; m_externalTerminal->terminate(); return false; } for (int i = 0; i < 800; i++) { if (!file.bytesAvailable()) { if (m_externalTerminal->state() == QProcess::NotRunning && m_externalTerminal->exitCode()) { break; } QCoreApplication::processEvents(QEventLoop::AllEvents, 100); usleep(8000); } else { qCDebug(DEBUGGERCOMMON) << "Received terminal output(tty)"; break; } } usleep(1000); ttySlave = file.readAll().trimmed(); file.close(); if (ttySlave.isEmpty()) { m_lastError = i18n("Can't receive %1 tty/pty. Check that %1 is actually a terminal and that it accepts these arguments: -e sh -c \"tty> %2 ;exec<&-;exec>&-;while :;do sleep 3600;done\"", appName, file.fileName()); } return true; } // ************************************************************************** diff --git a/debuggers/gdb/gdboutputwidget.h b/debuggers/gdb/gdboutputwidget.h index 87edf11f49..8e015ffecc 100644 --- a/debuggers/gdb/gdboutputwidget.h +++ b/debuggers/gdb/gdboutputwidget.h @@ -1,157 +1,156 @@ /* * GDB Debugger Support * * Copyright 2003 John Birch * Copyright 2007 Hamish Rodda * * 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. */ #ifndef _GDBOUTPUTWIDGET_H_ #define _GDBOUTPUTWIDGET_H_ #include "dbgglobal.h" #include #include #include #include -#include namespace KDevelop { class IDebugSession; } class KHistoryComboBox; class QTextEdit; class QToolButton; namespace KDevMI { namespace GDB { class GDBController; class CppDebuggerPlugin; class GDBOutputWidget : public QWidget { Q_OBJECT public: GDBOutputWidget(CppDebuggerPlugin* plugin, QWidget *parent=nullptr ); ~GDBOutputWidget() override; void savePartialProjectSession(); void restorePartialProjectSession(); bool showInternalCommands() const; public Q_SLOTS: void clear(); void slotInternalCommandStdout(const QString& line); void slotUserCommandStdout(const QString& line); void slotReceivedStderr(const char* line); void slotStateChanged(DBGStateFlags oldStatus, DBGStateFlags newStatus); void slotGDBCmd(); void flushPending(); void copyAll(); void toggleShowInternalCommands(); private Q_SLOTS: void currentSessionChanged(KDevelop::IDebugSession *session); void updateColors(); protected: void focusInEvent(QFocusEvent *e) override; void contextMenuEvent(QContextMenuEvent* e) override; Q_SIGNALS: void requestRaise(); void userGDBCmd(const QString &cmd); void breakInto(); private: void newStdoutLine(const QString& line, bool internal); /** Arranges for 'line' to be shown to the user. Adds 'line' to pendingOutput_ and makes sure updateTimer_ is running. */ void showLine(const QString& line); /** Makes 'l' no longer than 'max_size' by removing excessive elements from the top. */ void trimList(QStringList& l, int max_size); GDBController* m_controller; KHistoryComboBox* m_userGDBCmdEditor; QToolButton* m_Interrupt; QTextEdit* m_gdbView; bool m_cmdEditorHadFocus; void setShowInternalCommands(bool); friend class OutputText; /** The output from user commands only and from all commands. We keep it here so that if we switch "Show internal commands" on, we can show previous internal commands. */ QStringList userCommands_, allCommands_; /** Same output, without any fancy formatting. Keeping it here because I can't find any way to extract raw text, without formatting, out of QTextEdit except for selecting everything and calling 'copy()'. The latter approach is just ugly. */ QStringList userCommandsRaw_, allCommandsRaw_; /** For performance reasons, we don't immediately add new text to QTExtEdit. Instead we add it to pendingOutput_ and flush it on timer. */ QString pendingOutput_; QTimer updateTimer_; bool showInternalCommands_; int maxLines_; QColor gdbColor_; QColor errorColor_; }; class OutputTextEdit : public QTextEdit { Q_OBJECT public: OutputTextEdit(GDBOutputWidget* parent); protected: void contextMenuEvent(QContextMenuEvent* event) override; }; } // end of namespace GDB } // end of namespace KDevMI #endif diff --git a/debuggers/gdb/printers/qt.py b/debuggers/gdb/printers/qt.py index 86479ebd3c..bffd5811ff 100644 --- a/debuggers/gdb/printers/qt.py +++ b/debuggers/gdb/printers/qt.py @@ -1,733 +1,733 @@ # -*- coding: iso-8859-1 -*- # Pretty-printers for Qt 4 and Qt 5. # Copyright (C) 2009 Niko Sams # 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, see . import gdb import itertools import re import time from helper import * class QStringPrinter: def __init__(self, val): self.val = val def to_string(self): size = self.val['d']['size'] ret = "" # The QString object might be not yet initialized. In this case size is a bogus value # and the following 2 lines might throw memory access error. Hence the try/catch. try: isQt4 = has_field(self.val['d'], 'data') # Qt4 has d->data, Qt5 doesn't. if isQt4: dataAsCharPointer = self.val['d']['data'].cast(gdb.lookup_type("char").pointer()) else: dataAsCharPointer = (self.val['d'] + 1).cast(gdb.lookup_type("char").pointer()) ret = dataAsCharPointer.string(encoding = 'UTF-16', length = size * 2) except Exception: # swallow the exception and return empty string pass return ret def display_hint (self): return 'string' class QByteArrayPrinter: def __init__(self, val): self.val = val # Qt4 has 'data', Qt5 doesn't self.isQt4 = has_field(self.val['d'], 'data') class _iterator(Iterator): def __init__(self, data, size): self.data = data self.size = size self.count = 0 def __iter__(self): return self def __next__(self): if self.count >= self.size: raise StopIteration count = self.count self.count = self.count + 1 return ('[%d]' % count, self.data[count]) def stringData(self): if self.isQt4: return self.val['d']['data'] else: return self.val['d'].cast(gdb.lookup_type("char").const().pointer()) + self.val['d']['offset'] def children(self): return self._iterator(self.stringData(), self.val['d']['size']) def to_string(self): #todo: handle charset correctly return self.stringData() def display_hint (self): return 'string' class QListPrinter: "Print a QList" class _iterator(Iterator): def __init__(self, nodetype, d): self.nodetype = nodetype self.d = d self.count = 0 #from QTypeInfo::isLarge isLarge = self.nodetype.sizeof > gdb.lookup_type('void').pointer().sizeof isPointer = self.nodetype.code == gdb.TYPE_CODE_PTR #unfortunately we can't use QTypeInfo::isStatic as it's all inlined, so use #this list of types that use Q_DECLARE_TYPEINFO(T, Q_MOVABLE_TYPE) #(obviously it won't work for custom types) movableTypes = ['QRect', 'QRectF', 'QString', 'QMargins', 'QLocale', 'QChar', 'QDate', 'QTime', 'QDateTime', 'QVector', 'QRegExpr', 'QPoint', 'QPointF', 'QByteArray', 'QSize', 'QSizeF', 'QBitArray', 'QLine', 'QLineF', 'QModelIndex', 'QPersitentModelIndex', 'QVariant', 'QFileInfo', 'QUrl', 'QXmlStreamAttribute', 'QXmlStreamNamespaceDeclaration', 'QXmlStreamNotationDeclaration', 'QXmlStreamEntityDeclaration', 'QPair'] #this list of types that use Q_DECLARE_TYPEINFO(T, Q_PRIMITIVE_TYPE) (from qglobal.h) primitiveTypes = ['bool', 'char', 'signed char', 'unsigned char', 'short', 'unsigned short', 'int', 'unsigned int', 'long', 'unsigned long', 'long long', 'unsigned long long', 'float', 'double'] if movableTypes.count(self.nodetype.tag) or primitiveTypes.count(str(self.nodetype)): isStatic = False else: isStatic = not isPointer self.externalStorage = isLarge or isStatic #see QList::Node::t() def __iter__(self): return self def __next__(self): if self.count >= self.d['end'] - self.d['begin']: raise StopIteration count = self.count array = self.d['array'].address + self.d['begin'] + count if self.externalStorage: value = array.cast(gdb.lookup_type('QList<%s>::Node' % self.nodetype).pointer())['v'] else: value = array self.count = self.count + 1 return ('[%d]' % count, value.cast(self.nodetype.pointer()).dereference()) def __init__(self, val, container, itype): self.val = val self.container = container if itype == None: self.itype = self.val.type.template_argument(0) else: self.itype = gdb.lookup_type(itype) def children(self): return self._iterator(self.itype, self.val['d']) def to_string(self): if self.val['d']['end'] == self.val['d']['begin']: empty = "empty " else: empty = "" return "%s%s<%s>" % ( empty, self.container, self.itype ) class QVectorPrinter: "Print a QVector" class _iterator(Iterator): def __init__(self, nodetype, data, size): self.nodetype = nodetype self.data = data self.size = size self.count = 0 def __iter__(self): return self def __next__(self): if self.count >= self.size: raise StopIteration count = self.count self.count = self.count + 1 return ('[%d]' % count, self.data[count]) def __init__(self, val, container): self.val = val self.container = container self.itype = self.val.type.template_argument(0) def children(self): isQt4 = has_field(self.val['d'], 'p') # Qt4 has 'p', Qt5 doesn't if isQt4: return self._iterator(self.itype, self.val['p']['array'], self.val['p']['size']) else: data = self.val['d'].cast(gdb.lookup_type("char").const().pointer()) + self.val['d']['offset'] return self._iterator(self.itype, data.cast(self.itype.pointer()), self.val['d']['size']) def to_string(self): if self.val['d']['size'] == 0: empty = "empty " else: empty = "" return "%s%s<%s>" % ( empty, self.container, self.itype ) class QLinkedListPrinter: "Print a QLinkedList" class _iterator(Iterator): def __init__(self, nodetype, begin, size): self.nodetype = nodetype self.it = begin self.pos = 0 self.size = size def __iter__(self): return self def __next__(self): if self.pos >= self.size: raise StopIteration pos = self.pos val = self.it['t'] self.it = self.it['n'] self.pos = self.pos + 1 return ('[%d]' % pos, val) def __init__(self, val): self.val = val self.itype = self.val.type.template_argument(0) def children(self): return self._iterator(self.itype, self.val['e']['n'], self.val['d']['size']) def to_string(self): if self.val['d']['size'] == 0: empty = "empty " else: empty = "" return "%sQLinkedList<%s>" % ( empty, self.itype ) class QMapPrinter: "Print a QMap" class _iteratorQt4(Iterator): def __init__(self, val): self.val = val self.ktype = self.val.type.template_argument(0) self.vtype = self.val.type.template_argument(1) self.data_node = self.val['e']['forward'][0] self.count = 0 def __iter__(self): return self def payload (self): if gdb.parse_and_eval: ret = int(gdb.parse_and_eval('QMap<%s, %s>::payload()' % (self.ktype, self.vtype))) if (ret): return ret; #if the inferior function call didn't work, let's try to calculate ourselves #we can't use QMapPayloadNode as it's inlined #as a workaround take the sum of sizeof(members) ret = self.ktype.sizeof ret += self.vtype.sizeof ret += gdb.lookup_type('void').pointer().sizeof #but because of data alignment the value can be higher #so guess it's aliged by sizeof(void*) #TODO: find a real solution for this problem ret += ret % gdb.lookup_type('void').pointer().sizeof #for some reason booleans are different if str(self.vtype) == 'bool': ret += 2 ret -= gdb.lookup_type('void').pointer().sizeof return ret def concrete (self, data_node): node_type = gdb.lookup_type('QMapNode<%s, %s>' % (self.ktype, self.vtype)).pointer() return (data_node.cast(gdb.lookup_type('char').pointer()) - self.payload()).cast(node_type) def __next__(self): if self.data_node == self.val['e']: raise StopIteration node = self.concrete(self.data_node).dereference() if self.count % 2 == 0: item = node['key'] else: item = node['value'] self.data_node = node['forward'][0] result = ('[%d]' % self.count, item) self.count = self.count + 1 return result class _iteratorQt5: def __init__(self, val): realtype = val.type.strip_typedefs() keytype = realtype.template_argument(0) valtype = realtype.template_argument(1) node_type = gdb.lookup_type('QMapData<' + keytype.name + ',' + valtype.name + '>::Node') self.node_p_type = node_type.pointer() self.root = val['d']['header'] self.current = None self.next_is_key = True self.i = -1 # we store the path here to avoid keeping re-fetching # values from the inferior (also, skips the pointer # arithmetic involved in using the parent pointer) self.path = [] def __iter__(self): return self def moveToNextNode(self): if self.current is None: # find the leftmost node if not self.root['left']: return False self.current = self.root while self.current['left']: self.path.append(self.current) self.current = self.current['left'] elif self.current['right']: self.path.append(self.current) self.current = self.current['right'] while self.current['left']: self.path.append(self.current) self.current = self.current['left'] else: last = self.current self.current = self.path.pop() while self.current['right'] == last: last = self.current self.current = self.path.pop() # if there are no more parents, we are at the root if len(self.path) == 0: return False return True def __next__(self): if self.next_is_key: if not self.moveToNextNode(): raise StopIteration self.current_typed = self.current.reinterpret_cast(self.node_p_type) self.next_is_key = False self.i += 1 return ('key' + str(self.i), self.current_typed['key']) else: self.next_is_key = True return ('value' + str(self.i), self.current_typed['value']) def next(self): return self.__next__() def __init__(self, val, container): self.val = val self.container = container def children(self): if self.val['d']['size'] == 0: return [] isQt4 = has_field(self.val, 'e') # Qt4 has 'e', Qt5 doesn't if isQt4: return self._iteratorQt4(self.val) else: return self._iteratorQt5(self.val) def to_string(self): if self.val['d']['size'] == 0: empty = "empty " else: empty = "" return "%s%s<%s, %s>" % ( empty, self.container, self.val.type.template_argument(0), self.val.type.template_argument(1) ) def display_hint (self): return 'map' class QHashPrinter: "Print a QHash" class _iterator(Iterator): def __init__(self, val): self.val = val self.d = self.val['d'] self.ktype = self.val.type.template_argument(0) self.vtype = self.val.type.template_argument(1) self.end_node = self.d.cast(gdb.lookup_type('QHashData::Node').pointer()) self.data_node = self.firstNode() self.count = 0 def __iter__(self): return self def hashNode (self): "Casts the current QHashData::Node to a QHashNode and returns the result. See also QHash::concrete()" return self.data_node.cast(gdb.lookup_type('QHashNode<%s, %s>' % (self.ktype, self.vtype)).pointer()) def firstNode (self): "Get the first node, See QHashData::firstNode()." e = self.d.cast(gdb.lookup_type('QHashData::Node').pointer()) #print "QHashData::firstNode() e %s" % e bucketNum = 0 bucket = self.d['buckets'][bucketNum] #print "QHashData::firstNode() *bucket %s" % bucket n = self.d['numBuckets'] #print "QHashData::firstNode() n %s" % n while n: #print "QHashData::firstNode() in while, n %s" % n; if bucket != e: #print "QHashData::firstNode() in while, return *bucket %s" % bucket return bucket bucketNum += 1 bucket = self.d['buckets'][bucketNum] #print "QHashData::firstNode() in while, new bucket %s" % bucket n -= 1 #print "QHashData::firstNode() return e %s" % e return e def nextNode (self, node): "Get the nextNode after the current, see also QHashData::nextNode()." #print "******************************** nextNode" #print "nextNode: node %s" % node next = node['next'].cast(gdb.lookup_type('QHashData::Node').pointer()) e = next #print "nextNode: next %s" % next if next['next']: #print "nextNode: return next" return next #print "nextNode: node->h %s" % node['h'] #print "nextNode: numBuckets %s" % self.d['numBuckets'] start = (node['h'] % self.d['numBuckets']) + 1 bucketNum = start #print "nextNode: start %s" % start bucket = self.d['buckets'][start] #print "nextNode: bucket %s" % bucket n = self.d['numBuckets'] - start #print "nextNode: n %s" % n while n: #print "nextNode: in while; n %s" % n #print "nextNode: in while; e %s" % e #print "nextNode: in while; *bucket %s" % bucket if bucket != e: #print "nextNode: in while; return bucket %s" % bucket return bucket bucketNum += 1 bucket = self.d['buckets'][bucketNum] n -= 1 #print "nextNode: return e %s" % e return e def __next__(self): "GDB iteration, first call returns key, second value and then jumps to the next hash node." if self.data_node == self.end_node: raise StopIteration node = self.hashNode() if self.count % 2 == 0: item = node['key'] else: item = node['value'] self.data_node = self.nextNode(self.data_node) self.count = self.count + 1 return ('[%d]' % self.count, item) def __init__(self, val, container): self.val = val self.container = container def children(self): return self._iterator(self.val) def to_string(self): if self.val['d']['size'] == 0: empty = "empty " else: empty = "" return "%s%s<%s, %s>" % ( empty, self.container, self.val.type.template_argument(0), self.val.type.template_argument(1) ) def display_hint (self): return 'map' class QDatePrinter: def __init__(self, val): self.val = val def to_string(self): julianDay = self.val['jd'] if julianDay == 0: return "invalid QDate" # Copied from Qt sources if julianDay >= 2299161: # Gregorian calendar starting from October 15, 1582 # This algorithm is from Henry F. Fliegel and Thomas C. Van Flandern ell = julianDay + 68569; n = (4 * ell) / 146097; ell = ell - (146097 * n + 3) / 4; i = (4000 * (ell + 1)) / 1461001; ell = ell - (1461 * i) / 4 + 31; j = (80 * ell) / 2447; d = ell - (2447 * j) / 80; ell = j / 11; m = j + 2 - (12 * ell); y = 100 * (n - 49) + i + ell; else: # Julian calendar until October 4, 1582 # Algorithm from Frequently Asked Questions about Calendars by Claus Toendering julianDay += 32082; dd = (4 * julianDay + 3) / 1461; ee = julianDay - (1461 * dd) / 4; mm = ((5 * ee) + 2) / 153; d = ee - (153 * mm + 2) / 5 + 1; m = mm + 3 - 12 * (mm / 10); y = dd - 4800 + (mm / 10); if y <= 0: --y; return "%d-%02d-%02d" % (y, m, d) class QTimePrinter: def __init__(self, val): self.val = val def to_string(self): ds = self.val['mds'] if ds == -1: return "invalid QTime" MSECS_PER_HOUR = 3600000 SECS_PER_MIN = 60 MSECS_PER_MIN = 60000 hour = ds / MSECS_PER_HOUR minute = (ds % MSECS_PER_HOUR) / MSECS_PER_MIN second = (ds / 1000)%SECS_PER_MIN msec = ds % 1000 return "%02d:%02d:%02d.%03d" % (hour, minute, second, msec) class QDateTimePrinter: def __init__(self, val): self.val = val def to_string(self): time_t = gdb.parse_and_eval("reinterpret_cast(%s)->toTime_t()" % self.val.address) return time.ctime(int(time_t)) class QUrlPrinter: def __init__(self, val): self.val = val def to_string(self): # first try to access the Qt 5 data try: int_type = gdb.lookup_type('int') string_type = gdb.lookup_type('QString') string_pointer = string_type.pointer() addr = self.val['d'].cast(gdb.lookup_type('char').pointer()) # skip QAtomicInt ref addr += int_type.sizeof # handle int port port = addr.cast(int_type.pointer()).dereference() addr += int_type.sizeof # handle QString scheme scheme = QStringPrinter(addr.cast(string_pointer).dereference()).to_string() addr += string_type.sizeof # handle QString username username = QStringPrinter(addr.cast(string_pointer).dereference()).to_string() addr += string_type.sizeof # skip QString password addr += string_type.sizeof # handle QString host host = QStringPrinter(addr.cast(string_pointer).dereference()).to_string() addr += string_type.sizeof # handle QString path path = QStringPrinter(addr.cast(string_pointer).dereference()).to_string() addr += string_type.sizeof # handle QString query query = QStringPrinter(addr.cast(string_pointer).dereference()).to_string() addr += string_type.sizeof # handle QString fragment fragment = QStringPrinter(addr.cast(string_pointer).dereference()).to_string() url = "" if len(scheme) > 0: # TODO: always adding // is apparently not compliant in all cases url += scheme + "://" if len(host) > 0: if len(username) > 0: url += username + "@" url += host if port != -1: url += ":" + str(port) url += path if len(query) > 0: url += "?" + query if len(fragment) > 0: url += "#" + fragment return url except: pass # then try to print directly, but that might lead to issues (see http://sourceware-org.1504.n7.nabble.com/help-Calling-malloc-from-a-Python-pretty-printer-td284031.html) try: return gdb.parse_and_eval("reinterpret_cast(%s)->toString((QUrl::FormattingOptions)QUrl::PrettyDecoded)" % self.val.address) except: pass # if everything fails, maybe we deal with Qt 4 code try: return self.val['d']['encodedOriginal'] except RuntimeError: - #if no debug information is avaliable for Qt, try guessing the correct address for encodedOriginal + #if no debug information is available for Qt, try guessing the correct address for encodedOriginal #problem with this is that if QUrlPrivate members get changed, this fails offset = gdb.lookup_type('int').sizeof offset += offset % gdb.lookup_type('void').pointer().sizeof #alignment offset += gdb.lookup_type('QString').sizeof * 6 offset += gdb.lookup_type('QByteArray').sizeof encodedOriginal = self.val['d'].cast(gdb.lookup_type('char').pointer()); encodedOriginal += offset encodedOriginal = encodedOriginal.cast(gdb.lookup_type('QByteArray').pointer()).dereference(); encodedOriginal = encodedOriginal['d']['data'].string() return encodedOriginal class QSetPrinter: "Print a QSet" def __init__(self, val): self.val = val class _iterator(Iterator): def __init__(self, hashIterator): self.hashIterator = hashIterator self.count = 0 def __iter__(self): return self def __next__(self): if self.hashIterator.data_node == self.hashIterator.end_node: raise StopIteration node = self.hashIterator.hashNode() item = node['key'] self.hashIterator.data_node = self.hashIterator.nextNode(self.hashIterator.data_node) self.count = self.count + 1 return ('[%d]' % (self.count-1), item) def children(self): hashPrinter = QHashPrinter(self.val['q_hash'], None) hashIterator = hashPrinter._iterator(self.val['q_hash']) return self._iterator(hashIterator) def to_string(self): if self.val['q_hash']['d']['size'] == 0: empty = "empty " else: empty = "" return "%sQSet<%s>" % ( empty , self.val.type.template_argument(0) ) class QCharPrinter: def __init__(self, val): self.val = val def to_string(self): return unichr(self.val['ucs']) def display_hint (self): return 'string' class QUuidPrinter: def __init__(self, val): self.val = val def to_string(self): return "QUuid({%x-%x-%x-%x%x-%x%x%x%x%x%x})" % (int(self.val['data1']), int(self.val['data2']), int(self.val['data3']), int(self.val['data4'][0]), int(self.val['data4'][1]), int(self.val['data4'][2]), int(self.val['data4'][3]), int(self.val['data4'][4]), int(self.val['data4'][5]), int(self.val['data4'][6]), int(self.val['data4'][7])) def display_hint (self): return 'string' pretty_printers_dict = {} def register_qt_printers (obj): if obj == None: obj = gdb obj.pretty_printers.append(FunctionLookup(gdb, pretty_printers_dict)) def build_dictionary (): pretty_printers_dict[re.compile('^QString$')] = lambda val: QStringPrinter(val) pretty_printers_dict[re.compile('^QByteArray$')] = lambda val: QByteArrayPrinter(val) pretty_printers_dict[re.compile('^QList<.*>$')] = lambda val: QListPrinter(val, 'QList', None) pretty_printers_dict[re.compile('^QStringList$')] = lambda val: QListPrinter(val, 'QStringList', 'QString') pretty_printers_dict[re.compile('^QQueue')] = lambda val: QListPrinter(val, 'QQueue', None) pretty_printers_dict[re.compile('^QVector<.*>$')] = lambda val: QVectorPrinter(val, 'QVector') pretty_printers_dict[re.compile('^QStack<.*>$')] = lambda val: QVectorPrinter(val, 'QStack') pretty_printers_dict[re.compile('^QLinkedList<.*>$')] = lambda val: QLinkedListPrinter(val) pretty_printers_dict[re.compile('^QMap<.*>$')] = lambda val: QMapPrinter(val, 'QMap') pretty_printers_dict[re.compile('^QMultiMap<.*>$')] = lambda val: QMapPrinter(val, 'QMultiMap') pretty_printers_dict[re.compile('^QHash<.*>$')] = lambda val: QHashPrinter(val, 'QHash') pretty_printers_dict[re.compile('^QMultiHash<.*>$')] = lambda val: QHashPrinter(val, 'QMultiHash') pretty_printers_dict[re.compile('^QDate$')] = lambda val: QDatePrinter(val) pretty_printers_dict[re.compile('^QTime$')] = lambda val: QTimePrinter(val) pretty_printers_dict[re.compile('^QDateTime$')] = lambda val: QDateTimePrinter(val) pretty_printers_dict[re.compile('^QUrl$')] = lambda val: QUrlPrinter(val) pretty_printers_dict[re.compile('^QSet<.*>$')] = lambda val: QSetPrinter(val) pretty_printers_dict[re.compile('^QChar$')] = lambda val: QCharPrinter(val) pretty_printers_dict[re.compile('^QUuid')] = lambda val: QUuidPrinter(val) build_dictionary () diff --git a/debuggers/lldb/controllers/variable.cpp b/debuggers/lldb/controllers/variable.cpp index ff6c888444..4591a30f44 100644 --- a/debuggers/lldb/controllers/variable.cpp +++ b/debuggers/lldb/controllers/variable.cpp @@ -1,119 +1,119 @@ /* * LLDB-specific variable * 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 "variable.h" #include "debuglog.h" #include "debugsession.h" #include "mi/micommand.h" #include "stringhelpers.h" #include using namespace KDevelop; using namespace KDevMI::LLDB; using namespace KDevMI::MI; LldbVariable::LldbVariable(DebugSession *session, TreeModel *model, TreeItem *parent, const QString& expression, const QString& display) : MIVariable(session, model, parent, expression, display) { } void LldbVariable::refetch() { if (!topLevel() || varobj_.isEmpty()) { return; } if (!sessionIsAlive()) { return; } // update the value itself QPointer guarded_this(this); debugSession->addCommand(VarEvaluateExpression, varobj_, [guarded_this](const ResultRecord &r){ if (guarded_this && r.reason == "done" && r.hasField("value")) { guarded_this->setValue(r["value"].literal()); } }); // update children - // remove all children fisrt, this will cause some gliches in the UI, but there's no good way + // remove all children first, this will cause some gliches in the UI, but there's no good way // that we can know if there's anything changed if (isExpanded()) { deleteChildren(); fetchMoreChildren(); } } void LldbVariable::handleRawUpdate(const ResultRecord& r) { qCDebug(DEBUGGERLLDB) << "handleRawUpdate for variable" << varobj(); const Value& changelist = r["changelist"]; Q_ASSERT_X(changelist.size() <= 1, "LldbVariable::handleRawUpdate", "should only be used with one variable VarUpdate"); if (changelist.size() == 1) handleUpdate(changelist[0]); } void LldbVariable::formatChanged() { if(childCount()) { foreach(TreeItem* item, childItems) { Q_ASSERT(dynamic_cast(item)); if( MIVariable* var=dynamic_cast(item)) var->setFormat(format()); } } else { if (sessionIsAlive()) { QPointer guarded_this(this); debugSession->addCommand( VarSetFormat, QString(" %1 %2 ").arg(varobj_).arg(format2str(format())), [guarded_this](const ResultRecord &r){ if(guarded_this && r.hasField("changelist")) { if (r["changelist"].size() > 0) { guarded_this->handleRawUpdate(r); } } }); } } } QString LldbVariable::formatValue(const QString& value) const { // Data formatter emits value with unicode escape sequence for string and char, // translate them back. // Only check with first char is enough, as unquote will do the rest check if (value.startsWith('"')) { return Utils::quote(Utils::unquote(value, true)); } else if (value.startsWith('\'')) { return Utils::quote(Utils::unquote(value, true, '\''), '\''); } return value; } diff --git a/debuggers/lldb/formatters/qt.py b/debuggers/lldb/formatters/qt.py index a426f8acf7..67f4599ee0 100644 --- a/debuggers/lldb/formatters/qt.py +++ b/debuggers/lldb/formatters/qt.py @@ -1,1231 +1,1231 @@ # # LLDB data formatters for Qt types # 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 . # import calendar import time import datetime as dt from urlparse import urlsplit, urlunsplit import locale import lldb from helpers import * def __lldb_init_module(debugger, unused): debugger.HandleCommand('type synthetic add QString -w kdevelop-qt -l qt.QStringFormatter') debugger.HandleCommand('type summary add QString -w kdevelop-qt -F qt.QStringSummaryProvider') debugger.HandleCommand('type summary add QChar -w kdevelop-qt -F qt.QCharSummaryProvider') debugger.HandleCommand('type synthetic add QByteArray -w kdevelop-qt -l qt.QByteArrayFormatter') debugger.HandleCommand('type summary add QByteArray -w kdevelop-qt -e -F qt.QByteArraySummaryProvider') debugger.HandleCommand('type synthetic add -x "^QList<.+>$" -w kdevelop-qt -l qt.QListFormatter') debugger.HandleCommand('type summary add -x "^QList<.+>$" -w kdevelop-qt -e -s ""') debugger.HandleCommand('type synthetic add QStringList -w kdevelop-qt -l qt.QStringListFormatter') debugger.HandleCommand('type summary add QStringList -w kdevelop-qt -e -s ""') debugger.HandleCommand('type synthetic add -x "^QQueue<.+>$" -w kdevelop-qt -l qt.QQueueFormatter') debugger.HandleCommand('type summary add -x "^QQueue<.+>$" -w kdevelop-qt -e -s ""') debugger.HandleCommand('type synthetic add -x "^QVector<.+>$" -w kdevelop-qt -l qt.QVectorFormatter') debugger.HandleCommand('type summary add -x "^QVector<.+>$" -w kdevelop-qt -e -s ""') debugger.HandleCommand('type synthetic add -x "^QStack<.+>$" -w kdevelop-qt -l qt.QStackFormatter') debugger.HandleCommand('type summary add -x "^QStack<.+>$" -w kdevelop-qt -e -s ""') debugger.HandleCommand('type synthetic add -x "^QLinkedList<.+>$" -w kdevelop-qt -l qt.QLinkedListFormatter') debugger.HandleCommand('type summary add -x "^QLinkedList<.+>$" -w kdevelop-qt -e -s ""') debugger.HandleCommand('type synthetic add -x "^QMapNode<.+>$" -w kdevelop-qt -l qt.KeyValueFormatter') debugger.HandleCommand('type summary add -x "^QMapNode<.+>$" -w kdevelop-qt -F qt.KeyValueSummaryProvider') debugger.HandleCommand('type synthetic add -x "^QMap<.+>$" -w kdevelop-qt -l qt.QMapFormatter') debugger.HandleCommand('type summary add -x "^QMap<.+>$" -w kdevelop-qt -e -s ""') debugger.HandleCommand('type synthetic add -x "^QMultiMap<.+>$" -w kdevelop-qt -l qt.QMultiMapFormatter') debugger.HandleCommand('type summary add -x "^QMultiMap<.+>$" -w kdevelop-qt -e -s ""') debugger.HandleCommand('type synthetic add -x "^QHashNode<.+>$" -w kdevelop-qt -l qt.KeyValueFormatter') debugger.HandleCommand('type summary add -x "^QHashNode<.+>$" -w kdevelop-qt -F qt.KeyValueSummaryProvider') debugger.HandleCommand('type synthetic add -x "^QHash<.+>$" -w kdevelop-qt -l qt.QHashFormatter') debugger.HandleCommand('type summary add -x "^QHash<.+>$" -w kdevelop-qt -e -s ""') debugger.HandleCommand('type synthetic add -x "^QMultiHash<.+>$" -w kdevelop-qt -l qt.QMultiHashFormatter') debugger.HandleCommand('type summary add -x "^QMultiHash<.+>$" -w kdevelop-qt -e -s ""') debugger.HandleCommand('type synthetic add -x "^QSet<.+>$" -w kdevelop-qt -l qt.QSetFormatter') debugger.HandleCommand('type summary add -x "^QSet<.+>$" -w kdevelop-qt -e -s ""') debugger.HandleCommand('type synthetic add QDate -w kdevelop-qt -l qt.QDateFormatter') debugger.HandleCommand('type summary add QDate -w kdevelop-qt -e -F qt.QDateSummaryProvider') debugger.HandleCommand('type synthetic add QTime -w kdevelop-qt -l qt.QTimeFormatter') debugger.HandleCommand('type summary add -x QTime -w kdevelop-qt -e -F qt.QTimeSummaryProvider') debugger.HandleCommand('type synthetic add QDateTime -w kdevelop-qt -l qt.QDateTimeFormatter') debugger.HandleCommand('type summary add -x QDateTime -w kdevelop-qt -e -F qt.QDateTimeSummaryProvider') debugger.HandleCommand('type synthetic add QUrl -w kdevelop-qt -l qt.QUrlFormatter') debugger.HandleCommand('type summary add QUrl -w kdevelop-qt -e -s "${svar.(encoded)}"') debugger.HandleCommand('type synthetic add QUuid -w kdevelop-qt -l qt.QUuidFormatter') debugger.HandleCommand('type summary add QUuid -w kdevelop-qt -F qt.QUuidSummaryProvider') debugger.HandleCommand('type category enable kdevelop-qt') def printableQString(valobj): pointer = 0 length = 0 if valobj.IsValid(): d = valobj.GetChildMemberWithName('d') data = d.GetChildMemberWithName('data') offset = d.GetChildMemberWithName('offset') size = d.GetChildMemberWithName('size') isQt4 = data.IsValid() size_val = size.GetValueAsSigned(-1) alloc = d.GetChildMemberWithName('alloc').GetValueAsUnsigned(0) if isQt4: alloc += 1 # some sanity check to see if we are dealing with garbage if size_val < 0 or size_val >= alloc: return None, 0, 0 tooLarge = u'' if size_val > HiddenMemberProvider._capping_size(): tooLarge = u'...' size_val = HiddenMemberProvider._capping_size() if isQt4: pointer = data.GetValueAsUnsigned(0) elif offset.IsValid(): pointer = d.GetValueAsUnsigned(0) + offset.GetValueAsUnsigned(0) else: qarraydata_t = valobj.GetTarget().FindFirstType('QArrayData') if qarraydata_t.IsValid(): pointer = d.GetValueAsUnsigned(0) + qarraydata_t.GetByteSize() else: pointer = d.GetValueAsUnsigned(0) + 24 # Fallback to hardcoded value # size in the number of chars, each char is 2 bytes in UTF16 length = size_val * 2 if length == 0: return u'', pointer, length try: error = lldb.SBError() string_data = valobj.process.ReadMemory(pointer, length, error) # The QString object might be not yet initialized. In this case size is a bogus value, # and memory access may fail if error.Success(): string = string_data.decode('utf-16') return string + tooLarge, pointer, length except: pass return None, 0, 0 def QStringSummaryProvider(valobj, internal_dict): if valobj.IsValid(): content = valobj.GetChildMemberWithName('(content)') if content.IsValid(): return content.GetSummary() else: # No synthetic provider installed, get the content by ourselves printable, _, _ = printableQString(valobj) if printable is not None: return quote(printable) return '' class QStringFormatter(HiddenMemberProvider): """A lldb synthetic provider for QString""" def __init__(self, valobj, internal_dict): super(QStringFormatter, self).__init__(valobj, internal_dict) self._qchar_type = valobj.GetTarget().FindFirstType('QChar') self._qchar_size = self._qchar_type.GetByteSize() def _update(self): printable, dataPointer, byteLength = printableQString(self.valobj) strLength = byteLength / 2 self._num_children = strLength if printable is not None: for idx in range(0, strLength): var = self.valobj.CreateValueFromAddress('[{}]'.format(idx), dataPointer + idx * self._qchar_size, self._qchar_type) self._addChild(var) quoted_printable_expr = quote(printable) self._addChild(('(content)', quoted_printable_expr), hidden=True) def QCharSummaryProvider(valobj, internal_dict): if valobj.IsValid(): ucs = valobj.GetChildMemberWithName('ucs').GetValueAsUnsigned(0) return unichr(ucs).__repr__().lstrip('u') return None def printableQByteArray(valobj): if valobj.IsValid(): d = valobj.GetChildMemberWithName('d') data = d.GetChildMemberWithName('data') offset = d.GetChildMemberWithName('offset') size = d.GetChildMemberWithName('size') isQt4 = data.IsValid() size_val = size.GetValueAsSigned(-1) alloc = d.GetChildMemberWithName('alloc').GetValueAsUnsigned(0) if isQt4: alloc += 1 # sanity check if size_val < 0 or size_val >= alloc: return None, 0, 0 tooLarge = u'' if size_val > HiddenMemberProvider._capping_size(): tooLarge = u'...' size_val = HiddenMemberProvider._capping_size() if isQt4: pointer = data.GetValueAsUnsigned(0) elif offset.IsValid(): pointer = d.GetValueAsUnsigned(0) + offset.GetValueAsUnsigned(0) else: pointer = d.GetValueAsUnsigned(0) + 24 # Fallback to hardcoded value length = size_val if length == 0: return u'', pointer, length try: error = lldb.SBError() string_data = valobj.process.ReadMemory(pointer, length, error) # The object might be not yet initialized. In this case size is a bogus value, # and memory access may fail if error.Success(): # replace non-ascii byte with a space and get a printable version ls = list(string_data) for idx in range(0, length): byte = ord(ls[idx]) if byte >= 128 or byte < 0: ls[idx] = hex(byte).replace('0', '\\', 1) elif byte == 0: # specical handle for 0, as hex(0) returns '\\x0' ls[idx] = '\\x00' string = u''.join(ls) return string + tooLarge, pointer, length except: pass return None, 0, 0 def QByteArraySummaryProvider(valobj, internal_dict): if valobj.IsValid(): content = valobj.GetChildMemberWithName('(content)') if content.IsValid(): return content.GetSummary() else: # Our synthetic provider is not installed, get the content by ourselves printable, _, _ = printableQByteArray(valobj) if printable is not None: return quote(printable) return '' class QByteArrayFormatter(HiddenMemberProvider): """A lldb synthetic provider for QByteArray""" def __init__(self, valobj, internal_dict): super(QByteArrayFormatter, self).__init__(valobj, internal_dict) self._char_type = valobj.GetType().GetBasicType(lldb.eBasicTypeChar) self._char_size = self._char_type.GetByteSize() def _update(self): printable, dataPointer, byteLength = printableQByteArray(self.valobj) self._num_children = byteLength if printable is not None: for idx in range(0, self._num_children): var = self.valobj.CreateValueFromAddress('[{}]'.format(idx), dataPointer + idx * self._char_size, self._char_type) self._addChild(var) quoted_printable_expr = quote(printable) self._addChild(('(content)', quoted_printable_expr), hidden=True) class BasicListFormatter(HiddenMemberProvider): """A lldb synthetic provider for QList like types""" def __init__(self, valobj, internal_dict, item_typename): super(BasicListFormatter, self).__init__(valobj, internal_dict) if item_typename is None: self._item_type = valobj.GetType().GetTemplateArgumentType(0) else: self._item_type = valobj.GetTarget().FindFirstType(item_typename) pvoid_type = valobj.GetTarget().GetBasicType(lldb.eBasicTypeVoid).GetPointerType() self._pvoid_size = pvoid_type.GetByteSize() #from QTypeInfo::isLarge isLarge = self._item_type.GetByteSize() > self._pvoid_size #unfortunately we can't use QTypeInfo::isStatic as it's all inlined, so use #this list of types that use Q_DECLARE_TYPEINFO(T, Q_MOVABLE_TYPE) #(obviously it won't work for custom types) movableTypes = ['QRect', 'QRectF', 'QString', 'QMargins', 'QLocale', 'QChar', 'QDate', 'QTime', 'QDateTime', 'QVector', 'QRegExpr', 'QPoint', 'QPointF', 'QByteArray', 'QSize', 'QSizeF', 'QBitArray', 'QLine', 'QLineF', 'QModelIndex', 'QPersitentModelIndex', 'QVariant', 'QFileInfo', 'QUrl', 'QXmlStreamAttribute', 'QXmlStreamNamespaceDeclaration', 'QXmlStreamNotationDeclaration', 'QXmlStreamEntityDeclaration', 'QPair'] movableTypes = [valobj.GetTarget().FindFirstType(t) for t in movableTypes] #this list of types that use Q_DECLARE_TYPEINFO(T, Q_PRIMITIVE_TYPE) (from qglobal.h) primitiveTypes = ['bool', 'char', 'signed char', 'unsigned char', 'short', 'unsigned short', 'int', 'unsigned int', 'long', 'unsigned long', 'long long', 'unsigned long long', 'float', 'double'] primitiveTypes = [valobj.GetTarget().FindFirstType(t) for t in primitiveTypes] if self._item_type in movableTypes or self._item_type in primitiveTypes: isStatic = False else: isStatic = not self._item_type.IsPointerType() #see QList::Node::t() self._externalStorage = isLarge or isStatic # If is external storage, then the node (a void*) is a pointer to item # else the item is stored inside the node if self._externalStorage: self._node_type = self._item_type.GetPointerType() else: self._node_type = self._item_type def _update(self): d = self.valobj.GetChildMemberWithName('d') begin = d.GetChildMemberWithName('begin').GetValueAsSigned(-1) end = d.GetChildMemberWithName('end').GetValueAsSigned(-1) array = d.GetChildMemberWithName('array') # sanity check if begin < 0 or end < 0 or end < begin: return self._num_children = end - begin for idx in range(0, self._num_children): offset = (begin + idx) * self._pvoid_size name = '[{}]'.format(idx) var = array.CreateChildAtOffset(name, offset, self._node_type) if self._externalStorage: # can't use var.Dereference() directly, as the returned SBValue has '*' prepended # to its name. And SBValue name can't be changed once constructed. var = self.valobj.CreateValueFromData(name, var.GetPointeeData(), self._item_type) self._addChild(var) class QListFormatter(BasicListFormatter): """lldb synthetic provider for QList""" def __init__(self, valobj, internal_dict): super(QListFormatter, self).__init__(valobj, internal_dict, None) class QStringListFormatter(BasicListFormatter): """lldb synthetic provider for QStringList""" def __init__(self, valobj, internal_dict): super(QStringListFormatter, self).__init__(valobj, internal_dict, 'QString') class QQueueFormatter(BasicListFormatter): """lldb synthetic provider for QQueue""" def __init__(self, valobj, internal_dict): super(QQueueFormatter, self).__init__(valobj.GetChildAtIndex(0), internal_dict, None) self.actualobj = valobj def update(self): self.valobj = self.actualobj.GetChildAtIndex(0) super(QQueueFormatter, self).update() class BasicVectorFormatter(HiddenMemberProvider): """A lldb synthetic provider for QVector like types""" def __init__(self, valobj, internal_dict): super(BasicVectorFormatter, self).__init__(valobj, internal_dict) self._item_type = valobj.GetType().GetTemplateArgumentType(0) self._item_size = self._item_type.GetByteSize() def _update(self): d = self.valobj.GetChildMemberWithName('p') # Qt4 has 'p', Qt5 doesn't isQt4 = d.IsValid() if isQt4: pArray = d.GetChildMemberWithName('array').AddressOf().GetValueAsUnsigned(0) else: d = self.valobj.GetChildMemberWithName('d') offset = d.GetChildMemberWithName('offset') pArray = d.GetValueAsUnsigned(0) + offset.GetValueAsUnsigned(0) # sanity check if not toSBPointer(self.valobj, pArray, self._item_type).IsValid(): return #self._num_children = d.GetChildMemberWithName('size').GetValueAsUnsigned(0) self._num_children = d.GetChildMemberWithName('size').GetValueAsSigned(-1) if self._num_children < 0: return if self._num_children > self._capping_size(): self._num_children = self._capping_size() for idx in range(0, self._num_children): var = self.valobj.CreateValueFromAddress('[{}]'.format(idx), pArray + idx * self._item_size, self._item_type) self._addChild(var) class QVectorFormatter(BasicVectorFormatter): """lldb synthetic provider for QVector""" def __init__(self, valobj, internal_dict): super(QVectorFormatter, self).__init__(valobj, internal_dict) class QStackFormatter(BasicVectorFormatter): """lldb synthetic provider for QStack""" def __init__(self, valobj, internal_dict): super(QStackFormatter, self).__init__(valobj.GetChildAtIndex(0), internal_dict) self.actualobj = valobj def update(self): self.valobj = self.actualobj.GetChildAtIndex(0) super(QStackFormatter, self).update() class QLinkedListFormatter(HiddenMemberProvider): """A lldb synthetic provider for QLinkedList""" def __init__(self, valobj, internal_dict): super(QLinkedListFormatter, self).__init__(valobj, internal_dict) self._item_type = valobj.GetType().GetTemplateArgumentType(0) def _update(self): d = self.valobj.GetChildMemberWithName('d') self._num_children = d.GetChildMemberWithName('size').GetValueAsSigned(-1) if self._num_children < 0: return node = self.valobj.GetChildMemberWithName('e').GetChildMemberWithName('n') for idx in range(0, self._num_children): if not node.IsValid(): self._members = [] self._num_children = 0 return var = node.GetChildMemberWithName('t') node = node.GetChildMemberWithName('n') var = self.valobj.CreateValueFromData('[{}]'.format(idx), var.GetData(), self._item_type) self._addChild(var) class KeyValueFormatter(object): """A lldb synthetic provider for (key,value) pair like types""" def __init__(self, valobj, internal_dict): self.valobj = valobj self._key_item = None self._val_item = None def num_children(self): return 2 def has_children(self): return True def get_child_index(self, name): if name == 'key': return 0 elif name == 'value': return 1 return None def get_child_at_index(self, idx): if idx < 0 or idx >= 2: return None if idx == 0: return self._key_item elif idx == 1: return self._val_item return None def update(self): if not self.valobj.IsValid(): return self._key_item = self.valobj.GetChildMemberWithName('key') self._val_item = self.valobj.GetChildMemberWithName('value') def KeyValueSummaryProvider(valobj, internal_dict): if not valobj.IsValid(): return None key = valobj.GetChildMemberWithName('key') value = valobj.GetChildMemberWithName('value') return '({}, {})'.format(key.GetSummary(), value.GetValue()) class BasicMapFormatter(HiddenMemberProvider): """A lldb synthetic provider for QMap like types""" def __init__(self, valobj, internal_dict): super(BasicMapFormatter, self).__init__(valobj, internal_dict) self_type = valobj.GetType() key_type = self_type.GetTemplateArgumentType(0) val_type = self_type.GetTemplateArgumentType(1) # the ' ' between two template arguments is significant, # otherwise FindFirstType returns None node_typename = 'QMapNode<{}, {}>'.format(key_type.GetName(), val_type.GetName()) self._node_type = valobj.GetTarget().FindFirstType(node_typename) e = self.valobj.GetChildMemberWithName('e') self.isQt4 = e.IsValid() if self.isQt4: self._payload_size = self._qt4_calc_payload(key_type, val_type) def _qt4_calc_payload(self, key_type, val_type): """calculate payload size for Qt4""" str = lldb.SBStream() self.valobj.GetExpressionPath(str, True) expr = '{}.payload()'.format(str.GetData()) ret = lldb.frame.EvaluateExpression(expr).GetValueAsUnsigned(0) if ret != 0: return ret else: # if the inferior function call didn't work, let's try to calculate ourselves target = self.valobj.GetTarget() pvoid_type = target.GetBasicType(lldb.eBasicTypeVoid).GetPointerType() pvoid_size = pvoid_type.GetByteSize() # we can't use QMapPayloadNode as it's inlined # as a workaround take the sum of sizeof(members) ret = key_type.GetByteSize() ret += val_type.GetByteSize() ret += pvoid_size # but because of data alignment the value can be higher # so guess it's aliged by sizeof(void*) # TODO: find a real solution for this problem ret += ret % pvoid_size # for some reason booleans are different if val_type == target.GetBasicType(lldb.eBasicTypeBool): ret += 2 ret -= pvoid_size return ret class _iteratorQt4(Iterator): """Map iterator for Qt4""" def __init__(self, headerobj, node_type, payload_size): self.current = headerobj.GetChildMemberWithName('forward').GetChildAtIndex(0) self.header_addr = headerobj.GetValueAsUnsigned(0) self.node_type = node_type self.payload_size = payload_size # sanity check self.is_garbage = False if not validAddr(headerobj, self.header_addr): self.is_garbage = True if not validPointer(self.current): self.is_garbage = True def __iter__(self): return self def concrete(self, pdata_node): pnode_addr = pdata_node.GetValueAsUnsigned(0) pnode_addr -= self.payload_size return toSBPointer(self.current, pnode_addr, self.node_type) def __next__(self): if self.is_garbage: raise StopIteration if self.current.GetValueAsUnsigned(0) == self.header_addr: raise StopIteration pnode = self.concrete(self.current) self.current = self.current.GetChildMemberWithName('forward').GetChildAtIndex(0) return pnode class _iteratorQt5(Iterator): """Map iterator for Qt5""" def __init__(self, dataobj, pnode_type): self.pnode_type = pnode_type self.root = dataobj.GetChildMemberWithName('header') self.current = lldb.SBValue() # We store the path here to avoid keeping re-fetching # values from the inferior (also, skip the pointer # arithmetic involved in using the parent pointer self.path = [] def __iter__(self): return self def moveToNextNode(self): def isNullPointer(val): return not val.IsValid() or val.GetValueAsUnsigned(0) == 0 if isNullPointer(self.current): # find the leftmost node left = self.root.GetChildMemberWithName('left') if isNullPointer(left): return False self.current = self.root while not isNullPointer(left): self.path.append(self.current) self.current = left left = self.current.GetChildMemberWithName('left') else: right = self.current.GetChildMemberWithName('right') if not isNullPointer(right): self.path.append(self.current) self.current = right left = self.current.GetChildMemberWithName('left') while not isNullPointer(left): self.path.append(self.current) self.current = left left = self.current.GetChildMemberWithName('left') else: last = self.current self.current = self.path.pop() right = self.current.GetChildMemberWithName('right') while right.GetValueAsUnsigned(0) == last.GetValueAsUnsigned(0): last = self.current self.current = self.path.pop() right = self.current.GetChildMemberWithName('right') # if there are no more parents, we are at the root if len(self.path) == 0: return False return True def __next__(self): if not self.moveToNextNode(): raise StopIteration return self.current.Cast(self.pnode_type) def _update(self): pnode_type = self._node_type.GetPointerType() if self.isQt4: e = self.valobj.GetChildMemberWithName('e') it = self._iteratorQt4(e, self._node_type, self._payload_size) else: d = self.valobj.GetChildMemberWithName('d') it = self._iteratorQt5(d, pnode_type) self._num_children = 0 for pnode in it: # dereference node and change to a user friendly name name = '[{}]'.format(self._num_children) self._num_children += 1 var = self.valobj.CreateValueFromData(name, pnode.GetPointeeData(), self._node_type) self._addChild(var) class QMapFormatter(BasicMapFormatter): """lldb synthethic provider for QMap""" def __init__(self, valobj, internal_dict): super(QMapFormatter, self).__init__(valobj, internal_dict) class QMultiMapFormatter(BasicMapFormatter): """lldb synthethic provider for QMap""" def __init__(self, valobj, internal_dict): super(QMultiMapFormatter, self).__init__(valobj.GetChildAtIndex(0), internal_dict) self.actualobj = valobj def update(self): self.valobj = self.actualobj.GetChildAtIndex(0) super(QMultiMapFormatter, self).update() class BasicHashFormatter(HiddenMemberProvider): """A lldb synthetic provider for QHash like types""" def __init__(self, valobj, internal_dict): super(BasicHashFormatter, self).__init__(valobj, internal_dict) self_type = valobj.GetType() self._key_type = self_type.GetTemplateArgumentType(0) self._val_type = self_type.GetTemplateArgumentType(1) # the ' ' between two template arguments is significant, # otherwise FindFirstType returns None node_typename = 'QHashNode<{}, {}>'.format(self._key_type.GetName(), self._val_type.GetName()) self._node_type = valobj.GetTarget().FindFirstType(node_typename) class _iterator(Iterator): """Hash iterator""" def __init__(self, valobj, pnode_type): d = valobj.GetChildMemberWithName('d') self.buckets = d.GetChildMemberWithName('buckets') self.null_node = valobj.GetChildMemberWithName('e') self.pnode_type = pnode_type self.num_buckets = d.GetChildMemberWithName('numBuckets').GetValueAsSigned(-1) self.is_garbage = False if self.num_buckets < -1: self.is_garbage = True return self.current = self.firstNode() def __iter__(self): return self def findNode(self, start=0): """Iterate through buckets, start at `start`, return any bucket the is not the null_node, or the null_node itself if nothing found. - adapted from QHashData::fisrtNode + adapted from QHashData::firstNode """ null_node_addr = self.null_node.GetValueAsUnsigned(0) for idx in range(start, self.num_buckets): # self.buckets has type QHashData::Node**, not an array # calling GetChildAtIndex with use_synthetic=True so the pointer is used as an array bucket = self.buckets.GetChildAtIndex(idx, lldb.eDynamicCanRunTarget, True) if bucket.GetValueAsUnsigned(0) != null_node_addr: # in Qt4, QHashData::Node is incomplete type, but QHashNode is complete, # so always use QHashNode return bucket.Cast(self.pnode_type) return self.null_node def firstNode(self): return self.findNode() def moveToNextNode(self): """Get the nextNode after the current, see also QHashData::nextNode().""" next = self.current.GetChildMemberWithName('next') if next.GetValueAsUnsigned(0) != self.null_node.GetValueAsUnsigned(0): self.current = next else: h = self.current.GetChildMemberWithName('h').GetValueAsUnsigned(0) start = (h % self.num_buckets) + 1 self.current = self.findNode(start) def __next__(self): if self.is_garbage: raise StopIteration if self.current.GetValueAsUnsigned(0) == self.null_node.GetValueAsUnsigned(0): raise StopIteration pnode = self.current self.moveToNextNode() return pnode def _update(self): self._num_children = self.valobj.GetChildMemberWithName('d').GetChildMemberWithName('size').GetValueAsSigned(-1) if self._num_children < 0: return idx = 0 for pnode in self._iterator(self.valobj, self._node_type.GetPointerType()): # dereference node and change to a user friendly name name = '[{}]'.format(idx) idx += 1 var = self.valobj.CreateValueFromData(name, pnode.GetPointeeData(), self._node_type) self._addChild(var) if idx != self._num_children: self._members = [] self._num_children = 0 class QHashFormatter(BasicHashFormatter): """lldb synthethic provider for QHash""" def __init__(self, valobj, internal_dict): super(QHashFormatter, self).__init__(valobj, internal_dict) class QMultiHashFormatter(BasicHashFormatter): """lldb synthethic provider for QHash""" def __init__(self, valobj, internal_dict): super(QMultiHashFormatter, self).__init__(valobj.GetChildAtIndex(0), internal_dict) self.actualobj = valobj def update(self): self.valobj = self.actualobj.GetChildAtIndex(0) super(QMultiHashFormatter, self).update() class QSetFormatter(HiddenMemberProvider): """lldb synthetic provider for QSet""" def __init__(self, valobj, internal_dict): super(QSetFormatter, self).__init__(valobj, internal_dict) self._hash_formatter = QHashFormatter(valobj.GetChildMemberWithName('q_hash'), internal_dict) def num_children(self): return self._num_children pass def _update(self): self._hash_formatter.valobj = self.valobj.GetChildMemberWithName('q_hash') self._hash_formatter.update() self._num_children = 0 for node in self._hash_formatter._members: keydata = node.GetChildMemberWithName('key').GetData() name = '[{}]'.format(self._num_children) var = self.valobj.CreateValueFromData(name, keydata, self._hash_formatter._key_type) self._addChild(var) self._num_children += 1 class QDateFormatter(HiddenMemberProvider): """lldb synthetic provider for QDate""" def __init__(self, valobj, internal_dict): super(QDateFormatter, self).__init__(valobj, internal_dict) self._add_original = False self._qstring_type = valobj.GetTarget().FindFirstType('QString') def has_children(self): return True @staticmethod def parse(julianDay): """Copied from Qt srources""" if julianDay == 0: return None if julianDay >= 2299161: # Gregorian calendar starting from October 15, 1582 # This algorithm is from Henry F. Fliegel and Thomas C. Van Flandern ell = julianDay + 68569; n = (4 * ell) / 146097; ell = ell - (146097 * n + 3) / 4; i = (4000 * (ell + 1)) / 1461001; ell = ell - (1461 * i) / 4 + 31; j = (80 * ell) / 2447; d = ell - (2447 * j) / 80; ell = j / 11; m = j + 2 - (12 * ell); y = 100 * (n - 49) + i + ell; else: # Julian calendar until October 4, 1582 # Algorithm from Frequently Asked Questions about Calendars by Claus Toendering julianDay += 32082; dd = (4 * julianDay + 3) / 1461; ee = julianDay - (1461 * dd) / 4; mm = ((5 * ee) + 2) / 153; d = ee - (153 * mm + 2) / 5 + 1; m = mm + 3 - 12 * (mm / 10); y = dd - 4800 + (mm / 10); if y <= 0: return None return dt.date(y, m, d) def _update(self): # FIXME: Calling functions returns incorrect SBValue for complex type in lldb ## toString #res = invoke(self.valobj, 'toString', '0') #self._addChild(rename('toString', res)) # jd julianDay = self.valobj.GetChildMemberWithName('jd') self._addChild(julianDay) pydate = self.parse(julianDay.GetValueAsUnsigned(0)) if pydate is None: return # (ISO) iso_str = pydate.isoformat().decode().__repr__().lstrip("u'").rstrip("'") self._addChild(('(ISO)', quote(iso_str))) # (Locale) locale_encoding = [locale.getlocale()[1]] if locale_encoding[0] is None: locale_encoding = [] locale_str = pydate.strftime('%x').decode(*locale_encoding).__repr__().lstrip("u'").rstrip("'") self._addChild(('(Locale)', quote(locale_str))) def QDateSummaryProvider(valobj, internal_dict): if valobj.IsValid(): content = valobj.GetChildMemberWithName('(Locale)') if content.IsValid(): return content.GetSummary() else: # No synthetic provider installed, get the content by ourselves pydate = QDateFormatter.parse(valobj.GetChildMemberWithName('jd').GetValueAsUnsigned(0)) if pydate is not None: content = pydate.isoformat().decode().__repr__().lstrip("u'").rstrip("'") return quote(content) return '' class QTimeFormatter(HiddenMemberProvider): """lldb synthetic provider for QTime""" def __init__(self, valobj, internal_dict): super(QTimeFormatter, self).__init__(valobj, internal_dict) self._add_original = False def has_children(self): return True @staticmethod def parse(ds): if ds < 0: return None MSECS_PER_HOUR = 3600000 SECS_PER_MIN = 60 MSECS_PER_MIN = 60000 hour = ds / MSECS_PER_HOUR minute = (ds % MSECS_PER_HOUR) / MSECS_PER_MIN second = (ds / 1000)%SECS_PER_MIN msec = ds % 1000 return dt.time(hour, minute, second, msec) def _update(self): # FIXME: Calling functions returns incorrect SBValue for complex type in lldb ## toString #res = invoke(self.valobj, 'toString', '0') #self._addChild(rename('toString', res)) # mds mds = self.valobj.GetChildMemberWithName('mds') self._addChild(mds) pytime = self.parse(mds.GetValueAsUnsigned(0)) if pytime is None: return # (ISO) iso_str = pytime.isoformat().decode().__repr__().lstrip("u'").rstrip("'") self._addChild(('(ISO)', quote(iso_str))) # (Locale) locale_encoding = [locale.getlocale()[1]] if locale_encoding[0] is None: locale_encoding = [] locale_str = pytime.strftime('%X').decode(*locale_encoding).__repr__().lstrip("u'").rstrip("'") self._addChild(('(Locale)', quote(locale_str))) def QTimeSummaryProvider(valobj, internal_dict): if valobj.IsValid(): content = valobj.GetChildMemberWithName('(Locale)') if content.IsValid(): return content.GetSummary() else: # No synthetic provider installed, get the content by ourselves pytime = QTimeFormatter.parse(valobj.GetChildMemberWithName('mds').GetValueAsUnsigned(0)) if pytime is not None: content = pytime.isoformat().decode().__repr__().lstrip("u'").rstrip("'") return quote(content) return None class QDateTimeFormatter(HiddenMemberProvider): """lldb synthetic provider for QTime""" def __init__(self, valobj, internal_dict): super(QDateTimeFormatter, self).__init__(valobj, internal_dict) def has_children(self): return True @staticmethod def parse(time_t, utc=False): if time_t is None: return None totuple = time.gmtime if utc else time.localtime return totuple(time_t) @staticmethod def getdata(var): # FIXME: data member is in private structure, which has no complete type when no debug info # available for Qt.So we can only rely on function call. # The comments in Qt source code says data member will be inlined in Qt6, res = invoke(var, 'toTime_t') return res def _update(self): time_t = self.getdata(self.valobj) if not time_t.IsValid(): return locale_encoding = [locale.getlocale()[1]] if locale_encoding[0] is None: locale_encoding = [] # toTime_t self._addChild(rename('toTime_t', time_t)) # time tuple in local time and utc time local_tt = self.parse(time_t.GetValueAsUnsigned(0)) utc_tt = self.parse(time_t.GetValueAsUnsigned(0), utc=True) # (ISO) formatted = time.strftime('%Y-%m-%d %H:%M:%S', utc_tt).decode(*locale_encoding).__repr__().lstrip("u'").rstrip("'") self._addChild(('(ISO)', quote(formatted))) def locale_fmt(name, tt): formatted = time.strftime('%c', tt).decode(*locale_encoding).__repr__().lstrip("u'").rstrip("'") self._addChild((name, quote(formatted))) # (Locale) locale_fmt('(Locale)', local_tt) # (UTC) locale_fmt('(UTC)', utc_tt) # FIXME: Calling functions returns incorrect SBValue for complex type in lldb ## toString #res = invoke(self.valobj, 'toString', '0') #print 'tostring', res #self._addChild(rename('toString', res)) ## toLocalTime #res = invoke(self.valobj, 'toTimeSpec', '0') # Qt::LocalTime == 0 #print 'tolocaltime', res #self._addChild(rename('toLocalTime', res)) def QDateTimeSummaryProvider(valobj, internal_dict): if valobj.IsValid(): content = valobj.GetChildMemberWithName('(Locale)') if content.IsValid(): return content.GetSummary() else: # No synthetic provider installed, get the content by ourselves pytime = QDateTimeFormatter.parse(QDateTimeFormatter.getdata(valobj).GetValueAsUnsigned(0)) if pytime is not None: #content = pytime.isoformat().decode().__repr__().lstrip("u'").rstrip("'") #return quote(content) pass return None class QUrlFormatter(HiddenMemberProvider): """docstring for QUrlFormatter""" def __init__(self, valobj, internal_dict): super(QUrlFormatter, self).__init__(valobj, internal_dict) target = valobj.GetTarget() self._int_type = target.GetBasicType(lldb.eBasicTypeInt) self._pvoid_type = target.GetBasicType(lldb.eBasicTypeVoid).GetPointerType() self._qstring_type = target.FindFirstType('QString') self._qbytearray_type = target.FindFirstType('QByteArray') def parseQt5Data(self, dataobj): def constructEncoded(port, scheme, username, password, host, path, query, fragment): netloc = '' host_str = printableQString(host)[0] if host_str is not None: username_str = printableQString(username)[0] if username_str is not None: netloc += username_str password_str = printableQString(password)[0] if password_str is not None: netloc += ':' + password_str netloc += "@" + host_str port_num = port.GetValueAsSigned(-1) if port_num != -1: netloc += ":" + str(port_num) url = urlunsplit((printableQString(scheme)[0], netloc, printableQString(path)[0], printableQString(query)[0], printableQString(fragment)[0])) encoded = None if len(url) > 0: encoded = ('(encoded)', quote(url)) return (encoded, port, scheme, username, password, host, path, query, fragment) # try if there's debug info available port = dataobj.GetChildMemberWithName('port') if port.IsValid(): scheme = dataobj.GetChildMemberWithName('scheme') username = dataobj.GetChildMemberWithName('userName') password = dataobj.GetChildMemberWithName('password') host = dataobj.GetChildMemberWithName('host') path = dataobj.GetChildMemberWithName('path') query = dataobj.GetChildMemberWithName('query') fragment = dataobj.GetChildMemberWithName('fragment') return constructEncoded(port, scheme, username, password, host, path, query, fragment) - # if no debug information is avaliable for Qt, try guessing the correct address + # if no debug information is available for Qt, try guessing the correct address # problem with this is that if QUrlPrivate members get changed, this fails addr = dataobj.GetValueAsUnsigned(0) # skip QAtomicInt ref addr += self._int_type.GetByteSize() # handle int port port = dataobj.CreateValueFromAddress('(port)', addr, self._int_type) addr += self._int_type.GetByteSize() # handle QString scheme scheme = dataobj.CreateValueFromAddress('(scheme)', addr, self._qstring_type) addr += self._qstring_type.GetByteSize() # handle QString username username = dataobj.CreateValueFromAddress('(userName)', addr, self._qstring_type) addr += self._qstring_type.GetByteSize() # handle QString password password = dataobj.CreateValueFromAddress('(password)', addr, self._qstring_type) addr += self._qstring_type.GetByteSize() # handle QString host host = dataobj.CreateValueFromAddress('(host)', addr, self._qstring_type) addr += self._qstring_type.GetByteSize() # handle QString path path = dataobj.CreateValueFromAddress('(path)', addr, self._qstring_type) addr += self._qstring_type.GetByteSize() # handle QString query query = dataobj.CreateValueFromAddress('(query)', addr, self._qstring_type) addr += self._qstring_type.GetByteSize() # handle QString fragment fragment = dataobj.CreateValueFromAddress('(fragment)', addr, self._qstring_type) return constructEncoded(port, scheme, username, password, host, path, query, fragment) def parseQt4Data(self, dataobj): def parseComponents(encodedobj): url, _, _ = printableQByteArray(encodedobj) if url is None: return (None,) * 9 res = urlsplit(url) port = dataobj.CreateValueFromExpression('(port)', str(res.port if res.port is not None else -1)) scheme = ('(scheme)', quote(res.scheme)) username = ('(username)', quote(res.username if res.username is not None else '')) password = ('(password)', quote(res.password if res.password is not None else '')) host = ('(host)', quote(res.hostname if res.hostname is not None else '')) path = ('(path)', quote(res.path)) query = ('(query)', quote(res.query)) fragment = ('(fragment)', quote(res.fragment)) encoded = ('(encoded)', quote(url)) return (encoded, port, scheme, username, password, host, path, query, fragment) encodedOriginal = dataobj.GetChildMemberWithName('encodedOriginal') if encodedOriginal.IsValid(): return parseComponents(encodedOriginal) - # if no debug information is avaliable for Qt, try guessing the correct address + # if no debug information is available for Qt, try guessing the correct address # problem with this is that if QUrlPrivate members get changed, this fails addr = dataobj.GetValueAsUnsigned(0) if not validAddr(dataobj, addr): return (None,) * 9 # skip QAtomicInt ref addr += self._int_type.GetByteSize() # alignment, # The largest member is QString and QByteArray, which are 8 bytes (one sizeof(void*)), # int is aligned to 8 bytes addr += self._pvoid_type.GetByteSize() - self._int_type.GetByteSize() # These members are always empty: scheme, userName, password, host, path, query (QByteArray), fragment addr += self._qstring_type.GetByteSize() * 6 addr += self._qbytearray_type.GetByteSize() # handle QByteArray encodedOriginal encoded = dataobj.CreateValueFromAddress('(encoded)', addr, self._qbytearray_type) if not encoded.IsValid(): return (None,) * 9 return parseComponents(encoded) def _update(self): dataobj = self.valobj.GetChildMemberWithName('d') # first try to access Qt4 data (encoded, port, scheme, username, password, host, path, query, fragment) = self.parseQt4Data(dataobj) if encoded is not None: self._addChild(port) self._addChild(scheme) self._addChild(username) self._addChild(password) self._addChild(host) self._addChild(path) self._addChild(query) self._addChild(fragment) self._addChild(encoded, hidden=True) return # if this fails, maybe we deal with Qt5 (encoded, port, scheme, username, password, host, path, query, fragment) = self.parseQt5Data(dataobj) if encoded is not None: self._addChild(port) self._addChild(scheme) self._addChild(username) self._addChild(password) self._addChild(host) self._addChild(path) self._addChild(query) self._addChild(fragment) self._addChild(encoded, hidden=True) return # if above fails, try to print directly. # But this might not work, and could lead to issues # (see http://sourceware-org.1504.n7.nabble.com/help-Calling-malloc-from-a-Python-pretty-printer-td284031.html) res = invoke(self.valobj, 'toString', '(QUrl::FormattingOptions)0') # QUrl::PrettyDecoded == 0 if res.IsValid(): self._addChild(rename('(encoded)', res)) # if everything fails, we have no choice but to show the original member self._add_original = False self._addChild(self.valobj.GetChildMemberWithName('d')) class QUuidFormatter(HiddenMemberProvider): """A lldb synthetic provider for QUuid""" def __init__(self, valobj, internal_dict): super(QUuidFormatter, self).__init__(valobj, internal_dict) def has_children(self): return False def QUuidSummaryProvider(valobj, internal_dict): data = [valobj.GetChildMemberWithName(name).GetValueAsUnsigned(0) for name in ['data1', 'data2', 'data3']] data += [val.GetValueAsUnsigned(0) for val in valobj.GetChildMemberWithName('data4')] return 'QUuid({{{:02x}-{:02x}-{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}}})'.format(*data) diff --git a/debuggers/lldb/unittests/test_lldb.cpp b/debuggers/lldb/unittests/test_lldb.cpp index 0f7b62347b..d1a6283814 100644 --- a/debuggers/lldb/unittests/test_lldb.cpp +++ b/debuggers/lldb/unittests/test_lldb.cpp @@ -1,1904 +1,1904 @@ /* * 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 WritableEnvironmentGroupList : public EnvironmentGroupList { public: explicit WritableEnvironmentGroupList(KConfig* config) : EnvironmentGroupList(config) {} using EnvironmentGroupList::variables; using EnvironmentGroupList::saveSettings; using EnvironmentGroupList::removeGroup; }; class TestLaunchConfiguration : public ILaunchConfiguration { public: TestLaunchConfiguration(const QUrl& executable = findExecutable("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 QString("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: 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("org.kdevelop.IExecutePlugin", "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("lldb_debugeeechoenv")); cfg.config().writeEntry("EnvironmentGroup", "LldbTestGroup"); WritableEnvironmentGroupList envGroups(cfg.rootConfig()); envGroups.removeGroup("LldbTestGroup"); auto &envs = envGroups.variables("LldbTestGroup"); envs["VariableA"] = "-A' \" complex --value"; envs["VariableB"] = "-B' \" complex --value"; envGroups.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 begining of foo():23: ++i; + QCOMPARE(session->currentLine(), 23-1); // at the beginning of foo():23: ++i; session->addUserCommand(QString("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("x[0] == 'H'"); b = breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 23); b->setCondition("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("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("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("i"); b->setCondition("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("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("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("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("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("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("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("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, "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, "break disable 2"); session->addCommand(MI::NonMI, "break modify -c 'i == 1' 2"); session->addCommand(MI::NonMI, "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, "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("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("main"); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->addCommand(MI::NonMI, "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(QString("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("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("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("lldb_debugeemultilocbreakpoint")); breakpoints()->addCodeBreakpoint("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("lldb_debugeemultiplebreakpoint")); auto b = breakpoints()->addCodeBreakpoint("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("lldb_debugeemultilocbreakpoint")); breakpoints()->addCodeBreakpoint("main"); QVERIFY(session->startDebugging(&c, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); session->addCommand(MI::NonMI, "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, ""); 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("lldb_debugeeslow")); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint("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, "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("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, "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(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "foo()"); COMPARE_DATA(tIdx.child(0, 2), m_debugeeFileName+":23"); COMPARE_DATA(tIdx.child(1, 0), "1"); COMPARE_DATA(tIdx.child(1, 1), "main"); COMPARE_DATA(tIdx.child(1, 2), m_debugeeFileName+":29"); COMPARE_DATA(tIdx.child(2, 0), "2"); COMPARE_DATA(tIdx.child(2, 1), "__libc_start_main"); COMPARE_DATA(tIdx.child(3, 0), "3"); COMPARE_DATA(tIdx.child(3, 1), "_start"); session->stepOut(); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); COMPARE_DATA(tIdx, "#1 at main"); QCOMPARE(stackModel->rowCount(tIdx), 3); COMPARE_DATA(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), m_debugeeFileName+":30"); COMPARE_DATA(tIdx.child(1, 0), "1"); COMPARE_DATA(tIdx.child(1, 1), "__libc_start_main"); COMPARE_DATA(tIdx.child(2, 0), "2"); COMPARE_DATA(tIdx.child(2, 1), "_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("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(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "foo()"); COMPARE_DATA(tIdx.child(0, 2), fileName+":26"); COMPARE_DATA(tIdx.child(1, 0), "1"); COMPARE_DATA(tIdx.child(1, 1), "foo()"); COMPARE_DATA(tIdx.child(1, 2), fileName+":24"); COMPARE_DATA(tIdx.child(2, 0), "2"); COMPARE_DATA(tIdx.child(2, 1), "foo()"); COMPARE_DATA(tIdx.child(2, 2), fileName+":24"); COMPARE_DATA(tIdx.child(19, 0), "19"); COMPARE_DATA(tIdx.child(20, 0), "20"); stackModel->fetchMoreFrames(); WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 2); QCOMPARE(stackModel->rowCount(tIdx), 41); COMPARE_DATA(tIdx.child(20, 0), "20"); COMPARE_DATA(tIdx.child(21, 0), "21"); COMPARE_DATA(tIdx.child(22, 0), "22"); COMPARE_DATA(tIdx.child(39, 0), "39"); COMPARE_DATA(tIdx.child(40, 0), "40"); stackModel->fetchMoreFrames(); WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 3); QCOMPARE(stackModel->rowCount(tIdx), 121); COMPARE_DATA(tIdx.child(40, 0), "40"); COMPARE_DATA(tIdx.child(41, 0), "41"); COMPARE_DATA(tIdx.child(42, 0), "42"); COMPARE_DATA(tIdx.child(119, 0), "119"); COMPARE_DATA(tIdx.child(120, 0), "120"); stackModel->fetchMoreFrames(); WAIT_FOR_A_WHILE(session, 200); QCOMPARE(stackModel->fetchFramesCalled, 4); QCOMPARE(stackModel->rowCount(tIdx), 301); COMPARE_DATA(tIdx.child(120, 0), "120"); COMPARE_DATA(tIdx.child(121, 0), "121"); COMPARE_DATA(tIdx.child(122, 0), "122"); COMPARE_DATA(tIdx.child(298, 0), "298"); COMPARE_DATA(tIdx.child(298, 1), "main"); COMPARE_DATA(tIdx.child(298, 2), fileName+":30"); COMPARE_DATA(tIdx.child(299, 0), "299"); COMPARE_DATA(tIdx.child(299, 1), "__libc_start_main"); COMPARE_DATA(tIdx.child(300, 0), "300"); COMPARE_DATA(tIdx.child(300, 1), "_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(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), m_debugeeFileName+":30"); COMPARE_DATA(tIdx.child(1, 0), "1"); COMPARE_DATA(tIdx.child(1, 1), "__libc_start_main"); COMPARE_DATA(tIdx.child(2, 0), "2"); COMPARE_DATA(tIdx.child(2, 1), "_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("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(tIdx.child(0, 0), "0"); COMPARE_DATA(tIdx.child(0, 1), "main"); COMPARE_DATA(tIdx.child(0, 2), 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 << "nice" << findExecutable("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 << "lldb-server" << "gdbserver" << "*: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("core"); if (f.exists()) f.remove(); KProcess debugeeProcess; debugeeProcess.setOutputChannelMode(KProcess::MergedChannels); debugeeProcess << "bash" << "-c" << "ulimit -c unlimited; " + findExecutable("lldb_crash").toLocalFile(); debugeeProcess.start(); debugeeProcess.waitForFinished(); qDebug() << debugeeProcess.readAll(); bool coreFileFound = QFile::exists("core"); if (!coreFileFound) { // Try to use coredumpctl auto coredumpctl = QStandardPaths::findExecutable("coredumpctl"); if (!coredumpctl.isEmpty()) { QFileInfo fi("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("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() == "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("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("t\\\"t"); // the actual content const QString quotedTestString(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("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::testVariablesNonascii() { TestDebugSession *session = new TestDebugSession; TestLaunchConfiguration cfg(findExecutable("lldb_debugeeqt")); session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); QString fileName = findSourceFile("debugeeqt.cpp"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 30); QVERIFY(session->startDebugging(&cfg, m_iface)); WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); QCOMPARE(session->currentLine(), 30); COMPARE_DATA(localVariableIndexAt(0, 1), QString("\"\u4f60\u597d\u4e16\u754c\"")); 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("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("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("lldb_debugeeqt")); breakpoints()->addCodeBreakpoint("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("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("path with space/lldb_spacedebugee"); TestLaunchConfiguration c(debugee, KIO::upUrl(debugee)); KDevelop::Breakpoint* b = breakpoints()->addCodeBreakpoint("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("path with space/lldb_spacedebugee")); TestLaunchConfiguration cfg(findExecutable("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/file_templates/classes/c_gobject/class.c b/file_templates/classes/c_gobject/class.c index 1ae60fbd7b..f415c0c5da 100644 --- a/file_templates/classes/c_gobject/class.c +++ b/file_templates/classes/c_gobject/class.c @@ -1,99 +1,99 @@ {% load kdev_filters %} {% include "license_header_cpp.txt" %} #include "{{ output_file_header }}" {% with namespaces|join:"_"|default:"___"|add:"_"|cut:"____"|upper as uc_prefix %} {% with namespaces|join:"_"|default:"___"|add:"_"|cut:"____"|lower as lc_prefix %} {% with namespaces|join:"" as prefix %} {% with prefix|add:name as full_name %} /* * forward definitions */ G_DEFINE_TYPE ({{ full_name }}, {{ lc_prefix }}{{ name|lower }}, G_TYPE_OBJECT); /* /* forward declarations of default virtual methods */ {% for f in functions %} {% if f.isVirtual %} {% with f.arguments as arguments %} {{ f.returnType|default:"void" }} {{ lc_prefix }}{{ name|lower }}_real_{{ f.name }}({{ full_name }}* self{% if arguments %}, {% include "arguments_types_names.txt" %}{% endif %}); {% endwith %} {% endif %} {% endfor %} static void {{ lc_prefix }}{{ name|lower }}_dispose (GObject *gobject) { {{ full_name }} *self = {{ uc_prefix }}{{ name|upper }} (gobject); /* * In dispose, you are supposed to free all types referenced from this * object which might themselves hold a reference to self. Generally, * the most simple solution is to unref all members on which you own a * reference. */ /* Chain up to the parent class */ G_OBJECT_CLASS ({{ lc_prefix }}{{ name|lower }}_parent_class)->dispose (gobject); } static void {{ lc_prefix }}{{ name|lower }}_finalize (GObject *gobject) { {{ full_name }} *self = {{ uc_prefix }}{{ name|upper }} (gobject); /* Chain up to the parent class */ G_OBJECT_CLASS ({{ lc_prefix }}{{ name|lower }}_parent_class)->finalize (gobject); } static void {{ lc_prefix }}{{ name|lower }}_init ({{ full_name }} *self) { /* initialize all public and private members to reasonable default values. */ /* Default implementations for virtual methods * For pure-virtual functions, set these to NULL */ {% for f in functions %} {% if f.isVirtual %} klass->{{ f.name }} = {{ lc_prefix }}{{ name|lower }}_real_{{ f.name }}; {% endif %} {% endfor %} } {% for f in functions %} {% with f.arguments as arguments %} {{ f.returnType|default:"void" }} {{ lc_prefix }}{{ name|lower }}_{{ f.name }}({{ full_name }}* self{% if arguments %}, {% include "arguments_types_names.txt" %}{% endif %}) { g_return_if_fail ({{ uc_prefix }}IS_{{ name|upper }} (self)); {% if f.isVirtual %} {{ uc_prefix }}{{ name|upper }}_GET_CLASS (self)->{{ f.name }} (self{% if arguments %}, {% include "arguments_names.txt" %}{% endif %}); {% endif %} } {% if f.isVirtual %} {{ f.returnType|default:"void" }} {{ lc_prefix }}{{ name|lower }}_real_{{ f.name }}({{ full_name }}* self{% if arguments %}, {% include "arguments_types_names.txt" %}{% endif %}) { /* Default implementation for the virtual method {{ f.name }} */ } {% endif %} {% endwith %} {% endfor %} {% endwith %} {% endwith %} {% endwith %} -{% endwith %} \ No newline at end of file +{% endwith %} diff --git a/file_templates/classes/c_gobject_private/class.c b/file_templates/classes/c_gobject_private/class.c index d931964e2a..0cb648d5b8 100644 --- a/file_templates/classes/c_gobject_private/class.c +++ b/file_templates/classes/c_gobject_private/class.c @@ -1,125 +1,125 @@ {% load kdev_filters %} {% include "license_header_cpp.txt" %} #include "{{ output_file_header }}" {% with namespaces|join:"_"|default:"___"|add:"_"|cut:"____"|upper as uc_prefix %} {% with namespaces|join:"_"|default:"___"|add:"_"|cut:"____"|lower as lc_prefix %} {% with namespaces|join:"" as prefix %} {% with prefix|add:name as full_name %} #define {{ uc_prefix }}{{ name|upper }}_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), {{ uc_prefix }}TYPE_{{ name|upper }}, {{ full_name }}Private)) struct _{{ full_name }}Private { /* private members */ }; /* * forward definitions */ G_DEFINE_TYPE ({{ full_name }}, {{ lc_prefix }}{{ name|lower }}, G_TYPE_OBJECT); /* /* forward declarations of default virtual methods */ {% for f in functions %} {% if f.isVirtual %} {% with f.arguments as arguments %} {{ f.returnType|default:"void" }} {{ lc_prefix }}{{ name|lower }}_real_{{ f.name }}({{ full_name }}* self{% if arguments %}, {% include "arguments_types_names.txt" %}{% endif %}); {% endwith %} {% endif %} {% endfor %} static void {{ lc_prefix }}{{ name|lower }}_class_init ({{ full_name }}Class *klass) { g_type_class_add_private (klass, sizeof ({{ full_name }}Private)); } static void {{ lc_prefix }}{{ name|lower }}_dispose (GObject *gobject) { {{ full_name }} *self = {{ uc_prefix }}{{ name|upper }} (gobject); /* * In dispose, you are supposed to free all types referenced from this * object which might themselves hold a reference to self. Generally, * the most simple solution is to unref all members on which you own a * reference. */ /* Chain up to the parent class */ G_OBJECT_CLASS ({{ lc_prefix }}{{ name|lower }}_parent_class)->dispose (gobject); } static void {{ lc_prefix }}{{ name|lower }}_finalize (GObject *gobject) { {{ full_name }} *self = {{ uc_prefix }}{{ name|upper }} (gobject); /* Chain up to the parent class */ G_OBJECT_CLASS ({{ lc_prefix }}{{ name|lower }}_parent_class)->finalize (gobject); } static void {{ lc_prefix }}{{ name|lower }}_init ({{ full_name }} *self) { self->priv = {{ uc_prefix }}{{ name|upper }}_GET_PRIVATE (self); /* initialize all public and private members to reasonable default values. */ /* * Default implementations for virtual methods * For pure-virtual functions, set these to NULL */ {% for f in functions %} {% if f.isVirtual %} klass->{{ f.name }} = {{ lc_prefix }}{{ name|lower }}_real_{{ f.name }}; {% endif %} {% endfor %} } {% for f in functions %} {% with f.arguments as arguments %} {{ f.returnType|default:"void" }} {{ lc_prefix }}{{ name|lower }}_{{ f.name }}({{ full_name }}* self{% if arguments %}, {% include "arguments_types_names.txt" %}{% endif %}) { g_return_if_fail ({{ uc_prefix }}IS_{{ name|upper }} (self)); {% if f.isVirtual %} {{ uc_prefix }}{{ name|upper }}_GET_CLASS (self)->{{ f.name }} (self{% if arguments %}, {% include "arguments_names.txt" %}{% endif %}); {% endif %} } {% if f.isVirtual %} {{ f.returnType|default:"void" }} {{ lc_prefix }}{{ name|lower }}_real_{{ f.name }}({{ full_name }}* self{% if arguments %}, {% include "arguments_types_names.txt" %}{% endif %}) { /* Default implementation for the virtual method {{ f.name }} */ } {% endif %} {% endwith %} {% endfor %} {% endwith %} {% endwith %} {% endwith %} -{% endwith %} \ No newline at end of file +{% endwith %} diff --git a/file_templates/classes/c_gobject_properties/class.c b/file_templates/classes/c_gobject_properties/class.c index cbfb32b293..58e3a3c195 100644 --- a/file_templates/classes/c_gobject_properties/class.c +++ b/file_templates/classes/c_gobject_properties/class.c @@ -1,208 +1,208 @@ {% load kdev_filters %} {% include "license_header_cpp.txt" %} #include "{{ output_file_header }}" {% with namespaces|join:"_"|default:"___"|add:"_"|cut:"____"|upper as uc_prefix %} {% with namespaces|join:"_"|default:"___"|add:"_"|cut:"____"|lower as lc_prefix %} {% with namespaces|join:"" as prefix %} {% with prefix|add:name as full_name %} #define {{ uc_prefix }}{{ name|upper }}_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), {{ uc_prefix }}TYPE_{{ name|upper }}, {{ full_name }}Private)) struct _{{ full_name }}Private { /* private members */ }; /* * forward definitions */ G_DEFINE_TYPE ({{ full_name }}, {{ lc_prefix }}{{ name|lower }}, G_TYPE_OBJECT); /* /* forward declarations of default virtual methods */ {% for f in functions %} {% if f.isVirtual %} {% with f.arguments as arguments %} {{ f.returnType|default:"void" }} {{ lc_prefix }}{{ name|lower }}_real_{{ f.name }}({{ full_name }}* self{% if arguments %}, {% include "arguments_types_names.txt" %}{% endif %}); {% endwith %} {% endif %} {% endfor %} enum { PROP_0, {% for m in members %} PROP_{{ m.name|upper }}, {% endfor %} N_PROPERTIES }; static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; static void {{ lc_prefix }}{{ name|lower }}_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { {{ full_name }} *self = {{ uc_prefix}}{{ name|upper }} (object); switch (property_id) { {% for m in members %} case PROP_{{ m.name|upper }}: /* WARNING: Strings and custom types require special handling here */ self->priv->{{ m.name }} = g_value_get_{{ m.type }} (value); break; {% endfor %} default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void {{ lc_prefix }}{{ name|lower }}_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { {{ full_name }} *self = {{ uc_prefix}}{{ name|upper }} (object); switch (property_id) { {% for m in members %} case PROP_{{ m.name|upper }}: /* WARNING: Strings and custom types require special handling here */ g_value_set_{{ m.type }} (value, self->priv->{{ m.name }}); break; {% endfor %} default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void {{ lc_prefix }}{{ name|lower }}_class_init ({{ full_name }}Class *klass) { g_type_class_add_private (klass, sizeof ({{ full_name }}Private)); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->set_property = {{ lc_prefix }}{{ name|lower}}_set_property; gobject_class->get_property = {{ lc_prefix }}{{ name|lower}}_get_property; {% for m in members %} /* WARNING: Most property types require special handling here, check before compiling */ obj_properties[PROP_{{ m.name|upper }}] = g_param_spec_{{ m.type }} ("{{ m.name }}", "{{ m.name|capfirst }} property", "Set/Get {{ m.name }}", {{ m.value|default:0 }} /* default value */, G_PARAM_CONSTRUCT | G_PARAM_READWRITE); {% endfor %} g_object_class_install_properties (gobject_class, N_PROPERTIES, obj_properties); } static void {{ lc_prefix }}{{ name|lower }}_dispose (GObject *gobject) { {{ full_name }} *self = {{ uc_prefix }}{{ name|upper }} (gobject); /* * In dispose, you are supposed to free all types referenced from this * object which might themselves hold a reference to self. Generally, * the most simple solution is to unref all members on which you own a * reference. */ /* Chain up to the parent class */ G_OBJECT_CLASS ({{ lc_prefix }}{{ name|lower }}_parent_class)->dispose (gobject); } static void {{ lc_prefix }}{{ name|lower }}_finalize (GObject *gobject) { {{ full_name }} *self = {{ uc_prefix }}{{ name|upper }} (gobject); /* Chain up to the parent class */ G_OBJECT_CLASS ({{ lc_prefix }}{{ name|lower }}_parent_class)->finalize (gobject); } static void {{ lc_prefix }}{{ name|lower }}_init ({{ full_name }} *self) { self->priv = {{ uc_prefix }}{{ name|upper }}_GET_PRIVATE (self); /* initialize all public and private members to reasonable default values. */ /* * Default implementations for virtual methods * For pure-virtual functions, set these to NULL */ {% for f in functions %} {% if f.isVirtual %} klass->{{ f.name }} = {{ lc_prefix }}{{ name|lower }}_real_{{ f.name }}; {% endif %} {% endfor %} } {% for f in functions %} {% with f.arguments as arguments %} {{ f.returnType|default:"void" }} {{ lc_prefix }}{{ name|lower }}_{{ f.name }}({{ full_name }}* self{% if arguments %}, {% include "arguments_types_names.txt" %}{% endif %}) { g_return_if_fail ({{ uc_prefix }}IS_{{ name|upper }} (self)); {% if f.isVirtual %} {{ uc_prefix }}{{ name|upper }}_GET_CLASS (self)->{{ f.name }} (self{% if arguments %}, {% include "arguments_names.txt" %}{% endif %}); {% endif %} } {% if f.isVirtual %} {{ f.returnType|default:"void" }} {{ lc_prefix }}{{ name|lower }}_real_{{ f.name }}({{ full_name }}* self{% if arguments %}, {% include "arguments_types_names.txt" %}{% endif %}) { /* Default implementation for the virtual method {{ f.name }} */ } {% endif %} {% endwith %} {% endfor %} {% endwith %} {% endwith %} {% endwith %} -{% endwith %} \ No newline at end of file +{% endwith %} diff --git a/formatters/astyle/lib/ASBeautifier.cpp b/formatters/astyle/lib/ASBeautifier.cpp index 7a5607ad27..d44326d794 100644 --- a/formatters/astyle/lib/ASBeautifier.cpp +++ b/formatters/astyle/lib/ASBeautifier.cpp @@ -1,3268 +1,3268 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ASBeautifier.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 namespace astyle { // this must be global int g_preprocessorCppExternCBracket; /** * ASBeautifier's constructor * This constructor is called only once for each source file. * The cloned ASBeautifier objects are created with the copy constructor. */ ASBeautifier::ASBeautifier() { g_preprocessorCppExternCBracket = 0; waitingBeautifierStack = NULL; activeBeautifierStack = NULL; waitingBeautifierStackLengthStack = NULL; activeBeautifierStackLengthStack = NULL; headerStack = NULL; tempStacks = NULL; blockParenDepthStack = NULL; blockStatementStack = NULL; parenStatementStack = NULL; bracketBlockStateStack = NULL; inStatementIndentStack = NULL; inStatementIndentStackSizeStack = NULL; parenIndentStack = NULL; preprocIndentStack = NULL; sourceIterator = NULL; isModeManuallySet = false; shouldForceTabIndentation = false; setSpaceIndentation(4); setMinConditionalIndentOption(MINCOND_TWO); setMaxInStatementIndentLength(40); classInitializerIndents = 1; tabLength = 0; setClassIndent(false); setModifierIndent(false); setSwitchIndent(false); setCaseIndent(false); setBlockIndent(false); setBracketIndent(false); setNamespaceIndent(false); setLabelIndent(false); setEmptyLineFill(false); setCStyle(); setPreprocDefineIndent(false); setPreprocConditionalIndent(false); setAlignMethodColon(false); // initialize ASBeautifier member vectors beautifierFileType = 9; // reset to an invalid type headers = new vector; nonParenHeaders = new vector; assignmentOperators = new vector; nonAssignmentOperators = new vector; preBlockStatements = new vector; preCommandHeaders = new vector; indentableHeaders = new vector; } /** * ASBeautifier's copy constructor * Copy the vector objects to vectors in the new ASBeautifier * object so the new object can be destroyed without deleting * the vector objects in the copied vector. * This is the reason a copy constructor is needed. * * Must explicitly call the base class copy constructor. */ ASBeautifier::ASBeautifier(const ASBeautifier &other) : ASBase(other) { // these don't need to copy the stack waitingBeautifierStack = NULL; activeBeautifierStack = NULL; waitingBeautifierStackLengthStack = NULL; activeBeautifierStackLengthStack = NULL; // vector '=' operator performs a DEEP copy of all elements in the vector headerStack = new vector; *headerStack = *other.headerStack; tempStacks = copyTempStacks(other); blockParenDepthStack = new vector; *blockParenDepthStack = *other.blockParenDepthStack; blockStatementStack = new vector; *blockStatementStack = *other.blockStatementStack; parenStatementStack = new vector; *parenStatementStack = *other.parenStatementStack; bracketBlockStateStack = new vector; *bracketBlockStateStack = *other.bracketBlockStateStack; inStatementIndentStack = new vector; *inStatementIndentStack = *other.inStatementIndentStack; inStatementIndentStackSizeStack = new vector; *inStatementIndentStackSizeStack = *other.inStatementIndentStackSizeStack; parenIndentStack = new vector; *parenIndentStack = *other.parenIndentStack; preprocIndentStack = new vector >; *preprocIndentStack = *other.preprocIndentStack; // Copy the pointers to vectors. // This is ok because the original ASBeautifier object // is not deleted until end of job. beautifierFileType = other.beautifierFileType; headers = other.headers; nonParenHeaders = other.nonParenHeaders; assignmentOperators = other.assignmentOperators; nonAssignmentOperators = other.nonAssignmentOperators; preBlockStatements = other.preBlockStatements; preCommandHeaders = other.preCommandHeaders; indentableHeaders = other.indentableHeaders; // protected variables // variables set by ASFormatter // must also be updated in activeBeautifierStack inLineNumber = other.inLineNumber; horstmannIndentInStatement = other.horstmannIndentInStatement; nonInStatementBracket = other.nonInStatementBracket; lineCommentNoBeautify = other.lineCommentNoBeautify; isElseHeaderIndent = other.isElseHeaderIndent; isCaseHeaderCommentIndent = other.isCaseHeaderCommentIndent; isNonInStatementArray = other.isNonInStatementArray; isSharpAccessor = other.isSharpAccessor; isSharpDelegate = other.isSharpDelegate; isInExternC = other.isInExternC; isInBeautifySQL = other.isInBeautifySQL; isInIndentableStruct = other.isInIndentableStruct; // private variables sourceIterator = other.sourceIterator; currentHeader = other.currentHeader; previousLastLineHeader = other.previousLastLineHeader; probationHeader = other.probationHeader; lastLineHeader = other.lastLineHeader; indentString = other.indentString; verbatimDelimiter = other.verbatimDelimiter; isInQuote = other.isInQuote; isInVerbatimQuote = other.isInVerbatimQuote; haveLineContinuationChar = other.haveLineContinuationChar; isInAsm = other.isInAsm; isInAsmOneLine = other.isInAsmOneLine; isInAsmBlock = other.isInAsmBlock; isInComment = other.isInComment; isInPreprocessorComment = other.isInPreprocessorComment; isInHorstmannComment = other.isInHorstmannComment; isInCase = other.isInCase; isInQuestion = other.isInQuestion; isInStatement = other.isInStatement; isInHeader = other.isInHeader; isInTemplate = other.isInTemplate; isInDefine = other.isInDefine; isInDefineDefinition = other.isInDefineDefinition; classIndent = other.classIndent; isInClassInitializer = other.isInClassInitializer; isInClassHeaderTab = other.isInClassHeaderTab; isInObjCMethodDefinition = other.isInObjCMethodDefinition; isImmediatelyPostObjCMethodDefinition = other.isImmediatelyPostObjCMethodDefinition; isInObjCInterface = other.isInObjCInterface; isInEnum = other.isInEnum; modifierIndent = other.modifierIndent; switchIndent = other.switchIndent; caseIndent = other.caseIndent; namespaceIndent = other.namespaceIndent; bracketIndent = other.bracketIndent; blockIndent = other.blockIndent; labelIndent = other.labelIndent; isInConditional = other.isInConditional; isModeManuallySet = other.isModeManuallySet; shouldForceTabIndentation = other.shouldForceTabIndentation; emptyLineFill = other.emptyLineFill; lineOpensWithLineComment = other.lineOpensWithLineComment; lineOpensWithComment = other.lineOpensWithComment; lineStartsInComment = other.lineStartsInComment; backslashEndsPrevLine = other.backslashEndsPrevLine; blockCommentNoIndent = other.blockCommentNoIndent; blockCommentNoBeautify = other.blockCommentNoBeautify; previousLineProbationTab = other.previousLineProbationTab; lineBeginsWithOpenBracket = other.lineBeginsWithOpenBracket; lineBeginsWithCloseBracket = other.lineBeginsWithCloseBracket; shouldIndentBrackettedLine = other.shouldIndentBrackettedLine; isInClass = other.isInClass; isInSwitch = other.isInSwitch; foundPreCommandHeader = other.foundPreCommandHeader; foundPreCommandMacro = other.foundPreCommandMacro; shouldAlignMethodColon = other.shouldAlignMethodColon; shouldIndentPreprocDefine = other.shouldIndentPreprocDefine; shouldIndentPreprocConditional = other.shouldIndentPreprocConditional; indentCount = other.indentCount; spaceIndentCount = other.spaceIndentCount; spaceIndentObjCMethodDefinition = other.spaceIndentObjCMethodDefinition; colonIndentObjCMethodDefinition = other.colonIndentObjCMethodDefinition; lineOpeningBlocksNum = other.lineOpeningBlocksNum; lineClosingBlocksNum = other.lineClosingBlocksNum; fileType = other.fileType; minConditionalOption = other.minConditionalOption; minConditionalIndent = other.minConditionalIndent; parenDepth = other.parenDepth; indentLength = other.indentLength; tabLength = other.tabLength; blockTabCount = other.blockTabCount; maxInStatementIndent = other.maxInStatementIndent; classInitializerIndents = other.classInitializerIndents; templateDepth = other.templateDepth; squareBracketCount = other.squareBracketCount; prevFinalLineSpaceIndentCount = other.prevFinalLineSpaceIndentCount; prevFinalLineIndentCount = other.prevFinalLineIndentCount; defineIndentCount = other.defineIndentCount; quoteChar = other.quoteChar; prevNonSpaceCh = other.prevNonSpaceCh; currentNonSpaceCh = other.currentNonSpaceCh; currentNonLegalCh = other.currentNonLegalCh; prevNonLegalCh = other.prevNonLegalCh; } /** * ASBeautifier's destructor */ ASBeautifier::~ASBeautifier() { deleteBeautifierContainer(waitingBeautifierStack); deleteBeautifierContainer(activeBeautifierStack); deleteContainer(waitingBeautifierStackLengthStack); deleteContainer(activeBeautifierStackLengthStack); deleteContainer(headerStack); deleteTempStacksContainer(tempStacks); deleteContainer(blockParenDepthStack); deleteContainer(blockStatementStack); deleteContainer(parenStatementStack); deleteContainer(bracketBlockStateStack); deleteContainer(inStatementIndentStack); deleteContainer(inStatementIndentStackSizeStack); deleteContainer(parenIndentStack); deleteContainer(preprocIndentStack); } /** * initialize the ASBeautifier. * * This init() should be called every time a ABeautifier object is to start * beautifying a NEW source file. * It is called only when a new ASFormatter object is created. - * init() recieves a pointer to a ASSourceIterator object that will be + * init() receives a pointer to a ASSourceIterator object that will be * used to iterate through the source code. * * @param iter a pointer to the ASSourceIterator or ASStreamIterator object. */ void ASBeautifier::init(ASSourceIterator* iter) { sourceIterator = iter; initVectors(); ASBase::init(getFileType()); initContainer(waitingBeautifierStack, new vector); initContainer(activeBeautifierStack, new vector); initContainer(waitingBeautifierStackLengthStack, new vector); initContainer(activeBeautifierStackLengthStack, new vector); initContainer(headerStack, new vector); initTempStacksContainer(tempStacks, new vector*>); tempStacks->push_back(new vector); initContainer(blockParenDepthStack, new vector); initContainer(blockStatementStack, new vector); initContainer(parenStatementStack, new vector); initContainer(bracketBlockStateStack, new vector); bracketBlockStateStack->push_back(true); initContainer(inStatementIndentStack, new vector); initContainer(inStatementIndentStackSizeStack, new vector); inStatementIndentStackSizeStack->push_back(0); initContainer(parenIndentStack, new vector); initContainer(preprocIndentStack, new vector >); previousLastLineHeader = NULL; currentHeader = NULL; isInQuote = false; isInVerbatimQuote = false; haveLineContinuationChar = false; isInAsm = false; isInAsmOneLine = false; isInAsmBlock = false; isInComment = false; isInPreprocessorComment = false; isInHorstmannComment = false; isInStatement = false; isInCase = false; isInQuestion = false; isInClassInitializer = false; isInClassHeaderTab = false; isInObjCMethodDefinition = false; isImmediatelyPostObjCMethodDefinition = false; isInObjCInterface = false; isInEnum = false; isInHeader = false; isInTemplate = false; isInConditional = false; indentCount = 0; spaceIndentCount = 0; spaceIndentObjCMethodDefinition = 0; colonIndentObjCMethodDefinition = 0; lineOpeningBlocksNum = 0; lineClosingBlocksNum = 0; templateDepth = 0; squareBracketCount = 0; parenDepth = 0; blockTabCount = 0; prevFinalLineSpaceIndentCount = 0; prevFinalLineIndentCount = 0; defineIndentCount = 0; prevNonSpaceCh = '{'; currentNonSpaceCh = '{'; prevNonLegalCh = '{'; currentNonLegalCh = '{'; quoteChar = ' '; probationHeader = NULL; lastLineHeader = NULL; backslashEndsPrevLine = false; lineOpensWithLineComment = false; lineOpensWithComment = false; lineStartsInComment = false; isInDefine = false; isInDefineDefinition = false; lineCommentNoBeautify = false; isElseHeaderIndent = false; isCaseHeaderCommentIndent = false; blockCommentNoIndent = false; blockCommentNoBeautify = false; previousLineProbationTab = false; lineBeginsWithOpenBracket = false; lineBeginsWithCloseBracket = false; shouldIndentBrackettedLine = true; isInClass = false; isInSwitch = false; foundPreCommandHeader = false; foundPreCommandMacro = false; isNonInStatementArray = false; isSharpAccessor = false; isSharpDelegate = false; isInExternC = false; isInBeautifySQL = false; isInIndentableStruct = false; inLineNumber = 0; horstmannIndentInStatement = 0; nonInStatementBracket = 0; } /* * initialize the vectors */ void ASBeautifier::initVectors() { if (fileType == beautifierFileType) // don't build unless necessary return; beautifierFileType = fileType; headers->clear(); nonParenHeaders->clear(); assignmentOperators->clear(); nonAssignmentOperators->clear(); preBlockStatements->clear(); preCommandHeaders->clear(); indentableHeaders->clear(); ASResource::buildHeaders(headers, fileType, true); ASResource::buildNonParenHeaders(nonParenHeaders, fileType, true); ASResource::buildAssignmentOperators(assignmentOperators); ASResource::buildNonAssignmentOperators(nonAssignmentOperators); ASResource::buildPreBlockStatements(preBlockStatements, fileType); ASResource::buildPreCommandHeaders(preCommandHeaders, fileType); ASResource::buildIndentableHeaders(indentableHeaders); } /** * set indentation style to C/C++. */ void ASBeautifier::setCStyle() { fileType = C_TYPE; } /** * set indentation style to Java. */ void ASBeautifier::setJavaStyle() { fileType = JAVA_TYPE; } /** * set indentation style to C#. */ void ASBeautifier::setSharpStyle() { fileType = SHARP_TYPE; } /** * set mode manually set flag */ void ASBeautifier::setModeManuallySet(bool state) { isModeManuallySet = state; } /** * set tabLength equal to indentLength. - * This is done when tabLength is not explicitely set by + * This is done when tabLength is not explicitly set by * "indent=force-tab-x" * */ void ASBeautifier::setDefaultTabLength() { tabLength = indentLength; } /** * indent using a different tab setting for indent=force-tab * * @param length number of spaces per tab. */ void ASBeautifier::setForceTabXIndentation(int length) { // set tabLength instead of indentLength indentString = "\t"; tabLength = length; shouldForceTabIndentation = true; } /** * indent using one tab per indentation */ void ASBeautifier::setTabIndentation(int length, bool forceTabs) { indentString = "\t"; indentLength = length; shouldForceTabIndentation = forceTabs; } /** * indent using a number of spaces per indentation. * * @param length number of spaces per indent. */ void ASBeautifier::setSpaceIndentation(int length) { indentString = string(length, ' '); indentLength = length; } /** * set the maximum indentation between two lines in a multi-line statement. * * @param max maximum indentation length. */ void ASBeautifier::setMaxInStatementIndentLength(int max) { maxInStatementIndent = max; } /** * set the minimum conditional indentation option. * * @param min minimal indentation option. */ void ASBeautifier::setMinConditionalIndentOption(int min) { minConditionalOption = min; } /** * set minConditionalIndent from the minConditionalOption. */ void ASBeautifier::setMinConditionalIndentLength() { if (minConditionalOption == MINCOND_ZERO) minConditionalIndent = 0; else if (minConditionalOption == MINCOND_ONE) minConditionalIndent = indentLength; else if (minConditionalOption == MINCOND_ONEHALF) minConditionalIndent = indentLength / 2; // minConditionalOption = INDENT_TWO else minConditionalIndent = indentLength * 2; } /** * set the state of the bracket indentation option. If true, brackets will * be indented one additional indent. * * @param state state of option. */ void ASBeautifier::setBracketIndent(bool state) { bracketIndent = state; } /** * set the state of the block indentation option. If true, entire blocks * will be indented one additional indent, similar to the GNU indent style. * * @param state state of option. */ void ASBeautifier::setBlockIndent(bool state) { blockIndent = state; } /** * set the state of the class indentation option. If true, C++ class * definitions will be indented one additional indent. * * @param state state of option. */ void ASBeautifier::setClassIndent(bool state) { classIndent = state; } /** * set the state of the modifier indentation option. If true, C++ class * access modifiers will be indented one-half an indent. * * @param state state of option. */ void ASBeautifier::setModifierIndent(bool state) { modifierIndent = state; } /** * set the state of the switch indentation option. If true, blocks of 'switch' * statements will be indented one additional indent. * * @param state state of option. */ void ASBeautifier::setSwitchIndent(bool state) { switchIndent = state; } /** * set the state of the case indentation option. If true, lines of 'case' * statements will be indented one additional indent. * * @param state state of option. */ void ASBeautifier::setCaseIndent(bool state) { caseIndent = state; } /** * set the state of the namespace indentation option. * If true, blocks of 'namespace' statements will be indented one * additional indent. Otherwise, NO indentation will be added. * * @param state state of option. */ void ASBeautifier::setNamespaceIndent(bool state) { namespaceIndent = state; } /** * set the state of the label indentation option. * If true, labels will be indented one indent LESS than the * current indentation level. * If false, labels will be flushed to the left with NO * indent at all. * * @param state state of option. */ void ASBeautifier::setLabelIndent(bool state) { labelIndent = state; } /** * set the state of the preprocessor indentation option. * If true, multiline #define statements will be indented. * * @param state state of option. */ void ASBeautifier::setPreprocDefineIndent(bool state) { shouldIndentPreprocDefine = state; } void ASBeautifier::setPreprocConditionalIndent(bool state) { shouldIndentPreprocConditional = state; } /** * set the state of the empty line fill option. * If true, empty lines will be filled with the whitespace. * of their previous lines. * If false, these lines will remain empty. * * @param state state of option. */ void ASBeautifier::setEmptyLineFill(bool state) { emptyLineFill = state; } void ASBeautifier::setAlignMethodColon(bool state) { shouldAlignMethodColon = state; } /** * get the file type. */ int ASBeautifier::getFileType() const { return fileType; } /** * get the number of spaces per indent * * @return value of indentLength option. */ int ASBeautifier::getIndentLength(void) const { return indentLength; } /** * get the char used for indentation, space or tab * * @return the char used for indentation. */ string ASBeautifier::getIndentString(void) const { return indentString; } /** * get mode manually set flag */ bool ASBeautifier::getModeManuallySet() const { return isModeManuallySet; } /** * get the state of the force tab indentation option. * * @return state of force tab indentation. */ bool ASBeautifier::getForceTabIndentation(void) const { return shouldForceTabIndentation; } /** * get the state of the block indentation option. * * @return state of blockIndent option. */ bool ASBeautifier::getBlockIndent(void) const { return blockIndent; } /** * get the state of the bracket indentation option. * * @return state of bracketIndent option. */ bool ASBeautifier::getBracketIndent(void) const { return bracketIndent; } /** * get the state of the class indentation option. If true, blocks of * the 'class' statement will be indented one additional indent. * * @return state of classIndent option. */ bool ASBeautifier::getClassIndent(void) const { return classIndent; } /** * get the state of the switch indentation option. If true, blocks of * the 'switch' statement will be indented one additional indent. * * @return state of switchIndent option. */ bool ASBeautifier::getSwitchIndent(void) const { return switchIndent; } /** * get the state of the case indentation option. If true, lines of 'case' * statements will be indented one additional indent. * * @return state of caseIndent option. */ bool ASBeautifier::getCaseIndent(void) const { return caseIndent; } /** * get the state of the empty line fill option. * If true, empty lines will be filled with the whitespace. * of their previous lines. * If false, these lines will remain empty. * * @return state of emptyLineFill option. */ bool ASBeautifier::getEmptyLineFill(void) const { return emptyLineFill; } /** * get the state of the preprocessor indentation option. * If true, preprocessor "define" lines will be indented. * If false, preprocessor "define" lines will be unchanged. * * @return state of shouldIndentPreprocDefine option. */ bool ASBeautifier::getPreprocDefineIndent(void) const { return shouldIndentPreprocDefine; } /** * get the length of the tab indentation option. * * @return length of tab indent option. */ int ASBeautifier::getTabLength(void) const { return tabLength; } /** * beautify a line of source code. * every line of source code in a source code file should be sent * one after the other to the beautify method. * * @return the indented line. * @param originalLine the original unindented line. */ string ASBeautifier::beautify(const string &originalLine) { string line; bool isInQuoteContinuation = isInVerbatimQuote | haveLineContinuationChar; currentHeader = NULL; lastLineHeader = NULL; blockCommentNoBeautify = blockCommentNoIndent; isInClass = false; isInSwitch = false; lineBeginsWithOpenBracket = false; lineBeginsWithCloseBracket = false; shouldIndentBrackettedLine = true; isInAsmOneLine = false; lineOpensWithLineComment = false; lineOpensWithComment = false; lineStartsInComment = isInComment; previousLineProbationTab = false; haveLineContinuationChar = false; lineOpeningBlocksNum = 0; lineClosingBlocksNum = 0; if (isImmediatelyPostObjCMethodDefinition) clearObjCMethodDefinitionAlignment(); // handle and remove white spaces around the line: // If not in comment, first find out size of white space before line, // so that possible comments starting in the line continue in // relation to the preliminary white-space. if (isInQuoteContinuation) { // trim a single space added by ASFormatter, otherwise leave it alone if (!(originalLine.length() == 1 && originalLine[0] == ' ')) line = originalLine; } else if (isInComment || isInBeautifySQL) { // trim the end of comment and SQL lines line = originalLine; size_t trimEnd = line.find_last_not_of(" \t"); if (trimEnd == string::npos) trimEnd = 0; else trimEnd++; if (trimEnd < line.length()) line.erase(trimEnd); // does a bracket open the line size_t firstChar = line.find_first_not_of(" \t"); if (firstChar != string::npos) { if (line[firstChar] == '{') lineBeginsWithOpenBracket = true; else if (line[firstChar] == '}') lineBeginsWithCloseBracket = true; } } else { line = trim(originalLine); if (line.length() > 0) { if (line[0] == '{') lineBeginsWithOpenBracket = true; else if (line[0] == '}') lineBeginsWithCloseBracket = true; } isInHorstmannComment = false; size_t j = line.find_first_not_of(" \t{"); if (j != string::npos && line.compare(j, 2, "//") == 0) lineOpensWithLineComment = true; if (j != string::npos && line.compare(j, 2, "/*") == 0) { lineOpensWithComment = true; size_t k = line.find_first_not_of(" \t"); if (k != string::npos && line.compare(k, 1, "{") == 0) isInHorstmannComment = true; } } if (line.length() == 0) { if (backslashEndsPrevLine) // must continue to clear variables line = ' '; else if (emptyLineFill && !isInQuoteContinuation && (!headerStack->empty() || isInEnum)) return preLineWS(prevFinalLineIndentCount, prevFinalLineSpaceIndentCount); else return line; } // handle preprocessor commands if (!isInComment && !isInQuoteContinuation && line.length() > 0 && ((line[0] == '#' && !isIndentedPreprocessor(line, 0)) || backslashEndsPrevLine)) { if (line[0] == '#' && !isInDefine) { string preproc = extractPreprocessorStatement(line); processPreprocessor(preproc, line); if (shouldIndentPreprocConditional && preproc.length() > 0) { if (preproc.length() >= 2 && preproc.substr(0, 2) == "if") { pair entry; if (!isInDefine && activeBeautifierStack != NULL && !activeBeautifierStack->empty()) entry = activeBeautifierStack->back()->computePreprocessorIndent(); else entry = computePreprocessorIndent(); preprocIndentStack->push_back(entry); return preLineWS(preprocIndentStack->back().first, preprocIndentStack->back().second) + line; } else if (preproc == "else" || preproc == "elif") { if (preprocIndentStack->size() > 0) // if no entry don't indent return preLineWS(preprocIndentStack->back().first, preprocIndentStack->back().second) + line; } else if (preproc == "endif") { if (preprocIndentStack->size() > 0) // if no entry don't indent { string indentedLine = preLineWS(preprocIndentStack->back().first, preprocIndentStack->back().second) + line; preprocIndentStack->pop_back(); return indentedLine; } } } } // check if the last char is a backslash if (line.length() > 0) backslashEndsPrevLine = (line[line.length() - 1] == '\\'); // comments within the definition line can be continued without the backslash if (isInPreprocessorUnterminatedComment(line)) backslashEndsPrevLine = true; // check if this line ends a multi-line #define // if so, use the #define's cloned beautifier for the line's indentation // and then remove it from the active beautifier stack and delete it. if (!backslashEndsPrevLine && isInDefineDefinition && !isInDefine) { string beautifiedLine; ASBeautifier* defineBeautifier; isInDefineDefinition = false; defineBeautifier = activeBeautifierStack->back(); activeBeautifierStack->pop_back(); beautifiedLine = defineBeautifier->beautify(line); delete defineBeautifier; return beautifiedLine; } // unless this is a multi-line #define, return this precompiler line as is. if (!isInDefine && !isInDefineDefinition) return originalLine; } // if there exists any worker beautifier in the activeBeautifierStack, // then use it instead of me to indent the current line. // variables set by ASFormatter must be updated. if (!isInDefine && activeBeautifierStack != NULL && !activeBeautifierStack->empty()) { activeBeautifierStack->back()->inLineNumber = inLineNumber; activeBeautifierStack->back()->horstmannIndentInStatement = horstmannIndentInStatement; activeBeautifierStack->back()->nonInStatementBracket = nonInStatementBracket; activeBeautifierStack->back()->lineCommentNoBeautify = lineCommentNoBeautify; activeBeautifierStack->back()->isElseHeaderIndent = isElseHeaderIndent; activeBeautifierStack->back()->isCaseHeaderCommentIndent = isCaseHeaderCommentIndent; activeBeautifierStack->back()->isNonInStatementArray = isNonInStatementArray; activeBeautifierStack->back()->isSharpAccessor = isSharpAccessor; activeBeautifierStack->back()->isSharpDelegate = isSharpDelegate; activeBeautifierStack->back()->isInExternC = isInExternC; activeBeautifierStack->back()->isInBeautifySQL = isInBeautifySQL; activeBeautifierStack->back()->isInIndentableStruct = isInIndentableStruct; // must return originalLine not the trimmed line return activeBeautifierStack->back()->beautify(originalLine); } // Flag an indented header in case this line is a one-line block. // The header in the header stack will be deleted by a one-line block. bool isInExtraHeaderIndent = false; if (!headerStack->empty() && lineBeginsWithOpenBracket && (headerStack->back() != &AS_OPEN_BRACKET || probationHeader != NULL)) isInExtraHeaderIndent = true; size_t iPrelim = headerStack->size(); // calculate preliminary indentation based on headerStack and data from past lines computePreliminaryIndentation(); // parse characters in the current line. parseCurrentLine(line); // handle special cases of indentation adjustParsedLineIndentation(iPrelim, isInExtraHeaderIndent); // Objective-C continuation line if (isInObjCMethodDefinition) { // register indent for Objective-C continuation line if (line.length() > 0 && (line[0] == '-' || line[0] == '+')) { if (shouldAlignMethodColon) { colonIndentObjCMethodDefinition = line.find(':'); } else if (inStatementIndentStack->empty() || inStatementIndentStack->back() == 0) { inStatementIndentStack->push_back(indentLength); isInStatement = true; } } // set indent for last definition line else if (!lineBeginsWithOpenBracket) { if (shouldAlignMethodColon) spaceIndentCount = computeObjCColonAlignment(line, colonIndentObjCMethodDefinition); else if (inStatementIndentStack->empty()) spaceIndentCount = spaceIndentObjCMethodDefinition; } } if (isInDefine) { if (line.length() > 0 && line[0] == '#') { // the 'define' does not have to be attached to the '#' string preproc = trim(line.substr(1)); if (preproc.compare(0, 6, "define") == 0) { if (!inStatementIndentStack->empty() && inStatementIndentStack->back() > 0) { defineIndentCount = indentCount; } else { defineIndentCount = indentCount - 1; --indentCount; } } } indentCount -= defineIndentCount; } if (indentCount < 0) indentCount = 0; if (lineCommentNoBeautify || blockCommentNoBeautify || isInQuoteContinuation) indentCount = spaceIndentCount = 0; - // finally, insert indentations into begining of line + // finally, insert indentations into beginning of line string outBuffer = preLineWS(indentCount, spaceIndentCount) + line; prevFinalLineSpaceIndentCount = spaceIndentCount; prevFinalLineIndentCount = indentCount; if (lastLineHeader != NULL) previousLastLineHeader = lastLineHeader; return outBuffer; } string ASBeautifier::preLineWS(int lineIndentCount, int lineSpaceIndentCount) const { if (shouldForceTabIndentation) { if (tabLength != indentLength) { // adjust for different tab length int indentCountOrig = lineIndentCount; int spaceIndentCountOrig = lineSpaceIndentCount; lineIndentCount = ((indentCountOrig * indentLength) + spaceIndentCountOrig) / tabLength; lineSpaceIndentCount = ((indentCountOrig * indentLength) + spaceIndentCountOrig) % tabLength; } else { lineIndentCount += lineSpaceIndentCount / indentLength; lineSpaceIndentCount = lineSpaceIndentCount % indentLength; } } string ws; for (int i = 0; i < lineIndentCount; i++) ws += indentString; while ((lineSpaceIndentCount--) > 0) ws += string(" "); return ws; } /** * register an in-statement indent. */ void ASBeautifier::registerInStatementIndent(const string &line, int i, int spaceTabCount_, int tabIncrementIn, int minIndent, bool updateParenStack) { int inStatementIndent; int remainingCharNum = line.length() - i; int nextNonWSChar = getNextProgramCharDistance(line, i); // if indent is around the last char in the line, indent instead one indent from the previous indent if (nextNonWSChar == remainingCharNum) { int previousIndent = spaceTabCount_; if (!inStatementIndentStack->empty()) previousIndent = inStatementIndentStack->back(); int currIndent = /*2*/ indentLength + previousIndent; if (currIndent > maxInStatementIndent && line[i] != '{') currIndent = indentLength * 2 + spaceTabCount_; inStatementIndentStack->push_back(currIndent); if (updateParenStack) parenIndentStack->push_back(previousIndent); return; } if (updateParenStack) parenIndentStack->push_back(i + spaceTabCount_ - horstmannIndentInStatement); int tabIncrement = tabIncrementIn; // check for following tabs for (int j = i + 1; j < (i + nextNonWSChar); j++) { if (line[j] == '\t') tabIncrement += convertTabToSpaces(j, tabIncrement); } inStatementIndent = i + nextNonWSChar + spaceTabCount_ + tabIncrement; // check for run-in statement if (i > 0 && line[0] == '{') inStatementIndent -= indentLength; if (inStatementIndent < minIndent) inStatementIndent = minIndent + spaceTabCount_; // this is not done for an in-statement array if (inStatementIndent > maxInStatementIndent && !(prevNonLegalCh == '=' && currentNonLegalCh == '{')) inStatementIndent = indentLength * 2 + spaceTabCount_; if (!inStatementIndentStack->empty() && inStatementIndent < inStatementIndentStack->back()) inStatementIndent = inStatementIndentStack->back(); // the block opener is not indented for a NonInStatementArray if (isNonInStatementArray && !isInEnum && !bracketBlockStateStack->empty() && bracketBlockStateStack->back()) inStatementIndent = 0; inStatementIndentStack->push_back(inStatementIndent); } /** * Compute indentation for a preprocessor #if statement. * This may be called for the activeBeautiferStack * instead of the active ASBeautifier object. */ pair ASBeautifier::computePreprocessorIndent() { computePreliminaryIndentation(); pair entry (indentCount, spaceIndentCount); if (!headerStack->empty() && entry.first > 0 && (headerStack->back() == &AS_IF || headerStack->back() == &AS_ELSE || headerStack->back() == &AS_FOR || headerStack->back() == &AS_WHILE)) --entry.first; return entry; } /** * get distance to the next non-white space, non-comment character in the line. * if no such character exists, return the length remaining to the end of the line. */ int ASBeautifier::getNextProgramCharDistance(const string &line, int i) const { bool inComment = false; int remainingCharNum = line.length() - i; int charDistance; char ch; for (charDistance = 1; charDistance < remainingCharNum; charDistance++) { ch = line[i + charDistance]; if (inComment) { if (line.compare(i + charDistance, 2, "*/") == 0) { charDistance++; inComment = false; } continue; } else if (isWhiteSpace(ch)) continue; else if (ch == '/') { if (line.compare(i + charDistance, 2, "//") == 0) return remainingCharNum; else if (line.compare(i + charDistance, 2, "/*") == 0) { charDistance++; inComment = true; } } else return charDistance; } return charDistance; } // check if a specific line position contains a header. const string* ASBeautifier::findHeader(const string &line, int i, const vector* possibleHeaders) const { assert(isCharPotentialHeader(line, i)); // check the word size_t maxHeaders = possibleHeaders->size(); for (size_t p = 0; p < maxHeaders; p++) { const string* header = (*possibleHeaders)[p]; const size_t wordEnd = i + header->length(); if (wordEnd > line.length()) continue; int result = (line.compare(i, header->length(), *header)); if (result > 0) continue; if (result < 0) break; // check that this is not part of a longer word if (wordEnd == line.length()) return header; if (isLegalNameChar(line[wordEnd])) continue; const char peekChar = peekNextChar(line, wordEnd - 1); // is not a header if part of a definition if (peekChar == ',' || peekChar == ')') break; // the following accessor definitions are NOT headers // goto default; is NOT a header // default(int) keyword in C# is NOT a header else if ((header == &AS_GET || header == &AS_SET || header == &AS_DEFAULT) && (peekChar == ';' || peekChar == '(' || peekChar == '=')) break; return header; } return NULL; } // check if a specific line position contains an operator. const string* ASBeautifier::findOperator(const string &line, int i, const vector* possibleOperators) const { assert(isCharPotentialOperator(line[i])); // find the operator in the vector // the vector contains the LONGEST operators first // must loop thru the entire vector size_t maxOperators = possibleOperators->size(); for (size_t p = 0; p < maxOperators; p++) { const size_t wordEnd = i + (*(*possibleOperators)[p]).length(); if (wordEnd > line.length()) continue; if (line.compare(i, (*(*possibleOperators)[p]).length(), *(*possibleOperators)[p]) == 0) return (*possibleOperators)[p]; } return NULL; } /** * find the index number of a string element in a container of strings * * @return the index number of element in the container. -1 if element not found. * @param container a vector of strings. * @param element the element to find . */ int ASBeautifier::indexOf(vector &container, const string* element) const { vector::const_iterator where; where = find(container.begin(), container.end(), element); if (where == container.end()) return -1; else return (int) (where - container.begin()); } /** * convert tabs to spaces. * i is the position of the 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. */ int ASBeautifier::convertTabToSpaces(int i, int tabIncrementIn) const { int tabToSpacesAdjustment = indentLength - 1 - ((tabIncrementIn + i) % indentLength); return tabToSpacesAdjustment; } /** * trim removes the white space surrounding a line. * * @return the trimmed line. * @param str the line to trim. */ string ASBeautifier::trim(const string &str) const { int start = 0; int end = str.length() - 1; while (start < end && isWhiteSpace(str[start])) start++; while (start <= end && isWhiteSpace(str[end])) end--; // don't trim if it ends in a continuation if (end > -1 && str[end] == '\\') end = str.length() - 1; string returnStr(str, start, end + 1 - start); return returnStr; } /** * rtrim removes the white space from the end of a line. * * @return the trimmed line. * @param str the line to trim. */ string ASBeautifier::rtrim(const string &str) const { size_t len = str.length(); size_t end = str.find_last_not_of(" \t"); if (end == string::npos || end == len - 1) return str; string returnStr(str, 0, end + 1); return returnStr; } /** * Copy tempStacks for the copy constructor. * The value of the vectors must also be copied. */ vector*>* ASBeautifier::copyTempStacks(const ASBeautifier &other) const { vector*>* tempStacksNew = new vector*>; vector*>::iterator iter; for (iter = other.tempStacks->begin(); iter != other.tempStacks->end(); ++iter) { vector* newVec = new vector; *newVec = **iter; tempStacksNew->push_back(newVec); } return tempStacksNew; } /** * delete a member vectors to eliminate memory leak reporting */ void ASBeautifier::deleteBeautifierVectors() { beautifierFileType = 9; // reset to an invalid type delete headers; delete nonParenHeaders; delete preBlockStatements; delete preCommandHeaders; delete assignmentOperators; delete nonAssignmentOperators; delete indentableHeaders; } /** * delete a vector object * T is the type of vector * used for all vectors except tempStacks */ template void ASBeautifier::deleteContainer(T &container) { if (container != NULL) { container->clear(); delete (container); container = NULL; } } /** * Delete the ASBeautifier vector object. * This is a vector of pointers to ASBeautifier objects allocated with the 'new' operator. * Therefore the ASBeautifier objects have to be deleted in addition to the * ASBeautifier pointer entries. */ void ASBeautifier::deleteBeautifierContainer(vector* &container) { if (container != NULL) { vector::iterator iter = container->begin(); while (iter < container->end()) { delete *iter; ++iter; } container->clear(); delete (container); container = NULL; } } /** * Delete the tempStacks vector object. * The tempStacks is a vector of pointers to strings allocated with the 'new' operator. * Therefore the strings have to be deleted in addition to the tempStacks entries. */ void ASBeautifier::deleteTempStacksContainer(vector*>* &container) { if (container != NULL) { vector*>::iterator iter = container->begin(); while (iter < container->end()) { delete *iter; ++iter; } container->clear(); delete (container); container = NULL; } } /** * initialize a vector object * T is the type of vector used for all vectors */ template void ASBeautifier::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; } /** * Initialize the tempStacks vector object. * The tempStacks is a vector of pointers to strings allocated with the 'new' operator. * Any residual entries are deleted before the vector is initialized. */ void ASBeautifier::initTempStacksContainer(vector*>* &container, vector*>* value) { if (container != NULL) deleteTempStacksContainer(container); container = value; } /** * Determine if an assignment statement ends with a comma * that is not in a function argument. It ends with a * comma if a comma is the last char on the line. * * @return true if line ends with a comma, otherwise false. */ bool ASBeautifier::statementEndsWithComma(const string &line, int index) const { assert(line[index] == '='); bool isInComment_ = false; bool isInQuote_ = false; int parenCount = 0; size_t lineLength = line.length(); size_t i = 0; char quoteChar_ = ' '; for (i = index + 1; i < lineLength; ++i) { char 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) { if (isLineEndComment(line, i)) break; else { isInComment_ = true; ++i; continue; } } if (ch == '(') parenCount++; if (ch == ')') parenCount--; } if (isInComment_ || isInQuote_ || parenCount > 0) return false; size_t lastChar = line.find_last_not_of(" \t", i - 1); if (lastChar == string::npos || line[lastChar] != ',') return false; return true; } /** * check if current comment is a line-end comment * * @return is before a line-end comment. */ bool ASBeautifier::isLineEndComment(const string &line, int startPos) const { assert(line.compare(startPos, 2, "/*") == 0); // comment must be closed on this line with nothing after it size_t endNum = line.find("*/", startPos + 2); if (endNum != string::npos) { size_t nextChar = line.find_first_not_of(" \t", endNum + 2); if (nextChar == string::npos) return true; } return false; } /** * get the previous word index for an assignment operator * * @return is the index to the previous word (the in statement indent). */ int ASBeautifier::getInStatementIndentAssign(const string &line, size_t currPos) const { assert(line[currPos] == '='); if (currPos == 0) return 0; // get the last legal word (may be a number) size_t end = line.find_last_not_of(" \t", currPos - 1); if (end == string::npos || !isLegalNameChar(line[end])) return 0; int start; // start of the previous word for (start = end; start > -1; start--) { if (!isLegalNameChar(line[start]) || line[start] == '.') break; } start++; return start; } /** * get the instatement indent for a comma * * @return is the indent to the second word on the line (the in statement indent). */ int ASBeautifier::getInStatementIndentComma(const string &line, size_t currPos) const { assert(line[currPos] == ','); // get first word on a line size_t indent = line.find_first_not_of(" \t"); if (indent == string::npos || !isLegalNameChar(line[indent])) return 0; // bypass first word for (; indent < currPos; indent++) { if (!isLegalNameChar(line[indent])) break; } indent++; if (indent >= currPos || indent < 4) return 0; // point to second word or assignment operator indent = line.find_first_not_of(" \t", indent); if (indent == string::npos || indent >= currPos) return 0; return indent; } /** * get the next word on a line * the argument 'currPos' must point to the current position. * * @return is the next word or an empty string if none found. */ string ASBeautifier::getNextWord(const string &line, size_t currPos) const { size_t lineLength = line.length(); // get the last legal word (may be a number) if (currPos == lineLength - 1) return string(); size_t start = line.find_first_not_of(" \t", currPos + 1); if (start == string::npos || !isLegalNameChar(line[start])) return string(); size_t end; // end of the current word for (end = start + 1; end <= lineLength; end++) { if (!isLegalNameChar(line[end]) || line[end] == '.') break; } return line.substr(start, end - start); } /** * Check if a preprocessor directive is always indented. * C# "region" and "endregion" are always indented. * C/C++ "pragma omp" is always indented. * * @return is true or false. */ bool ASBeautifier::isIndentedPreprocessor(const string &line, size_t currPos) const { assert(line[0] == '#'); string nextWord = getNextWord(line, currPos); if (nextWord == "region" || nextWord == "endregion") return true; // is it #pragma omp if (nextWord == "pragma") { // find pragma size_t start = line.find("pragma"); if (start == string::npos || !isLegalNameChar(line[start])) return false; // bypass pragma for (; start < line.length(); start++) { if (!isLegalNameChar(line[start])) break; } start++; if (start >= line.length()) return false; // point to start of second word start = line.find_first_not_of(" \t", start); if (start == string::npos) return false; // point to end of second word size_t end; for (end = start; end < line.length(); end++) { if (!isLegalNameChar(line[end])) break; } // check for "pragma omp" string word = line.substr(start, end - start); if (word == "omp" || word == "region" || word == "endregion") return true; } return false; } /** * Check if a preprocessor directive is checking for __cplusplus defined. * * @return is true or false. */ bool ASBeautifier::isPreprocessorConditionalCplusplus(const string &line) const { string preproc = trim(line.substr(1)); if (preproc.compare(0, 5, "ifdef") == 0 && getNextWord(preproc, 4) == "__cplusplus") return true; if (preproc.compare(0, 2, "if") == 0) { // check for " #if defined(__cplusplus)" size_t charNum = 2; charNum = preproc.find_first_not_of(" \t", charNum); if (preproc.compare(charNum, 7, "defined") == 0) { charNum += 7; charNum = preproc.find_first_not_of(" \t", charNum); if (preproc.compare(charNum, 1, "(") == 0) { ++charNum; charNum = preproc.find_first_not_of(" \t", charNum); if (preproc.compare(charNum, 11, "__cplusplus") == 0) return true; } } } return false; } /** * Check if a preprocessor definition contains an unterminated comment. * Comments within a preprocessor definition can be continued without the backslash. * * @return is true or false. */ bool ASBeautifier::isInPreprocessorUnterminatedComment(const string &line) { if (!isInPreprocessorComment) { size_t startPos = line.find("/*"); if (startPos == string::npos) return false; } size_t endNum = line.find("*/"); if (endNum != string::npos) { isInPreprocessorComment = false; return false; } isInPreprocessorComment = true; return true; } void ASBeautifier::popLastInStatementIndent() { assert(!inStatementIndentStackSizeStack->empty()); int previousIndentStackSize = inStatementIndentStackSizeStack->back(); if (inStatementIndentStackSizeStack->size() > 1) inStatementIndentStackSizeStack->pop_back(); while (previousIndentStackSize < (int) inStatementIndentStack->size()) inStatementIndentStack->pop_back(); } // for unit testing int ASBeautifier::getBeautifierFileType() const { return beautifierFileType; } /** * Process preprocessor statements and update the beautifier stacks. */ void ASBeautifier::processPreprocessor(const string &preproc, const string &line) { // When finding a multi-lined #define statement, the original beautifier // 1. sets its isInDefineDefinition flag // 2. clones a new beautifier that will be used for the actual indentation // of the #define. This clone is put into the activeBeautifierStack in order // to be called for the actual indentation. // The original beautifier will have isInDefineDefinition = true, isInDefine = false // The cloned beautifier will have isInDefineDefinition = true, isInDefine = true if (shouldIndentPreprocDefine && preproc == "define" && line[line.length() - 1] == '\\') { if (!isInDefineDefinition) { ASBeautifier* defineBeautifier; // this is the original beautifier isInDefineDefinition = true; // push a new beautifier into the active stack // this beautifier will be used for the indentation of this define defineBeautifier = new ASBeautifier(*this); activeBeautifierStack->push_back(defineBeautifier); } else { // the is the cloned beautifier that is in charge of indenting the #define. isInDefine = true; } } else if (preproc.length() >= 2 && preproc.substr(0, 2) == "if") { if (isPreprocessorConditionalCplusplus(line) && !g_preprocessorCppExternCBracket) g_preprocessorCppExternCBracket = 1; // push a new beautifier into the stack waitingBeautifierStackLengthStack->push_back(waitingBeautifierStack->size()); activeBeautifierStackLengthStack->push_back(activeBeautifierStack->size()); if (activeBeautifierStackLengthStack->back() == 0) waitingBeautifierStack->push_back(new ASBeautifier(*this)); else waitingBeautifierStack->push_back(new ASBeautifier(*activeBeautifierStack->back())); } else if (preproc == "else") { if (waitingBeautifierStack && !waitingBeautifierStack->empty()) { // MOVE current waiting beautifier to active stack. activeBeautifierStack->push_back(waitingBeautifierStack->back()); waitingBeautifierStack->pop_back(); } } else if (preproc == "elif") { if (waitingBeautifierStack && !waitingBeautifierStack->empty()) { // append a COPY current waiting beautifier to active stack, WITHOUT deleting the original. activeBeautifierStack->push_back(new ASBeautifier(*(waitingBeautifierStack->back()))); } } else if (preproc == "endif") { int stackLength; ASBeautifier* beautifier; if (waitingBeautifierStackLengthStack != NULL && !waitingBeautifierStackLengthStack->empty()) { stackLength = waitingBeautifierStackLengthStack->back(); waitingBeautifierStackLengthStack->pop_back(); while ((int) waitingBeautifierStack->size() > stackLength) { beautifier = waitingBeautifierStack->back(); waitingBeautifierStack->pop_back(); delete beautifier; } } if (!activeBeautifierStackLengthStack->empty()) { stackLength = activeBeautifierStackLengthStack->back(); activeBeautifierStackLengthStack->pop_back(); while ((int) activeBeautifierStack->size() > stackLength) { beautifier = activeBeautifierStack->back(); activeBeautifierStack->pop_back(); delete beautifier; } } } } // Compute the preliminary indentation based on data in the headerStack // and data from previous lines. // Update the class variable indentCount. void ASBeautifier::computePreliminaryIndentation() { indentCount = 0; spaceIndentCount = 0; if (isInObjCMethodDefinition && !inStatementIndentStack->empty()) spaceIndentObjCMethodDefinition = inStatementIndentStack->back(); if (!inStatementIndentStack->empty()) spaceIndentCount = inStatementIndentStack->back(); for (size_t i = 0; i < headerStack->size(); i++) { isInClass = false; if (blockIndent) { // do NOT indent opening block for these headers if (!((*headerStack)[i] == &AS_NAMESPACE || (*headerStack)[i] == &AS_CLASS || (*headerStack)[i] == &AS_STRUCT || (*headerStack)[i] == &AS_UNION || (*headerStack)[i] == &AS_INTERFACE || (*headerStack)[i] == &AS_THROWS || (*headerStack)[i] == &AS_STATIC)) ++indentCount; } else if (!(i > 0 && (*headerStack)[i - 1] != &AS_OPEN_BRACKET && (*headerStack)[i] == &AS_OPEN_BRACKET)) ++indentCount; if (!isJavaStyle() && !namespaceIndent && i > 0 && (*headerStack)[i - 1] == &AS_NAMESPACE && (*headerStack)[i] == &AS_OPEN_BRACKET) --indentCount; if (isCStyle() && i >= 1 && (*headerStack)[i - 1] == &AS_CLASS && (*headerStack)[i] == &AS_OPEN_BRACKET) { if (classIndent) ++indentCount; isInClass = true; } // is the switchIndent option is on, indent switch statements an additional indent. else if (switchIndent && i > 1 && (*headerStack)[i - 1] == &AS_SWITCH && (*headerStack)[i] == &AS_OPEN_BRACKET) { ++indentCount; isInSwitch = true; } } // end of for loop if (isInClassInitializer) { if (lineStartsInComment || lineOpensWithComment) { if (!lineBeginsWithOpenBracket) --indentCount; } // is this class initializer (not in a class) or class definition? else if (isCStyle() && (headerStack->empty() || headerStack->back() != &AS_CLASS)) { isInClassHeaderTab = true; indentCount += classInitializerIndents; } else if (blockIndent) { if (!lineBeginsWithOpenBracket) ++indentCount; } } // Objective-C interface continuation line if (isInObjCInterface) ++indentCount; // unindent a class closing bracket... if (!lineStartsInComment && isCStyle() && isInClass && classIndent && headerStack->size() >= 2 && (*headerStack)[headerStack->size() - 2] == &AS_CLASS && (*headerStack)[headerStack->size() - 1] == &AS_OPEN_BRACKET && lineBeginsWithCloseBracket && bracketBlockStateStack->back() == true) --indentCount; // unindent an indented switch closing bracket... else if (!lineStartsInComment && isInSwitch && switchIndent && headerStack->size() >= 2 && (*headerStack)[headerStack->size() - 2] == &AS_SWITCH && (*headerStack)[headerStack->size() - 1] == &AS_OPEN_BRACKET && lineBeginsWithCloseBracket) --indentCount; // handle special case of horstmann comment in an indented class statement if (isInClass && classIndent && isInHorstmannComment && !lineOpensWithComment && headerStack->size() > 1 && (*headerStack)[headerStack->size() - 2] == &AS_CLASS) --indentCount; if (isInConditional) --indentCount; if (g_preprocessorCppExternCBracket >= 4) --indentCount; } void ASBeautifier::adjustParsedLineIndentation(size_t iPrelim, bool isInExtraHeaderIndent) { if (lineStartsInComment) return; // unindent a one-line statement in a header indent if (!blockIndent && lineBeginsWithOpenBracket && headerStack->size() < iPrelim && isInExtraHeaderIndent && (lineOpeningBlocksNum > 0 && lineOpeningBlocksNum <= lineClosingBlocksNum) && shouldIndentBrackettedLine) --indentCount; /* * if '{' doesn't follow an immediately previous '{' in the headerStack * (but rather another header such as "for" or "if", then unindent it * by one indentation relative to its block. */ else if (!blockIndent && lineBeginsWithOpenBracket && !(lineOpeningBlocksNum > 0 && lineOpeningBlocksNum <= lineClosingBlocksNum) && (headerStack->size() > 1 && (*headerStack)[headerStack->size() - 2] != &AS_OPEN_BRACKET) && shouldIndentBrackettedLine) --indentCount; // must check one less in headerStack if more than one header on a line (allow-addins)... else if (headerStack->size() > iPrelim + 1 && !blockIndent && lineBeginsWithOpenBracket && !(lineOpeningBlocksNum > 0 && lineOpeningBlocksNum <= lineClosingBlocksNum) && (headerStack->size() > 2 && (*headerStack)[headerStack->size() - 3] != &AS_OPEN_BRACKET) && shouldIndentBrackettedLine) --indentCount; // unindent a closing bracket... else if (lineBeginsWithCloseBracket && shouldIndentBrackettedLine) --indentCount; // correctly indent one-line-blocks... else if (lineOpeningBlocksNum > 0 && lineOpeningBlocksNum == lineClosingBlocksNum && previousLineProbationTab) --indentCount; // correctly indent class continuation lines... else if (!lineOpensWithComment && isInClassHeaderTab && !blockIndent && lineOpeningBlocksNum == 0 && lineOpeningBlocksNum == lineClosingBlocksNum && (!headerStack->empty() && headerStack->back() == &AS_CLASS)) --indentCount; if (indentCount < 0) indentCount = 0; // take care of extra bracket indentation option... if (!lineStartsInComment && bracketIndent && shouldIndentBrackettedLine && (lineBeginsWithOpenBracket || lineBeginsWithCloseBracket)) ++indentCount; } /** * Compute indentCount adjustment when in a series of else-if statements * and shouldBreakElseIfs is requested. * It increments by one for each 'else' in the tempStack. */ int ASBeautifier::adjustIndentCountForBreakElseIfComments() const { assert(isElseHeaderIndent && !tempStacks->empty()); int indentCountIncrement = 0; vector* lastTempStack = tempStacks->back(); if (lastTempStack != NULL) { for (size_t i = 0; i < lastTempStack->size(); i++) { if (*lastTempStack->at(i) == AS_ELSE) indentCountIncrement++; } } return indentCountIncrement; } /** * Extract a preprocessor statement without the #. * If a error occurs an empty string is returned. */ string ASBeautifier::extractPreprocessorStatement(const string &line) const { string preproc; size_t start = line.find_first_not_of("#/ \t"); if (start == string::npos) return preproc; size_t end = line.find_first_of("/ \t", start); if (end == string::npos) end = line.length(); preproc = line.substr(start, end - start); return preproc; } /** * Clear the variables used to align the Objective-C method definitions. */ void ASBeautifier::clearObjCMethodDefinitionAlignment() { assert(isImmediatelyPostObjCMethodDefinition); spaceIndentCount = 0; spaceIndentObjCMethodDefinition = 0; colonIndentObjCMethodDefinition = 0; isInObjCMethodDefinition = false; isImmediatelyPostObjCMethodDefinition = false; if (!inStatementIndentStack->empty()) inStatementIndentStack->pop_back(); } /** * Compute the spaceIndentCount necessary to align the current line colon * with the colon position in the argument. * If it cannot be aligned indentLength is returned and a new colon * position is calculated. */ int ASBeautifier::computeObjCColonAlignment(string &line, int colonAlignPosition) const { int colonPosition = line.find(':'); if (colonPosition < 0 || colonPosition > colonAlignPosition) return indentLength; return (colonAlignPosition - colonPosition); } /** * Parse the current line to update indentCount and spaceIndentCount. */ void ASBeautifier::parseCurrentLine(const string &line) { bool isInLineComment = false; bool isInOperator = false; bool isSpecialChar = false; bool haveCaseIndent = false; bool haveAssignmentThisLine = false; bool closingBracketReached = false; bool previousLineProbation = (probationHeader != NULL); char ch = ' '; int tabIncrementIn = 0; for (size_t i = 0; i < line.length(); i++) { ch = line[i]; if (isInBeautifySQL) continue; if (isWhiteSpace(ch)) { if (ch == '\t') tabIncrementIn += convertTabToSpaces(i, tabIncrementIn); continue; } // handle special characters (i.e. backslash+character such as \n, \t, ...) if (isInQuote && !isInVerbatimQuote) { if (isSpecialChar) { isSpecialChar = false; continue; } if (line.compare(i, 2, "\\\\") == 0) { i++; continue; } if (ch == '\\') { if (peekNextChar(line, i) == ' ') // is this '\' at end of line haveLineContinuationChar = true; else isSpecialChar = true; continue; } } else if (isInDefine && ch == '\\') continue; // handle quotes (such as 'x' and "Hello Dolly") if (!(isInComment || isInLineComment) && (ch == '"' || ch == '\'')) { if (!isInQuote) { quoteChar = ch; isInQuote = true; char prevCh = i > 0 ? line[i - 1] : ' '; if (isCStyle() && prevCh == 'R') { int parenPos = line.find('(', i); if (parenPos != -1) { isInVerbatimQuote = true; verbatimDelimiter = line.substr(i + 1, parenPos - i - 1); } } else if (isSharpStyle() && prevCh == '@') isInVerbatimQuote = true; // check for "C" following "extern" else if (g_preprocessorCppExternCBracket == 2 && line.compare(i, 3, "\"C\"") == 0) ++g_preprocessorCppExternCBracket; } else if (isInVerbatimQuote && ch == '"') { if (isCStyle()) { string delim = ')' + verbatimDelimiter; int delimStart = i - delim.length(); if (delimStart > 0 && line.substr(delimStart, delim.length()) == delim) { isInQuote = false; isInVerbatimQuote = false; } } else if (isSharpStyle()) { if (peekNextChar(line, i) == '"') // check consecutive quotes i++; else { isInQuote = false; isInVerbatimQuote = false; } } } else if (quoteChar == ch) { isInQuote = false; isInStatement = true; continue; } } if (isInQuote) continue; // handle comments if (!(isInComment || isInLineComment) && line.compare(i, 2, "//") == 0) { // if there is a 'case' statement after these comments unindent by 1 if (isCaseHeaderCommentIndent) --indentCount; // isElseHeaderIndent is set by ASFormatter if shouldBreakElseIfs is requested // if there is an 'else' after these comments a tempStacks indent is required if (isElseHeaderIndent && lineOpensWithLineComment && !tempStacks->empty()) indentCount += adjustIndentCountForBreakElseIfComments(); isInLineComment = true; i++; continue; } else if (!(isInComment || isInLineComment) && line.compare(i, 2, "/*") == 0) { // if there is a 'case' statement after these comments unindent by 1 if (isCaseHeaderCommentIndent && lineOpensWithComment) --indentCount; // isElseHeaderIndent is set by ASFormatter if shouldBreakElseIfs is requested // if there is an 'else' after these comments a tempStacks indent is required if (isElseHeaderIndent && lineOpensWithComment && !tempStacks->empty()) indentCount += adjustIndentCountForBreakElseIfComments(); isInComment = true; i++; if (!lineOpensWithComment) // does line start with comment? blockCommentNoIndent = true; // if no, cannot indent continuation lines continue; } else if ((isInComment || isInLineComment) && line.compare(i, 2, "*/") == 0) { size_t firstText = line.find_first_not_of(" \t"); // if there is a 'case' statement after these comments unindent by 1 // only if the ending comment is the first entry on the line if (isCaseHeaderCommentIndent && firstText == i) --indentCount; // if this comment close starts the line, must check for else-if indent // isElseHeaderIndent is set by ASFormatter if shouldBreakElseIfs is requested // if there is an 'else' after these comments a tempStacks indent is required if (firstText == i) { if (isElseHeaderIndent && !lineOpensWithComment && !tempStacks->empty()) indentCount += adjustIndentCountForBreakElseIfComments(); } isInComment = false; i++; blockCommentNoIndent = false; // ok to indent next comment continue; } // treat indented preprocessor lines as a line comment else if (line[0] == '#' && isIndentedPreprocessor(line, i)) { isInLineComment = true; } if (isInLineComment) { // bypass rest of the comment up to the comment end while (i + 1 < line.length()) i++; continue; } if (isInComment) { // if there is a 'case' statement after these comments unindent by 1 if (!lineOpensWithComment && isCaseHeaderCommentIndent) --indentCount; // isElseHeaderIndent is set by ASFormatter if shouldBreakElseIfs is requested // if there is an 'else' after these comments a tempStacks indent is required if (!lineOpensWithComment && isElseHeaderIndent && !tempStacks->empty()) indentCount += adjustIndentCountForBreakElseIfComments(); // bypass rest of the comment up to the comment end while (i + 1 < line.length() && line.compare(i + 1, 2, "*/") != 0) i++; continue; } // if we have reached this far then we are NOT in a comment or string of special character... if (probationHeader != NULL) { if ((probationHeader == &AS_STATIC && ch == '{') || (probationHeader == &AS_SYNCHRONIZED && ch == '(')) { // insert the probation header as a new header isInHeader = true; headerStack->push_back(probationHeader); // handle the specific probation header isInConditional = (probationHeader == &AS_SYNCHRONIZED); isInStatement = false; // if the probation comes from the previous line, then indent by 1 tab count. if (previousLineProbation && ch == '{' && !(blockIndent && probationHeader == &AS_STATIC)) { ++indentCount; previousLineProbationTab = true; } previousLineProbation = false; } // dismiss the probation header probationHeader = NULL; } prevNonSpaceCh = currentNonSpaceCh; currentNonSpaceCh = ch; if (!isLegalNameChar(ch) && ch != ',' && ch != ';') { prevNonLegalCh = currentNonLegalCh; currentNonLegalCh = ch; } if (isInHeader) { isInHeader = false; currentHeader = headerStack->back(); } else currentHeader = NULL; if (isCStyle() && isInTemplate && (ch == '<' || ch == '>') && !(line.length() > i + 1 && line.compare(i, 2, ">=") == 0)) { if (ch == '<') { ++templateDepth; inStatementIndentStackSizeStack->push_back(inStatementIndentStack->size()); registerInStatementIndent(line, i, spaceIndentCount, tabIncrementIn, 0, true); } else if (ch == '>') { popLastInStatementIndent(); if (--templateDepth <= 0) { ch = ';'; isInTemplate = false; templateDepth = 0; } } } // handle parentheses if (ch == '(' || ch == '[' || ch == ')' || ch == ']') { if (ch == '(' || ch == '[') { isInOperator = false; // if have a struct header, this is a declaration not a definition if (ch == '(' && (isInClassInitializer || isInClassHeaderTab) && !headerStack->empty() && headerStack->back() == &AS_STRUCT) { headerStack->pop_back(); isInClassInitializer = false; // -1 for isInClassInitializer, -2 for isInClassHeaderTab if (isInClassHeaderTab) { indentCount -= (1 + classInitializerIndents); isInClassHeaderTab = false; } if (indentCount < 0) indentCount = 0; } if (parenDepth == 0) { parenStatementStack->push_back(isInStatement); isInStatement = true; } parenDepth++; if (ch == '[') ++squareBracketCount; inStatementIndentStackSizeStack->push_back(inStatementIndentStack->size()); if (currentHeader != NULL) registerInStatementIndent(line, i, spaceIndentCount, tabIncrementIn, minConditionalIndent/*indentLength*2*/, true); else registerInStatementIndent(line, i, spaceIndentCount, tabIncrementIn, 0, true); } else if (ch == ')' || ch == ']') { if (ch == ']') --squareBracketCount; if (squareBracketCount < 0) squareBracketCount = 0; foundPreCommandHeader = false; parenDepth--; if (parenDepth == 0) { if (!parenStatementStack->empty()) // in case of unmatched closing parens { isInStatement = parenStatementStack->back(); parenStatementStack->pop_back(); } ch = ' '; isInAsm = false; isInConditional = false; } if (!inStatementIndentStackSizeStack->empty()) { popLastInStatementIndent(); if (!parenIndentStack->empty()) { int poppedIndent = parenIndentStack->back(); parenIndentStack->pop_back(); if (i == 0) spaceIndentCount = poppedIndent; } } } continue; } if (ch == '{') { // first, check if '{' is a block-opener or a static-array opener bool isBlockOpener = ((prevNonSpaceCh == '{' && bracketBlockStateStack->back()) || prevNonSpaceCh == '}' || prevNonSpaceCh == ')' || prevNonSpaceCh == ';' || peekNextChar(line, i) == '{' || foundPreCommandHeader || foundPreCommandMacro || isInClassInitializer || isNonInStatementArray || isInObjCMethodDefinition || isInObjCInterface || isSharpAccessor || isSharpDelegate || isInExternC || getNextWord(line, i) == AS_NEW || (isInDefine && (prevNonSpaceCh == '(' || isLegalNameChar(prevNonSpaceCh)))); // remove inStatementIndent for C++ class initializer if (isInClassInitializer) { if (!inStatementIndentStack->empty()) inStatementIndentStack->pop_back(); isInStatement = false; if (lineBeginsWithOpenBracket) spaceIndentCount = 0; isInClassInitializer = false; } if (isInObjCMethodDefinition) isImmediatelyPostObjCMethodDefinition = true; if (!isBlockOpener && !isInStatement && !isInEnum) { if (headerStack->empty()) isBlockOpener = true; else if (headerStack->back() == &AS_NAMESPACE || headerStack->back() == &AS_CLASS || headerStack->back() == &AS_STRUCT) isBlockOpener = true; } if (!isBlockOpener && currentHeader != NULL) { for (size_t n = 0; n < nonParenHeaders->size(); n++) if (currentHeader == (*nonParenHeaders)[n]) { isBlockOpener = true; break; } } bracketBlockStateStack->push_back(isBlockOpener); if (!isBlockOpener) { inStatementIndentStackSizeStack->push_back(inStatementIndentStack->size()); registerInStatementIndent(line, i, spaceIndentCount, tabIncrementIn, 0, true); parenDepth++; if (i == 0) shouldIndentBrackettedLine = false; continue; } // this bracket is a block opener... ++lineOpeningBlocksNum; if (isInClassHeaderTab) { isInClassHeaderTab = false; // decrease tab count if bracket is broken size_t firstChar = line.find_first_not_of(" \t"); if (firstChar != string::npos && line[firstChar] == '{' && firstChar == i) { indentCount -= classInitializerIndents; // decrease one more if an empty class if (!headerStack->empty() && (*headerStack).back() == &AS_CLASS) { int nextChar = getNextProgramCharDistance(line, i); if ((int)line.length() > nextChar && line[nextChar] == '}') --indentCount; } } } if (isInObjCInterface) { isInObjCInterface = false; if (lineBeginsWithOpenBracket) --indentCount; } if (bracketIndent && !namespaceIndent && !headerStack->empty() && (*headerStack).back() == &AS_NAMESPACE) { shouldIndentBrackettedLine = false; --indentCount; } // an indentable struct is treated like a class in the header stack if (!headerStack->empty() && (*headerStack).back() == &AS_STRUCT && isInIndentableStruct) (*headerStack).back() = &AS_CLASS; blockParenDepthStack->push_back(parenDepth); blockStatementStack->push_back(isInStatement); if (!inStatementIndentStack->empty()) { // completely purge the inStatementIndentStack while (!inStatementIndentStack->empty()) popLastInStatementIndent(); spaceIndentCount = 0; } blockTabCount += (isInStatement ? 1 : 0); if (g_preprocessorCppExternCBracket == 3) ++g_preprocessorCppExternCBracket; parenDepth = 0; isInStatement = false; isInQuestion = false; foundPreCommandHeader = false; foundPreCommandMacro = false; isInExternC = false; tempStacks->push_back(new vector); headerStack->push_back(&AS_OPEN_BRACKET); lastLineHeader = &AS_OPEN_BRACKET; continue; } // end '{' //check if a header has been reached bool isPotentialHeader = isCharPotentialHeader(line, i); if (isPotentialHeader && !squareBracketCount) { const string* newHeader = findHeader(line, i, headers); if (newHeader != NULL) { // if we reached here, then this is a header... bool isIndentableHeader = true; isInHeader = true; vector* lastTempStack; if (tempStacks->empty()) lastTempStack = NULL; else lastTempStack = tempStacks->back(); // if a new block is opened, push a new stack into tempStacks to hold the // future list of headers in the new block. // take care of the special case: 'else if (...)' if (newHeader == &AS_IF && lastLineHeader == &AS_ELSE) { headerStack->pop_back(); } // take care of 'else' else if (newHeader == &AS_ELSE) { if (lastTempStack != NULL) { int indexOfIf = indexOf(*lastTempStack, &AS_IF); if (indexOfIf != -1) { // recreate the header list in headerStack up to the previous 'if' // from the temporary snapshot stored in lastTempStack. int restackSize = lastTempStack->size() - indexOfIf - 1; for (int r = 0; r < restackSize; r++) { headerStack->push_back(lastTempStack->back()); lastTempStack->pop_back(); } if (!closingBracketReached) indentCount += restackSize; } /* * If the above if is not true, i.e. no 'if' before the 'else', * then nothing beautiful will come out of this... * I should think about inserting an Exception here to notify the caller of this... */ } } // check if 'while' closes a previous 'do' else if (newHeader == &AS_WHILE) { if (lastTempStack != NULL) { int indexOfDo = indexOf(*lastTempStack, &AS_DO); if (indexOfDo != -1) { // recreate the header list in headerStack up to the previous 'do' // from the temporary snapshot stored in lastTempStack. int restackSize = lastTempStack->size() - indexOfDo - 1; for (int r = 0; r < restackSize; r++) { headerStack->push_back(lastTempStack->back()); lastTempStack->pop_back(); } if (!closingBracketReached) indentCount += restackSize; } } } // check if 'catch' closes a previous 'try' or 'catch' else if (newHeader == &AS_CATCH || newHeader == &AS_FINALLY) { if (lastTempStack != NULL) { int indexOfTry = indexOf(*lastTempStack, &AS_TRY); if (indexOfTry == -1) indexOfTry = indexOf(*lastTempStack, &AS_CATCH); if (indexOfTry != -1) { // recreate the header list in headerStack up to the previous 'try' // from the temporary snapshot stored in lastTempStack. int restackSize = lastTempStack->size() - indexOfTry - 1; for (int r = 0; r < restackSize; r++) { headerStack->push_back(lastTempStack->back()); lastTempStack->pop_back(); } if (!closingBracketReached) indentCount += restackSize; } } } else if (newHeader == &AS_CASE) { isInCase = true; if (!haveCaseIndent) { haveCaseIndent = true; if (!lineBeginsWithOpenBracket) --indentCount; } } else if (newHeader == &AS_DEFAULT) { isInCase = true; --indentCount; } else if (newHeader == &AS_STATIC || newHeader == &AS_SYNCHRONIZED) { if (!headerStack->empty() && (headerStack->back() == &AS_STATIC || headerStack->back() == &AS_SYNCHRONIZED)) { isIndentableHeader = false; } else { isIndentableHeader = false; probationHeader = newHeader; } } else if (newHeader == &AS_TEMPLATE) { isInTemplate = true; isIndentableHeader = false; } if (isIndentableHeader) { headerStack->push_back(newHeader); isInStatement = false; if (indexOf(*nonParenHeaders, newHeader) == -1) { isInConditional = true; } lastLineHeader = newHeader; } else isInHeader = false; i += newHeader->length() - 1; continue; } // newHeader != NULL if (findHeader(line, i, preCommandHeaders)) foundPreCommandHeader = true; // Objective-C NSException macros are preCommandHeaders if (isCStyle() && findKeyword(line, i, AS_NS_DURING)) foundPreCommandMacro = true; if (isCStyle() && findKeyword(line, i, AS_NS_HANDLER)) foundPreCommandMacro = true; // this applies only to C enums if (isCStyle() && parenDepth == 0 && findKeyword(line, i, AS_ENUM)) isInEnum = true; } // isPotentialHeader if (ch == '?') isInQuestion = true; // special handling of colons if (ch == ':') { if (line.length() > i + 1 && line[i + 1] == ':') // look for :: { ++i; ch = ' '; continue; } else if (isInQuestion) { // do nothing special } else if (parenDepth > 0) { // found an objective-C statement // so do nothing special } else if (isInEnum) { // found an enum with a base-type // so do nothing special } else if (!headerStack->empty() && headerStack->back() == &AS_FOR && parenDepth > 0) { // found a range-based 'for' loop 'for (auto i : container)' // so do nothing special } else if (isInClassInitializer || isInObjCInterface) { // is in a 'class A : public B' definition // so do nothing special } else if (isInAsm || isInAsmOneLine || isInAsmBlock) { // do nothing special } else if (isDigit(peekNextChar(line, i))) { // found a bit field // so do nothing special } else if (isCStyle() && isInClass && prevNonSpaceCh != ')') { // found a 'private:' or 'public:' inside a class definition --indentCount; if (modifierIndent) spaceIndentCount += (indentLength / 2); } else if (isCStyle() && !isInClass && headerStack->size() >= 2 && (*headerStack)[headerStack->size() - 2] == &AS_CLASS && (*headerStack)[headerStack->size() - 1] == &AS_OPEN_BRACKET) { // found a 'private:' or 'public:' inside a class definition // and on the same line as the class opening bracket // do nothing } else if (isCStyle() && prevNonSpaceCh == ')' && !isInCase) { // found a 'class A : public B' definition isInClassInitializer = true; isInStatement = false; // so an inStatement indent will register if (i == 0) indentCount += classInitializerIndents; } else if (isJavaStyle() && lastLineHeader == &AS_FOR) { // found a java for-each statement // so do nothing special } else { currentNonSpaceCh = ';'; // so that brackets after the ':' will appear as block-openers char peekedChar = peekNextChar(line, i); if (isInCase) { isInCase = false; ch = ';'; // from here on, treat char as ';' } else if (isCStyle() || (isSharpStyle() && peekedChar == ';')) { // is in a label (e.g. 'label1:') if (labelIndent) --indentCount; // unindent label by one indent else if (!lineBeginsWithOpenBracket) indentCount = 0; // completely flush indent to left } } } if ((ch == ';' || (parenDepth > 0 && ch == ',')) && !inStatementIndentStackSizeStack->empty()) while ((int) inStatementIndentStackSizeStack->back() + (parenDepth > 0 ? 1 : 0) < (int) inStatementIndentStack->size()) inStatementIndentStack->pop_back(); else if (ch == ',' && isInEnum && isNonInStatementArray && !inStatementIndentStack->empty()) inStatementIndentStack->pop_back(); // handle commas // previous "isInStatement" will be from an assignment operator or class initializer if (ch == ',' && parenDepth == 0 && !isInStatement && !isNonInStatementArray) { // is comma at end of line size_t nextChar = line.find_first_not_of(" \t", i + 1); if (nextChar != string::npos) { if (line.compare(nextChar, 2, "//") == 0 || line.compare(nextChar, 2, "/*") == 0) nextChar = string::npos; } // register indent if (nextChar == string::npos) { // register indent at first word after the colon of a C++ class initializer if (isInClassInitializer) { size_t firstChar = line.find_first_not_of(" \t"); if (firstChar != string::npos && line[firstChar] == ':') { size_t firstWord = line.find_first_not_of(" \t", firstChar + 1); if (firstChar != string::npos) { int inStatementIndent = firstWord + spaceIndentCount + tabIncrementIn; inStatementIndentStack->push_back(inStatementIndent); isInStatement = true; } } } // register indent at previous word else if (!isInTemplate) { int prevWord = getInStatementIndentComma(line, i); int inStatementIndent = prevWord + spaceIndentCount + tabIncrementIn; inStatementIndentStack->push_back(inStatementIndent); isInStatement = true; } } } // handle ends of statements if ((ch == ';' && parenDepth == 0) || ch == '}') { if (ch == '}') { // first check if this '}' closes a previous block, or a static array... if (bracketBlockStateStack->size() > 1) { bool bracketBlockState = bracketBlockStateStack->back(); bracketBlockStateStack->pop_back(); if (!bracketBlockState) { if (!inStatementIndentStackSizeStack->empty()) { // this bracket is a static array popLastInStatementIndent(); parenDepth--; if (i == 0) shouldIndentBrackettedLine = false; if (!parenIndentStack->empty()) { int poppedIndent = parenIndentStack->back(); parenIndentStack->pop_back(); if (i == 0) spaceIndentCount = poppedIndent; } } continue; } } // this bracket is block closer... ++lineClosingBlocksNum; if (!inStatementIndentStackSizeStack->empty()) popLastInStatementIndent(); if (!blockParenDepthStack->empty()) { parenDepth = blockParenDepthStack->back(); blockParenDepthStack->pop_back(); isInStatement = blockStatementStack->back(); blockStatementStack->pop_back(); if (isInStatement) blockTabCount--; } closingBracketReached = true; if (i == 0) spaceIndentCount = 0; // close these just in case isInAsm = isInAsmOneLine = isInQuote = false; int headerPlace = indexOf(*headerStack, &AS_OPEN_BRACKET); if (headerPlace != -1) { const string* popped = headerStack->back(); while (popped != &AS_OPEN_BRACKET) { headerStack->pop_back(); popped = headerStack->back(); } headerStack->pop_back(); if (headerStack->empty()) g_preprocessorCppExternCBracket = 0; // do not indent namespace bracket unless namespaces are indented if (!namespaceIndent && !headerStack->empty() && (*headerStack).back() == &AS_NAMESPACE && i == 0) // must be the first bracket on the line shouldIndentBrackettedLine = false; if (!tempStacks->empty()) { vector* temp = tempStacks->back(); tempStacks->pop_back(); delete temp; } } ch = ' '; // needed due to cases such as '}else{', so that headers ('else' tn tih case) will be identified... } // ch == '}' /* * Create a temporary snapshot of the current block's header-list in the * uppermost inner stack in tempStacks, and clear the headerStack up to * the beginning of the block. * Thus, the next future statement will think it comes one indent past * the block's '{' unless it specifically checks for a companion-header * (such as a previous 'if' for an 'else' header) within the tempStacks, * and recreates the temporary snapshot by manipulating the tempStacks. */ if (!tempStacks->back()->empty()) while (!tempStacks->back()->empty()) tempStacks->back()->pop_back(); while (!headerStack->empty() && headerStack->back() != &AS_OPEN_BRACKET) { tempStacks->back()->push_back(headerStack->back()); headerStack->pop_back(); } if (parenDepth == 0 && ch == ';') isInStatement = false; if (isInObjCMethodDefinition) isImmediatelyPostObjCMethodDefinition = true; previousLastLineHeader = NULL; isInClassInitializer = false; isInEnum = false; isInQuestion = false; isInObjCInterface = false; foundPreCommandHeader = false; foundPreCommandMacro = false; squareBracketCount = 0; continue; } if (isPotentialHeader) { // check for preBlockStatements in C/C++ ONLY if not within parentheses // (otherwise 'struct XXX' statements would be wrongly interpreted...) if (!isInTemplate && !(isCStyle() && parenDepth > 0)) { const string* newHeader = findHeader(line, i, preBlockStatements); if (newHeader != NULL && !(isCStyle() && newHeader == &AS_CLASS && isInEnum)) // is it 'enum class' { isInClassInitializer = true; if (!isSharpStyle()) headerStack->push_back(newHeader); // do not need 'where' in the headerStack // do not need second 'class' statement in a row else if (!(newHeader == &AS_WHERE || (newHeader == &AS_CLASS && !headerStack->empty() && headerStack->back() == &AS_CLASS))) headerStack->push_back(newHeader); i += newHeader->length() - 1; continue; } } const string* foundIndentableHeader = findHeader(line, i, indentableHeaders); if (foundIndentableHeader != NULL) { // must bypass the header before registering the in statement i += foundIndentableHeader->length() - 1; if (!isInOperator && !isInTemplate && !isNonInStatementArray) { registerInStatementIndent(line, i, spaceIndentCount, tabIncrementIn, 0, false); isInStatement = true; } continue; } if (isCStyle() && findKeyword(line, i, AS_OPERATOR)) isInOperator = true; if (g_preprocessorCppExternCBracket == 1 && findKeyword(line, i, AS_EXTERN)) ++g_preprocessorCppExternCBracket; if (g_preprocessorCppExternCBracket == 3) // extern "C" is not followed by a '{' g_preprocessorCppExternCBracket = 0; // "new" operator is a pointer, not a calculation if (findKeyword(line, i, AS_NEW)) { if (isInStatement && !inStatementIndentStack->empty() && prevNonSpaceCh == '=' ) inStatementIndentStack->back() = 0; } if (isCStyle()) { if (findKeyword(line, i, AS_ASM) || findKeyword(line, i, AS__ASM__)) { isInAsm = true; } else if (findKeyword(line, i, AS_MS_ASM) // microsoft specific || findKeyword(line, i, AS_MS__ASM)) { int index = 4; if (peekNextChar(line, i) == '_') // check for __asm index = 5; char peekedChar = ASBase::peekNextChar(line, i + index); if (peekedChar == '{' || peekedChar == ' ') isInAsmBlock = true; else isInAsmOneLine = true; } } // bypass the entire name for all others string name = getCurrentWord(line, i); i += name.length() - 1; continue; } // Handle Objective-C statements if (ch == '@' && isCharPotentialHeader(line, i + 1)) { string curWord = getCurrentWord(line, i + 1); if (curWord == AS_INTERFACE && headerStack->empty()) { isInObjCInterface = true; string name = '@' + curWord; i += name.length() - 1; continue; } else if (curWord == AS_PUBLIC || curWord == AS_PRIVATE || curWord == AS_PROTECTED) { --indentCount; if (modifierIndent) spaceIndentCount += (indentLength / 2); string name = '@' + curWord; i += name.length() - 1; continue; } else if (curWord == AS_END) { if (isInObjCInterface) --indentCount; isInObjCInterface = false; isInObjCMethodDefinition = false; string name = '@' + curWord; i += name.length() - 1; continue; } } else if ((ch == '-' || ch == '+') && peekNextChar(line, i) == '(' && headerStack->empty() && line.find_first_not_of(" \t") == i) { if (isInObjCInterface) --indentCount; isInObjCInterface = false; isInObjCMethodDefinition = true; continue; } // Handle operators bool isPotentialOperator = isCharPotentialOperator(ch); if (isPotentialOperator) { // Check if an operator has been reached. const string* foundAssignmentOp = findOperator(line, i, assignmentOperators); const string* foundNonAssignmentOp = findOperator(line, i, nonAssignmentOperators); if (foundNonAssignmentOp == &AS_LAMBDA) foundPreCommandHeader = true; if (isInTemplate && foundNonAssignmentOp == &AS_GR_GR) foundNonAssignmentOp = NULL; // Since findHeader's boundary checking was not used above, it is possible // that both an assignment op and a non-assignment op where found, // e.g. '>>' and '>>='. If this is the case, treat the LONGER one as the // found operator. if (foundAssignmentOp != NULL && foundNonAssignmentOp != NULL) { if (foundAssignmentOp->length() < foundNonAssignmentOp->length()) foundAssignmentOp = NULL; else foundNonAssignmentOp = NULL; } if (foundNonAssignmentOp != NULL) { if (foundNonAssignmentOp->length() > 1) i += foundNonAssignmentOp->length() - 1; // For C++ input/output, operator<< and >> should be // aligned, if we are not in a statement already and // also not in the "operator<<(...)" header line if (!isInOperator && inStatementIndentStack->empty() && isCStyle() && (foundNonAssignmentOp == &AS_GR_GR || foundNonAssignmentOp == &AS_LS_LS)) { // this will be true if the line begins with the operator if (i < 2 && spaceIndentCount == 0) spaceIndentCount += 2 * indentLength; // align to the beginning column of the operator registerInStatementIndent(line, i - foundNonAssignmentOp->length(), spaceIndentCount, tabIncrementIn, 0, false); } } else if (foundAssignmentOp != NULL) { foundPreCommandHeader = false; // clears this for array assignments foundPreCommandMacro = false; if (foundAssignmentOp->length() > 1) i += foundAssignmentOp->length() - 1; if (!isInOperator && !isInTemplate && (!isNonInStatementArray || isInEnum)) { // if multiple assignments, align on the previous word if (foundAssignmentOp == &AS_ASSIGN && prevNonSpaceCh != ']' // an array && statementEndsWithComma(line, i)) { if (!haveAssignmentThisLine) // only one assignment indent per line { // register indent at previous word haveAssignmentThisLine = true; int prevWordIndex = getInStatementIndentAssign(line, i); int inStatementIndent = prevWordIndex + spaceIndentCount + tabIncrementIn; inStatementIndentStack->push_back(inStatementIndent); } } else { if (i == 0 && spaceIndentCount == 0) spaceIndentCount += indentLength; registerInStatementIndent(line, i, spaceIndentCount, tabIncrementIn, 0, false); } isInStatement = true; } } } } // end of for loop * end of for loop * end of for loop * end of for loop * end of for loop * } } // end namespace astyle diff --git a/formatters/astyle/lib/ASFormatter.cpp b/formatters/astyle/lib/ASFormatter.cpp index 41f4732a60..50996914f1 100755 --- 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 programing language + * 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 thru 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 thru 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(); } } - // explicitely break a line when a line comment's end is found. + // 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; 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++; } // 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/duchain/builder.cpp b/languages/clang/duchain/builder.cpp index 22bafd9f36..66475f7512 100644 --- a/languages/clang/duchain/builder.cpp +++ b/languages/clang/duchain/builder.cpp @@ -1,1519 +1,1518 @@ /* * This file is part of KDevelop * * Copyright 2013 Olivier de Gaalon * Copyright 2015 Milian Wolff * * 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 "builder.h" #include "util/clangdebug.h" #include "templatehelpers.h" #include "cursorkindtraits.h" #include "clangducontext.h" #include "macrodefinition.h" #include "types/classspecializationtype.h" -#include "util/clangdebug.h" #include "util/clangutils.h" #include "util/clangtypes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /// Turn on for debugging the declaration building #define IF_DEBUG(x) namespace { // TODO: this is ugly, can we find a better alternative? bool jsonTestRun() { static bool runningTest = qEnvironmentVariableIsSet("KDEV_CLANG_JSON_TEST_RUN"); return runningTest; } //BEGIN helpers /** * Find the cursor that cursor @p cursor references * * First tries to get the referenced cursor via clang_getCursorReferenced, * and if that fails, tries to get them via clang_getOverloadedDecl * (which returns the referenced cursor for CXCursor_OverloadedDeclRef, for example) * * @return Valid cursor on success, else null cursor */ CXCursor referencedCursor(CXCursor cursor) { auto referenced = clang_getCursorReferenced(cursor); if (!clang_equalCursors(cursor, referenced)) { return referenced; } // get the first result for now referenced = clang_getOverloadedDecl(cursor, 0); if (!clang_Cursor_isNull(referenced)) { return referenced; } return clang_getNullCursor(); } Identifier makeId(CXCursor cursor) { if (CursorKindTraits::isClassTemplate(cursor.kind)) { // TODO: how to handle functions here? We don't want to add the "real" function arguments here // and there does not seem to be an API to get the template arguments for non-specializations easily // NOTE: using the QString overload of the Identifier ctor here, so that the template name gets parsed return Identifier(ClangString(clang_getCursorDisplayName(cursor)).toString()); } return Identifier(ClangString(clang_getCursorSpelling(cursor)).toIndexed()); } QByteArray makeComment(CXComment comment) { if (Q_UNLIKELY(jsonTestRun())) { auto kind = clang_Comment_getKind(comment); if (kind == CXComment_Text) return ClangString(clang_TextComment_getText(comment)).toByteArray(); QByteArray text; int numChildren = clang_Comment_getNumChildren(comment); for (int i = 0; i < numChildren; ++i) text += makeComment(clang_Comment_getChild(comment, i)); return text; } return ClangString(clang_FullComment_getAsHTML(comment)).toByteArray(); } AbstractType* createDelayedType(CXType type) { auto t = new DelayedType; QString typeName = ClangString(clang_getTypeSpelling(type)).toString(); typeName.remove(QStringLiteral("const ")); typeName.remove(QStringLiteral("volatile ")); t->setIdentifier(IndexedTypeIdentifier(typeName)); return t; } void contextImportDecl(DUContext* context, const DeclarationPointer& decl) { auto top = context->topContext(); if (auto import = decl->logicalInternalContext(top)) { context->addImportedParentContext(import); context->topContext()->updateImportsCache(); } } //END helpers CXChildVisitResult visitCursor(CXCursor cursor, CXCursor parent, CXClientData data); //BEGIN IdType template struct IdType; template struct IdType::type> { typedef StructureType Type; }; template struct IdType::type> { typedef TypeAliasType Type; }; template struct IdType::type> { typedef TypeAliasType Type; }; template struct IdType::type> { typedef EnumerationType Type; }; template struct IdType::type> { typedef EnumeratorType Type; }; //END IdType //BEGIN DeclType template struct DeclType; template struct DeclType::type> { typedef Declaration Type; }; template struct DeclType::type> { typedef MacroDefinition Type; }; template struct DeclType::type> { typedef ForwardDeclaration Type; }; template struct DeclType::type> { typedef ClassDeclaration Type; }; template struct DeclType::type> { typedef ClassFunctionDeclaration Type; }; template struct DeclType::type> { typedef FunctionDeclaration Type; }; template struct DeclType::type> { typedef FunctionDefinition Type; }; template struct DeclType::type> { typedef NamespaceAliasDeclaration Type; }; template struct DeclType::type> { typedef ClassMemberDeclaration Type; }; //END DeclType //BEGIN CurrentContext struct CurrentContext { CurrentContext(DUContext* context, QSet keepAliveContexts) : context(context) , keepAliveContexts(keepAliveContexts) { DUChainReadLocker lock; previousChildContexts = context->childContexts(); previousChildDeclarations = context->localDeclarations(); } ~CurrentContext() { DUChainWriteLocker lock; foreach (auto childContext, previousChildContexts) { if (!keepAliveContexts.contains(childContext)) { delete childContext; } } qDeleteAll(previousChildDeclarations); if (resortChildContexts) { context->resortChildContexts(); } if (resortLocalDeclarations) { context->resortLocalDeclarations(); } } DUContext* context; // when updating, this contains child contexts of the current parent context QVector previousChildContexts; // when updating, this contains contexts that must not be deleted QSet keepAliveContexts; // when updating, this contains child declarations of the current parent context QVector previousChildDeclarations; bool resortChildContexts = false; bool resortLocalDeclarations = false; }; //END CurrentContext //BEGIN Visitor struct Visitor { explicit Visitor(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update); AbstractType *makeType(CXType type, CXCursor parent); AbstractType::Ptr makeAbsType(CXType type, CXCursor parent) { return AbstractType::Ptr(makeType(type, parent)); } //BEGIN dispatch* template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template AbstractType *dispatchType(CXType type, CXCursor cursor) { IF_DEBUG(clangDebug() << "TK:" << type.kind;) auto kdevType = createType(type, cursor); if (kdevType) { setTypeModifiers(type, kdevType); } return kdevType; } //BEGIN dispatch* //BEGIN build* template CXChildVisitResult buildDeclaration(CXCursor cursor); CXChildVisitResult buildUse(CXCursor cursor); CXChildVisitResult buildMacroExpansion(CXCursor cursor); CXChildVisitResult buildCompoundStatement(CXCursor cursor); CXChildVisitResult buildCXXBaseSpecifier(CXCursor cursor); CXChildVisitResult buildParmDecl(CXCursor cursor); //END build* //BEGIN create* template DeclType* createDeclarationCommon(CXCursor cursor, const Identifier& id) { auto range = ClangHelpers::cursorSpellingNameRange(cursor, id); if (id.isEmpty()) { // This is either an anonymous function parameter e.g.: void f(int); // Or anonymous struct/class/union e.g.: struct {} anonymous; // Set empty range for it range.end = range.start; } // check if cursor is inside a macro expansion auto clangRange = clang_Cursor_getSpellingNameRange(cursor, 0, 0); unsigned int expansionLocOffset; const auto spellingLocation = clang_getRangeStart(clangRange); clang_getExpansionLocation(spellingLocation, nullptr, nullptr, nullptr, &expansionLocOffset); if (m_macroExpansionLocations.contains(expansionLocOffset)) { unsigned int spellingLocOffset; clang_getSpellingLocation(spellingLocation, nullptr, nullptr, nullptr, &spellingLocOffset); // Set empty ranges for declarations inside macro expansion if (spellingLocOffset == expansionLocOffset) { range.end = range.start; } } if (m_update) { const IndexedIdentifier indexedId(id); DUChainWriteLocker lock; auto it = m_parentContext->previousChildDeclarations.begin(); while (it != m_parentContext->previousChildDeclarations.end()) { auto decl = dynamic_cast(*it); if (decl && decl->indexedIdentifier() == indexedId) { decl->setRange(range); m_parentContext->resortLocalDeclarations = true; setDeclData(cursor, decl); m_cursorToDeclarationCache[cursor] = decl; m_parentContext->previousChildDeclarations.erase(it); return decl; } ++it; } } auto decl = new DeclType(range, nullptr); decl->setIdentifier(id); m_cursorToDeclarationCache[cursor] = decl; setDeclData(cursor, decl); { DUChainWriteLocker lock; decl->setContext(m_parentContext->context); } return decl; } template Declaration* createDeclaration(CXCursor cursor, const Identifier& id, DUContext *context) { auto decl = createDeclarationCommon(cursor, id); auto type = createType(cursor); DUChainWriteLocker lock; if (context) decl->setInternalContext(context); setDeclType(decl, type); setDeclInCtxtData(cursor, decl); return decl; } template DUContext* createContext(CXCursor cursor, const QualifiedIdentifier& scopeId = {}) { // wtf: why is the DUContext API requesting a QID when it needs a plain Id?! // see: testNamespace auto range = ClangRange(clang_getCursorExtent(cursor)).toRangeInRevision(); DUChainWriteLocker lock; if (m_update) { const IndexedQualifiedIdentifier indexedScopeId(scopeId); auto it = m_parentContext->previousChildContexts.begin(); while (it != m_parentContext->previousChildContexts.end()) { auto ctx = *it; if (ctx->type() == Type && ctx->indexedLocalScopeIdentifier() == indexedScopeId) { ctx->setRange(range); m_parentContext->resortChildContexts = true; m_parentContext->previousChildContexts.erase(it); return ctx; } ++it; } } //TODO: (..type, id..) constructor for DUContext? auto context = new ClangNormalDUContext(range, m_parentContext->context); context->setType(Type); context->setLocalScopeIdentifier(scopeId); if (Type == DUContext::Other || Type == DUContext::Function) context->setInSymbolTable(false); if (CK == CXCursor_CXXMethod) { CXCursor semParent = clang_getCursorSemanticParent(cursor); // only import the semantic parent if it differs from the lexical parent if (!clang_Cursor_isNull(semParent) && !clang_equalCursors(semParent, clang_getCursorLexicalParent(cursor))) { auto semParentDecl = findDeclaration(semParent); if (semParentDecl) { contextImportDecl(context, semParentDecl); } } } return context; } template = dummy> AbstractType *createType(CXType, CXCursor) { // TODO: would be nice to instantiate a ConstantIntegralType here and set a value if possible // but unfortunately libclang doesn't offer API to that // also see http://marc.info/?l=cfe-commits&m=131609142917881&w=2 return new IntegralType(CursorKindTraits::integralType(TK)); } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto ptr = new PointerType; ptr->setBaseType(makeAbsType(clang_getPointeeType(type), parent)); return ptr; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto arr = new ArrayType; arr->setDimension((TK == CXType_IncompleteArray || TK == CXType_VariableArray || TK == CXType_DependentSizedArray) ? 0 : clang_getArraySize(type)); arr->setElementType(makeAbsType(clang_getArrayElementType(type), parent)); return arr; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto ref = new ReferenceType; ref->setIsRValue(type.kind == CXType_RValueReference); ref->setBaseType(makeAbsType(clang_getPointeeType(type), parent)); return ref; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto func = new FunctionType; func->setReturnType(makeAbsType(clang_getResultType(type), parent)); const int numArgs = clang_getNumArgTypes(type); for (int i = 0; i < numArgs; ++i) { func->addArgument(makeAbsType(clang_getArgType(type, i), parent)); } if (clang_isFunctionTypeVariadic(type)) { auto type = new DelayedType; static const auto id = IndexedTypeIdentifier(QStringLiteral("...")); type->setIdentifier(id); type->setKind(DelayedType::Unresolved); func->addArgument(AbstractType::Ptr(type)); } return func; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { DeclarationPointer decl = findDeclaration(clang_getTypeDeclaration(type)); DUChainReadLocker lock; if (!decl) { // probably a forward-declared type decl = ClangHelpers::findForwardDeclaration(type, m_parentContext->context, parent); } if (clang_Type_getNumTemplateArguments(type) != -1) { return createClassTemplateSpecializationType(type, decl); } auto t = new StructureType; if (decl) { t->setDeclaration(decl.data()); } else { // fallback, at least give the spelling to the user t->setDeclarationId(DeclarationId(IndexedQualifiedIdentifier(QualifiedIdentifier(ClangString(clang_getTypeSpelling(type)).toString())))); } return t; } template = dummy> AbstractType *createType(CXType type, CXCursor) { auto t = new EnumerationType; setIdTypeDecl(clang_getTypeDeclaration(type), t); return t; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto t = new TypeAliasType; CXCursor location = clang_getTypeDeclaration(type); t->setType(makeAbsType(clang_getTypedefDeclUnderlyingType(location), parent)); setIdTypeDecl(location, t); return t; } template = dummy> AbstractType *createType(CXType, CXCursor /*parent*/) { auto t = new DelayedType; static const IndexedTypeIdentifier id(QLatin1String(CursorKindTraits::delayedTypeName(TK))); t->setIdentifier(id); return t; } template = dummy> AbstractType *createType(CXType type, CXCursor /*parent*/) { return createDelayedType(type); } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto numTA = clang_Type_getNumTemplateArguments(type); // TODO: We should really expose more types to libclang! if (numTA != -1 && ClangString(clang_getTypeSpelling(type)).toString().contains(QLatin1Char('<'))) { return createClassTemplateSpecializationType(type); } // Maybe it's the ElaboratedType. E.g.: "struct Type foo();" or "NS::Type foo();" or "void foo(enum Enum e);" e.t.c. auto oldType = type; type = clang_getCanonicalType(type); bool isElaboratedType = type.kind != CXType_FunctionProto && type.kind != CXType_FunctionNoProto && type.kind != CXType_Unexposed && type.kind != CXType_Invalid && type.kind != CXType_Record; return !isElaboratedType ? createDelayedType(oldType) : makeType(type, parent); } template = dummy> typename IdType::Type *createType(CXCursor) { return new typename IdType::Type; } template = dummy> EnumeratorType *createType(CXCursor cursor) { auto type = new EnumeratorType; type->setValue(clang_getEnumConstantDeclUnsignedValue(cursor)); return type; } template = dummy> TypeAliasType *createType(CXCursor cursor) { auto type = new TypeAliasType; type->setType(makeAbsType(clang_getTypedefDeclUnderlyingType(cursor), cursor)); return type; } template = dummy> AbstractType* createType(CXCursor cursor) { auto clangType = clang_getCursorType(cursor); #if CINDEX_VERSION_MINOR < 31 if (clangType.kind == CXType_Unexposed) { // Clang sometimes can return CXType_Unexposed for CXType_FunctionProto kind. E.g. if it's AttributedType. return dispatchType(clangType, cursor); } #endif return makeType(clangType, cursor); } template = dummy> AbstractType *createType(CXCursor) { auto t = new DelayedType; static const IndexedTypeIdentifier id(QStringLiteral("Label")); t->setIdentifier(id); return t; } template = dummy> AbstractType *createType(CXCursor cursor) { auto clangType = clang_getCursorType(cursor); return makeType(clangType, cursor); } #if CINDEX_VERSION_MINOR >= 32 template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto deducedType = clang_getCanonicalType(type); bool isDeduced = deducedType.kind != CXType_Invalid && deducedType.kind != CXType_Auto; return !isDeduced ? createDelayedType(type) : makeType(deducedType, parent); } #endif #if CINDEX_VERSION_MINOR >= 34 template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto underyingType = clang_Type_getNamedType(type); return makeType(underyingType, parent); } #endif /// @param declaration an optional declaration that will be associated with created type AbstractType* createClassTemplateSpecializationType(CXType type, const DeclarationPointer declaration = {}) { auto numTA = clang_Type_getNumTemplateArguments(type); Q_ASSERT(numTA != -1); auto typeDecl = clang_getTypeDeclaration(type); if (!declaration && typeDecl.kind == CXCursor_NoDeclFound) { // clang_getTypeDeclaration doesn't handle all types, fall back to delayed type... return createDelayedType(type); } QStringList typesStr; QString tStr = ClangString(clang_getTypeSpelling(type)).toString(); ParamIterator iter(QStringLiteral("<>"), tStr); while (iter) { typesStr.append(*iter); ++iter; } auto cst = new ClassSpecializationType; for (int i = 0; i < numTA; i++) { auto argumentType = clang_Type_getTemplateArgumentAsType(type, i); AbstractType::Ptr currentType; if (argumentType.kind == CXType_Invalid) { if(i >= typesStr.size()){ currentType = createDelayedType(argumentType); } else { auto t = new DelayedType; t->setIdentifier(IndexedTypeIdentifier(typesStr[i])); currentType = t; } } else { currentType = makeType(argumentType, typeDecl); } if (currentType) { cst->addParameter(currentType->indexed()); } } auto decl = declaration ? declaration : findDeclaration(typeDecl); DUChainReadLocker lock; if (decl) { cst->setDeclaration(decl.data()); } else { // fallback, at least give the spelling to the user Identifier id(tStr); id.clearTemplateIdentifiers(); cst->setDeclarationId(DeclarationId(IndexedQualifiedIdentifier(QualifiedIdentifier(id)))); } return cst; } //END create* //BEGIN setDeclData template void setDeclData(CXCursor cursor, Declaration *decl, bool setComment = true) const; template void setDeclData(CXCursor cursor, MacroDefinition* decl) const; template void setDeclData(CXCursor cursor, ClassMemberDeclaration *decl) const; template = dummy> void setDeclData(CXCursor cursor, ClassDeclaration* decl) const; template = dummy> void setDeclData(CXCursor cursor, ClassDeclaration* decl) const; template void setDeclData(CXCursor cursor, AbstractFunctionDeclaration* decl) const; template void setDeclData(CXCursor cursor, ClassFunctionDeclaration* decl) const; template void setDeclData(CXCursor cursor, FunctionDeclaration *decl, bool setComment = true) const; template void setDeclData(CXCursor cursor, FunctionDefinition *decl) const; template void setDeclData(CXCursor cursor, NamespaceAliasDeclaration *decl) const; //END setDeclData //BEGIN setDeclInCtxtData template void setDeclInCtxtData(CXCursor, Declaration*) { //No-op } template void setDeclInCtxtData(CXCursor cursor, ClassFunctionDeclaration *decl) { // HACK to retrieve function-constness // This looks like a bug in Clang -- In theory setTypeModifiers should take care of setting the const modifier // however, clang_isConstQualifiedType() for TK == CXType_FunctionProto always returns false // TODO: Debug further auto type = decl->abstractType(); if (type) { if (clang_CXXMethod_isConst(cursor)) { type->setModifiers(type->modifiers() | AbstractType::ConstModifier); decl->setAbstractType(type); } } } template void setDeclInCtxtData(CXCursor cursor, FunctionDefinition *def) { setDeclInCtxtData(cursor, static_cast(def)); const CXCursor canon = clang_getCanonicalCursor(cursor); if (auto decl = findDeclaration(canon)) { def->setDeclaration(decl.data()); } } //END setDeclInCtxtData //BEGIN setDeclType template void setDeclType(Declaration *decl, typename IdType::Type *type) { setDeclType(decl, static_cast(type)); setDeclType(decl, static_cast(type)); } template void setDeclType(Declaration *decl, IdentifiedType *type) { type->setDeclaration(decl); } template void setDeclType(Declaration *decl, AbstractType *type) { decl->setAbstractType(AbstractType::Ptr(type)); } //END setDeclType template void setTypeModifiers(CXType type, AbstractType* kdevType) const; const CXFile m_file; const IncludeFileContexts &m_includes; DeclarationPointer findDeclaration(CXCursor cursor) const; void setIdTypeDecl(CXCursor typeCursor, IdentifiedType* idType) const; std::unordered_map> m_uses; /// At these location offsets (cf. @ref clang_getExpansionLocation) we encountered macro expansions QSet m_macroExpansionLocations; mutable QHash m_cursorToDeclarationCache; CurrentContext *m_parentContext; const bool m_update; }; //BEGIN setTypeModifiers template void Visitor::setTypeModifiers(CXType type, AbstractType* kdevType) const { quint64 modifiers = 0; if (clang_isConstQualifiedType(type)) { modifiers |= AbstractType::ConstModifier; } if (clang_isVolatileQualifiedType(type)) { modifiers |= AbstractType::VolatileModifier; } if (TK == CXType_Short || TK == CXType_UShort) { modifiers |= AbstractType::ShortModifier; } if (TK == CXType_Long || TK == CXType_LongDouble || TK == CXType_ULong) { modifiers |= AbstractType::LongModifier; } if (TK == CXType_LongLong || TK == CXType_ULongLong) { modifiers |= AbstractType::LongLongModifier; } if (TK == CXType_SChar) { modifiers |= AbstractType::SignedModifier; } if (TK == CXType_UChar || TK == CXType_UInt || TK == CXType_UShort || TK == CXType_UInt128 || TK == CXType_ULong || TK == CXType_ULongLong) { modifiers |= AbstractType::UnsignedModifier; } kdevType->setModifiers(modifiers); } //END setTypeModifiers //BEGIN dispatchCursor template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { const bool decision = CursorKindTraits::isClass(clang_getCursorKind(parent)); return decision ? dispatchCursor(cursor, parent) : dispatchCursor(cursor, parent); } template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { IF_DEBUG(clangDebug() << "IsInClass:" << IsInClass << "- isDefinition:" << IsDefinition;) const bool isDefinition = clang_isCursorDefinition(cursor); return isDefinition ? dispatchCursor(cursor, parent) : dispatchCursor(cursor, parent); } template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { IF_DEBUG(clangDebug() << "IsInClass:" << IsInClass << "- isDefinition:" << IsDefinition;) // We may end up visiting the same cursor twice in some cases // see discussion on https://git.reviewboard.kde.org/r/119526/ // TODO: Investigate why this is happening in libclang if ((CursorKindTraits::isClass(CK) || CK == CXCursor_EnumDecl) && clang_getCursorKind(parent) == CXCursor_VarDecl) { return CXChildVisit_Continue; } constexpr bool isClassMember = IsInClass == Decision::True; constexpr bool isDefinition = IsDefinition == Decision::True; // always build a context for class templates and functions, otherwise we "leak" // the function/template parameter declarations into the surrounding context, // which can lead to interesting bugs, like https://bugs.kde.org/show_bug.cgi?id=368067 constexpr bool hasContext = isDefinition || CursorKindTraits::isFunction(CK) || CursorKindTraits::isClassTemplate(CK); return buildDeclaration::Type, hasContext>(cursor); } //END dispatchCursor //BEGIN setDeclData template void Visitor::setDeclData(CXCursor cursor, Declaration *decl, bool setComment) const { if (setComment) decl->setComment(makeComment(clang_Cursor_getParsedComment(cursor))); if (CursorKindTraits::isAliasType(CK)) { decl->setIsTypeAlias(true); } if (CK == CXCursor_Namespace) decl->setKind(Declaration::Namespace); if (CK == CXCursor_EnumDecl || CK == CXCursor_EnumConstantDecl || CursorKindTraits::isClass(CK) || CursorKindTraits::isAliasType(CK)) decl->setKind(Declaration::Type); int isAlwaysDeprecated; clang_getCursorPlatformAvailability(cursor, &isAlwaysDeprecated, nullptr, nullptr, nullptr, nullptr, 0); decl->setDeprecated(isAlwaysDeprecated); } template void Visitor::setDeclData(CXCursor cursor, MacroDefinition* decl) const { setDeclData(cursor, static_cast(decl)); if (m_update) { decl->clearParameters(); } auto unit = clang_Cursor_getTranslationUnit(cursor); auto range = clang_getCursorExtent(cursor); // TODO: Quite lacking API in libclang here. // No way to find out if this macro is function-like or not // cf. http://clang.llvm.org/doxygen/classclang_1_1MacroInfo.html // And no way to get the actual definition text range // Should be quite easy to expose that in libclang, though // Let' still get some basic support for this and parse on our own, it's not that difficult const QString contents = QString::fromUtf8(ClangUtils::getRawContents(unit, range)); const int firstOpeningParen = contents.indexOf(QLatin1Char('(')); const int firstWhitespace = contents.indexOf(QLatin1Char(' ')); const bool isFunctionLike = (firstOpeningParen != -1) && (firstOpeningParen < firstWhitespace); decl->setFunctionLike(isFunctionLike); // now extract the actual definition text int start = -1; if (isFunctionLike) { const int closingParen = findClose(contents, firstOpeningParen); if (closingParen != -1) { start = closingParen + 2; // + ')' + ' ' // extract macro function parameters const QString parameters = contents.mid(firstOpeningParen, closingParen - firstOpeningParen + 1); ParamIterator paramIt(QStringLiteral("():"), parameters, 0); while (paramIt) { decl->addParameter(IndexedString(*paramIt)); ++paramIt; } } } else { start = firstWhitespace + 1; // + ' ' } if (start == -1) { // unlikely: invalid macro definition, insert the complete #define statement decl->setDefinition(IndexedString(QLatin1String("#define ") + contents)); } else if (start < contents.size()) { decl->setDefinition(IndexedString(contents.mid(start))); } // else: macro has no body => leave the definition text empty } template void Visitor::setDeclData(CXCursor cursor, ClassMemberDeclaration *decl) const { setDeclData(cursor, static_cast(decl)); //A CXCursor_VarDecl in a class is static (otherwise it'd be a CXCursor_FieldDecl) if (CK == CXCursor_VarDecl) decl->setStatic(true); decl->setAccessPolicy(CursorKindTraits::kdevAccessPolicy(clang_getCXXAccessSpecifier(cursor))); #if CINDEX_VERSION_MINOR >= 32 decl->setMutable(clang_CXXField_isMutable(cursor)); #endif #if CINDEX_VERSION_MINOR >= 30 if (!jsonTestRun()) { auto offset = clang_Cursor_getOffsetOfField(cursor); if (offset >= 0) { // don't add this info to the json tests, it invalidates the comment structure auto type = clang_getCursorType(cursor); auto sizeOf = clang_Type_getSizeOf(type); auto alignedTo = clang_Type_getAlignOf(type); const auto byteOffset = offset / 8; const auto bitOffset = offset % 8; const QString byteOffsetStr = i18np("1 Byte", "%1 Bytes", byteOffset); const QString bitOffsetStr = bitOffset ? i18np("1 Bit", "%1 Bits", bitOffset) : QString(); const QString offsetStr = bitOffset ? i18nc("%1: bytes, %2: bits", "%1, %2", byteOffsetStr, bitOffsetStr) : byteOffsetStr; decl->setComment(decl->comment() + i18n("

offset in parent: %1; " "size: %2 Bytes; " "aligned to: %3 Bytes

", offsetStr, sizeOf, alignedTo).toUtf8()); } } #endif } template> void Visitor::setDeclData(CXCursor cursor, ClassDeclaration* decl) const { CXCursorKind kind = clang_getTemplateCursorKind(cursor); switch (kind) { case CXCursor_UnionDecl: setDeclData(cursor, decl); break; case CXCursor_StructDecl: setDeclData(cursor, decl); break; case CXCursor_ClassDecl: setDeclData(cursor, decl); break; default: Q_ASSERT(false); break; } } template> void Visitor::setDeclData(CXCursor cursor, ClassDeclaration* decl) const { if (m_update) { decl->clearBaseClasses(); } setDeclData(cursor, static_cast(decl)); if (CK == CXCursor_UnionDecl) decl->setClassType(ClassDeclarationData::Union); if (CK == CXCursor_StructDecl) decl->setClassType(ClassDeclarationData::Struct); if (clang_isCursorDefinition(cursor)) { decl->setDeclarationIsDefinition(true); } if (!jsonTestRun()) { // don't add this info to the json tests, it invalidates the comment structure auto type = clang_getCursorType(cursor); auto sizeOf = clang_Type_getSizeOf(type); auto alignOf = clang_Type_getAlignOf(type); if (sizeOf >= 0 && alignOf >= 0) { decl->setComment(decl->comment() + i18n("

size: %1 Bytes; " "aligned to: %2 Bytes

", sizeOf, alignOf).toUtf8()); } } } template void Visitor::setDeclData(CXCursor cursor, AbstractFunctionDeclaration* decl) const { if (m_update) { decl->clearDefaultParameters(); } // No setDeclData(...) here: AbstractFunctionDeclaration is an interface // TODO: Can we get the default arguments directly from Clang? // also see http://clang-developers.42468.n3.nabble.com/Finding-default-value-for-function-argument-with-clang-c-API-td4036919.html const QVector defaultArgs = ClangUtils::getDefaultArguments(cursor, ClangUtils::MinimumSize); foreach (const QString& defaultArg, defaultArgs) { decl->addDefaultParameter(IndexedString(defaultArg)); } } template void Visitor::setDeclData(CXCursor cursor, ClassFunctionDeclaration* decl) const { setDeclData(cursor, static_cast(decl)); setDeclData(cursor, static_cast(decl)); decl->setAbstract(clang_CXXMethod_isPureVirtual(cursor)); decl->setStatic(clang_CXXMethod_isStatic(cursor)); decl->setVirtual(clang_CXXMethod_isVirtual(cursor)); const auto qtAttribute = ClangUtils::specialQtAttributes(cursor); decl->setIsSignal(qtAttribute == ClangUtils::QtSignalAttribute); decl->setIsSlot(qtAttribute == ClangUtils::QtSlotAttribute); } template void Visitor::setDeclData(CXCursor cursor, FunctionDeclaration *decl, bool setComment) const { setDeclData(cursor, static_cast(decl)); setDeclData(cursor, static_cast(decl), setComment); } template void Visitor::setDeclData(CXCursor cursor, FunctionDefinition *decl) const { bool setComment = clang_equalCursors(clang_getCanonicalCursor(cursor), cursor); setDeclData(cursor, static_cast(decl), setComment); } template void Visitor::setDeclData(CXCursor cursor, NamespaceAliasDeclaration *decl) const { setDeclData(cursor, static_cast(decl)); clang_visitChildren(cursor, [] (CXCursor cursor, CXCursor parent, CXClientData data) -> CXChildVisitResult { if (clang_getCursorKind(cursor) == CXCursor_NamespaceRef) { const auto id = QualifiedIdentifier(ClangString(clang_getCursorSpelling(cursor)).toString()); reinterpret_cast(data)->setImportIdentifier(id); return CXChildVisit_Break; } else { return visitCursor(cursor, parent, data); } }, decl); } //END setDeclData //BEGIN build* template CXChildVisitResult Visitor::buildDeclaration(CXCursor cursor) { auto id = makeId(cursor); if (CK == CXCursor_UnexposedDecl && id.isEmpty()) { // skip unexposed declarations that have no identifier set // this is useful to skip e.g. friend declarations return CXChildVisit_Recurse; } IF_DEBUG(clangDebug() << "id:" << id << "- CK:" << CK << "- DeclType:" << typeid(DeclType).name() << "- hasContext:" << hasContext;) // Code path for class declarations that may be defined "out-of-line", e.g. // "SomeNameSpace::SomeClass {};" QScopedPointer helperContext; if (CursorKindTraits::isClass(CK) || CursorKindTraits::isFunction(CK)) { const auto lexicalParent = clang_getCursorLexicalParent(cursor); const auto semanticParent = clang_getCursorSemanticParent(cursor); const bool isOutOfLine = !clang_equalCursors(lexicalParent, semanticParent); if (isOutOfLine) { const QString scope = ClangUtils::getScope(cursor); auto context = createContext(cursor, QualifiedIdentifier(scope)); helperContext.reset(new CurrentContext(context, m_parentContext->keepAliveContexts)); } } // if helperContext is null, this is a no-op PushValue pushCurrent(m_parentContext, helperContext.isNull() ? m_parentContext : helperContext.data()); if (hasContext) { auto context = createContext(cursor, QualifiedIdentifier(id)); createDeclaration(cursor, id, context); CurrentContext newParent(context, m_parentContext->keepAliveContexts); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, &visitCursor, this); return CXChildVisit_Continue; } createDeclaration(cursor, id, nullptr); return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildParmDecl(CXCursor cursor) { return buildDeclaration::Type, false>(cursor); } CXChildVisitResult Visitor::buildUse(CXCursor cursor) { m_uses[m_parentContext->context].push_back(cursor); return cursor.kind == CXCursor_DeclRefExpr || cursor.kind == CXCursor_MemberRefExpr ? CXChildVisit_Recurse : CXChildVisit_Continue; } CXChildVisitResult Visitor::buildMacroExpansion(CXCursor cursor) { buildUse(cursor); // cache that we encountered a macro expansion at this location unsigned int offset; clang_getSpellingLocation(clang_getCursorLocation(cursor), nullptr, nullptr, nullptr, &offset); m_macroExpansionLocations << offset; return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildCompoundStatement(CXCursor cursor) { if (m_parentContext->context->type() == DUContext::Function) { auto context = createContext(cursor); CurrentContext newParent(context, m_parentContext->keepAliveContexts); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, &visitCursor, this); return CXChildVisit_Continue; } return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildCXXBaseSpecifier(CXCursor cursor) { auto currentContext = m_parentContext->context; bool virtualInherited = clang_isVirtualBase(cursor); Declaration::AccessPolicy access = CursorKindTraits::kdevAccessPolicy(clang_getCXXAccessSpecifier(cursor)); auto classDeclCursor = clang_getCursorReferenced(cursor); auto decl = findDeclaration(classDeclCursor); if (!decl) { // this happens for templates with template-dependent base classes e.g. - dunno whether we can/should do more here clangDebug() << "failed to find declaration for base specifier:" << clang_getCursorDisplayName(cursor); return CXChildVisit_Recurse; } DUChainWriteLocker lock; contextImportDecl(currentContext, decl); auto classDecl = dynamic_cast(currentContext->owner()); Q_ASSERT(classDecl); classDecl->addBaseClass({decl->indexedType(), access, virtualInherited}); return CXChildVisit_Recurse; } //END build* DeclarationPointer Visitor::findDeclaration(CXCursor cursor) const { const auto it = m_cursorToDeclarationCache.constFind(cursor); if (it != m_cursorToDeclarationCache.constEnd()) { return *it; } // fallback, and cache result auto decl = ClangHelpers::findDeclaration(cursor, m_includes); m_cursorToDeclarationCache.insert(cursor, decl); return decl; } void Visitor::setIdTypeDecl(CXCursor typeCursor, IdentifiedType* idType) const { DeclarationPointer decl = findDeclaration(typeCursor); DUChainReadLocker lock; if (decl) { idType->setDeclaration(decl.data()); } } AbstractType *Visitor::makeType(CXType type, CXCursor parent) { #define UseKind(TypeKind) case TypeKind: return dispatchType(type, parent) switch (type.kind) { UseKind(CXType_Void); UseKind(CXType_Bool); UseKind(CXType_Short); UseKind(CXType_UShort); UseKind(CXType_Int); UseKind(CXType_UInt); UseKind(CXType_Long); UseKind(CXType_ULong); UseKind(CXType_LongLong); UseKind(CXType_ULongLong); UseKind(CXType_Float); UseKind(CXType_LongDouble); UseKind(CXType_Double); UseKind(CXType_Char_U); UseKind(CXType_Char_S); UseKind(CXType_UChar); UseKind(CXType_SChar); UseKind(CXType_Char16); UseKind(CXType_Char32); UseKind(CXType_Pointer); UseKind(CXType_BlockPointer); UseKind(CXType_MemberPointer); UseKind(CXType_ObjCObjectPointer); UseKind(CXType_ConstantArray); UseKind(CXType_VariableArray); UseKind(CXType_IncompleteArray); UseKind(CXType_DependentSizedArray); UseKind(CXType_LValueReference); UseKind(CXType_RValueReference); UseKind(CXType_FunctionNoProto); UseKind(CXType_FunctionProto); UseKind(CXType_Record); UseKind(CXType_Enum); UseKind(CXType_Typedef); UseKind(CXType_Int128); UseKind(CXType_UInt128); UseKind(CXType_Vector); UseKind(CXType_Unexposed); UseKind(CXType_WChar); UseKind(CXType_ObjCInterface); UseKind(CXType_ObjCId); UseKind(CXType_ObjCClass); UseKind(CXType_ObjCSel); UseKind(CXType_NullPtr); #if CINDEX_VERSION_MINOR >= 32 UseKind(CXType_Auto); #endif #if CINDEX_VERSION_MINOR >= 34 UseKind(CXType_Elaborated); #endif case CXType_Invalid: return nullptr; default: qCWarning(KDEV_CLANG) << "Unhandled type:" << type.kind << clang_getTypeSpelling(type); return nullptr; } #undef UseKind } RangeInRevision rangeInRevisionForUse(CXCursor cursor, CXCursorKind referencedCursorKind, CXSourceRange useRange, const QSet& macroExpansionLocations) { auto range = ClangRange(useRange).toRangeInRevision(); //TODO: Fix in clang, happens for operator<<, operator<, probably more if (clang_Range_isNull(useRange)) { useRange = clang_getCursorExtent(cursor); range = ClangRange(useRange).toRangeInRevision(); } if (referencedCursorKind == CXCursor_ConversionFunction) { range.end = range.start; range.start.column--; } // For uses inside macro expansions, create an empty use range at the spelling location // the empty range is required in order to not "overlap" the macro expansion range // and to allow proper navigation for the macro expansion // also see JSON test 'macros.cpp' if (clang_getCursorKind(cursor) != CXCursor_MacroExpansion) { unsigned int expansionLocOffset; const auto spellingLocation = clang_getRangeStart(useRange); clang_getExpansionLocation(spellingLocation, nullptr, nullptr, nullptr, &expansionLocOffset); if (macroExpansionLocations.contains(expansionLocOffset)) { unsigned int spellingLocOffset; clang_getSpellingLocation(spellingLocation, nullptr, nullptr, nullptr, &spellingLocOffset); if (spellingLocOffset == expansionLocOffset) { range.end = range.start; } } } else { // Workaround for wrong use range returned by clang for macro expansions const auto contents = ClangUtils::getRawContents(clang_Cursor_getTranslationUnit(cursor), useRange); const int firstOpeningParen = contents.indexOf('('); if (firstOpeningParen != -1) { range.end.column = range.start.column + firstOpeningParen; range.end.line = range.start.line; } } return range; } Visitor::Visitor(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update) : m_file(file) , m_includes(includes) , m_parentContext(nullptr) , m_update(update) { CXCursor tuCursor = clang_getTranslationUnitCursor(tu); auto top = includes[file]; // when updating, this contains child contexts that should be kept alive // even when they are not part of the AST anymore // this is required for some assistants, such as the signature assistant QSet keepAliveContexts; { DUChainReadLocker lock; foreach (const auto& problem, top->problems()) { const auto& desc = problem->description(); if (desc.startsWith(QLatin1String("Return type of out-of-line definition of '")) && desc.endsWith(QLatin1String("' differs from that in the declaration"))) { auto ctx = top->findContextAt(problem->range().start); // keep the context and its parents alive // this also keeps declarations in this context alive while (ctx) { keepAliveContexts << ctx; ctx = ctx->parentContext(); } } } } CurrentContext parent(top, keepAliveContexts); m_parentContext = &parent; clang_visitChildren(tuCursor, &visitCursor, this); if (m_update) { DUChainWriteLocker lock; top->deleteUsesRecursively(); } for (const auto &contextUses : m_uses) { for (const auto &cursor : contextUses.second) { auto referenced = referencedCursor(cursor); if (clang_Cursor_isNull(referenced)) { continue; } // first, try the canonical referenced cursor // this is important to get the correct function declaration e.g. auto canonicalReferenced = clang_getCanonicalCursor(referenced); auto used = findDeclaration(canonicalReferenced); if (!used) { // if the above failed, try the non-canonicalized version as a fallback // this is required for friend declarations that occur before // the real declaration. there, the canonical cursor points to // the friend declaration which is not what we are looking for used = findDeclaration(referenced); } if (!used) { // as a last resort, try to resolve the forward declaration DUChainReadLocker lock; DeclarationPointer decl = ClangHelpers::findForwardDeclaration(clang_getCursorType(referenced), contextUses.first, referenced); used = decl; if (!used) { continue; } } #if CINDEX_VERSION_MINOR >= 29 if (clang_Cursor_getNumTemplateArguments(referenced) >= 0) { // Ideally, we don't need this, but for function templates clang_getCanonicalCursor returns a function definition // See also the testUsesCreatedForDeclarations test DUChainReadLocker lock; used = DUChainUtils::declarationForDefinition(used.data()); } #endif const auto useRange = clang_getCursorReferenceNameRange(cursor, 0, 0); const auto range = rangeInRevisionForUse(cursor, referenced.kind, useRange, m_macroExpansionLocations); DUChainWriteLocker lock; auto usedIndex = top->indexForUsedDeclaration(used.data()); contextUses.first->createUse(usedIndex, range); } } } //END Visitor CXChildVisitResult visitCursor(CXCursor cursor, CXCursor parent, CXClientData data) { Visitor *visitor = static_cast(data); const auto kind = clang_getCursorKind(cursor); auto location = clang_getCursorLocation(cursor); CXFile file; clang_getFileLocation(location, &file, nullptr, nullptr, nullptr); // don't skip MemberRefExpr with invalid location, see also: // http://lists.cs.uiuc.edu/pipermail/cfe-dev/2015-May/043114.html if (!ClangUtils::isFileEqual(file, visitor->m_file) && (file || kind != CXCursor_MemberRefExpr)) { return CXChildVisit_Continue; } #define UseCursorKind(CursorKind, ...) case CursorKind: return visitor->dispatchCursor(__VA_ARGS__); switch (kind) { UseCursorKind(CXCursor_UnexposedDecl, cursor, parent); UseCursorKind(CXCursor_StructDecl, cursor, parent); UseCursorKind(CXCursor_UnionDecl, cursor, parent); UseCursorKind(CXCursor_ClassDecl, cursor, parent); UseCursorKind(CXCursor_EnumDecl, cursor, parent); UseCursorKind(CXCursor_FieldDecl, cursor, parent); UseCursorKind(CXCursor_EnumConstantDecl, cursor, parent); UseCursorKind(CXCursor_FunctionDecl, cursor, parent); UseCursorKind(CXCursor_VarDecl, cursor, parent); UseCursorKind(CXCursor_TypeAliasDecl, cursor, parent); UseCursorKind(CXCursor_TypedefDecl, cursor, parent); UseCursorKind(CXCursor_CXXMethod, cursor, parent); UseCursorKind(CXCursor_Namespace, cursor, parent); UseCursorKind(CXCursor_NamespaceAlias, cursor, parent); UseCursorKind(CXCursor_Constructor, cursor, parent); UseCursorKind(CXCursor_Destructor, cursor, parent); UseCursorKind(CXCursor_ConversionFunction, cursor, parent); UseCursorKind(CXCursor_TemplateTypeParameter, cursor, parent); UseCursorKind(CXCursor_NonTypeTemplateParameter, cursor, parent); UseCursorKind(CXCursor_TemplateTemplateParameter, cursor, parent); UseCursorKind(CXCursor_FunctionTemplate, cursor, parent); UseCursorKind(CXCursor_ClassTemplate, cursor, parent); UseCursorKind(CXCursor_ClassTemplatePartialSpecialization, cursor, parent); UseCursorKind(CXCursor_ObjCInterfaceDecl, cursor, parent); UseCursorKind(CXCursor_ObjCCategoryDecl, cursor, parent); UseCursorKind(CXCursor_ObjCProtocolDecl, cursor, parent); UseCursorKind(CXCursor_ObjCPropertyDecl, cursor, parent); UseCursorKind(CXCursor_ObjCIvarDecl, cursor, parent); UseCursorKind(CXCursor_ObjCInstanceMethodDecl, cursor, parent); UseCursorKind(CXCursor_ObjCClassMethodDecl, cursor, parent); UseCursorKind(CXCursor_ObjCImplementationDecl, cursor, parent); UseCursorKind(CXCursor_ObjCCategoryImplDecl, cursor, parent); UseCursorKind(CXCursor_MacroDefinition, cursor, parent); UseCursorKind(CXCursor_LabelStmt, cursor, parent); case CXCursor_TypeRef: case CXCursor_TemplateRef: case CXCursor_NamespaceRef: case CXCursor_MemberRef: case CXCursor_LabelRef: case CXCursor_OverloadedDeclRef: case CXCursor_VariableRef: case CXCursor_DeclRefExpr: case CXCursor_MemberRefExpr: case CXCursor_ObjCClassRef: return visitor->buildUse(cursor); case CXCursor_MacroExpansion: return visitor->buildMacroExpansion(cursor); case CXCursor_CompoundStmt: return visitor->buildCompoundStatement(cursor); case CXCursor_CXXBaseSpecifier: return visitor->buildCXXBaseSpecifier(cursor); case CXCursor_ParmDecl: return visitor->buildParmDecl(cursor); default: return CXChildVisit_Recurse; } } } namespace Builder { void visit(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update) { Visitor visitor(tu, file, includes, update); } } diff --git a/languages/clang/duchain/unknowndeclarationproblem.cpp b/languages/clang/duchain/unknowndeclarationproblem.cpp index 5bb626a120..c5718cbbe1 100644 --- a/languages/clang/duchain/unknowndeclarationproblem.cpp +++ b/languages/clang/duchain/unknowndeclarationproblem.cpp @@ -1,539 +1,539 @@ /* * Copyright 2014 Jørgen Kvalsvik * Copyright 2014 Kevin Funk * * 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 "unknowndeclarationproblem.h" #include "clanghelpers.h" #include "parsesession.h" #include "../util/clangdebug.h" #include "../util/clangutils.h" #include "../util/clangtypes.h" #include "../clangsettings/clangsettingsmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { /** Under some conditions, such as when looking up suggestions * for the undeclared namespace 'std' we will get an awful lot * of suggestions. This parameter limits how many suggestions * will pop up, as rarely more than a few will be relevant anyways * * Forward declaration suggestions are included in this number */ const int maxSuggestions = 5; /** * We don't want anything from the bits directory - * we'd rather prefer forwarding includes, such as */ bool isBlacklisted(const QString& path) { if (ClangHelpers::isSource(path)) return true; // Do not allow including directly from the bits directory. // Instead use one of the forwarding headers in other directories, when possible. if (path.contains( QLatin1String("bits") ) && path.contains(QLatin1String("/include/c++/"))) return true; return false; } QStringList scanIncludePaths( const QString& identifier, const QDir& dir, int maxDepth = 3 ) { if (!maxDepth) { return {}; } QStringList candidates; const auto path = dir.absolutePath(); if( isBlacklisted( path ) ) { return {}; } const QStringList nameFilters = {identifier, identifier + QLatin1String(".*")}; for (const auto& file : dir.entryList(nameFilters, QDir::Files)) { if (identifier.compare(file, Qt::CaseInsensitive) == 0 || ClangHelpers::isHeader(file)) { const QString filePath = path + QLatin1Char('/') + file; clangDebug() << "Found candidate file" << filePath; candidates.append( filePath ); } } maxDepth--; for( const auto& subdir : dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) ) candidates += scanIncludePaths( identifier, QDir{ path + QLatin1Char('/') + subdir }, maxDepth ); return candidates; } /** * Find files in dir that match the given identifier. Matches common C++ header file extensions only. */ QStringList scanIncludePaths( const QualifiedIdentifier& identifier, const KDevelop::Path::List& includes ) { const auto stripped_identifier = identifier.last().toString(); QStringList candidates; for( const auto& include : includes ) { candidates += scanIncludePaths( stripped_identifier, QDir{ include.toLocalFile() } ); } std::sort( candidates.begin(), candidates.end() ); candidates.erase( std::unique( candidates.begin(), candidates.end() ), candidates.end() ); return candidates; } /** * Determine how much path is shared between two includes. * boost/tr1/unordered_map * boost/tr1/unordered_set * have a shared path of 2 where * boost/tr1/unordered_map * boost/vector * have a shared path of 1 */ int sharedPathLevel(const QString& a, const QString& b) { int shared = -1; for(auto x = a.begin(), y = b.begin(); *x == *y && x != a.end() && y != b.end() ; ++x, ++y ) { if( *x == QDir::separator() ) { ++shared; } } return shared; } /** * Try to find a proper include position from the DUChain: * * look at existing imports (i.e. #include's) and find a fitting * file with the same/similar path to the new include file and use that * * TODO: Implement a fallback scheme */ KDevelop::DocumentRange includeDirectivePosition(const KDevelop::Path& source, const QString& includeFile) { DUChainReadLocker lock; const TopDUContext* top = DUChainUtils::standardContextForUrl( source.toUrl() ); if( !top ) { clangDebug() << "unable to find standard context for" << source.toLocalFile() << "Creating null range"; return KDevelop::DocumentRange::invalid(); } int line = -1; // look at existing #include statements and re-use them int currentMatchQuality = -1; for( const auto& import : top->importedParentContexts() ) { const int matchQuality = sharedPathLevel( import.context(top)->url().str(), includeFile ); if( matchQuality < currentMatchQuality ) { continue; } line = import.position.line + 1; currentMatchQuality = matchQuality; } if( line == -1 ) { /* Insert at the top of the document */ return {IndexedString(source.pathOrUrl()), {0, 0, 0, 0}}; } return {IndexedString(source.pathOrUrl()), {line, 0, line, 0}}; } KDevelop::DocumentRange forwardDeclarationPosition(const QualifiedIdentifier& identifier, const KDevelop::Path& source) { DUChainReadLocker lock; const TopDUContext* top = DUChainUtils::standardContextForUrl( source.toUrl() ); if( !top ) { clangDebug() << "unable to find standard context for" << source.toLocalFile() << "Creating null range"; return KDevelop::DocumentRange::invalid(); } if (!top->findDeclarations(identifier).isEmpty()) { // Already forward-declared return KDevelop::DocumentRange::invalid(); } int line = std::numeric_limits< int >::max(); for( const auto decl : top->localDeclarations() ) { line = std::min( line, decl->range().start.line ); } if( line == std::numeric_limits< int >::max() ) { return KDevelop::DocumentRange::invalid(); } // We want it one line above the first declaration line = std::max( line - 1, 0 ); return {IndexedString(source.pathOrUrl()), {line, 0, line, 0}}; } /** * Iteratively build all levels of the current scope. A (missing) type anywhere - * can be aribtrarily namespaced, so we create the permutations of possible + * can be arbitrarily namespaced, so we create the permutations of possible * nestings of namespaces it can currently be in, * * TODO: add detection of namespace aliases, such as 'using namespace KDevelop;' * * namespace foo { * namespace bar { * function baz() { * type var; * } * } * } * * Would give: * foo::bar::baz::type * foo::bar::type * foo::type * type */ QVector findPossibleQualifiedIdentifiers( const QualifiedIdentifier& identifier, const KDevelop::Path& file, const KDevelop::CursorInRevision& cursor ) { DUChainReadLocker lock; const TopDUContext* top = DUChainUtils::standardContextForUrl( file.toUrl() ); if( !top ) { clangDebug() << "unable to find standard context for" << file.toLocalFile() << "Not creating duchain candidates"; return {}; } const auto* context = top->findContextAt( cursor ); if( !context ) { clangDebug() << "No context found at" << cursor; return {}; } QVector declarations{ identifier }; for( auto scopes = context->scopeIdentifier(); !scopes.isEmpty(); scopes.pop() ) { declarations.append( scopes + identifier ); } clangDebug() << "Possible declarations:" << declarations; return declarations; } QStringList findMatchingIncludeFiles(const QVector declarations) { QStringList candidates; for (const auto decl: declarations) { // skip declarations that don't belong to us const auto& file = decl->topContext()->parsingEnvironmentFile(); if (!file || file->language() != ParseSession::languageString()) { continue; } if( dynamic_cast( decl ) ) { continue; } if( decl->isForwardDeclaration() ) { continue; } const auto filepath = decl->url().toUrl().toLocalFile(); if( !isBlacklisted( filepath ) ) { candidates << filepath; clangDebug() << "Adding" << filepath << "determined from candidate" << decl->toString(); } for( const auto importer : file->importers() ) { if( importer->imports().count() != 1 && !isBlacklisted( filepath ) ) { continue; } if( importer->topContext()->localDeclarations().count() ) { continue; } const auto filePath = importer->url().toUrl().toLocalFile(); if( isBlacklisted( filePath ) ) { continue; } /* This file is a forwarder, such as * does not actually implement the functions, but include other headers that do * we prefer this to other headers */ candidates << filePath; clangDebug() << "Adding forwarder file" << filePath << "to the result set"; } } std::sort( candidates.begin(), candidates.end() ); candidates.erase( std::unique( candidates.begin(), candidates.end() ), candidates.end() ); clangDebug() << "Candidates: " << candidates; return candidates; } /** * Takes a filepath and the include paths and determines what directive to use. */ ClangFixit directiveForFile( const QString& includefile, const KDevelop::Path::List& includepaths, const KDevelop::Path& source ) { const auto sourceFolder = source.parent(); const Path canonicalFile( QFileInfo( includefile ).canonicalFilePath() ); QString shortestDirective; bool isRelative = false; // we can include the file directly if (sourceFolder == canonicalFile.parent()) { shortestDirective = canonicalFile.lastPathSegment(); isRelative = true; } else { // find the include directive with the shortest length for( const auto& includePath : includepaths ) { QString relative = includePath.relativePath( canonicalFile ); if( relative.startsWith( QLatin1String("./") ) ) relative = relative.mid( 2 ); if( shortestDirective.isEmpty() || relative.length() < shortestDirective.length() ) { shortestDirective = relative; isRelative = includePath == sourceFolder; } } } if( shortestDirective.isEmpty() ) { // Item not found in include path return {}; } const auto range = DocumentRange(IndexedString(source.pathOrUrl()), includeDirectivePosition(source, canonicalFile.lastPathSegment())); if( !range.isValid() ) { clangDebug() << "unable to determine valid position for" << includefile << "in" << source.pathOrUrl(); return {}; } QString directive; if( isRelative ) { directive = QStringLiteral("#include \"%1\"").arg(shortestDirective); } else { directive = QStringLiteral("#include <%1>").arg(shortestDirective); } return ClangFixit{directive + QLatin1Char('\n'), range, QObject::tr("Insert \'%1\'").arg(directive)}; } KDevelop::Path::List includePaths( const KDevelop::Path& file ) { // Find project's custom include paths const auto source = file.toLocalFile(); const auto item = ICore::self()->projectController()->projectModel()->itemForPath( KDevelop::IndexedString( source ) ); return IDefinesAndIncludesManager::manager()->includes(item); } /** * Return a list of header files viable for inclusions. All elements will be unique */ QStringList includeFiles(const QualifiedIdentifier& identifier, const QVector declarations, const KDevelop::Path& file) { const auto includes = includePaths( file ); if( includes.isEmpty() ) { clangDebug() << "Include path is empty"; return {}; } const auto candidates = findMatchingIncludeFiles(declarations); if( !candidates.isEmpty() ) { // If we find a candidate from the duchain we don't bother scanning the include paths return candidates; } return scanIncludePaths(identifier, includes); } /** * Construct viable forward declarations for the type name. */ ClangFixits forwardDeclarations(const QVector& matchingDeclarations, const Path& source) { DUChainReadLocker lock; ClangFixits fixits; for (const auto decl : matchingDeclarations) { const auto qid = decl->qualifiedIdentifier(); if (qid.count() > 1) { // TODO: Currently we're not able to determine what is namespaces, class names etc // and makes a suitable forward declaration, so just suggest "vanilla" declarations. continue; } const auto range = forwardDeclarationPosition(qid, source); if (!range.isValid()) { continue; // do not know where to insert } if (const auto classDecl = dynamic_cast(decl)) { const auto name = qid.last().toString(); switch (classDecl->classType()) { case ClassDeclarationData::Class: fixits += { QLatin1String("class ") + name + QLatin1String(";\n"), range, QObject::tr("Forward declare as 'class'") }; break; case ClassDeclarationData::Struct: fixits += { QLatin1String("struct ") + name + QLatin1String(";\n"), range, QObject::tr("Forward declare as 'struct'") }; break; default: break; } } } return fixits; } /** * Search the persistent symbol table for matching declarations for identifiers @p identifiers */ QVector findMatchingDeclarations(const QVector& identifiers) { DUChainReadLocker lock; QVector matchingDeclarations; matchingDeclarations.reserve(identifiers.size()); for (const auto& declaration : identifiers) { clangDebug() << "Considering candidate declaration" << declaration; const IndexedDeclaration* declarations; uint declarationCount; PersistentSymbolTable::self().declarations( declaration , declarationCount, declarations ); for (uint i = 0; i < declarationCount; ++i) { // Skip if the declaration is invalid or if it is an alias declaration - // we want the actual declaration (and its file) if (auto decl = declarations[i].declaration()) { matchingDeclarations << decl; } } } return matchingDeclarations; } ClangFixits fixUnknownDeclaration( const QualifiedIdentifier& identifier, const KDevelop::Path& file, const KDevelop::DocumentRange& docrange ) { ClangFixits fixits; const CursorInRevision cursor{docrange.start().line(), docrange.start().column()}; const auto possibleIdentifiers = findPossibleQualifiedIdentifiers(identifier, file, cursor); const auto matchingDeclarations = findMatchingDeclarations(possibleIdentifiers); if (ClangSettingsManager::self()->assistantsSettings().forwardDeclare) { for (const auto& fixit : forwardDeclarations(matchingDeclarations, file)) { fixits << fixit; if (fixits.size() == maxSuggestions) { return fixits; } } } const auto includefiles = includeFiles(identifier, matchingDeclarations, file); if (includefiles.isEmpty()) { // return early as the computation of the include paths is quite expensive return fixits; } const auto includepaths = includePaths( file ); /* create fixits for candidates */ for( const auto& includeFile : includefiles ) { const auto fixit = directiveForFile( includeFile, includepaths, file /* UP */ ); if (!fixit.range.isValid()) { clangDebug() << "unable to create directive for" << includeFile << "in" << file.toLocalFile(); continue; } fixits << fixit; if (fixits.size() == maxSuggestions) { return fixits; } } return fixits; } QString symbolFromDiagnosticSpelling(const QString& str) { /* in all error messages the symbol is in in the first pair of quotes */ const auto split = str.split( QLatin1Char('\'') ); auto symbol = split.value( 1 ); if( str.startsWith( QLatin1String("No member named") ) ) { symbol = split.value( 3 ) + QLatin1String("::") + split.value( 1 ); } return symbol; } } UnknownDeclarationProblem::UnknownDeclarationProblem(CXDiagnostic diagnostic, CXTranslationUnit unit) : ClangProblem(diagnostic, unit) { setSymbol(QualifiedIdentifier(symbolFromDiagnosticSpelling(description()))); } void UnknownDeclarationProblem::setSymbol(const QualifiedIdentifier& identifier) { m_identifier = identifier; } IAssistant::Ptr UnknownDeclarationProblem::solutionAssistant() const { const Path path(finalLocation().document.str()); const auto fixits = allFixits() + fixUnknownDeclaration(m_identifier, path, finalLocation()); return IAssistant::Ptr(new ClangFixitAssistant(fixits)); } diff --git a/languages/clang/kdevclangsupport.rc b/languages/clang/kdevclangsupport.rc index 78274028b2..04e81b54cb 100644 --- a/languages/clang/kdevclangsupport.rc +++ b/languages/clang/kdevclangsupport.rc @@ -1,9 +1,9 @@ Code - \ No newline at end of file + diff --git a/languages/clang/tests/minimal_visitor.cpp b/languages/clang/tests/minimal_visitor.cpp index 88bf2a72d8..55ba983842 100644 --- a/languages/clang/tests/minimal_visitor.cpp +++ b/languages/clang/tests/minimal_visitor.cpp @@ -1,62 +1,62 @@ /* * Copyright 2015 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) 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 #include CXChildVisitResult visitCursor(CXCursor cursor, CXCursor /*parent*/, CXClientData /*data*/) { auto range = clang_getCursorExtent(cursor); auto start = clang_getRangeStart(range); auto end = clang_getRangeEnd(range); CXFile file; unsigned startLine, startColumn; clang_getSpellingLocation(start, &file, &startLine, &startColumn, nullptr); unsigned endLine, endColumn; clang_getSpellingLocation(end, nullptr, &endLine, &endColumn, nullptr); auto str = clang_getCursorSpelling(cursor); auto fileStr = clang_getFileName(file); auto typeStr = clang_getCursorKindSpelling(cursor.kind); printf("\"%s\" [(%d, %d), (%d, %d)] in %s | %s\n", clang_getCString(str), startLine, startColumn, endLine, endColumn, clang_getCString(fileStr), clang_getCString(typeStr)); clang_disposeString(str); clang_disposeString(fileStr); clang_disposeString(typeStr); return CXChildVisit_Recurse; } int main(int argc, char** argv) { if (argc != 2) return 1; auto index = clang_createIndex(0, 0); CXTranslationUnit unit; clang_parseTranslationUnit2(index, argv[1], nullptr, 0, nullptr, 0, 0, &unit); auto tuCursor = clang_getTranslationUnitCursor(unit); clang_visitChildren(tuCursor, &visitCursor, nullptr); return 0; -} \ No newline at end of file +} diff --git a/languages/clang/tests/test_buddies.cpp b/languages/clang/tests/test_buddies.cpp index b22c76a6fa..51b36626f0 100644 --- a/languages/clang/tests/test_buddies.cpp +++ b/languages/clang/tests/test_buddies.cpp @@ -1,516 +1,516 @@ /*************************************************************************** * Copyright 2011 Martin Heide * * Copyright 2012 Milian Wolff * * * * 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 "test_buddies.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 #include #include using namespace KDevelop; #include #include #include Sublime::MainWindow* toSublimeWindow(KParts::MainWindow* window) { Sublime::MainWindow* ret = dynamic_cast(window); Q_ASSERT(ret); return ret; } void TestBuddies::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport")); AutoTestShell::init({QStringLiteral("kdevclangsupport")}); TestCore::initialize(); m_documentController = ICore::self()->documentController(); m_uiController = ICore::self()->uiController(); m_sublimeController = m_uiController->controller(); } void TestBuddies::cleanupTestCase() { TestCore::shutdown(); } void TestBuddies::init() { // Make sure we start with an empty document set QCOMPARE(m_documentController->openDocuments().count(), 0); } void TestBuddies::cleanup() { m_documentController->closeAllDocuments(); } void TestBuddies::verifyFilename(Sublime::View *view, const QString& endOfFilename) { QVERIFY(view); if (view) { Sublime::UrlDocument *urlDoc = dynamic_cast(view->document()); QVERIFY(urlDoc); if (urlDoc) { qDebug() << urlDoc->url().toLocalFile() << endOfFilename; QVERIFY(urlDoc->url().toLocalFile().endsWith(endOfFilename)); } } } void TestBuddies::createFile(const QDir& dir, const QString& filename) { QFile file(dir.filePath(filename)); QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text)); file.close(); } void TestBuddies::enableBuddies(bool enable) { { KConfigGroup uiGroup = KSharedConfig::openConfig()->group("UiSettings"); uiGroup.writeEntry("TabBarArrangeBuddies", (enable ? 1 : 0)); uiGroup.sync(); } m_sublimeController->loadSettings(); QCOMPARE(m_sublimeController->arrangeBuddies(), enable); } void TestBuddies::enableOpenAfterCurrent(bool enable) { { KConfigGroup uiGroup = KSharedConfig::openConfig()->group("UiSettings"); uiGroup.writeEntry("TabBarOpenAfterCurrent", (enable ? 1 : 0)); uiGroup.sync(); } m_sublimeController->loadSettings(); QCOMPARE(m_sublimeController->openAfterCurrent(), enable); } void TestBuddies::testDeclarationDefinitionOrder() { enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir tempDirA; QDir dirA(tempDirA.path()); createFile(dirA, "a.cpp"); createFile(dirA, "b.cpp"); createFile(dirA, "c.cpp"); createFile(dirA, "a.h"); createFile(dirA, "b.h"); createFile(dirA, "c.h"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("b.h"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("c.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("b.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.h"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("c.h"))); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(toSublimeWindow(m_uiController->activeMainWindow())->activeView()); QCOMPARE(m_documentController->openDocuments().count(), 6); //QCOMPARE(m_uiController->documents().count(), 6); QCOMPARE(areaIndex->viewCount(), 6); verifyFilename(areaIndex->views().value(0), "a.h"); verifyFilename(areaIndex->views().value(1), "a.cpp"); verifyFilename(areaIndex->views().value(2), "b.h"); verifyFilename(areaIndex->views().value(3), "b.cpp"); verifyFilename(areaIndex->views().value(4), "c.h"); verifyFilename(areaIndex->views().value(5), "c.cpp"); } void TestBuddies::testMultiDotFilenames() { enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir tempDirA; QDir dirA(tempDirA.path()); createFile(dirA, "a.cpp"); createFile(dirA, "lots.of.dots.cpp"); createFile(dirA, "b.cpp"); createFile(dirA, "lots.of.dots.h"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("lots.of.dots.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("b.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("lots.of.dots.h"))); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(toSublimeWindow(m_uiController->activeMainWindow())->activeView()); QCOMPARE(m_documentController->openDocuments().count(), 4); //QCOMPARE(m_sublimeController->documents().count(), 4); QCOMPARE(areaIndex->viewCount(), 4); verifyFilename(areaIndex->views().value(0), "a.cpp"); verifyFilename(areaIndex->views().value(1), "lots.of.dots.h"); verifyFilename(areaIndex->views().value(2), "lots.of.dots.cpp"); verifyFilename(areaIndex->views().value(3), "b.cpp"); } void TestBuddies::testActivation() { enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir tempDirA; QDir dirA(tempDirA.path()); createFile(dirA, "a.h"); createFile(dirA, "a.cpp"); createFile(dirA, "b.cpp"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.h"))); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "a.h"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("b.cpp"))); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "b.cpp"); } void TestBuddies::testDisableBuddies() { -/* 3. Disactivate buddy option, Activate open next to active tab +/* 3. Deactivate buddy option, Activate open next to active tab Open a.cpp a.h Verify order (a.cpp a.h) Verify that a.h is activated Activate a.cpp Open b.cpp Verify order (a.cpp b.cpp a.h) */ enableBuddies(false); enableOpenAfterCurrent(); QTemporaryDir tempDirA; QDir dirA(tempDirA.path()); createFile(dirA, "a.h"); createFile(dirA, "a.cpp"); createFile(dirA, "b.cpp"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.h"))); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(toSublimeWindow(m_uiController->activeMainWindow())->activeView()); // Buddies disabled => order of tabs should be the order of file opening verifyFilename(areaIndex->views().value(0), "a.cpp"); verifyFilename(areaIndex->views().value(1), "a.h"); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "a.h"); //activate a.cpp => new doc should be opened right next to it toSublimeWindow(m_uiController->activeMainWindow())->activateView(areaIndex->views().value(0)); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("b.cpp"))); verifyFilename(areaIndex->views().value(0), "a.cpp"); verifyFilename(areaIndex->views().value(1), "b.cpp"); verifyFilename(areaIndex->views().value(2), "a.h"); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "b.cpp"); } void TestBuddies::testDisableOpenAfterCurrent() { /* 5. Enable buddy option, Disable open next to active tab Open foo.h bar.cpp foo.cpp Verify order (foo.h foo.cpp bar.cpp) Verify that foo.cpp is activated Open x.cpp => tab must be placed at the end Verify order (foo.h foo.cpp bar.cpp x.cpp) Verify that x.cpp is activated*/ enableBuddies(); enableOpenAfterCurrent(false); QTemporaryDir tempDirA; QDir dirA; createFile(dirA, "foo.h"); createFile(dirA, "bar.cpp"); createFile(dirA, "foo.cpp"); createFile(dirA, "x.cpp"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("foo.h"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("bar.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("foo.cpp"))); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(toSublimeWindow(m_uiController->activeMainWindow())->activeView()); verifyFilename(areaIndex->views().value(0), "foo.h"); verifyFilename(areaIndex->views().value(1), "foo.cpp"); verifyFilename(areaIndex->views().value(2), "bar.cpp"); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "foo.cpp"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("x.cpp"))); verifyFilename(areaIndex->views().value(0), "foo.h"); verifyFilename(areaIndex->views().value(1), "foo.cpp"); verifyFilename(areaIndex->views().value(2), "bar.cpp"); verifyFilename(areaIndex->views().value(3), "x.cpp"); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "x.cpp"); } void TestBuddies::testDisableAll() { /* 6. Disable buddy option, Disable open next to active tab Open foo.cpp bar.h foo.h Activate bar.h Open bar.cpp Verify order (foo.cpp bar.h foo.h bar.cpp) Verify that bar.cpp is activated*/ enableBuddies(false); enableOpenAfterCurrent(false); QTemporaryDir tempDirA; QDir dirA(tempDirA.path()); createFile(dirA, "foo.h"); createFile(dirA, "foo.cpp"); createFile(dirA, "bar.h"); createFile(dirA, "bar.cpp"); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("foo.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("bar.h"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("foo.h"))); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(toSublimeWindow(m_uiController->activeMainWindow())->activeView()); //activate bar.h toSublimeWindow(m_uiController->activeMainWindow())->activateView(areaIndex->views().value(1)); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("bar.cpp"))); verifyFilename(areaIndex->views().value(0), "foo.cpp"); verifyFilename(areaIndex->views().value(1), "bar.h"); verifyFilename(areaIndex->views().value(2), "foo.h"); verifyFilename(areaIndex->views().value(3), "bar.cpp"); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "bar.cpp"); } void TestBuddies::testMultipleFolders() { /* 4. Multiple folders: Activate buddy option Open f/a.cpp f/xyz.cpp g/a.h Verify g/a.h is activated Verify order (f/a.cpp f/xyz.cpp g/a.h)*/ enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir tempDirA; QDir dirA(tempDirA.path()); createFile(dirA, "a.cpp"); createFile(dirA, "x.cpp"); QTemporaryDir tempDirB; QDir dirB(tempDirB.path()); createFile(dirB, "a.h"); // different folder => not dirA/a.cpp's buddy! m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("a.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("x.cpp"))); m_documentController->openDocument(QUrl::fromLocalFile(dirB.filePath("a.h"))); Sublime::Area *area = m_uiController->activeArea(); Sublime::AreaIndex* areaIndex = area->indexOf(toSublimeWindow(m_uiController->activeMainWindow())->activeView()); verifyFilename(areaIndex->views().value(0), "a.cpp"); verifyFilename(areaIndex->views().value(1), "x.cpp"); verifyFilename(areaIndex->views().value(2), "a.h"); verifyFilename(toSublimeWindow(m_uiController->activeMainWindow())->activeView(), "a.h"); } void TestBuddies::testSplitViewBuddies() { Sublime::MainWindow *pMainWindow = toSublimeWindow(m_uiController->activeMainWindow()); enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir tempDirA; QDir dirA(tempDirA.path()); createFile(dirA, "classA.cpp"); createFile(dirA, "classA.h"); Sublime::Area *pCodeArea = m_uiController->activeArea(); QVERIFY(pCodeArea); IDocument *pClassAHeader = m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("classA.h"))); QVERIFY(pClassAHeader); pMainWindow->activeView()->setObjectName("classA.h"); // now, create a splitted view of the active view (pClassAHeader) Sublime::View *pNewView = pMainWindow->activeView()->document()->createView(); pNewView->setObjectName("splitOf" + pMainWindow->activeView()->objectName()); pCodeArea->addView(pNewView, pMainWindow->activeView(), Qt::Vertical); // and activate it pMainWindow->activateView(pNewView); // get the current view's container from the mainwindow QWidget *pCentral = pMainWindow->centralWidget(); QVERIFY(pCentral); QVERIFY(pCentral->inherits("QWidget")); QWidget *pSplitter = pCentral->findChild(); QVERIFY(pSplitter); QVERIFY(pSplitter->inherits("QSplitter")); Sublime::Container *pContainer = pSplitter->findChild(); QVERIFY(pContainer); // check that it only contains pNewView QVERIFY(pContainer->count() == 1 && pContainer->hasWidget(pNewView->widget())); // now open the correponding definition file, classA.cpp IDocument *pClassAImplem = m_documentController->openDocument(QUrl::fromLocalFile(dirA.filePath("classA.cpp"))); QVERIFY(pClassAImplem); pMainWindow->activeView()->setObjectName("classA.cpp"); // and check its presence alongside pNewView in pContainer QVERIFY(pContainer->hasWidget(pNewView->widget())); QVERIFY(pContainer->hasWidget(pMainWindow->activeView()->widget())); } void TestBuddies::testDUChainBuddy() { enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir dirA; TestFile header("void myfunction();\n", "h", nullptr, dirA.path()); TestFile other("void otherfunction() {}\n", "cpp", nullptr, dirA.path()); TestFile source( QString("#include \"%1\"\nvoid myfunction() {}\n").arg(header.url().toUrl().fileName()), "cpp", nullptr, dirA.path() ); header.parseAndWait(); other.parseAndWait(); source.parseAndWait(); // Test IBuddyDocumentFinder::getPotentialBuddies() QMimeDatabase db; IBuddyDocumentFinder* sourceBuddyFinder = IBuddyDocumentFinder::finderForMimeType(db.mimeTypeForUrl(source.url().toUrl()).name()); QVector< QUrl > sourceBuddies = sourceBuddyFinder->getPotentialBuddies(source.url().toUrl()); if (!sourceBuddies.contains(header.url().toUrl())) { qDebug() << "got source buddies: " << sourceBuddies; qDebug() << "expected: " << header.url().toUrl(); QFAIL("Failed to find buddy for source file"); } QVERIFY2(!sourceBuddies.contains(other.url().toUrl()), "source buddy list contains unrelated file"); IBuddyDocumentFinder* headerBuddyFinder = IBuddyDocumentFinder::finderForMimeType(db.mimeTypeForUrl(header.url().toUrl()).name()); QVector< QUrl > headerBuddies = headerBuddyFinder->getPotentialBuddies(header.url().toUrl()); if (!headerBuddies.contains(source.url().toUrl())) { qDebug() << "got header buddies: " << headerBuddies; qDebug() << "expected: " << source.url().toUrl(); QFAIL("Failed to find buddy for header file"); } QVERIFY2(!headerBuddies.contains(other.url().toUrl()), "header buddy list contains unrelated file"); // Test IBuddyDocumentFinder::areBuddies() QVERIFY(sourceBuddyFinder->areBuddies(source.url().toUrl(), header.url().toUrl())); } void TestBuddies::testDUChainBuddyVote() { /* * Test that the DUChain buddy system finds the buddy file with the most * common declarations/definitions */ enableBuddies(); enableOpenAfterCurrent(); QTemporaryDir dirA; TestFile header("void func1();\nvoid func2();\nvoid func3();\n", "h", nullptr, dirA.path()); TestFile source1( QString("#include \"%1\"\nvoid func1() {}\n").arg(header.url().toUrl().fileName()), "cpp", nullptr, dirA.path() ); TestFile source2( QString("#include \"%1\"\nvoid func2() {}\nvoid func3() {}\n").arg(header.url().toUrl().fileName()), "cpp", nullptr, dirA.path() ); // -> buddy(header) should resolve to source2 header.parseAndWait(); source1.parseAndWait(); source2.parseAndWait(); // Test IBuddyDocumentFinder::getPotentialBuddies() QMimeDatabase db; IBuddyDocumentFinder* sourceBuddyFinder = IBuddyDocumentFinder::finderForMimeType(db.mimeTypeForUrl(source1.url().toUrl()).name()); QVector< QUrl > sourceBuddies = sourceBuddyFinder->getPotentialBuddies(source1.url().toUrl()); if (!sourceBuddies.contains(header.url().toUrl())) { qDebug() << "got source buddies: " << sourceBuddies; qDebug() << "expected: " << header.url().toUrl(); QFAIL("Failed to find buddy for source file"); } IBuddyDocumentFinder* source2BuddyFinder = IBuddyDocumentFinder::finderForMimeType(db.mimeTypeForUrl(source2.url().toUrl()).name()); QVector< QUrl > source2Buddies = source2BuddyFinder->getPotentialBuddies(source2.url().toUrl()); if (!source2Buddies.contains(header.url().toUrl())) { qDebug() << "got source2 buddies: " << source2Buddies; qDebug() << "expected: " << header.url().toUrl(); QFAIL("Failed to find buddy for source2 file"); } IBuddyDocumentFinder* headerBuddyFinder = IBuddyDocumentFinder::finderForMimeType(db.mimeTypeForUrl(header.url().toUrl()).name()); QVector< QUrl > headerBuddies = headerBuddyFinder->getPotentialBuddies(header.url().toUrl()); if (!headerBuddies.contains(source2.url().toUrl())) { qDebug() << "got header buddies: " << headerBuddies; qDebug() << "expected: " << source2.url().toUrl(); QFAIL("Failed to find buddy for header file"); } QVERIFY2(!headerBuddies.contains(source1.url().toUrl()), "header buddy list contains weaker file"); } QTEST_MAIN(TestBuddies) diff --git a/languages/clang/tests/test_codecompletion.cpp b/languages/clang/tests/test_codecompletion.cpp index bdcb659c40..18ab74478b 100644 --- a/languages/clang/tests/test_codecompletion.cpp +++ b/languages/clang/tests/test_codecompletion.cpp @@ -1,1222 +1,1222 @@ /* * Copyright 2014 David Stevens * Copyright 2014 Kevin Funk * Copyright 2015 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 "test_codecompletion.h" #include #include #include #include #include #include "duchain/parsesession.h" #include "util/clangtypes.h" #include #include #include #include #include #include "codecompletion/completionhelper.h" #include "codecompletion/context.h" #include "codecompletion/includepathcompletioncontext.h" #include "../clangsettings/clangsettingsmanager.h" #include #include #include #include QTEST_MAIN(TestCodeCompletion); static const auto NoMacroOrBuiltin = ClangCodeCompletionContext::ContextFilters( ClangCodeCompletionContext::NoBuiltins | ClangCodeCompletionContext::NoMacros); using namespace KDevelop; using ClangCodeCompletionItemTester = CodeCompletionItemTester; struct CompletionItems { CompletionItems(){} CompletionItems(const KTextEditor::Cursor& position, const QStringList& completions, const QStringList& declarationItems = {}) : position(position) , completions(completions) , declarationItems(declarationItems) {}; KTextEditor::Cursor position; QStringList completions; QStringList declarationItems; ///< completion items that have associated declarations. Declarations with higher match quality at the top. @sa KTextEditor::CodeCompletionModel::MatchQuality }; Q_DECLARE_TYPEINFO(CompletionItems, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(CompletionItems); struct CompletionPriorityItem { CompletionPriorityItem(const QString name, int matchQuality = 0, int inheritanceDepth = 0, const QString failMessage = {}) : name(name) , failMessage(failMessage) , matchQuality(matchQuality) , inheritanceDepth(inheritanceDepth) {} QString name; QString failMessage; int matchQuality; int inheritanceDepth; }; struct CompletionPriorityItems : public CompletionItems { CompletionPriorityItems(){} CompletionPriorityItems(const KTextEditor::Cursor& position, const QList& completions) : CompletionItems(position, {}) , completions(completions) {}; QList completions; }; Q_DECLARE_TYPEINFO(CompletionPriorityItems, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(CompletionPriorityItems); namespace { struct NoopTestFunction { void operator()(const ClangCodeCompletionItemTester& /*tester*/) const { } }; template void executeCompletionTest(const ReferencedTopDUContext& top, const CompletionItems& expectedCompletionItems, const ClangCodeCompletionContext::ContextFilters& filters = NoMacroOrBuiltin, CustomTestFunction customTestFunction = {}) { DUChainReadLocker lock; const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); lock.unlock(); QString text; if (auto doc = ICore::self()->documentController()->documentForUrl(top->url().toUrl())) { text = doc->textDocument()->text({{0, 0}, expectedCompletionItems.position}); } // TODO: We should not need to pass 'session' to the context, should just use the base class ctor auto context = new ClangCodeCompletionContext(DUContextPointer(top), sessionData, top->url().toUrl(), expectedCompletionItems.position, text); context->setFilters(filters); lock.lock(); auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(context)); int previousMatchQuality = 10; for(const auto& declarationName : expectedCompletionItems.declarationItems){ const auto declarationItem = tester.findItem(declarationName); QVERIFY(declarationItem); QVERIFY(declarationItem->declaration()); auto matchQuality = tester.itemData(declarationItem, KTextEditor::CodeCompletionModel::Name, KTextEditor::CodeCompletionModel::MatchQuality).toInt(); QVERIFY(matchQuality <= previousMatchQuality); previousMatchQuality = matchQuality; } tester.names.sort(); QEXPECT_FAIL("look-ahead function primary type argument", "No API in LibClang to determine expected code completion type", Continue); QEXPECT_FAIL("look-ahead template parameter substitution", "No parameters substitution so far", Continue); #if CINDEX_VERSION_MINOR < 30 QEXPECT_FAIL("look-ahead auto item", "Auto type, like many other types, is not exposed through LibClang. We assign DelayedType to it instead of IdentifiedType", Continue); #endif if (QTest::currentTestFunction() == QByteArrayLiteral("testImplementAfterEdit") && expectedCompletionItems.position.line() == 3) { QEXPECT_FAIL("", "TU is not properly updated after edit", Continue); } if (tester.names.size() != expectedCompletionItems.completions.size()) { qDebug() << "different results:\nactual:" << tester.names << "\nexpected:" << expectedCompletionItems.completions; } QCOMPARE(tester.names, expectedCompletionItems.completions); customTestFunction(tester); } template void executeCompletionTest(const QString& code, const CompletionItems& expectedCompletionItems, const ClangCodeCompletionContext::ContextFilters& filters = NoMacroOrBuiltin, CustomTestFunction customTestFunction = {}) { TestFile file(code, "cpp"); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); executeCompletionTest(file.topContext(), expectedCompletionItems, filters, customTestFunction); } void executeCompletionPriorityTest(const QString& code, const CompletionPriorityItems& expectedCompletionItems, const ClangCodeCompletionContext::ContextFilters& filters = NoMacroOrBuiltin) { TestFile file(code, "cpp"); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); DUContextPointer topPtr(top); // don't hold DUChain lock when constructing ClangCodeCompletionContext lock.unlock(); auto context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), expectedCompletionItems.position, QString()); context->setFilters(filters); lock.lock(); auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(context)); for(const auto& declaration : expectedCompletionItems.completions){ const auto declarationItem = tester.findItem(declaration.name); QVERIFY(declarationItem); QVERIFY(declarationItem->declaration()); auto matchQuality = tester.itemData(declarationItem, KTextEditor::CodeCompletionModel::Name, KTextEditor::CodeCompletionModel::MatchQuality).toInt(); auto inheritanceDepth = declarationItem->inheritanceDepth(); if(!declaration.failMessage.isEmpty()){ QEXPECT_FAIL("", declaration.failMessage.toUtf8().constData(), Continue); } QVERIFY(matchQuality == declaration.matchQuality && inheritanceDepth == declaration.inheritanceDepth); } } void executeMemberAccessReplacerTest(const QString& code, const CompletionItems& expectedCompletionItems, const ClangCodeCompletionContext::ContextFilters& filters = NoMacroOrBuiltin) { TestFile file(code, "cpp"); auto document = ICore::self()->documentController()->openDocument(file.url().toUrl()); QVERIFY(document); ICore::self()->documentController()->activateDocument(document); auto view = ICore::self()->documentController()->activeTextDocumentView(); Q_ASSERT(view); view->setCursorPosition(expectedCompletionItems.position); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); DUContextPointer topPtr(top); lock.unlock(); QExplicitlySharedDataPointer context( new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), expectedCompletionItems.position, code)); QApplication::processEvents(); document->close(KDevelop::IDocument::Silent); // The previous ClangCodeCompletionContext call should replace member access. // That triggers an update request in the duchain which we are not interested in, // so let's stop that request. ICore::self()->languageController()->backgroundParser()->removeDocument(file.url()); context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), expectedCompletionItems.position, QString()); context->setFilters(filters); lock.lock(); auto tester = ClangCodeCompletionItemTester(context); tester.names.sort(); QCOMPARE(tester.names, expectedCompletionItems.completions); } using IncludeTester = CodeCompletionItemTester; QExplicitlySharedDataPointer executeIncludePathCompletion(TestFile* file, const KTextEditor::Cursor& position) { if (!file->parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)) { QTest::qFail("Failed to parse source file.", __FILE__, __LINE__); return {}; } DUChainReadLocker lock; auto top = file->topContext(); if (!top) { QTest::qFail("Failed to parse source file.", __FILE__, __LINE__); return {}; } const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); if (!sessionData) { QTest::qFail("Failed to acquire parse session data.", __FILE__, __LINE__); return {}; } DUContextPointer topPtr(top); lock.unlock(); auto text = file->fileContents(); int textLength = -1; if (position.isValid()) { textLength = 0; for (int i = 0; i < position.line(); ++i) { textLength = text.indexOf('\n', textLength) + 1; } textLength += position.column(); } auto context = new IncludePathCompletionContext(topPtr, sessionData, file->url().toUrl(), position, text.mid(0, textLength)); return QExplicitlySharedDataPointer{context}; } } void TestCodeCompletion::testClangCodeCompletion() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeCompletionTest(code, expectedItems); } void TestCodeCompletion::testClangCodeCompletion_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("assignment") << "int foo = 5; \nint bar = " << CompletionItems{{1,9}, { "bar", "foo", }, {"bar","foo"}}; QTest::newRow("dotmemberaccess") << "class Foo { public: void foo() {} bool operator=(Foo &&) }; int main() { Foo f; \nf. " << CompletionItems{{1, 2}, { "foo", "operator=" }, {"foo", "operator="}}; QTest::newRow("arrowmemberaccess") << "class Foo { public: void foo() {} }; int main() { Foo* f = new Foo; \nf-> }" << CompletionItems{{1, 3}, { "foo" }, {"foo"}}; QTest::newRow("enum-case") << "enum Foo { foo, bar }; int main() { Foo f; switch (f) {\ncase " << CompletionItems{{1,4}, { "bar", "foo", }, {"foo", "bar"}}; QTest::newRow("only-private") << "class SomeStruct { private: void priv() {} };\n" "int main() { SomeStruct s;\ns. " << CompletionItems{{2, 2}, { }}; QTest::newRow("private-friend") << "class SomeStruct { private: void priv() {} friend int main(); };\n" "int main() { SomeStruct s;\ns. " << CompletionItems{{2, 2}, { "priv", }, {"priv"}}; QTest::newRow("private-public") << "class SomeStruct { public: void pub() {} private: void priv() {} };\n" "int main() { SomeStruct s;\ns. " << CompletionItems{{2, 2}, { "pub", }, {"pub"}}; QTest::newRow("protected-public") << "class SomeStruct { public: void pub() {} protected: void prot() {} };\n" "int main() { SomeStruct s;\ns. " << CompletionItems{{2, 2}, { "pub", }, {"pub"}}; QTest::newRow("localVariable") << "int main() { int localVariable;\nloc " << CompletionItems{{1, 3}, {"localVariable","main"}, {"localVariable", "main"} }; QTest::newRow("globalVariable") << "int globalVariable;\nint main() { \ngl " << CompletionItems{{2, 2}, {"globalVariable","main"}, {"globalVariable", "main"} }; QTest::newRow("namespaceVariable") << "namespace NameSpace{int variable};\nint main() { \nNameSpace:: " << CompletionItems{{2, 11}, {"variable"}, {"variable"} }; QTest::newRow("parentVariable") << "class A{public: int m_variable;};class B : public A{};\nint main() { B b;\nb. " << CompletionItems{{2, 2}, {"m_variable"}, {"m_variable"} }; QTest::newRow("itemsPriority") << "class A; class B; void f(A); int main(){ A c; B b;f(\n} " << CompletionItems{{1, 0}, {"A", "B", "b", "c", "f", #if CINDEX_VERSION_MINOR >= 30 "f", #endif "main"}, {"c", "A", "b", "B"} }; QTest::newRow("function-arguments") << "class Abc; int f(Abc){\n " << CompletionItems{{1, 0}, { "Abc", "f", }}; QTest::newRow("look-ahead int") << "struct LookAhead { int intItem;}; int main() {LookAhead* pInstance; LookAhead instance; int i =\n }" << CompletionItems{{1, 0}, { "LookAhead", "i", "instance", "instance.intItem", "main", "pInstance", "pInstance->intItem", }}; QTest::newRow("look-ahead class") << "class Class{}; struct LookAhead {Class classItem;}; int main() {LookAhead* pInstance; LookAhead instance; Class cl =\n }" << CompletionItems{{1, 0}, { "Class", "LookAhead", "cl", "instance", "instance.classItem", "main", "pInstance", "pInstance->classItem", }}; QTest::newRow("look-ahead function argument") << "class Class{}; struct LookAhead {Class classItem;}; void function(Class cl);" "int main() {LookAhead* pInstance; LookAhead instance; function(\n }" << CompletionItems{{1, 0}, { "Class", "LookAhead", "function", #if CINDEX_VERSION_MINOR >= 30 "function", #endif "instance", "instance.classItem", "main", "pInstance", "pInstance->classItem", }}; QTest::newRow("look-ahead function primary type argument") << "struct LookAhead {double doubleItem;}; void function(double double);" "int main() {LookAhead* pInstance; LookAhead instance; function(\n }" << CompletionItems{{1, 0}, { "LookAhead", "function", "instance", "instance.doubleItem", "main", "pInstance", "pInstance->doubleItem", }}; QTest::newRow("look-ahead typedef") << "typedef double DOUBLE; struct LookAhead {DOUBLE doubleItem;};" "int main() {LookAhead* pInstance; LookAhead instance; double i =\n " << CompletionItems{{1, 0}, { "DOUBLE", "LookAhead", "i", "instance", "instance.doubleItem", "main", "pInstance", "pInstance->doubleItem", }}; QTest::newRow("look-ahead pointer") << "struct LookAhead {int* pInt;};" "int main() {LookAhead* pInstance; LookAhead instance; int* i =\n " << CompletionItems{{1, 0}, { "LookAhead", "i", "instance", "instance.pInt", "main", "pInstance", "pInstance->pInt", }}; QTest::newRow("look-ahead template") << "template struct LookAhead {int intItem;};" "int main() {LookAhead* pInstance; LookAhead instance; int i =\n " << CompletionItems{{1, 0}, { "LookAhead", "i", "instance", "instance.intItem", "main", "pInstance", "pInstance->intItem", }}; QTest::newRow("look-ahead template parameter substitution") << "template struct LookAhead {T itemT;};" "int main() {LookAhead* pInstance; LookAhead instance; int i =\n " << CompletionItems{{1, 0}, { "LookAhead", "i", "instance", "instance.itemT", "main", "pInstance", "pInstance->itemT", }}; QTest::newRow("look-ahead item access") << "class Class { public: int publicInt; protected: int protectedInt; private: int privateInt;};" "int main() {Class cl; int i =\n " << CompletionItems{{1, 0}, { "Class", "cl", "cl.publicInt", "i", "main", }}; QTest::newRow("look-ahead auto item") << "struct LookAhead { int intItem; };" "int main() {auto instance = LookAhead(); int i = \n " << CompletionItems{{1, 0}, { "LookAhead", "i", "instance", "instance.intItem", "main" }}; QTest::newRow("variadic template recursive class") << R"( template struct my_class : Head, my_class { using base = Head; };)" << CompletionItems{{3, 17}, { "Head", "Tail", "my_class" }}; } void TestCodeCompletion::testReplaceMemberAccess() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeMemberAccessReplacerTest(code, expectedItems); } void TestCodeCompletion::testReplaceMemberAccess_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("replace arrow to dot") << "struct Struct { void function(); };" "int main() { Struct s; \ns-> " << CompletionItems{{1, 3}, { "function" }}; QTest::newRow("replace dot to arrow") << "struct Struct { void function(); };" "int main() { Struct* s; \ns. " << CompletionItems{{1, 3}, { "function" }}; QTest::newRow("no replacement needed") << "int main() { double a = \n0. " << CompletionItems{{1, 2}, { }}; } void TestCodeCompletion::testVirtualOverride() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeCompletionTest(code, expectedItems, ClangCodeCompletionContext::NoClangCompletion); } void TestCodeCompletion::testVirtualOverride_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("basic") << "class Foo { virtual void foo(); virtual void foo(char c); virtual char foo(char c, int i, double d); };\n" "class Bar : Foo \n{void foo(char c) override;\n}" << CompletionItems{{3, 1}, {"foo()", "foo(char c, int i, double d)"}}; QTest::newRow("template") << "template class Foo { virtual T2 foo(T1 a, T2 b, int i); virtual T2 overridden(T1 a); } ;\n" "class Bar : Foo \n{double overridden(char a) override;\n}" << CompletionItems{{3, 1}, {"foo(char a, double b, int i)"}}; QTest::newRow("nested-template") << "template class Foo { virtual T2 foo(T1 a, T2 b, int i); virtual T2 overridden(T1 a, T2 b, int i); } ;\n" "template class Baz { };\n" "class Bar : Foo> \n{Baz overridden(char a, Baz b, int i) override;\n}" << CompletionItems{{4, 1}, {"foo(char a, Baz b, int i)"}}; QTest::newRow("multi") << "class Foo { virtual int foo(int i); virtual int overridden(int i); };\n" "class Baz { virtual char baz(char c); };\n" "class Bar : Foo, Baz \n{int overridden(int i) override;\n}" << CompletionItems{{4, 1}, {"baz(char c)", "foo(int i)"}}; QTest::newRow("deep") << "class Foo { virtual int foo(int i); virtual int overridden(int i); };\n" "class Baz : Foo { };\n" - "class Bar : Baz \n{int overridden(int i) overriden;\n}" + "class Bar : Baz \n{int overridden(int i) overridden;\n}" << CompletionItems{{4, 1}, {"foo(int i)"}}; QTest::newRow("pure") << "class Foo { virtual void foo() = 0; virtual void overridden() = 0;};\n" "class Bar : Foo \n{void overridden() override;\n};" << CompletionItems{{3, 0}, {"foo() = 0"}}; QTest::newRow("const") << "class Foo { virtual void foo(const int b) const; virtual void overridden(const int b) const; }\n;" "class Bar : Foo \n{void overridden(const int b) const override;\n}" << CompletionItems{{3, 1}, {"foo(const int b) const"}}; QTest::newRow("dont-override") << R"(class A { virtual void something() = 0; }; class B : public A { public: void foo(); }; void B::foo() {} )" << CompletionItems{{8, 14}, {}}; } void TestCodeCompletion::testImplement() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeCompletionTest(code, expectedItems, ClangCodeCompletionContext::NoClangCompletion); } void TestCodeCompletion::testImplement_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("basic") << "int foo(char c, int i); \n" << CompletionItems{{1, 0}, {"foo(char c, int i)"}}; QTest::newRow("class") << "class Foo { \n" "int bar(char c, int i); \n\n" "}; \n" << CompletionItems{{2, 0}, {}}; QTest::newRow("class2") << "class Foo { \n" "int bar(char c, int i); \n\n" "}; \n" << CompletionItems{{4, 0}, {"Foo::bar(char c, int i)"}}; QTest::newRow("namespace") << "namespace Foo { \n" "int bar(char c, int i); \n\n" "}; \n" << CompletionItems{{2, 0}, {"bar(char c, int i)"}}; QTest::newRow("anonymous-namespace") << R"( namespace { int bar(char c, int i); }; )" << CompletionItems{{3, 0}, {"bar(char c, int i)"}}; QTest::newRow("anonymous-namespace2") << R"( namespace { int bar(char c, int i); }; )" << CompletionItems{{4, 0}, {}}; QTest::newRow("namespace2") << "namespace Foo { \n" "int bar(char c, int i); \n\n" "}; \n" << CompletionItems{{4, 0}, {"Foo::bar(char c, int i)"}}; QTest::newRow("two-namespace") << "namespace Foo { int bar(char c, int i); };\n" "namespace Foo {\n" "};\n" << CompletionItems{{2, 0}, {"bar(char c, int i)"}}; QTest::newRow("destructor") << "class Foo { ~Foo(); }\n" << CompletionItems{{1, 0}, {"Foo::~Foo()"}}; QTest::newRow("constructor") << "class Foo { \n" "Foo(int i); \n" "}; \n" << CompletionItems{{3, 1}, {"Foo::Foo(int i)"}}; QTest::newRow("template") << "template class Foo { T bar(T t); };\n" << CompletionItems{{1, 1}, {"Foo::bar(T t)"}}; QTest::newRow("specialized-template") << "template class Foo { \n" "T bar(T t); \n" "}; \n" "template T Foo::bar(T t){} \n" "template<> class Foo { \n" "int bar(int t); \n" "}\n" << CompletionItems{{6, 1}, {"Foo::bar(int t)"}}; QTest::newRow("nested-class") << "class Foo { \n" "class Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{3, 1}, {}}; QTest::newRow("nested-class2") << "class Foo { \n" "class Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{5, 1}, {}}; QTest::newRow("nested-class3") << "class Foo { \n" "class Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{7, 1}, {"Foo::Bar::baz(char c, int i)"}}; QTest::newRow("nested-namespace") << "namespace Foo { \n" "namespace Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{3, 1}, {"baz(char c, int i)"}}; QTest::newRow("nested-namespace2") << "namespace Foo { \n" "namespace Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{5, 1}, {"Bar::baz(char c, int i)"}}; QTest::newRow("nested-namespace3") << "namespace Foo { \n" "namespace Bar { \n" "int baz(char c, int i); \n\n" "}; \n\n" "}; \n\n" << CompletionItems {{7, 1}, {"Foo::Bar::baz(char c, int i)"}}; QTest::newRow("partial-template") << "template class Foo { \n" "template class Bar;\n" "template class Bar { void baz(T t, U u); }\n" "}\n" << CompletionItems{{5,1}, {"Foo::Bar::baz(T t, U u)"}}; QTest::newRow("variadic") << "int foo(...); int bar(int i, ...); \n" << CompletionItems{{1, 1}, {"bar(int i, ...)", "foo(...)"}}; QTest::newRow("const") << "class Foo { int bar() const; };" << CompletionItems{{3, 1}, {"Foo::bar() const"}}; QTest::newRow("multiple-methods") << "class Foo { int bar(); void foo(); char asdf() const; };" << CompletionItems{{1, 1}, {"Foo::asdf() const", "Foo::bar()", "Foo::foo()"}}; // explicitly deleted/defaulted functions should not appear in the implements completion QTest::newRow("deleted-copy-ctor") << "struct S { S(); S(const S&) = /*some comment*/ delete; };" << CompletionItems{{1,1}, {"S::S()"}}; QTest::newRow("deleted-overload-member") << "struct Foo {\n" " int x();\n" " int x(int) =delete;\n" "};\n" << CompletionItems{{5,1}, {"Foo::x()"}}; QTest::newRow("deleted-overload-global") << "int x();\n" "int x(int)= delete;\n" << CompletionItems{{2,1}, {"x()"}}; QTest::newRow("defaulted-copy-ctor") << "struct S { S(); S(const S&) = default; };" << CompletionItems{{1,1}, {"S::S()"}}; QTest::newRow("defaulted-dtor") << "struct Foo {\n" " Foo();\n" " ~Foo() =default;\n" "};\n" << CompletionItems{{5,1}, {"Foo::Foo()"}}; QTest::newRow("bug355163") << R"( #include namespace test { template struct IsSafeConversion : public std::is_same::type> { }; } // namespace test )" << CompletionItems{{7,0}, {}}; QTest::newRow("bug355954") << R"( struct Hello { struct Private; }; struct Hello::Private { void test(); }; )" << CompletionItems{{8,0}, {"Hello::Private::test()"}}; QTest::newRow("lineOfNextFunction") << "void foo();\nvoid bar() {}" << CompletionItems{{1,0}, {"foo()"}}; QTest::newRow("pure") << R"( struct Hello { virtual void foo() = 0; virtual void bar(); }; )" << CompletionItems{{5, 0}, {"Hello::bar()"}}; } void TestCodeCompletion::testImplementOtherFile() { TestFile header1("void foo();", "h"); QVERIFY(header1.parseAndWait()); TestFile header2("void bar();", "h"); QVERIFY(header2.parseAndWait()); TestFile impl(QString("#include \"%1\"\n" "#include \"%2\"\n" "void asdf();\n\n") .arg(header1.url().str()) .arg(header2.url().str()), "cpp", &header1); CompletionItems expectedItems{{3,1}, {"asdf()", "foo()"}}; QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); executeCompletionTest(impl.topContext(), expectedItems); } void TestCodeCompletion::testImplementAfterEdit() { TestFile header1("void foo();", "h"); QVERIFY(header1.parseAndWait()); TestFile impl(QString("#include \"%1\"\n" "void asdf() {}\nvoid bar() {}") .arg(header1.url().str()), "cpp", &header1); auto document = ICore::self()->documentController()->openDocument(impl.url().toUrl()); QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); CompletionItems expectedItems{{2,0}, {"foo()"}}; executeCompletionTest(impl.topContext(), expectedItems); document->textDocument()->insertText(expectedItems.position, "\n"); expectedItems.position.setLine(3); executeCompletionTest(impl.topContext(), expectedItems); document->close(IDocument::Discard); } void TestCodeCompletion::testInvalidCompletions() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeCompletionTest(code, expectedItems); } void TestCodeCompletion::testInvalidCompletions_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("invalid-context-incomment") << "class Foo { int bar() const; };\n/*\n*/" << CompletionItems{{2, 0}, {}}; } void TestCodeCompletion::testIncludePathCompletion_data() { QTest::addColumn("code"); QTest::addColumn("cursor"); QTest::addColumn("itemId"); QTest::addColumn("result"); QTest::newRow("global-1") << QString("#include ") << KTextEditor::Cursor(0, 9) << QString("iostream") << QString("#include "); QTest::newRow("global-2") << QString("#include <") << KTextEditor::Cursor(0, 9) << QString("iostream") << QString("#include "); QTest::newRow("global-3") << QString("#include <") << KTextEditor::Cursor(0, 10) << QString("iostream") << QString("#include "); QTest::newRow("global-4") << QString("# include <") << KTextEditor::Cursor(0, 12) << QString("iostream") << QString("# include "); QTest::newRow("global-5") << QString("# include <") << KTextEditor::Cursor(0, 14) << QString("iostream") << QString("# include "); QTest::newRow("global-6") << QString("# include <> /* 1 */") << KTextEditor::Cursor(0, 14) << QString("iostream") << QString("# include /* 1 */"); QTest::newRow("global-7") << QString("# include /* 1 */ <> /* 1 */") << KTextEditor::Cursor(0, 21) << QString("iostream") << QString("# include /* 1 */ /* 1 */"); QTest::newRow("global-8") << QString("# /* 1 */ include /* 1 */ <> /* 1 */") << KTextEditor::Cursor(0, 28) << QString("iostream") << QString("# /* 1 */ include /* 1 */ /* 1 */"); QTest::newRow("global-9") << QString("#include ") << KTextEditor::Cursor(0, 10) << QString("iostream") << QString("#include "); QTest::newRow("global-10") << QString("#include ") << KTextEditor::Cursor(0, 14) << QString("cstdint") << QString("#include "); QTest::newRow("global-11") << QString("#include ") << KTextEditor::Cursor(0, 17) << QString("cstdint") << QString("#include "); QTest::newRow("local-0") << QString("#include \"") << KTextEditor::Cursor(0, 10) << QString("foo/") << QString("#include \"foo/\""); QTest::newRow("local-1") << QString("#include \"foo/\"") << KTextEditor::Cursor(0, 14) << QString("bar/") << QString("#include \"foo/bar/\""); QTest::newRow("local-2") << QString("#include \"foo/") << KTextEditor::Cursor(0, 14) << QString("bar/") << QString("#include \"foo/bar/\""); QTest::newRow("local-3") << QString("# /* 1 */ include /* 1 */ \"\" /* 1 */") << KTextEditor::Cursor(0, 28) << QString("foo/") << QString("# /* 1 */ include /* 1 */ \"foo/\" /* 1 */"); QTest::newRow("local-4") << QString("# /* 1 */ include /* 1 */ \"foo/\" /* 1 */") << KTextEditor::Cursor(0, 31) << QString("bar/") << QString("# /* 1 */ include /* 1 */ \"foo/bar/\" /* 1 */"); QTest::newRow("local-5") << QString("#include \"foo/\"") << KTextEditor::Cursor(0, 10) << QString("foo/") << QString("#include \"foo/\""); QTest::newRow("local-6") << QString("#include \"foo/asdf\"") << KTextEditor::Cursor(0, 10) << QString("foo/") << QString("#include \"foo/\""); QTest::newRow("local-7") << QString("#include \"foo/asdf\"") << KTextEditor::Cursor(0, 14) << QString("bar/") << QString("#include \"foo/bar/\""); } void TestCodeCompletion::testIncludePathCompletion() { QFETCH(QString, code); QFETCH(KTextEditor::Cursor, cursor); QFETCH(QString, itemId); QFETCH(QString, result); QTemporaryDir tempDir; QDir dir(tempDir.path()); QVERIFY(dir.mkpath("foo/bar/asdf")); TestFile file(code, "cpp", nullptr, tempDir.path()); IncludeTester tester(executeIncludePathCompletion(&file, cursor)); QVERIFY(tester.completionContext); QVERIFY(tester.completionContext->isValid()); auto item = tester.findItem(itemId); QVERIFY(item); auto view = createView(file.url().toUrl(), this); qDebug() << view.get(); QVERIFY(view.get()); auto doc = view->document(); item->execute(view.get(), KTextEditor::Range(cursor, cursor)); QCOMPARE(doc->text(), result); const auto newCursor = view->cursorPosition(); QCOMPARE(newCursor.line(), cursor.line()); if (!itemId.endsWith('/')) { // file inserted, cursor should be at end of line QCOMPARE(newCursor.column(), doc->lineLength(cursor.line())); } else { // directory inserted, cursor should be before the " or > const auto cursorChar = doc->characterAt(newCursor); QVERIFY(cursorChar == '"' || cursorChar == '>'); } } void TestCodeCompletion::testIncludePathCompletionLocal() { TestFile header("int foo() { return 42; }\n", "h"); TestFile impl("#include \"", "cpp", &header); IncludeTester tester(executeIncludePathCompletion(&impl, {0, 10})); QVERIFY(tester.names.contains(header.url().toUrl().fileName())); QVERIFY(!tester.names.contains("iostream")); } void TestCodeCompletion::testOverloadedFunctions() { TestFile file("void f(); int f(int); void f(int, double){\n ", "cpp"); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); DUContextPointer topPtr(top); lock.unlock(); const auto context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), {1, 0}, QString()); context->setFilters(NoMacroOrBuiltin); lock.lock(); const auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(context)); QCOMPARE(tester.items.size(), 3); for (const auto& item : tester.items) { auto function = item->declaration()->type(); const QString display = item->declaration()->identifier().toString() + function->partToString(FunctionType::SignatureArguments); const QString itemDisplay = tester.itemData(item).toString() + tester.itemData(item, KTextEditor:: CodeCompletionModel::Arguments).toString(); QCOMPARE(display, itemDisplay); } QVERIFY(tester.items[0]->declaration().data() != tester.items[1]->declaration().data()); QVERIFY(tester.items[0]->declaration().data() != tester.items[2]->declaration().data()); QVERIFY(tester.items[1]->declaration().data() != tester.items[2]->declaration().data()); } void TestCodeCompletion::testCompletionPriority() { QFETCH(QString, code); QFETCH(CompletionPriorityItems, expectedItems); executeCompletionPriorityTest(code, expectedItems); } void TestCodeCompletion::testCompletionPriority_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("pointer") << "class A{}; class B{}; class C : public B{}; int main(){A* a; B* b; C* c; b =\n " << CompletionPriorityItems{{1,0}, {{"a", 0, 21}, {"b", 9, 0}, {"c", 8, 0, QStringLiteral("Pointer to derived class is not added to the Best Matches group")}}}; QTest::newRow("class") << "class A{}; class B{}; class C : public B{}; int main(){A a; B b; C c; b =\n " << CompletionPriorityItems{{1,0}, {{"a", 0, 21}, {"b", 9, 0}, {"c", 8, 0, QStringLiteral("Derived class is not added to the Best Matches group")}}}; QTest::newRow("primary-types") << "class A{}; int main(){A a; int b; bool c = \n " << CompletionPriorityItems{{1,0}, {{"a", 0, 34}, {"b", 8, 0}, {"c", 9, 0}}}; QTest::newRow("reference") << "class A{}; class B{}; class C : public B{};" "int main(){A tmp; A& a = tmp; C tmp2; C& c = tmp2; B& b =\n ;}" << CompletionPriorityItems{{1,0}, {{"a", 0, 21}, {"b", 9, 0}, {"c", 8, 0, QStringLiteral("Reference to derived class is not added to the Best Matches group")}}}; QTest::newRow("typedef") << "struct A{}; struct B{}; typedef A AA; typedef B BB; void f(A p);" "int main(){ BB b; AA a; f(\n }" << CompletionPriorityItems{{1,0}, {{"a", 9, 0}, {"b", 0, 21}}}; QTest::newRow("returnType") << "struct A{}; struct B{}; struct Test{A f();B g(); Test() { A a =\n }};" << CompletionPriorityItems{{1,0}, {{"f", 9, 0}, {"g", 0, 21}}}; QTest::newRow("template") << "template class Class{}; template class Class2{};" "int main(){ Class a; Class2 b =\n }" << CompletionPriorityItems{{1,0}, {{"b", 9, 0}, {"a", 0, 21}}}; QTest::newRow("protected-access") << "class Base { protected: int m_protected; };" "class Derived: public Base {public: void g(){\n }};" << CompletionPriorityItems{{1,0}, {{"m_protected", 0, 37}}}; QTest::newRow("protected-access2") << "class Base { protected: int m_protected; };" "class Derived: public Base {public: void f();};" "void Derived::f(){\n }" << CompletionPriorityItems{{1,0}, {{"m_protected", 0, 37}}}; } void TestCodeCompletion::testVariableScope() { TestFile file("int var; \nvoid test(int var) {int tmp =\n }", "cpp"); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); DUContextPointer topPtr(top); lock.unlock(); const auto context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), {2, 0}, QString()); context->setFilters(NoMacroOrBuiltin); lock.lock(); const auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(context)); QCOMPARE(tester.items.size(), 4); auto item = tester.findItem(QStringLiteral("var")); VERIFY(item); QCOMPARE(item->declaration()->range().start, CursorInRevision(1, 14)); } void TestCodeCompletion::testArgumentHintCompletion() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); executeCompletionTest(code, expectedItems); } void TestCodeCompletion::testArgumentHintCompletion_data() { #if CINDEX_VERSION_MINOR < 30 QSKIP("You need at least LibClang 3.7"); #endif QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::newRow("global function") << "void foo(int);\n" "int main() { \nfoo( " << CompletionItems{{2,4}, { "foo", "foo", "main" }}; QTest::newRow("member function") << "struct Struct{ void foo(int);}\n" "int main() {Struct s; \ns.foo( " << CompletionItems{{2,6}, { "Struct", "foo", "main", "s" }}; QTest::newRow("template function") << "template void foo(T);\n" "int main() { \nfoo( " << CompletionItems{{2,6}, { "foo", "foo", "main" }}; QTest::newRow("overloaded functions") << "void foo(int); void foo(int, double)\n" "int main() { \nfoo( " << CompletionItems{{2,6}, { "foo", "foo", "foo", "foo", "main" }}; QTest::newRow("overloaded functions2") << "void foo(int); void foo(int, double)\n" "int main() { foo(1,\n " << CompletionItems{{2,1}, { "foo", "foo", "foo", "main" }}; } void TestCodeCompletion::testArgumentHintCompletionDefaultParameters() { #if CINDEX_VERSION_MINOR < 30 QSKIP("You need at least LibClang 3.7"); #endif TestFile file("void f(int i, int j = 0, double k =1){\nf( ", "cpp"); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); const ParseSessionData::Ptr sessionData(dynamic_cast(top->ast().data())); QVERIFY(sessionData); DUContextPointer topPtr(top); lock.unlock(); const auto context = new ClangCodeCompletionContext(topPtr, sessionData, file.url().toUrl(), {1, 2}, QString()); context->setFilters(NoMacroOrBuiltin); lock.lock(); const auto tester = ClangCodeCompletionItemTester(QExplicitlySharedDataPointer(context)); QExplicitlySharedDataPointer f; for (const auto& item : tester.items) { if (item->argumentHintDepth() == 1) { f = item; break; } } QVERIFY(f.data()); const QString itemDisplay = tester.itemData(f).toString() + tester.itemData(f, KTextEditor:: CodeCompletionModel::Arguments).toString(); QCOMPARE(QStringLiteral("f(int i, int j, double k)"), itemDisplay); } void TestCodeCompletion::testCompleteFunction() { QFETCH(QString, code); QFETCH(CompletionItems, expectedItems); QFETCH(QString, itemToExecute); QFETCH(QString, expectedCode); auto executeItem = [=] (const ClangCodeCompletionItemTester& tester) { auto item = tester.findItem(itemToExecute); QVERIFY(item); auto view = createView(tester.completionContext->duContext()->url().toUrl(), this); item->execute(view.get(), view->document()->wordRangeAt(expectedItems.position)); QCOMPARE(view->document()->text(), expectedCode); }; executeCompletionTest(code, expectedItems, NoMacroOrBuiltin, executeItem); } void TestCodeCompletion::testCompleteFunction_data() { QTest::addColumn("code"); QTest::addColumn("expectedItems"); QTest::addColumn("itemToExecute"); QTest::addColumn("expectedCode"); QTest::newRow("add-parens") << "int foo();\nint main() {\n\n}" << CompletionItems({2, 0}, {"foo", "main"}) << "foo" << "int foo();\nint main() {\nfoo()\n}"; QTest::newRow("keep-parens") << "int foo();\nint main() {\nfoo();\n}" << CompletionItems({2, 0}, {"foo", "main"}) << "main" << "int foo();\nint main() {\nmain();\n}"; } void TestCodeCompletion::testIgnoreGccBuiltins() { // TODO: make it easier to change the compiler provider for testing purposes QTemporaryDir dir; auto project = new TestProject(Path(dir.path()), this); auto definesAndIncludesConfig = project->projectConfiguration()->group("CustomDefinesAndIncludes"); auto pathConfig = definesAndIncludesConfig.group("ProjectPath0"); pathConfig.writeEntry("Path", "."); pathConfig.group("Compiler").writeEntry("Name", "GCC"); m_projectController->addProject(project); { TestFile file("", "cpp", project, dir.path()); QVERIFY(file.parseAndWait(TopDUContext::AllDeclarationsContextsUsesAndAST)); executeCompletionTest(file.topContext(), {}); } } diff --git a/languages/clang/util/clangutils.h b/languages/clang/util/clangutils.h index 46e57515f7..225ea30d61 100644 --- a/languages/clang/util/clangutils.h +++ b/languages/clang/util/clangutils.h @@ -1,170 +1,170 @@ /* * Copyright 2014 Kevin Funk * * 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 . * */ #ifndef CLANGUTILS_H #define CLANGUTILS_H #include #include #include #include "clangprivateexport.h" namespace KDevelop { class IndexedString; } namespace ClangUtils { /** * Finds the most specific CXCursor which applies to the the specified line and column * in the given translation unit and file. * * @param line The 0-indexed line number at which to search. * @param column The 0-indexed column number at which to search. * @param unit The translation unit to examine. * @param file The file in the translation unit to examine. * * @return The cursor at the specified location */ CXCursor getCXCursor(int line, int column, const CXTranslationUnit& unit, const CXFile& file); enum DefaultArgumentsMode { FixedSize, ///< The vector will have length equal to the number of arguments to the function /// and any arguments without a default parameter will be represented with an empty string. MinimumSize ///< The vector will have a length equal to the number of default values }; /** * Given a cursor representing a function, returns a vector containing the string * representations of the default arguments of the function which are defined at - * the occurance of the cursor. Note that this is not necessarily all of the default + * the occurence of the cursor. Note that this is not necessarily all of the default * arguments of the function. * * @param cursor The cursor to examine * @return a vector of QStrings representing the default arguments, or an empty * vector if cursor does not represent a function */ QVector getDefaultArguments(CXCursor cursor, DefaultArgumentsMode mode = FixedSize); /** * @return true when the cursor kind references a named scope. */ bool isScopeKind(CXCursorKind kind); /** * Given a cursor and destination context, returns the string representing the * cursor's scope at its current location. * * @param cursor The cursor to examine * @param context The destination context from which the cursor should be referenced. * By default this will be set to the cursors lexical parent. * @return the cursor's scope as a string */ KDEVCLANGPRIVATE_EXPORT QString getScope(CXCursor cursor, CXCursor context = clang_getNullCursor()); /** * Given a cursor representing some sort of function, returns its signature. The * effect of this function when passed a non-function cursor is undefined. * * @param cursor The cursor to work with * @param scope The scope of the cursor (e.g. "SomeNS::SomeClass") * @return A QString of the function's signature */ QString getCursorSignature(CXCursor cursor, const QString& scope, const QVector& defaultArgs = QVector()); /** * Given a cursor representing the template argument list, return a * list of the argument types. * * @param cursor The cursor to work with * @return A QStringList of the template's arguments */ KDEVCLANGPRIVATE_EXPORT QStringList templateArgumentTypes(CXCursor cursor); /** * Extract the raw contents of the range @p range * * @note This will return the exact textual representation of the code, * no whitespace stripped, etc. * * TODO: It would better if we'd be able to just memcpy parts of the file buffer * that's stored inside Clang (cf. llvm::MemoryBuffer for files), but libclang * doesn't offer API for that. This implementation here is a lot more expensive. * * @param unit Translation unit this range is part of */ KDEVCLANGPRIVATE_EXPORT QByteArray getRawContents(CXTranslationUnit unit, CXSourceRange range); /** * @brief Return true if file @p file1 and file @p file2 are equal * * @see clang_File_isEqual */ inline bool isFileEqual(CXFile file1, CXFile file2) { #if CINDEX_VERSION_MINOR >= 28 return clang_File_isEqual(file1, file2); #else // note: according to the implementation of clang_File_isEqual, file1 and file2 can still be equal, // regardless of whether file1 == file2 is true or not - // however, we didn't see any problems with pure pointer comparisions until now, so fall back to that + // however, we didn't see any problems with pure pointer comparisons until now, so fall back to that return file1 == file2; #endif } /** * @brief Return true if the cursor @p cursor refers to an explicitly deleted/defaulted function * such as the default constructor in "struct Foo { Foo() = delete; }" * * TODO: do we need isExplicitlyDefaulted() + isExplicitlyDeleted()? * Currently this is only used by the implements completion to hide deleted+defaulted functions so * we don't need to know the difference. We need to tokenize the source code because there is no * such API in libclang so having one function to check both cases is more efficient (only tokenize once) */ bool isExplicitlyDefaultedOrDeleted(CXCursor cursor); /** * Extract the range of the path-spec inside the include-directive in line @p line * * Example: line = "#include " => returns {0, 10, 0, 16} * * @param originalRange This is the range that the resulting range will be based on * * @return Range pointing to the path-spec of the include or invalid range if there is no #include directive on the line. */ KDEVCLANGPRIVATE_EXPORT KTextEditor::Range rangeForIncludePathSpec(const QString& line, const KTextEditor::Range& originalRange = KTextEditor::Range()); enum SpecialQtAttributes { NoQtAttribute, QtSignalAttribute, QtSlotAttribute }; /** * Given a cursor representing a CXXmethod */ SpecialQtAttributes specialQtAttributes(CXCursor cursor); }; #endif // CLANGUTILS_H diff --git a/languages/plugins/custom-definesandincludes/CMakeLists.txt b/languages/plugins/custom-definesandincludes/CMakeLists.txt index e9c79f3210..8ef46d8202 100644 --- a/languages/plugins/custom-definesandincludes/CMakeLists.txt +++ b/languages/plugins/custom-definesandincludes/CMakeLists.txt @@ -1,63 +1,63 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevcustomdefinesandincludes\") add_subdirectory(tests) add_subdirectory(compilerprovider) add_subdirectory(noprojectincludesanddefines) set( kdevdefinesandincludesmanager_SRCS definesandincludesmanager.cpp debugarea.cpp kcm_widget/projectpathsmodel.cpp kcm_widget/definesmodel.cpp kcm_widget/includesmodel.cpp kcm_widget/includeswidget.cpp kcm_widget/defineswidget.cpp kcm_widget/projectpathswidget.cpp kcm_widget/definesandincludesconfigpage.cpp kcm_widget/parserwidget.cpp - compilerprovider/icompiler.cpp # TODO: is this really neccessary + compilerprovider/icompiler.cpp # TODO: is this really necessary ) ki18n_wrap_ui(kdevdefinesandincludesmanager_SRCS kcm_widget/batchedit.ui kcm_widget/includeswidget.ui kcm_widget/defineswidget.ui kcm_widget/projectpathswidget.ui kcm_widget/parserwidget.ui ) kconfig_add_kcfg_files( kdevdefinesandincludesmanager_SRCS kcm_widget/customdefinesandincludes.kcfgc) kdevplatform_add_plugin(kdevdefinesandincludesmanager JSON kdevdefinesandincludesmanager.json SOURCES ${kdevdefinesandincludesmanager_SRCS}) target_link_libraries( kdevdefinesandincludesmanager LINK_PRIVATE KDev::Project KDev::Util KDev::Language kdevnoprojectincludesanddefines kdevcompilerprovider) option(BUILD_kdev_includepathsconverter "Build utility to modify include paths of a project from command line." ON) if(BUILD_kdev_includepathsconverter) add_executable(kdev_includepathsconverter includepathsconverter.cpp) ecm_mark_nongui_executable(kdev_includepathsconverter) target_link_libraries(kdev_includepathsconverter LINK_PRIVATE KDev::Project kdevcompilerprovider ) install(TARGETS kdev_includepathsconverter ${INSTALL_TARGETS_DEFAULT_ARGS} ) endif() install(FILES idefinesandincludesmanager.h DESTINATION ${INCLUDE_INSTALL_DIR}/kdevelop/custom-definesandincludes COMPONENT Devel ) add_library(kdevdefinesandincludesmanager_interface INTERFACE) add_library(KDev::DefinesAndIncludesManager ALIAS kdevdefinesandincludesmanager_interface) target_include_directories(kdevdefinesandincludesmanager_interface INTERFACE "$" "$" ) set_target_properties(kdevdefinesandincludesmanager_interface PROPERTIES EXPORT_NAME DefinesAndIncludesManager ) install(TARGETS kdevdefinesandincludesmanager_interface EXPORT KDevelopTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/languages/plugins/custom-definesandincludes/compilerprovider/compilerprovider.h b/languages/plugins/custom-definesandincludes/compilerprovider/compilerprovider.h index a76b59211c..ee8f5dbf84 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/compilerprovider.h +++ b/languages/plugins/custom-definesandincludes/compilerprovider/compilerprovider.h @@ -1,75 +1,75 @@ /* * 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 . * */ #ifndef COMPILERSPROVIDER_H #define COMPILERSPROVIDER_H #include "icompilerfactory.h" #include class SettingsManager; class CompilerProvider : public QObject, public KDevelop::IDefinesAndIncludesManager::Provider { Q_OBJECT public: explicit CompilerProvider( SettingsManager* settings, QObject* parent = nullptr ); ~CompilerProvider() override; KDevelop::Defines defines( KDevelop::ProjectBaseItem* item ) const override; KDevelop::Path::List includes( KDevelop::ProjectBaseItem* item ) const override; KDevelop::Path::List frameworkDirectories( KDevelop::ProjectBaseItem* item ) const override; KDevelop::IDefinesAndIncludesManager::Type type() const override; /// @return current compiler for the @p item CompilerPointer compilerForItem( KDevelop::ProjectBaseItem* item ) const; /// @return list of all available compilers QVector compilers() const; /** * Adds compiler to the list of available compilers * @return true on success (if there is no compiler with the same name registered). */ bool registerCompiler(const CompilerPointer& compiler); /// Removes compiler from the list of available compilers void unregisterCompiler( const CompilerPointer& compiler ); /// @return All available factories QVector compilerFactories() const; - /// Checks wheter the @p compiler exist, if so returns it. Otherwise returns default compiler + /// Checks whether the @p compiler exist, if so returns it. Otherwise returns default compiler CompilerPointer checkCompilerExists( const CompilerPointer& compiler ) const; private Q_SLOTS: void retrieveUserDefinedCompilers(); private: QVector m_compilers; QVector m_factories; SettingsManager* m_settings; }; #endif // COMPILERSPROVIDER_H diff --git a/languages/plugins/custom-definesandincludes/compilerprovider/msvcdefinehelper.cpp b/languages/plugins/custom-definesandincludes/compilerprovider/msvcdefinehelper.cpp index 5f4d55408b..0017fd1be9 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/msvcdefinehelper.cpp +++ b/languages/plugins/custom-definesandincludes/compilerprovider/msvcdefinehelper.cpp @@ -1,64 +1,64 @@ /* * This file is part of KDevelop * * Copyright 2010 Patrick Spendrin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * This helper program is needed for the Visual Studio compiler * to find out the standard macros, there is no "gcc -dM -E -". * A trick is used to find out the standard macros. You can * exchange the compiler. The standard macros are written in -* a enviroment variable. This helper programm analysis this +* a environment variable. This helper program analysis this * variable and prints the output to stdout. */ #include #include #include int main(int argc, char**argv) { char *next, *token, *tmp, *equal; int i = 0, nextlen, currentlen; next = getenv("MSC_CMD_FLAGS"); while (next != NULL) { token = next; next = strstr(next + 1, " -"); if(strncmp(token, " -D", 3) == 0) { if(next != NULL) nextlen = strlen(next); else nextlen = 0; currentlen = strlen(token); tmp = new char[(currentlen - nextlen + 1)]; strncpy(tmp, token, currentlen - nextlen); tmp[currentlen - nextlen] = '\0'; equal = strchr(tmp, '='); if(equal != NULL) *equal = ' '; printf("#define %s\n", tmp + 3); delete [] tmp; } } // return an error so that the compiler doesn't check for the output return 1; } diff --git a/languages/qmljs/codecompletion/items/completionitem.h b/languages/qmljs/codecompletion/items/completionitem.h index 1b0d10f47a..8ab40c95c1 100644 --- a/languages/qmljs/codecompletion/items/completionitem.h +++ b/languages/qmljs/codecompletion/items/completionitem.h @@ -1,60 +1,60 @@ /* * 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 . * */ #ifndef __COMPLETIONITEM_H__ #define __COMPLETIONITEM_H__ #include namespace QmlJS { class CompletionItem : public KDevelop::NormalDeclarationCompletionItem { public: /** * @brief Decoration to put around an item when it is inserted into the code editor */ enum Decoration { NoDecoration, /*!< @brief No decoration at all */ Quotes, /*!< @brief Wrap the item in quotes: item becomes "item" */ QuotesAndBracket, /*!< @brief Wrap the item as in array subscripts: item becomes "item"] */ ColonOrBracket, /*!< @brief Append a colon or a bracket after the item: item becomes "item:" or "item {|}" if item has a structure type */ Brackets, /*!< @brief Append brackets after the item and put the cursor in-between them: item becomes item(|) */ }; CompletionItem(KDevelop::DeclarationPointer decl, int inheritanceDepth, Decoration decoration); virtual QVariant data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const override; virtual QString declarationName() const override; virtual KTextEditor::CodeCompletionModel::CompletionProperties completionProperties() const override; protected: virtual void execute(KTextEditor::View* view, const KTextEditor::Range& word) override; private: Decoration m_decoration; }; } -#endif \ No newline at end of file +#endif diff --git a/languages/qmljs/codecompletion/items/functioncalltipcompletionitem.h b/languages/qmljs/codecompletion/items/functioncalltipcompletionitem.h index d1b7e041e7..d67d9403c7 100644 --- a/languages/qmljs/codecompletion/items/functioncalltipcompletionitem.h +++ b/languages/qmljs/codecompletion/items/functioncalltipcompletionitem.h @@ -1,65 +1,65 @@ /* * 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 . * */ #ifndef __FUNCTIONCALLTIPCOMPLETIONITEM_H__ #define __FUNCTIONCALLTIPCOMPLETIONITEM_H__ #include #include namespace QmlJS { /** * @brief Call-tip for functions * * Display the signature of a function and highlight its argument currently being * edited. */ class FunctionCalltipCompletionItem : public KDevelop::CompletionTreeItem { public: /** @note The DU-Chain lock must be held when calling this constructor */ FunctionCalltipCompletionItem(const KDevelop::DeclarationPointer& decl, int depth, int argumentIndex); KDevelop::AbstractType::Ptr currentArgumentType() const; virtual QVariant data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const override; virtual KDevelop::DeclarationPointer declaration() const override; virtual int argumentHintDepth() const override; virtual int inheritanceDepth() const override; virtual KTextEditor::CodeCompletionModel::CompletionProperties completionProperties() const override; private: KDevelop::DeclarationPointer m_declaration; KDevelop::AbstractType::Ptr m_currentArgumentType; QString m_prefix; QString m_arguments; int m_depth; int m_currentArgumentStart; int m_currentArgumentLength; }; } -#endif \ No newline at end of file +#endif diff --git a/languages/qmljs/duchain/functiontype.cpp b/languages/qmljs/duchain/functiontype.cpp index f09298d9e9..9d899dc91a 100644 --- a/languages/qmljs/duchain/functiontype.cpp +++ b/languages/qmljs/duchain/functiontype.cpp @@ -1,63 +1,63 @@ /* * 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 "functiontype.h" #include #include namespace QmlJS { FunctionType::FunctionType() : FunctionTypeBase(createData()) { } FunctionType::FunctionType(const FunctionType& rhs) : FunctionTypeBase(copyData(*static_cast(rhs.d_func()))) { } FunctionType::FunctionType(Data& data) : FunctionTypeBase(data) { } FunctionType::~FunctionType() { } KDevelop::AbstractType* FunctionType::clone() const { return new FunctionType(*this); } uint FunctionType::hash() const { return KDevHash(KDevelop::FunctionType::hash()) << KDevelop::IdentifiedType::hash(); } QString FunctionType::toString() const { return KDevelop::FunctionType::toString(); } -} \ No newline at end of file +} diff --git a/languages/qmljs/duchain/functiontype.h b/languages/qmljs/duchain/functiontype.h index 50be38517d..a704066ded 100644 --- a/languages/qmljs/duchain/functiontype.h +++ b/languages/qmljs/duchain/functiontype.h @@ -1,63 +1,63 @@ /* * 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 . * */ #ifndef __FUNCTIONTYPE_H__ #define __FUNCTIONTYPE_H__ #include "duchainexport.h" #include #include #include #include namespace QmlJS { typedef KDevelop::MergeIdentifiedType FunctionTypeBase; /** * Function type bound to a function declaration, so that the name of its parameters * can be displayed when needed */ class KDEVQMLJSDUCHAIN_EXPORT FunctionType : public FunctionTypeBase { public: typedef KDevelop::TypePtr Ptr; FunctionType(); FunctionType(const FunctionType& rhs); FunctionType(Data& data); virtual ~FunctionType(); virtual KDevelop::AbstractType* clone() const override; virtual QString toString() const override; virtual uint hash() const override; enum { Identity = 30 }; typedef FunctionTypeBase::Data Data; }; } -#endif \ No newline at end of file +#endif diff --git a/languages/qmljs/duchain/navigation/declarationnavigationcontext.h b/languages/qmljs/duchain/navigation/declarationnavigationcontext.h index c043354043..13f58bd8e6 100644 --- a/languages/qmljs/duchain/navigation/declarationnavigationcontext.h +++ b/languages/qmljs/duchain/navigation/declarationnavigationcontext.h @@ -1,44 +1,44 @@ /* * 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 . * */ #ifndef __DECLARATIONNAVIGATIONCONTEXT_H__ #define __DECLARATIONNAVIGATIONCONTEXT_H__ #include #include namespace QmlJS { class DeclarationNavigationContext : public KDevelop::AbstractDeclarationNavigationContext { public: DeclarationNavigationContext(KDevelop::DeclarationPointer decl, KDevelop::TopDUContextPointer topContext, KDevelop::AbstractNavigationContext* previousContext = nullptr); protected: virtual void htmlIdentifiedType(KDevelop::AbstractType::Ptr type, const KDevelop::IdentifiedType* idType) override; virtual void eventuallyMakeTypeLinks(KDevelop::AbstractType::Ptr type) override; }; } -#endif \ No newline at end of file +#endif diff --git a/languages/qmljs/tests/files/custom_component/CustomComponent.qml b/languages/qmljs/tests/files/custom_component/CustomComponent.qml index 662195692d..24f4e658d7 100644 --- a/languages/qmljs/tests/files/custom_component/CustomComponent.qml +++ b/languages/qmljs/tests/files/custom_component/CustomComponent.qml @@ -1,9 +1,9 @@ import QtQuick 2.0 /** * "type" : { "toString" : "CustomComponent" }, * "useCount" : 1 */ Text { id: toplevel_component -} \ No newline at end of file +} diff --git a/languages/qmljs/tests/files/custom_component/CustomComponentUser.qml b/languages/qmljs/tests/files/custom_component/CustomComponentUser.qml index 8175a1c86f..c874a2795f 100644 --- a/languages/qmljs/tests/files/custom_component/CustomComponentUser.qml +++ b/languages/qmljs/tests/files/custom_component/CustomComponentUser.qml @@ -1,3 +1,3 @@ CustomComponent { id: instance -} \ No newline at end of file +} diff --git a/languages/qmljs/tests/files/directory_to_import/MyComponent.qml b/languages/qmljs/tests/files/directory_to_import/MyComponent.qml index 8634d1cacd..acf1f5a3e5 100644 --- a/languages/qmljs/tests/files/directory_to_import/MyComponent.qml +++ b/languages/qmljs/tests/files/directory_to_import/MyComponent.qml @@ -1,5 +1,5 @@ import QtQuick 2.0 Item { id: root -} \ No newline at end of file +} diff --git a/projectmanagers/cmake/cmakedocumentation.cpp b/projectmanagers/cmake/cmakedocumentation.cpp index ea7716ab5c..57c82e1d81 100644 --- a/projectmanagers/cmake/cmakedocumentation.cpp +++ b/projectmanagers/cmake/cmakedocumentation.cpp @@ -1,208 +1,207 @@ /* KDevelop CMake Support * * Copyright 2009 Aleix Pol * * 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 "cmakedocumentation.h" #include "cmakeutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cmakemanager.h" #include "cmakeparserutils.h" -#include "cmakeutils.h" #include "cmakehelpdocumentation.h" #include "cmakedoc.h" #include "debug.h" K_PLUGIN_FACTORY_WITH_JSON(CMakeSupportDocFactory, "kdevcmakedocumentation.json", registerPlugin(); ) CMakeDocumentation* CMakeDoc::s_provider=nullptr; KDevelop::IDocumentationProvider* CMakeDoc::provider() const { return s_provider; } CMakeDocumentation::CMakeDocumentation(QObject* parent, const QVariantList&) : KDevelop::IPlugin( "kdevcmakedocumentation", parent ) , mCMakeCmd(CMake::findExecutable()) , m_index(nullptr) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IDocumentationProvider ) KDEV_USE_EXTENSION_INTERFACE( ICMakeDocumentation ) if (mCMakeCmd.isEmpty()) { setErrorDescription(i18n("Unable to find cmake executable. Is it installed on the system?") ); return; } CMakeDoc::s_provider=this; m_index= new QStringListModel(this); initializeModel(); } static const char* args[] = { "--help-command", "--help-variable", "--help-module", "--help-property", nullptr, nullptr }; void CMakeDocumentation::delayedInitialization() { for(int i=0; i<=Property; i++) { collectIds(QString(args[i])+"-list", (Type) i); } m_index->setStringList(m_typeForName.keys()); } void CMakeDocumentation::collectIds(const QString& param, Type type) { QStringList ids=CMake::executeProcess(mCMakeCmd, QStringList(param)).split('\n'); ids.takeFirst(); foreach(const QString& name, ids) { m_typeForName[name]=type; } } QStringList CMakeDocumentation::names(CMakeDocumentation::Type t) const { return m_typeForName.keys(t); } QString CMakeDocumentation::descriptionForIdentifier(const QString& id, Type t) const { QString desc; if(args[t]) { desc = CMake::executeProcess(mCMakeCmd, { args[t], id.simplified() }); desc = desc.remove(":ref:"); const QString rst2html = QStandardPaths::findExecutable(QStringLiteral("rst2html")); if (rst2html.isEmpty()) { desc = ("
" + desc.toHtmlEscaped() + "
" + i18n("

For better cmake documentation rendering, install rst2html

") + ""); } else { QProcess p; p.start(rst2html, { "--no-toc-backlinks" }); p.write(desc.toUtf8()); p.closeWriteChannel(); p.waitForFinished(); desc = QString::fromUtf8(p.readAllStandardOutput()); } } return desc; } KDevelop::IDocumentation::Ptr CMakeDocumentation::description(const QString& identifier, const QUrl &file) const { initializeModel(); //make it not queued if (!file.isEmpty() && !QMimeDatabase().mimeTypeForUrl(file).inherits("text/x-cmake")) { return KDevelop::IDocumentation::Ptr(); } QString desc; if(m_typeForName.contains(identifier)) { desc=descriptionForIdentifier(identifier, m_typeForName[identifier]); } else if(m_typeForName.contains(identifier.toLower())) { desc=descriptionForIdentifier(identifier, m_typeForName[identifier.toLower()]); } else if(m_typeForName.contains(identifier.toUpper())) { desc=descriptionForIdentifier(identifier, m_typeForName[identifier.toUpper()]); } KDevelop::IProject* p=KDevelop::ICore::self()->projectController()->findProjectForUrl(file); ICMakeManager* m=nullptr; if(p) m=p->managerPlugin()->extension(); if(m) { QPair entry = m->cacheValue(p, identifier); if(!entry.first.isEmpty()) desc += i18n("
Cache Value: %1\n", entry.first); if(!entry.second.isEmpty()) desc += i18n("
Cache Documentation: %1\n", entry.second); } if(desc.isEmpty()) return KDevelop::IDocumentation::Ptr(); else return KDevelop::IDocumentation::Ptr(new CMakeDoc(identifier, desc)); } KDevelop::IDocumentation::Ptr CMakeDocumentation::documentationForDeclaration(KDevelop::Declaration* decl) const { return description(decl->identifier().toString(), decl->url().toUrl()); } KDevelop::IDocumentation::Ptr CMakeDocumentation::documentationForIndex(const QModelIndex& idx) const { return description(idx.data().toString(), QUrl()); } QAbstractListModel* CMakeDocumentation::indexModel() const { initializeModel(); return m_index; } QIcon CMakeDocumentation::icon() const { return QIcon::fromTheme("cmake"); } QString CMakeDocumentation::name() const { return "CMake"; } KDevelop::IDocumentation::Ptr CMakeDocumentation::homePage() const { if(m_typeForName.isEmpty()) const_cast(this)->delayedInitialization(); // initializeModel(); return KDevelop::IDocumentation::Ptr(new CMakeHomeDocumentation); } void CMakeDocumentation::initializeModel() const { if(!m_typeForName.isEmpty()) return; QMetaObject::invokeMethod(const_cast(this), "delayedInitialization", Qt::QueuedConnection); } //////////CMakeDoc QWidget* CMakeDoc::documentationWidget(KDevelop::DocumentationFindWidget* findWidget, QWidget* parent) { KDevelop::StandardDocumentationView* view = new KDevelop::StandardDocumentationView(findWidget, parent); view->setHtml(mDesc); return view; } #include "cmakedocumentation.moc" diff --git a/projectmanagers/cmake/cmakeutils.h b/projectmanagers/cmake/cmakeutils.h index 2fdf4566a5..a539cd5891 100644 --- a/projectmanagers/cmake/cmakeutils.h +++ b/projectmanagers/cmake/cmakeutils.h @@ -1,225 +1,225 @@ /* KDevelop CMake Support * * Copyright 2009 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. */ #ifndef CMAKEUTILS_H #define CMAKEUTILS_H #include "cmakecommonexport.h" #include #include #include namespace KDevelop { class ProjectBaseItem; class IProject; } class ICMakeDocumentation; class CMakeCacheModel; namespace CMake { /** - * Checks wether there's a need to run cmake for the given project item + * Checks whether there's a need to run cmake for the given project item * This is the case if no builddir has been specified, in which case * it asks for one. * * @returns true if configure should be run, false otherwise */ KDEVCMAKECOMMON_EXPORT bool checkForNeedingConfigure( KDevelop::IProject* project ); /** * @returns the current builddir for the given project or an empty url if none * has been set by the user. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path currentBuildDir( KDevelop::IProject* project ); /** * @returns the path to the 'compile_commands.json' file in the current builddir for the given project * or an empty url if none has been set by the user. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path commandsFile( KDevelop::IProject* project ); /** * @returns the path to the 'CMakeFiles/TargetDirectories.txt' file in the current builddir for the given project * or an empty url if none has been set by the user. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path targetDirectoriesFile( KDevelop::IProject* project ); /** * @returns the current build type for the given project or "Release" as default value. */ KDEVCMAKECOMMON_EXPORT QString currentBuildType( KDevelop::IProject* project ); /** * @returns the cmake executable, taking into account standard * installation dirs on Windows, or empty string in case of failure. */ KDEVCMAKECOMMON_EXPORT QString findExecutable(); /** * @returns the current cmake binary for the given project or * QStandardPaths::findExecutable("cmake") as default value. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path currentCMakeBinary( KDevelop::IProject* project ); /** * @returns the current install dir for the given project or "/usr/local" as default value. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path currentInstallDir( KDevelop::IProject* project ); /** * @returns the current extra arguments for the given project or "" as default value. */ KDEVCMAKECOMMON_EXPORT QString currentExtraArguments( KDevelop::IProject* project ); /** * @returns the current build dir for the given project. */ KDEVCMAKECOMMON_EXPORT QString projectRootRelative( KDevelop::IProject* project ); /** * @returns whether there's projectRootRelative defined */ KDEVCMAKECOMMON_EXPORT bool hasProjectRootRelative( KDevelop::IProject* project ); /** * Extracts target names from builddir/CMakeFiles/TargetDirectories.txt and maps corresponding source locations to them. */ KDEVCMAKECOMMON_EXPORT QHash enumerateTargets(const KDevelop::Path& targetsFilePath, const QString& sourceDir, const KDevelop::Path &buildDir); /** * Convenience function to get the project root. */ KDEVCMAKECOMMON_EXPORT KDevelop::Path projectRoot( KDevelop::IProject* project ); /** * @returns the environment configuration for a @p project */ KDEVCMAKECOMMON_EXPORT QString currentEnvironment( KDevelop::IProject* project ); /** * Sets the current install dir for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentInstallDir( KDevelop::IProject* project, const KDevelop::Path &path ); /** * Sets the current build type for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentBuildType( KDevelop::IProject* project, const QString& type ); /** * Sets the current cmake binary for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentCMakeBinary( KDevelop::IProject* project, const KDevelop::Path &path ); /** * Sets the current build dir for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentBuildDir( KDevelop::IProject* project, const KDevelop::Path& path ); /** * Sets the current build dir for the given project. */ KDEVCMAKECOMMON_EXPORT void setProjectRootRelative( KDevelop::IProject* project, const QString& path); /** * Sets the current extra arguments for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentExtraArguments( KDevelop::IProject* project, const QString& args ); /** * Obtains a cmake documentation instance if it exists */ KDEVCMAKECOMMON_EXPORT ICMakeDocumentation* cmakeDocumentation(); /** * Retrieves the configured build directories for @p project. */ KDEVCMAKECOMMON_EXPORT QStringList allBuildDirs(KDevelop::IProject* project); /** * Attempts to migrate the CMake configuration to per-builddir format. * Silently returns if the migration has already been performed. */ KDEVCMAKECOMMON_EXPORT void attemptMigrate( KDevelop::IProject* project ); /** * Attempts to update CMake configuration keys from the cache data. * * @param model The CMake cache model to load data from. If NULL, the model is created based on build directory path for the given index. */ KDEVCMAKECOMMON_EXPORT void updateConfig( KDevelop::IProject* project, int buildDirectory); /** * Returns the current build directory count. */ KDEVCMAKECOMMON_EXPORT int buildDirCount( KDevelop::IProject* project ); /** * Sets the build directory count (equivalent to adding a new build directory). */ KDEVCMAKECOMMON_EXPORT void setBuildDirCount( KDevelop::IProject* project, int count ); /** * @returns the current builddir index for the given project or -1 if none * has been set by the user. */ KDEVCMAKECOMMON_EXPORT int currentBuildDirIndex( KDevelop::IProject *project ); /** * Sets the current build dir index for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentBuildDirIndex( KDevelop::IProject* project, int buildDirIndex ); /** * A hack to avoid adding an optional "build directory index" parameter to all functions here. * This function sets an alternate build directory index key that overrides regular build directory index. */ KDEVCMAKECOMMON_EXPORT void setOverrideBuildDirIndex( KDevelop::IProject* project, int overrideBuildDirIndex ); /** * This removes build directory override key (\ref setOverrideBuildDirIndex). * Silently returns if there is no override. * * @param writeToMainIndex Whether the overridden index should be saved to regular */ KDEVCMAKECOMMON_EXPORT void removeOverrideBuildDirIndex( KDevelop::IProject* project, bool writeToMainIndex = false ); /** * Sets the environment configuration for the given project. */ KDEVCMAKECOMMON_EXPORT void setCurrentEnvironment( KDevelop::IProject* project, const QString& environment ); /** * Removes current build directory (overridden or not) from the project configuration. * Override is then cleared and index set to -1. */ KDEVCMAKECOMMON_EXPORT void removeBuildDirConfig( KDevelop::IProject* project ); KDEVCMAKECOMMON_EXPORT KDevelop::Path::List resolveSystemDirs(KDevelop::IProject* project, const QStringList& dirs); /** Runs the process specified by @p execName with @p args */ KDEVCMAKECOMMON_EXPORT QString executeProcess(const QString& execName, const QStringList& args=QStringList()); } #endif diff --git a/projectmanagers/cmake/settings/cmakepreferences.cpp b/projectmanagers/cmake/settings/cmakepreferences.cpp index fb3e2487cf..29e456bc3d 100644 --- a/projectmanagers/cmake/settings/cmakepreferences.cpp +++ b/projectmanagers/cmake/settings/cmakepreferences.cpp @@ -1,368 +1,367 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007-2008 Aleix Pol * * 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 "cmakepreferences.h" #include #include #include #include #include #include #include #include "ui_cmakebuildsettings.h" #include "cmakecachedelegate.h" #include "cmakebuilddirchooser.h" #include "../debug.h" #include #include #include #include #include #include #include #include -#include #include using namespace KDevelop; CMakePreferences::CMakePreferences(IPlugin* plugin, const ProjectConfigOptions& options, QWidget* parent) : ConfigPage(plugin, nullptr, parent), m_project(options.project), m_currentModel(nullptr) { QVBoxLayout* l = new QVBoxLayout( this ); QWidget* w = new QWidget; m_prefsUi = new Ui::CMakeBuildSettings; m_prefsUi->setupUi( w ); l->addWidget( w ); m_prefsUi->addBuildDir->setIcon(QIcon::fromTheme( "list-add" )); m_prefsUi->removeBuildDir->setIcon(QIcon::fromTheme( "list-remove" )); m_prefsUi->addBuildDir->setText(QString()); m_prefsUi->removeBuildDir->setText(QString()); m_prefsUi->cacheList->setItemDelegate(new CMakeCacheDelegate(m_prefsUi->cacheList)); m_prefsUi->cacheList->setSelectionMode(QAbstractItemView::SingleSelection); m_prefsUi->cacheList->horizontalHeader()->setStretchLastSection(true); m_prefsUi->cacheList->verticalHeader()->hide(); connect(m_prefsUi->buildDirs, static_cast(&KComboBox::currentIndexChanged), this, &CMakePreferences::buildDirChanged); connect(m_prefsUi->showInternal, &QCheckBox::stateChanged, this, &CMakePreferences::showInternal); connect(m_prefsUi->addBuildDir, &QPushButton::pressed, this, &CMakePreferences::createBuildDir); connect(m_prefsUi->removeBuildDir, &QPushButton::pressed, this, &CMakePreferences::removeBuildDir); connect(m_prefsUi->showAdvanced, &QPushButton::toggled, this, &CMakePreferences::showAdvanced); connect(m_prefsUi->environment, &EnvironmentSelectionWidget::currentProfileChanged, this, &CMakePreferences::changed); connect(m_prefsUi->configureEnvironment, &EnvironmentConfigureButton::environmentConfigured, this, &CMakePreferences::changed); showInternal(m_prefsUi->showInternal->checkState()); m_subprojFolder = Path(options.projectTempFile).parent(); qCDebug(CMAKE) << "Source folder: " << m_srcFolder << options.projectTempFile; // foreach(const QVariant &v, args) // { // qCDebug(CMAKE) << "arg: " << v.toString(); // } m_prefsUi->configureEnvironment->setSelectionWidget(m_prefsUi->environment); m_prefsUi->showAdvanced->setChecked(false); showAdvanced(false); reset(); // load the initial values } CMakePreferences::~CMakePreferences() { CMake::removeOverrideBuildDirIndex(m_project); delete m_prefsUi; } void CMakePreferences::reset() { qCDebug(CMAKE) << "********loading"; m_prefsUi->buildDirs->clear(); m_prefsUi->buildDirs->addItems( CMake::allBuildDirs(m_project) ); CMake::removeOverrideBuildDirIndex(m_project); // addItems() triggers buildDirChanged(), compensate for it m_prefsUi->buildDirs->setCurrentIndex( CMake::currentBuildDirIndex(m_project) ); m_prefsUi->environment->setCurrentProfile( CMake::currentEnvironment(m_project) ); m_srcFolder = m_project->path(); m_prefsUi->removeBuildDir->setEnabled(m_prefsUi->buildDirs->count()!=0); // QString cmDir=group.readEntry("CMakeDirectory"); // m_prefsUi->kcfg_cmakeDir->setUrl(QUrl(cmDir)); // qCDebug(CMAKE) << "cmakedir" << cmDir; } void CMakePreferences::apply() { qCDebug(CMAKE) << "*******saving"; // the build directory list is incrementally maintained through createBuildDir() and removeBuildDir(). // We won't rewrite it here based on the data from m_prefsUi->buildDirs. CMake::removeOverrideBuildDirIndex( m_project, true ); // save current selection int savedBuildDir = CMake::currentBuildDirIndex(m_project); if (savedBuildDir < 0) { // no build directory exists: skip any writing to config file as well as configuring return; } CMake::setCurrentEnvironment( m_project, m_prefsUi->environment->currentProfile() ); qCDebug(CMAKE) << "writing to cmake config: using builddir " << CMake::currentBuildDirIndex(m_project); qCDebug(CMAKE) << "writing to cmake config: builddir path " << CMake::currentBuildDir(m_project); qCDebug(CMAKE) << "writing to cmake config: installdir " << CMake::currentInstallDir(m_project); qCDebug(CMAKE) << "writing to cmake config: build type " << CMake::currentBuildType(m_project); qCDebug(CMAKE) << "writing to cmake config: cmake binary " << CMake::currentCMakeBinary(m_project); qCDebug(CMAKE) << "writing to cmake config: environment " << CMake::currentEnvironment(m_project); //We run cmake on the builddir to generate it configure(); } void CMakePreferences::defaults() { // do nothing } void CMakePreferences::configureCacheView() { // Sets up the cache view after model re-creation/reset. // Emits changed(false) because model re-creation probably means // mass programmatical invocation of itemChanged(), which invokes changed(true) - which is not what we want. m_prefsUi->cacheList->setModel(m_currentModel); m_prefsUi->cacheList->hideColumn(1); m_prefsUi->cacheList->hideColumn(3); m_prefsUi->cacheList->hideColumn(4); m_prefsUi->cacheList->horizontalHeader()->resizeSection(0, 200); if( m_currentModel ) { m_prefsUi->cacheList->setEnabled( true ); foreach(const QModelIndex & idx, m_currentModel->persistentIndices()) { m_prefsUi->cacheList->openPersistentEditor(idx); } } else { m_prefsUi->cacheList->setEnabled( false ); } showInternal(m_prefsUi->showInternal->checkState()); } void CMakePreferences::updateCache(const Path &newBuildDir) { const Path file = newBuildDir.isValid() ? Path(newBuildDir, "CMakeCache.txt") : Path(); if(QFile::exists(file.toLocalFile())) { if (m_currentModel) { m_currentModel->deleteLater(); } m_currentModel = new CMakeCacheModel(this, file); configureCacheView(); connect(m_currentModel, &CMakeCacheModel::itemChanged, this, &CMakePreferences::cacheEdited); connect(m_currentModel, &CMakeCacheModel::modelReset, this, &CMakePreferences::configureCacheView); connect(m_prefsUi->cacheList->selectionModel(), &QItemSelectionModel::currentChanged, this, &CMakePreferences::listSelectionChanged); } else { disconnect(m_prefsUi->cacheList->selectionModel(), &QItemSelectionModel::currentChanged, this, nullptr); if (m_currentModel) { m_currentModel->deleteLater(); m_currentModel = nullptr; } configureCacheView(); } if( !m_currentModel ) emit changed(); } void CMakePreferences::listSelectionChanged(const QModelIndex & index, const QModelIndex& ) { qCDebug(CMAKE) << "item " << index << " selected"; QModelIndex idx = index.sibling(index.row(), 3); QModelIndex idxType = index.sibling(index.row(), 1); QString comment=QString("%1. %2") .arg(m_currentModel->itemFromIndex(idxType)->text()) .arg(m_currentModel->itemFromIndex(idx)->text()); m_prefsUi->commentText->setText(comment); } void CMakePreferences::showInternal(int state) { if(!m_currentModel) return; bool showAdv=(state == Qt::Checked); for(int i=0; irowCount(); i++) { bool hidden=m_currentModel->isInternal(i) || (!showAdv && m_currentModel->isAdvanced(i)); m_prefsUi->cacheList->setRowHidden(i, hidden); } } void CMakePreferences::buildDirChanged(int index) { CMake::setOverrideBuildDirIndex( m_project, index ); const Path buildDir = CMake::currentBuildDir(m_project); m_prefsUi->environment->setCurrentProfile( CMake::currentEnvironment( m_project ) ); updateCache(buildDir); qCDebug(CMAKE) << "builddir Changed" << buildDir; emit changed(); } void CMakePreferences::cacheUpdated() { const Path buildDir = CMake::currentBuildDir(m_project); updateCache(buildDir); qCDebug(CMAKE) << "cache updated for" << buildDir; } void CMakePreferences::createBuildDir() { CMakeBuildDirChooser bdCreator; bdCreator.setSourceFolder( m_srcFolder ); // NOTE: (on removing the trailing slashes) // Generally, we have no clue about how shall a trailing slash look in the current system. // Moreover, the slash may be a part of the filename. // It may be '/' or '\', so maybe should we rely on CMake::allBuildDirs() for returning well-formed pathes? QStringList used = CMake::allBuildDirs( m_project ); bdCreator.setAlreadyUsed(used); bdCreator.setCMakeBinary(Path(CMake::findExecutable())); if(bdCreator.exec()) { QString newbuilddir = bdCreator.buildFolder().toLocalFile(); m_prefsUi->buildDirs->addItem(newbuilddir); int buildDirCount = m_prefsUi->buildDirs->count(); int addedBuildDirIndex = buildDirCount - 1; m_prefsUi->buildDirs->setCurrentIndex(addedBuildDirIndex); m_prefsUi->removeBuildDir->setEnabled(true); // Initialize the kconfig items with the values from the dialog, this ensures the settings // end up in the config file once the changes are saved qCDebug(CMAKE) << "adding to cmake config: new builddir index" << addedBuildDirIndex; qCDebug(CMAKE) << "adding to cmake config: builddir path " << bdCreator.buildFolder(); qCDebug(CMAKE) << "adding to cmake config: installdir " << bdCreator.installPrefix(); qCDebug(CMAKE) << "adding to cmake config: extra args" << bdCreator.extraArguments(); qCDebug(CMAKE) << "adding to cmake config: build type " << bdCreator.buildType(); qCDebug(CMAKE) << "adding to cmake config: cmake binary " << bdCreator.cmakeBinary(); qCDebug(CMAKE) << "adding to cmake config: environment empty"; CMake::setBuildDirCount( m_project, buildDirCount ); CMake::setCurrentBuildDir( m_project, bdCreator.buildFolder() ); CMake::setCurrentInstallDir( m_project, bdCreator.installPrefix() ); CMake::setCurrentExtraArguments( m_project, bdCreator.extraArguments() ); CMake::setCurrentBuildType( m_project, bdCreator.buildType() ); CMake::setCurrentCMakeBinary( m_project, bdCreator.cmakeBinary() ); CMake::setCurrentEnvironment( m_project, QString() ); qCDebug(CMAKE) << "Emitting changed signal for cmake kcm"; emit changed(); } //TODO: Save it for next runs } void CMakePreferences::removeBuildDir() { int curr=m_prefsUi->buildDirs->currentIndex(); if(curr < 0) return; Path removedPath = CMake::currentBuildDir( m_project ); QString removed = removedPath.toLocalFile(); if(QDir(removed).exists()) { KMessageBox::ButtonCode ret = KMessageBox::warningYesNo(this, i18n("The %1 directory is about to be removed in KDevelop's list.\n" "Do you want KDevelop to remove it in the file system as well?", removed)); if(ret == KMessageBox::Yes) { bool correct = KIO::del(removedPath.toUrl())->exec(); if(!correct) KMessageBox::error(this, i18n("Could not remove: %1", removed)); } } qCDebug(CMAKE) << "removing from cmake config: using builddir " << curr; qCDebug(CMAKE) << "removing from cmake config: builddir path " << removedPath; qCDebug(CMAKE) << "removing from cmake config: installdir " << CMake::currentInstallDir( m_project ); qCDebug(CMAKE) << "removing from cmake config: extra args" << CMake::currentExtraArguments( m_project ); qCDebug(CMAKE) << "removing from cmake config: buildtype " << CMake::currentBuildType( m_project ); qCDebug(CMAKE) << "removing from cmake config: cmake binary " << CMake::currentCMakeBinary( m_project ); qCDebug(CMAKE) << "removing from cmake config: environment " << CMake::currentEnvironment( m_project ); CMake::removeBuildDirConfig(m_project); m_prefsUi->buildDirs->removeItem( curr ); // this triggers buildDirChanged() if(m_prefsUi->buildDirs->count()==0) m_prefsUi->removeBuildDir->setEnabled(false); emit changed(); } void CMakePreferences::configure() { IProjectBuilder *b=m_project->buildSystemManager()->builder(); KJob* job=b->configure(m_project); if( m_currentModel ) { QVariantMap map = m_currentModel->changedValues(); job->setProperty("extraCMakeCacheValues", map); connect(job, &KJob::finished, m_currentModel, &CMakeCacheModel::reset); } else { connect(job, &KJob::finished, this, &CMakePreferences::cacheUpdated); } connect(job, &KJob::finished, m_project, &IProject::reloadModel); ICore::self()->runController()->registerJob(job); } void CMakePreferences::showAdvanced(bool v) { qCDebug(CMAKE) << "toggle pressed: " << v; m_prefsUi->advancedBox->setHidden(!v); } QString CMakePreferences::name() const { return i18n("CMake"); } QString CMakePreferences::fullName() const { return i18n("Configure CMake settings"); } QIcon CMakePreferences::icon() const { return QIcon::fromTheme("cmake"); } diff --git a/projectmanagers/cmake/testing/ctestrunjob.cpp b/projectmanagers/cmake/testing/ctestrunjob.cpp index a38eb6ef50..c272af7650 100644 --- a/projectmanagers/cmake/testing/ctestrunjob.cpp +++ b/projectmanagers/cmake/testing/ctestrunjob.cpp @@ -1,222 +1,222 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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 "ctestrunjob.h" #include "ctestsuite.h" #include "qttestdelegate.h" #include "../debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; CTestRunJob::CTestRunJob(CTestSuite* suite, const QStringList& cases, OutputJob::OutputJobVerbosity verbosity, bool expectFail, QObject* parent) : KJob(parent) , m_suite(suite) , m_cases(cases) , m_job(nullptr) , m_outputJob(nullptr) , m_verbosity(verbosity) , m_expectFail(expectFail) { foreach (const QString& testCase, cases) { m_caseResults[testCase] = TestResult::NotRun; } setCapabilities(Killable); } KJob* createTestJob(QString launchModeId, QStringList arguments ) { LaunchConfigurationType* type = ICore::self()->runController()->launchConfigurationTypeForId( "Native Application" ); ILaunchMode* mode = ICore::self()->runController()->launchModeForId( launchModeId ); qCDebug(CMAKE) << "got mode and type:" << type << type->id() << mode << mode->id(); Q_ASSERT(type && mode); ILauncher* launcher = nullptr; foreach (ILauncher *l, type->launchers()) { - //qCDebug(CMAKE) << "avaliable launcher" << l << l->id() << l->supportedModes(); + //qCDebug(CMAKE) << "available launcher" << l << l->id() << l->supportedModes(); if (l->supportedModes().contains(mode->id())) { launcher = l; break; } } Q_ASSERT(launcher); ILaunchConfiguration* ilaunch = nullptr; QList launchConfigurations = ICore::self()->runController()->launchConfigurations(); foreach (ILaunchConfiguration *l, launchConfigurations) { if (l->type() == type && l->config().readEntry("ConfiguredByCTest", false)) { ilaunch = l; break; } } if (!ilaunch) { ilaunch = ICore::self()->runController()->createLaunchConfiguration( type, qMakePair( mode->id(), launcher->id() ), nullptr, //TODO add project i18n("CTest") ); ilaunch->config().writeEntry("ConfiguredByCTest", true); //qCDebug(CMAKE) << "created config, launching"; } else { //qCDebug(CMAKE) << "reusing generated config, launching"; } type->configureLaunchFromCmdLineArguments( ilaunch->config(), arguments ); return ICore::self()->runController()->execute(launchModeId, ilaunch); } void CTestRunJob::start() { // if (!m_suite->cases().isEmpty()) // { // TODO: Find a better way of determining whether QTestLib is used by this test // qCDebug(CMAKE) << "Setting a QtTestDelegate"; // setDelegate(new QtTestDelegate); // } // setStandardToolView(IOutputView::RunView); QStringList arguments = m_cases; if (m_cases.isEmpty() && !m_suite->arguments().isEmpty()) { arguments = m_suite->arguments(); } QStringList cases_selected = arguments; arguments.prepend(m_suite->executable().toLocalFile()); m_job = createTestJob("execute", arguments); if (ExecuteCompositeJob* cjob = qobject_cast(m_job)) { m_outputJob = cjob->findChild(); Q_ASSERT(m_outputJob); m_outputJob->setVerbosity(m_verbosity); QString testName = arguments.value(0).split('/').last(); QString title; if (cases_selected.count() == 1) title = i18nc("running test %1, %2 test case", "CTest %1: %2", testName, cases_selected.value(0)); else title = i18ncp("running test %1, %2 number of test cases", "CTest %2 (%1)", "CTest %2 (%1)", cases_selected.count(), testName); m_outputJob->setTitle(title); connect(m_outputJob->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int))); } connect(m_job, SIGNAL(finished(KJob*)), SLOT(processFinished(KJob*))); ICore::self()->testController()->notifyTestRunStarted(m_suite, cases_selected); } bool CTestRunJob::doKill() { if (m_job) { m_job->kill(); } return true; } void CTestRunJob::processFinished(KJob* job) { TestResult result; result.testCaseResults = m_caseResults; if (job->error() == OutputJob::FailedShownError) { result.suiteResult = TestResult::Failed; } else if (job->error() == KJob::NoError) { result.suiteResult = TestResult::Passed; } else { result.suiteResult = TestResult::Error; } // in case the job was killed, mark this job as killed as well if (job->error() == KJob::KilledJobError) { setError(KJob::KilledJobError); setErrorText("Child job was killed."); } qCDebug(CMAKE) << result.suiteResult << result.testCaseResults; ICore::self()->testController()->notifyTestRunFinished(m_suite, result); emitResult(); } void CTestRunJob::rowsInserted(const QModelIndex &parent, int startRow, int endRow) { // This regular expresion matches the name of the testcase (whatever between "::" and "(", indeed ) // For example, from: // PASS : ExpTest::testExp(sum) // matches "testExp" static QRegExp caseRx("::(.*)\\(", Qt::CaseSensitive, QRegExp::RegExp2); for (int row = startRow; row <= endRow; ++row) { QString line = m_outputJob->model()->data(m_outputJob->model()->index(row, 0, parent), Qt::DisplayRole).toString(); QString testCase; if (caseRx.indexIn(line) >= 0) { testCase = caseRx.cap(1); } TestResult::TestCaseResult prevResult = m_caseResults.value(testCase, TestResult::NotRun); if (prevResult == TestResult::Passed || prevResult == TestResult::NotRun) { TestResult::TestCaseResult result = TestResult::NotRun; if (line.startsWith("PASS :")) { result = m_expectFail ? TestResult::UnexpectedPass : TestResult::Passed; } else if (line.startsWith("FAIL! :")) { result = m_expectFail ? TestResult::ExpectedFail : TestResult::Failed; } else if (line.startsWith("XFAIL :")) { result = TestResult::ExpectedFail; } else if (line.startsWith("XPASS :")) { result = TestResult::UnexpectedPass; } else if (line.startsWith("SKIP :")) { result = TestResult::Skipped; } if (result != TestResult::NotRun) { m_caseResults[testCase] = result; } } } } diff --git a/projectmanagers/cmake/tests/cmakeloadprojecttest.cpp b/projectmanagers/cmake/tests/cmakeloadprojecttest.cpp index eb3bd0dced..a7ddbda6c2 100644 --- a/projectmanagers/cmake/tests/cmakeloadprojecttest.cpp +++ b/projectmanagers/cmake/tests/cmakeloadprojecttest.cpp @@ -1,211 +1,209 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * * 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 "cmakeloadprojecttest.h" #include "cmake-test-paths.h" #include #include #include -#include "cmake-test-paths.h" - #include "cmListFileLexer.h" #include "cmakelistsparser.h" #include "cmakeprojectvisitor.h" #include "cmakeast.h" #include #include #include #include inline QDebug &operator<<(QDebug debug, const Target &target) { debug << target.name; return debug.maybeSpace(); } QTEST_MAIN( CMakeLoadProjectTest ) using namespace KDevelop; void CMakeLoadProjectTest::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void CMakeLoadProjectTest::cleanupTestCase() { TestCore::shutdown(); } void CMakeLoadProjectTest::testTinyCMakeProject() { CMakeProjectData v = parseProject( QString(CMAKE_TESTS_PROJECTS_DIR)+"/tiny_project" ); QCOMPARE(v.targets.count(), 1); QCOMPARE(v.targets.at( 0 ).name, QString("foo") ); QCOMPARE(v.targets.at( 0 ).files, QStringList() << "foo.cpp" ); QCOMPARE(v.vm.value("CMAKE_INCLUDE_CURRENT_DIR"), QStringList("OFF")); } #if QT_VERSION <= 0x050000 void CMakeLoadProjectTest::testBug335803() { CMakeProjectData v = parseProject(CMAKE_TESTS_PROJECTS_DIR "/bug335803"); QCOMPARE(v.projectName, QString("bug335803")); QStringList names; foreach(const Target& t, v.targets) { names << t.name; } QCOMPARE(v.targets.count(), 8); names.sort(); QCOMPARE(names[0], QLatin1String("echo-a")); QCOMPARE(names[1], QLatin1String("echo-b")); QCOMPARE(names[2], QLatin1String("echo-custom_name")); QCOMPARE(names[3], QLatin1String("echo-d")); QCOMPARE(names[4], QLatin1String("echo2-a")); QCOMPARE(names[5], QLatin1String("echo2-b")); QCOMPARE(names[6], QLatin1String("echo2-custom_name")); QCOMPARE(names[7], QLatin1String("echo2-d")); // This one was missing before bug #335803 got fixed } void CMakeLoadProjectTest::testSmallQt4Project() { CMakeProjectData v = parseProject(CMAKE_TESTS_PROJECTS_DIR "/qt4app"); QCOMPARE(v.targets.count(), 1); QCOMPARE(v.projectName, QString("qt4app")); QCOMPARE(v.targets.at( 0 ).name, QString("qt4app") ); QCOMPARE(v.targets.at( 0 ).files, QStringList() << "qt4app.cpp" << "main.cpp" ); } int findTarget(const QVector& targets, const QString& name) { for(int i=0, count=targets.count(); i=0); QCOMPARE(v.targets.at( idx ).name, QString("kde4app") ); QCOMPARE(v.targets.at( idx ).files, QStringList() << "kde4app.cpp" << "main.cpp" << "kde4appview.cpp" << CMAKE_TESTS_PROJECTS_DIR "/kde4app/ui_kde4appview_base.h" << CMAKE_TESTS_PROJECTS_DIR "/kde4app/ui_prefs_base.h" << CMAKE_TESTS_PROJECTS_DIR "/kde4app/settings.cpp" << CMAKE_TESTS_PROJECTS_DIR "/kde4app/settings.h" ); int testIdx = findTarget(v.targets, "testkde4app"); QVERIFY(testIdx>=0); QCOMPARE(v.targets.at( testIdx ).name, QString("testkde4app") ); QCOMPARE(v.targets.at( testIdx ).files, QStringList() << "kde4app.cpp"); int uninstallIdx = findTarget(v.targets, "uninstall"); QVERIFY(uninstallIdx>=0); QCOMPARE(v.targets.at( uninstallIdx ).name, QString("uninstall") ); QCOMPARE(v.vm.value("CMAKE_INCLUDE_CURRENT_DIR"), QStringList("ON")); } #endif void CMakeLoadProjectTest::testSmallProjectWithTests() { CMakeProjectData v = parseProject(CMAKE_TESTS_PROJECTS_DIR "/unit_tests"); QCOMPARE(v.testSuites.count(), 5); QCOMPARE(v.projectName, QString("unittests")); QCOMPARE(v.testSuites.at(0).name, QString("success")); QCOMPARE(v.testSuites.at(0).arguments.count(), 0); QCOMPARE(v.testSuites.at(3).name, QString("test_four")); QCOMPARE(v.testSuites.at(3).arguments.count(), 1); QCOMPARE(v.testSuites.at(3).arguments.at(0), QString("4")); } #if QT_VERSION <= 0x050000 void CMakeLoadProjectTest::testKDE4ProjectWithTests() { CMakeProjectData v = parseProject(CMAKE_TESTS_PROJECTS_DIR "/unit_tests_kde"); QCOMPARE(v.testSuites.count(), 1); //cmake-test-unittestskde QCOMPARE(v.projectName, QString("unittestskde")); QCOMPARE(v.testSuites.at(0).name, QString("cmake-test-unittestskde")); QCOMPARE(v.testSuites.at(0).arguments.count(), 0); } #endif CMakeProjectData CMakeLoadProjectTest::parseProject( const QString& sourcedir ) { QString projectfile = sourcedir+"/CMakeLists.txt"; CMakeFileContent code=CMakeListsParser::readCMakeFile(projectfile); QPair initials = CMakeParserUtils::initialVariables(); CMakeProjectData data; data.vm = initials.first; data.vm.insert("CMAKE_SOURCE_DIR", QStringList(sourcedir)); data.vm.insert("CMAKE_PREFIX_PATH", QString::fromLatin1(TEST_PREFIX_PATH).split(';', QString::SkipEmptyParts)); KDevelop::ReferencedTopDUContext buildstrapContext=new TopDUContext(IndexedString("buildstrap"), RangeInRevision(0,0, 0,0)); DUChain::self()->addDocumentChain(buildstrapContext); ReferencedTopDUContext ref=buildstrapContext; QStringList modulesPath = data.vm["CMAKE_MODULE_PATH"]; foreach(const QString& script, initials.second) { ref = CMakeParserUtils::includeScript(CMakeProjectVisitor::findFile(script, modulesPath, QStringList()), ref, &data, sourcedir, QMap()); } data.vm.insert("CMAKE_CURRENT_BINARY_DIR", QStringList(sourcedir)); data.vm.insert("CMAKE_CURRENT_LIST_FILE", QStringList(projectfile)); data.vm.insert("CMAKE_CURRENT_SOURCE_DIR", QStringList(sourcedir)); data.vm.insert("KDE4_BUILD_TESTS", QStringList("True")); CMakeProjectVisitor v(projectfile, ref); v.setVariableMap(&data.vm); v.setMacroMap(&data.mm); v.setCacheValues(&data.cache); v.setModulePath(modulesPath); v.setProperties(data.properties); QMap env; env["CMAKE_PREFIX_PATH"] = QString::fromLatin1(TEST_ENV_PREFIX_PATH); env["CMAKE_INCLUDE_PATH"] = QString::fromLatin1(TEST_ENV_INCLUDE_PATH); env["CMAKE_LIBRARY_PATH"] = QString::fromLatin1(TEST_ENV_LIBRARY_PATH); v.setEnvironmentProfile( env ); v.walk(code, 0); data.projectName=v.projectName(); data.subdirectories=v.subdirectories(); data.targets=v.targets(); data.properties=v.properties(); data.testSuites=v.testSuites(); //printSubdirectories(data->subdirectories); data.vm.remove("CMAKE_CURRENT_LIST_FILE"); data.vm.remove("CMAKE_CURRENT_LIST_DIR"); data.vm.remove("CMAKE_CURRENT_SOURCE_DIR"); data.vm.remove("CMAKE_CURRENT_BINARY_DIR"); return data; } diff --git a/projectmanagers/cmake/tests/manual/unit_tests/CMakeLists.txt b/projectmanagers/cmake/tests/manual/unit_tests/CMakeLists.txt index 9884a9b774..bb13f5eb27 100644 --- a/projectmanagers/cmake/tests/manual/unit_tests/CMakeLists.txt +++ b/projectmanagers/cmake/tests/manual/unit_tests/CMakeLists.txt @@ -1,14 +1,14 @@ project(unittests) enable_testing() add_executable(test_success success.cpp) add_test(success test_success) add_executable(test_fail fail.cpp) set_target_properties(test_fail PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) add_test(fail test_fail) add_executable(four_test math_test.cpp) add_test(test_three four_test 3) add_test(test_four four_test 4) -add_test(test_five four_test 5) \ No newline at end of file +add_test(test_five four_test 5) diff --git a/projectmanagers/cmake/tests/manual/unit_tests/fail.cpp b/projectmanagers/cmake/tests/manual/unit_tests/fail.cpp index b51c7e8c95..2227c3aa8f 100644 --- a/projectmanagers/cmake/tests/manual/unit_tests/fail.cpp +++ b/projectmanagers/cmake/tests/manual/unit_tests/fail.cpp @@ -1,4 +1,4 @@ int main() { return 1; -} \ No newline at end of file +} diff --git a/projectmanagers/cmake/tests/manual/unit_tests/math_test.cpp b/projectmanagers/cmake/tests/manual/unit_tests/math_test.cpp index 17e2e4341e..37bf93e645 100644 --- a/projectmanagers/cmake/tests/manual/unit_tests/math_test.cpp +++ b/projectmanagers/cmake/tests/manual/unit_tests/math_test.cpp @@ -1,16 +1,16 @@ int main(int argc, char** argv) { if (argc < 2) { return 1; } if (atoi(argv[1]) == 4) { return 0; } else { return 1; } -} \ No newline at end of file +} diff --git a/projectmanagers/cmake/tests/manual/unit_tests/success.cpp b/projectmanagers/cmake/tests/manual/unit_tests/success.cpp index 5bc549337b..905869dfa3 100644 --- a/projectmanagers/cmake/tests/manual/unit_tests/success.cpp +++ b/projectmanagers/cmake/tests/manual/unit_tests/success.cpp @@ -1,4 +1,4 @@ int main() { return 0; -} \ No newline at end of file +} diff --git a/projectmanagers/qmake/qmakemanager.cpp b/projectmanagers/qmake/qmakemanager.cpp index 2ed766f65f..7b53c131d7 100644 --- a/projectmanagers/qmake/qmakemanager.cpp +++ b/projectmanagers/qmake/qmakemanager.cpp @@ -1,521 +1,520 @@ /* 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 "qmakemanager.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 #include #include "qmakemodelitems.h" #include "qmakeprojectfile.h" #include "qmakecache.h" #include "qmakemkspecs.h" #include "qmakejob.h" #include "qmakebuilddirchooserdialog.h" #include "qmakeconfig.h" #include "qmakeutils.h" #include "debug.h" using namespace KDevelop; // BEGIN Helpers QMakeFolderItem* findQMakeFolderParent(ProjectBaseItem* item) { QMakeFolderItem* p = nullptr; while (!p && item) { p = dynamic_cast(item); item = item->parent(); } return p; } // END Helpers K_PLUGIN_FACTORY_WITH_JSON(QMakeSupportFactory, "kdevqmakemanager.json", registerPlugin();) QMakeProjectManager* QMakeProjectManager::m_self = nullptr; QMakeProjectManager* QMakeProjectManager::self() { return m_self; } QMakeProjectManager::QMakeProjectManager(QObject* parent, const QVariantList&) : AbstractFileManagerPlugin("kdevqmakemanager", parent) , IBuildSystemManager() , m_builder(nullptr) , m_runQMake(nullptr) { Q_ASSERT(!m_self); m_self = this; KDEV_USE_EXTENSION_INTERFACE(IBuildSystemManager) IPlugin* i = core()->pluginController()->pluginForExtension("org.kdevelop.IQMakeBuilder"); Q_ASSERT(i); m_builder = i->extension(); Q_ASSERT(m_builder); connect(this, SIGNAL(folderAdded(KDevelop::ProjectFolderItem*)), this, SLOT(slotFolderAdded(KDevelop::ProjectFolderItem*))); m_runQMake = new QAction(QIcon::fromTheme("qtlogo"), i18n("Run QMake"), this); connect(m_runQMake, SIGNAL(triggered(bool)), this, SLOT(slotRunQMake())); } QMakeProjectManager::~QMakeProjectManager() { m_self = nullptr; } IProjectFileManager::Features QMakeProjectManager::features() const { return Features(Folders | Targets | Files); } bool QMakeProjectManager::isValid(const Path& path, const bool isFolder, IProject* project) const { if (!isFolder && path.lastPathSegment().startsWith("Makefile")) { return false; } return AbstractFileManagerPlugin::isValid(path, isFolder, project); } Path QMakeProjectManager::buildDirectory(ProjectBaseItem* item) const { /// TODO: support includes by some other parent or sibling in a different file-tree-branch QMakeFolderItem* qmakeItem = findQMakeFolderParent(item); Path dir; if (qmakeItem) { if (!qmakeItem->parent()) { // build root item dir = QMakeConfig::buildDirFromSrc(qmakeItem->project(), qmakeItem->path()); } else { // build sub-item foreach (QMakeProjectFile* pro, qmakeItem->projectFiles()) { if (QDir(pro->absoluteDir()) == QFileInfo(qmakeItem->path().toUrl().toLocalFile() + '/').absoluteDir() || pro->hasSubProject(qmakeItem->path().toUrl().toLocalFile())) { // get path from project root and it to buildDir dir = QMakeConfig::buildDirFromSrc(qmakeItem->project(), Path(pro->absoluteDir())); break; } } } } qCDebug(KDEV_QMAKE) << "build dir for" << item->text() << item->path() << "is:" << dir; return dir; } ProjectFolderItem* QMakeProjectManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { if (!parent) { return projectRootItem(project, path); } else if (ProjectFolderItem* buildFolder = buildFolderItem(project, path, parent)) { // child folder in a qmake folder return buildFolder; } else { return AbstractFileManagerPlugin::createFolderItem(project, path, parent); } } ProjectFolderItem* QMakeProjectManager::projectRootItem(IProject* project, const Path& path) { QFileInfo fi(path.toLocalFile()); QDir dir(path.toLocalFile()); QStringList l = dir.entryList(QStringList() << "*.pro"); QString projectfile; if (l.count() && l.indexOf(project->name() + ".pro") != -1) projectfile = project->name() + ".pro"; if (l.isEmpty() || (l.count() && l.indexOf(fi.baseName() + ".pro") != -1)) { projectfile = fi.baseName() + ".pro"; } else { projectfile = l.first(); } QHash qmvars = QMakeUtils::queryQMake(project); const QString mkSpecFile = QMakeConfig::findBasicMkSpec(qmvars); Q_ASSERT(!mkSpecFile.isEmpty()); QMakeMkSpecs* mkspecs = new QMakeMkSpecs(mkSpecFile, qmvars); mkspecs->setProject(project); mkspecs->read(); QMakeCache* cache = findQMakeCache(project); if (cache) { cache->setMkSpecs(mkspecs); cache->read(); } Path proPath(path, projectfile); /// TODO: use Path in QMakeProjectFile QMakeProjectFile* scope = new QMakeProjectFile(proPath.toLocalFile()); scope->setProject(project); scope->setMkSpecs(mkspecs); if (cache) { scope->setQMakeCache(cache); } scope->read(); qCDebug(KDEV_QMAKE) << "top-level scope with variables:" << scope->variables(); auto item = new QMakeFolderItem(project, path); item->addProjectFile(scope); return item; } ProjectFolderItem* QMakeProjectManager::buildFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { // find .pro or .pri files in dir QDir dir(path.toLocalFile()); QStringList projectFiles = dir.entryList(QStringList() << "*.pro" << "*.pri", QDir::Files); if (projectFiles.isEmpty()) { return nullptr; } auto folderItem = new QMakeFolderItem(project, path, parent); // TODO: included by not-parent file (in a nother file-tree-branch). QMakeFolderItem* qmakeParent = findQMakeFolderParent(parent); if (!qmakeParent) { // happens for bad qmake configurations return nullptr; } foreach (const QString& file, projectFiles) { const QString absFile = dir.absoluteFilePath(file); // TODO: multiple includes by different .pro's QMakeProjectFile* parentPro = nullptr; foreach (QMakeProjectFile* p, qmakeParent->projectFiles()) { if (p->hasSubProject(absFile)) { parentPro = p; break; } } if (!parentPro && file.endsWith(".pri")) { continue; } qCDebug(KDEV_QMAKE) << "add project file:" << absFile; if (parentPro) { qCDebug(KDEV_QMAKE) << "parent:" << parentPro->absoluteFile(); } else { qCDebug(KDEV_QMAKE) << "no parent, assume project root"; } auto qmscope = new QMakeProjectFile(absFile); qmscope->setProject(project); const QFileInfo info(absFile); const QDir d = info.dir(); /// TODO: cleanup if (parentPro) { // subdir if (QMakeCache* cache = findQMakeCache(project, Path(d.canonicalPath()))) { cache->setMkSpecs(parentPro->mkSpecs()); cache->read(); qmscope->setQMakeCache(cache); } else { qmscope->setQMakeCache(parentPro->qmakeCache()); } qmscope->setMkSpecs(parentPro->mkSpecs()); } else { // new project QMakeFolderItem* root = dynamic_cast(project->projectItem()); Q_ASSERT(root); qmscope->setMkSpecs(root->projectFiles().first()->mkSpecs()); if (root->projectFiles().first()->qmakeCache()) { qmscope->setQMakeCache(root->projectFiles().first()->qmakeCache()); } } if (qmscope->read()) { // TODO: only on read? folderItem->addProjectFile(qmscope); } else { delete qmscope; return nullptr; } } return folderItem; } void QMakeProjectManager::slotFolderAdded(ProjectFolderItem* folder) { QMakeFolderItem* qmakeParent = dynamic_cast(folder); if (!qmakeParent) { return; } qCDebug(KDEV_QMAKE) << "adding targets for" << folder->path(); foreach (QMakeProjectFile* pro, qmakeParent->projectFiles()) { foreach (const QString& s, pro->targets()) { if (!isValid(Path(folder->path(), s), false, folder->project())) { continue; } qCDebug(KDEV_QMAKE) << "adding target:" << s; Q_ASSERT(!s.isEmpty()); auto target = new QMakeTargetItem(pro, folder->project(), s, folder); foreach (const QString& path, pro->filesForTarget(s)) { new ProjectFileItem(folder->project(), Path(path), target); /// TODO: signal? } } } } ProjectFolderItem* QMakeProjectManager::import(IProject* project) { const Path dirName = project->path(); if (dirName.isRemote()) { // FIXME turn this into a real warning qCWarning(KDEV_QMAKE) << "not a local file. QMake support doesn't handle remote projects"; return nullptr; } QMakeUtils::checkForNeedingConfigure(project); ProjectFolderItem* ret = AbstractFileManagerPlugin::import(project); connect(projectWatcher(project), SIGNAL(dirty(QString)), this, SLOT(slotDirty(QString))); return ret; } void QMakeProjectManager::slotDirty(const QString& path) { if (!path.endsWith(".pro") && !path.endsWith(".pri")) { return; } QFileInfo info(path); if (!info.isFile()) { return; } const QUrl url = QUrl::fromLocalFile(path); if (!isValid(Path(url), false, nullptr)) { return; } IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if (!project) { // this can happen when we create/remove lots of files in a // sub dir of a project - ignore such cases for now return; } bool finished = false; foreach (ProjectFolderItem* folder, project->foldersForPath(IndexedString(KIO::upUrl(url)))) { if (QMakeFolderItem* qmakeFolder = dynamic_cast(folder)) { foreach (QMakeProjectFile* pro, qmakeFolder->projectFiles()) { if (pro->absoluteFile() == path) { // TODO: children // TODO: cache added qDebug() << "reloading" << pro << path; pro->read(); } } finished = true; } else if (ProjectFolderItem* newFolder = buildFolderItem(project, folder->path(), folder->parent())) { qDebug() << "changing from normal folder to qmake project folder:" << folder->path().toUrl(); // .pro / .pri file did not exist before while (folder->rowCount()) { newFolder->appendRow(folder->takeRow(0)); } folder->parent()->removeRow(folder->row()); folder = newFolder; finished = true; } if (finished) { // remove existing targets and readd them for (int i = 0; i < folder->rowCount(); ++i) { if (folder->child(i)->target()) { folder->removeRow(i); } } /// TODO: put into it's own function once we add more stuff to that slot slotFolderAdded(folder); break; } } } QList QMakeProjectManager::targets(ProjectFolderItem* item) const { Q_UNUSED(item) return QList(); } IProjectBuilder* QMakeProjectManager::builder() const { Q_ASSERT(m_builder); return m_builder; } Path::List QMakeProjectManager::collectDirectories(ProjectBaseItem* item, const bool collectIncludes) const { Path::List list; QMakeFolderItem* folder = findQMakeFolderParent(item); if (folder) { foreach (QMakeProjectFile* pro, folder->projectFiles()) { if (pro->files().contains(item->path().toLocalFile())) { const QStringList directories = collectIncludes ? pro->includeDirectories() : pro->frameworkDirectories(); foreach (const QString& dir, directories) { Path path(dir); if (!list.contains(path)) { list << path; } } } } if (list.isEmpty()) { // fallback for new files, use all possible include dirs foreach (QMakeProjectFile* pro, folder->projectFiles()) { const QStringList directories = collectIncludes ? pro->includeDirectories() : pro->frameworkDirectories(); foreach (const QString& dir, directories) { Path path(dir); if (!list.contains(path)) { list << path; } } } } // make sure the base dir is included if (!list.contains(folder->path())) { list << folder->path(); } // qCDebug(KDEV_QMAKE) << "include dirs for" << item->path() << ":" << list; } return list; } Path::List QMakeProjectManager::includeDirectories(ProjectBaseItem* item) const { return collectDirectories(item); } Path::List QMakeProjectManager::frameworkDirectories(ProjectBaseItem* item) const { return collectDirectories(item, false); } QHash QMakeProjectManager::defines(ProjectBaseItem* item) const { QHash d; QMakeFolderItem* folder = findQMakeFolderParent(item); if (!folder) { // happens for bad qmake configurations return d; } foreach (QMakeProjectFile* pro, folder->projectFiles()) { foreach (QMakeProjectFile::DefinePair def, pro->defines()) { d.insert(def.first, def.second); } } return d; } bool QMakeProjectManager::hasBuildInfo(KDevelop::ProjectBaseItem* item) const { return findQMakeFolderParent(item); } QMakeCache* QMakeProjectManager::findQMakeCache(IProject* project, const Path& path) const { QDir curdir(QMakeConfig::buildDirFromSrc(project, !path.isValid() ? project->path() : path).toLocalFile()); curdir.makeAbsolute(); while (!curdir.exists(".qmake.cache") && !curdir.isRoot() && curdir.cdUp()) { qDebug() << curdir; } if (curdir.exists(".qmake.cache")) { qDebug() << "Found QMake cache in " << curdir.absolutePath(); return new QMakeCache(curdir.canonicalPath() + "/.qmake.cache"); } return nullptr; } ContextMenuExtension QMakeProjectManager::contextMenuExtension(Context* context) { ContextMenuExtension ext; if (context->hasType(Context::ProjectItemContext)) { ProjectItemContext* pic = dynamic_cast(context); Q_ASSERT(pic); if (pic->items().isEmpty()) { return ext; } m_actionItem = dynamic_cast(pic->items().first()); if (m_actionItem) { ext.addAction(ContextMenuExtension::ProjectGroup, m_runQMake); } } return ext; } void QMakeProjectManager::slotRunQMake() { Q_ASSERT(m_actionItem); Path srcDir = m_actionItem->path(); Path buildDir = QMakeConfig::buildDirFromSrc(m_actionItem->project(), srcDir); QMakeJob* job = new QMakeJob(srcDir.toLocalFile(), buildDir.toLocalFile(), this); job->setQMakePath(QMakeConfig::qmakeBinary(m_actionItem->project())); KConfigGroup cg(m_actionItem->project()->projectConfiguration(), QMakeConfig::CONFIG_GROUP); QString installPrefix = cg.readEntry(QMakeConfig::INSTALL_PREFIX, QString()); if (!installPrefix.isEmpty()) job->setInstallPrefix(installPrefix); job->setBuildType(cg.readEntry(QMakeConfig::BUILD_TYPE, 0)); job->setExtraArguments(cg.readEntry(QMakeConfig::EXTRA_ARGUMENTS, QString())); KDevelop::ICore::self()->runController()->registerJob(job); } #include "qmakemanager.moc" diff --git a/projectmanagers/qmake/qmakeutils.h b/projectmanagers/qmake/qmakeutils.h index 32e86b5c21..a6c619dfc5 100644 --- a/projectmanagers/qmake/qmakeutils.h +++ b/projectmanagers/qmake/qmakeutils.h @@ -1,47 +1,47 @@ /* * Copyright 2015 Kevin Funk * * 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 . * */ #ifndef QMAKEUTILS_H #define QMAKEUTILS_H #include namespace KDevelop { class IProject; } namespace QMakeUtils { /** - * Checks wether there's a need to run qmake for the given project item + * Checks whether there's a need to run qmake for the given project item * This is the case if no builddir has been specified, in which case * it asks for one. * * @returns true if configure should be run, false otherwise */ bool checkForNeedingConfigure(KDevelop::IProject* project); QHash queryQMake(KDevelop::IProject*); } #endif // QMAKEUTILS_H diff --git a/utils/okteta/kastentoolviewwidget.cpp b/utils/okteta/kastentoolviewwidget.cpp index 5d6b245520..6913ac790e 100644 --- a/utils/okteta/kastentoolviewwidget.cpp +++ b/utils/okteta/kastentoolviewwidget.cpp @@ -1,82 +1,82 @@ /* 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 "kastentoolviewwidget.h" // plugin #include "oktetaview.h" // Okteta Kasten #include "kasten/okteta/bytearrayview.h" // Kasten #include #include // KDev #include #include #include #include // Qt #include namespace KDevelop { -// TODO: find if hiddden, than unset target model +// TODO: find if hidden, than unset target model KastenToolViewWidget::KastenToolViewWidget( Kasten::AbstractToolView* toolView, QWidget* parent ) : QWidget( parent ), mToolView( toolView ) { Sublime::Controller* controller = ICore::self()->uiController()->controller(); connect( controller, &Sublime::Controller::mainWindowAdded, this, &KastenToolViewWidget::onMainWindowAdded ); const QList& mainWindows = controller->mainWindows(); foreach( Sublime::MainWindow* mainWindow, mainWindows ) onMainWindowAdded( mainWindow ); QVBoxLayout* layout = new QVBoxLayout( this ); layout->setMargin( 0 ); layout->addWidget( mToolView->widget() ); } void KastenToolViewWidget::onMainWindowAdded( Sublime::MainWindow* mainWindow ) { connect( mainWindow, &Sublime::MainWindow::activeViewChanged, this, &KastenToolViewWidget::onActiveViewChanged ); onActiveViewChanged( mainWindow->activeView() ); } void KastenToolViewWidget::onActiveViewChanged( Sublime::View* view ) { // TODO: check if own mainWindow OktetaView* oktetaView = qobject_cast( view ); Kasten::ByteArrayView* byteArrayView = oktetaView ? oktetaView->byteArrayView() : nullptr; mToolView->tool()->setTargetModel( byteArrayView ); } KastenToolViewWidget::~KastenToolViewWidget() { Kasten::AbstractTool* tool = mToolView->tool(); delete mToolView; delete tool; } }