|  | /*
  ==============================================================================
   This file is part of the JUCE library.
   Copyright (c) 2015 - ROLI Ltd.
   Permission is granted to use this software under the terms of either:
   a) the GPL v2 (or any later version)
   b) the Affero GPL v3
   Details of these licenses can be found at: www.gnu.org/licenses
   JUCE 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.
   ------------------------------------------------------------------------------
   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.juce.com for more information.
  ==============================================================================
*/
#include "../jucer_Headers.h"
#include "../Application/jucer_Application.h"
#include "../Project Saving/jucer_ProjectExporter.h"
#include "projucer_MessageIDs.h"
#include "projucer_CppHelpers.h"
#include "projucer_SourceCodeRange.h"
#include "projucer_ClassDatabase.h"
#include "projucer_DiagnosticMessage.h"
#include "projucer_ProjectBuildInfo.h"
#include "projucer_ClientServerMessages.h"
#include "projucer_CompileEngineClient.h"
#include "../LiveBuildEngine/projucer_CompileEngineServer.h"
#ifndef RUN_CLANG_IN_CHILD_PROCESS
 #error
#endif
//==============================================================================
namespace ProjectProperties
{
    const Identifier liveSettingsType ("LIVE_SETTINGS");
   #if JUCE_MAC
    const Identifier liveSettingsSubtype ("OSX");
   #elif JUCE_WINDOWS
    const Identifier liveSettingsSubtype ("WINDOWS");
   #elif JUCE_LINUX
    const Identifier liveSettingsSubtype ("LINUX");
   #endif
    static ValueTree getLiveSettings (Project& project)
    {
        return project.getProjectRoot().getOrCreateChildWithName (liveSettingsType, nullptr)
                                       .getOrCreateChildWithName (liveSettingsSubtype, nullptr);
    }
    static const ValueTree getLiveSettingsConst (Project& project)
    {
        return project.getProjectRoot().getChildWithName (liveSettingsType)
                                       .getChildWithName (liveSettingsSubtype);
    }
    static Value getLiveSetting    (Project& p, const Identifier& i)  { return getLiveSettings (p).getPropertyAsValue (i, p.getUndoManagerFor (getLiveSettings (p))); }
    static var   getLiveSettingVar (Project& p, const Identifier& i)  { return getLiveSettingsConst (p) [i]; }
    static Value  getUserHeaderPathValue (Project& p)          { return getLiveSetting    (p, Ids::headerPath); }
    static String getUserHeaderPathString (Project& p)         { return getLiveSettingVar (p, Ids::headerPath); }
    static Value  getSystemHeaderPathValue (Project& p)        { return getLiveSetting    (p, Ids::systemHeaderPath); }
    static String getSystemHeaderPathString (Project& p)       { return getLiveSettingVar (p, Ids::systemHeaderPath); }
    static Value  getExtraDLLsValue (Project& p)               { return getLiveSetting    (p, Ids::extraDLLs); }
    static String getExtraDLLsString (Project& p)              { return getLiveSettingVar (p, Ids::extraDLLs); }
    static Value  getExtraCompilerFlagsValue (Project& p)      { return getLiveSetting    (p, Ids::extraCompilerFlags); }
    static String getExtraCompilerFlagsString (Project& p)     { return getLiveSettingVar (p, Ids::extraCompilerFlags); }
    static Value  getExtraPreprocessorDefsValue (Project& p)   { return getLiveSetting    (p, Ids::defines); }
    static String getExtraPreprocessorDefsString (Project& p)  { return getLiveSettingVar (p, Ids::defines); }
    static File getProjucerTempFolder()
    {
       #if JUCE_MAC
        return File ("~/Library/Caches/com.juce.projucer");
       #else
        return File::getSpecialLocation (File::tempDirectory).getChildFile ("com.juce.projucer");
       #endif
    }
    static File getCacheLocation (Project& project)
    {
        String cacheFolderName = project.getProjectFilenameRoot() + "_" + project.getProjectUID();
       #if JUCE_DEBUG
        cacheFolderName += "_debug";
       #endif
        return getProjucerTempFolder()
                .getChildFile ("Intermediate Files")
                .getChildFile (cacheFolderName);
    }
}
//==============================================================================
void LiveBuildProjectSettings::getLiveSettings (Project& project, PropertyListBuilder& props)
{
    using namespace ProjectProperties;
    props.addSearchPathProperty (getUserHeaderPathValue (project), "User header paths", "User header search paths.");
    props.addSearchPathProperty (getSystemHeaderPathValue (project), "System header paths", "System header search paths.");
    props.add (new TextPropertyComponent (getExtraPreprocessorDefsValue (project), "Preprocessor Definitions", 32768, true),
               "Extra preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace or commas "
               "to separate the items - to include a space or comma in a definition, precede it with a backslash.");
    props.add (new TextPropertyComponent (getExtraCompilerFlagsValue (project), "Extra compiler flags", 2048, true),
               "Extra command-line flags to be passed to the compiler. This string can contain references to preprocessor"
               " definitions in the form ${NAME_OF_DEFINITION}, which will be replaced with their values.");
    props.add (new TextPropertyComponent (getExtraDLLsValue (project), "Extra dynamic libraries", 2048, true),
               "Extra dynamic libs that the running code may require. Use new-lines or commas to separate the items");
}
void LiveBuildProjectSettings::updateNewlyOpenedProject (Project&) { /* placeholder */ }
bool LiveBuildProjectSettings::isBuildDisabled (Project& p)
{
    const bool defaultBuildDisabled = true;
    return p.getStoredProperties().getBoolValue ("buildDisabled", defaultBuildDisabled);
}
void LiveBuildProjectSettings::setBuildDisabled (Project& p, bool b)    { p.getStoredProperties().setValue ("buildDisabled", b); }
bool LiveBuildProjectSettings::areWarningsDisabled (Project& p)         { return p.getStoredProperties().getBoolValue ("warningsDisabled"); }
void LiveBuildProjectSettings::setWarningsDisabled (Project& p, bool b) { p.getStoredProperties().setValue ("warningsDisabled", b); }
//==============================================================================
class ClientIPC  : public MessageHandler,
                   private InterprocessConnection,
                   private Timer
{
public:
    ClientIPC (CompileEngineChildProcess& cp)
       : InterprocessConnection (true), owner (cp)
    {
        launchServer();
    }
    ~ClientIPC()
    {
       #if RUN_CLANG_IN_CHILD_PROCESS
        if (childProcess.isRunning())
            killServerPolitely();
       #endif
    }
    void launchServer()
    {
        DBG ("Client: Launching Server...");
        const String pipeName ("ipc_" + String::toHexString (Random().nextInt64()));
        const String command (createCommandLineForLaunchingServer (pipeName,
                                                                   owner.project.getProjectUID(),
                                                                   ProjectProperties::getCacheLocation (owner.project)));
       #if RUN_CLANG_IN_CHILD_PROCESS
        if (! childProcess.start (command))
        {
            jassertfalse;
        }
       #else
        server = createClangServer (command);
       #endif
        bool ok = connectToPipe (pipeName, 10000);
        jassert (ok);
        if (ok)
            MessageTypes::sendPing (*this);
        startTimer (serverKeepAliveTimeout);
    }
    void killServerPolitely()
    {
        DBG ("Client: Killing Server...");
        MessageTypes::sendQuit (*this);
        disconnect();
        stopTimer();
       #if RUN_CLANG_IN_CHILD_PROCESS
        childProcess.waitForProcessToFinish (5000);
       #endif
        killServerWithoutMercy();
    }
    void killServerWithoutMercy()
    {
        disconnect();
        stopTimer();
       #if RUN_CLANG_IN_CHILD_PROCESS
        childProcess.kill();
       #else
        destroyClangServer (server);
        server = nullptr;
       #endif
    }
    void connectionMade()
    {
        DBG ("Client: connected");
        stopTimer();
    }
    void connectionLost()
    {
        DBG ("Client: disconnected");
        startTimer (100);
    }
    bool sendMessage (const ValueTree& m)
    {
        return InterprocessConnection::sendMessage (MessageHandler::convertMessage (m));
    }
    void messageReceived (const MemoryBlock& message)
    {
       #if RUN_CLANG_IN_CHILD_PROCESS
        startTimer (serverKeepAliveTimeout);
       #else
        stopTimer();
       #endif
        MessageTypes::dispatchToClient (owner, MessageHandler::convertMessage (message));
    }
    enum { serverKeepAliveTimeout = 10000 };
private:
    CompileEngineChildProcess& owner;
   #if RUN_CLANG_IN_CHILD_PROCESS
    ChildProcess childProcess;
   #else
    void* server;
   #endif
    void timerCallback()    { owner.handleCrash (String()); }
};
//==============================================================================
class CompileEngineChildProcess::ChildProcess    : private ValueTree::Listener,
                                                   private Timer
{
public:
    ChildProcess (CompileEngineChildProcess& proc, Project& p)
        : owner (proc), project (p)
    {
        projectRoot = project.getProjectRoot();
        restartServer();
        projectRoot.addListener (this);
        openedOk = true;
    }
    ~ChildProcess()
    {
        projectRoot.removeListener (this);
        if (isRunningApp && server != nullptr)
            server->killServerWithoutMercy();
        server = nullptr;
    }
    void restartServer()
    {
        server = nullptr;
        server = new ClientIPC (owner);
        sendRebuild();
    }
    void sendRebuild()
    {
        stopTimer();
        ProjectBuildInfo build;
        if (! doesProjectMatchSavedHeaderState (project))
        {
            MessageTypes::sendNewBuild (*server, build);
            owner.errorList.resetToError ("Project structure does not match the saved headers! "
                                          "Please re-save your project to enable compilation");
            return;
        }
        if (areAnyModulesMissing (project))
        {
            MessageTypes::sendNewBuild (*server, build);
            owner.errorList.resetToError ("Some of your JUCE modules can't be found! "
                                          "Please check that all the module paths are correct");
            return;
        }
        build.setSystemIncludes (getSystemIncludePaths());
        build.setUserIncludes (getUserIncludes());
        build.setGlobalDefs (getGlobalDefs (project));
        build.setCompileFlags (ProjectProperties::getExtraCompilerFlagsString (project).trim());
        build.setExtraDLLs (getExtraDLLs());
        build.setJuceModulesFolder (EnabledModuleList::findDefaultModulesFolder (project).getFullPathName());
        build.setUtilsCppInclude (project.getAppIncludeFile().getFullPathName());
        scanForProjectFiles (project, build);
        owner.updateAllEditors();
        MessageTypes::sendNewBuild (*server, build);
    }
    void cleanAll()
    {
        MessageTypes::sendCleanAll (*server);
        sendRebuild();
    }
    void reinstantiatePreviews()
    {
        MessageTypes::sendReinstantiate (*server);
    }
    bool launchApp()
    {
        MessageTypes::sendLaunchApp (*server);
        return true;
    }
    ScopedPointer<ClientIPC> server;
    bool openedOk = false;
    bool isRunningApp = false;
private:
    CompileEngineChildProcess& owner;
    Project& project;
    ValueTree projectRoot;
    void projectStructureChanged()
    {
        startTimer (100);
    }
    void timerCallback() override
    {
        sendRebuild();
    }
    void valueTreePropertyChanged (ValueTree&, const Identifier&) override       { projectStructureChanged(); }
    void valueTreeChildAdded (ValueTree&, ValueTree&) override                   { projectStructureChanged(); }
    void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override            { projectStructureChanged(); }
    void valueTreeParentChanged (ValueTree&) override                            { projectStructureChanged(); }
    void valueTreeChildOrderChanged (ValueTree&, int, int) override              {}
    static String getGlobalDefs (Project& proj)
    {
        String defs (ProjectProperties::getExtraPreprocessorDefsString (proj));
        for (Project::ExporterIterator exporter (proj); exporter.next();)
            if (exporter->canLaunchProject())
                defs << " " << exporter->getExporterIdentifierMacro() << "=1";
        return defs;
    }
    static void scanProjectItem (const Project::Item& projectItem, Array<File>& compileUnits, Array<File>& userFiles)
    {
        if (projectItem.isGroup())
        {
            for (int i = 0; i < projectItem.getNumChildren(); ++i)
                scanProjectItem (projectItem.getChild(i), compileUnits, userFiles);
            return;
        }
        if (projectItem.shouldBeCompiled())
        {
            const File f (projectItem.getFile());
            if (f.exists())
                compileUnits.add (f);
        }
        if (projectItem.shouldBeAddedToTargetProject() && ! projectItem.shouldBeAddedToBinaryResources())
        {
            const File f (projectItem.getFile());
            if (f.exists())
                userFiles.add (f);
        }
    }
    void scanForProjectFiles (Project& proj, ProjectBuildInfo& build)
    {
        Array<File> compileUnits, userFiles;
        scanProjectItem (proj.getMainGroup(), compileUnits, userFiles);
        {
            OwnedArray<LibraryModule> modules;
            proj.getModules().createRequiredModules (modules);
            for (Project::ExporterIterator exporter (proj); exporter.next();)
            {
                if (exporter->canLaunchProject())
                {
                    for (const LibraryModule* m : modules)
                    {
                        const File localModuleFolder = proj.getModules().shouldCopyModuleFilesLocally (m->moduleInfo.getID()).getValue()
                                                          ? proj.getLocalModuleFolder (m->moduleInfo.getID())
                                                          : m->moduleInfo.getFolder();
                        m->findAndAddCompiledUnits (*exporter, nullptr, compileUnits);
                    }
                    break;
                }
            }
        }
        for (int i = 0; ; ++i)
        {
            const File binaryDataCpp (proj.getBinaryDataCppFile (i));
            if (! binaryDataCpp.exists())
                break;
            compileUnits.add (binaryDataCpp);
        }
        for (int i = compileUnits.size(); --i >= 0;)
            if (compileUnits.getReference(i).hasFileExtension (".r"))
                compileUnits.remove (i);
        build.setFiles (compileUnits, userFiles);
    }
    static bool doesProjectMatchSavedHeaderState (Project& project)
    {
        ValueTree liveModules (project.getProjectRoot().getChildWithName (Ids::MODULES));
        ScopedPointer<XmlElement> xml (XmlDocument::parse (project.getFile()));
        if (xml == nullptr || ! xml->hasTagName (Ids::JUCERPROJECT.toString()))
            return false;
        ValueTree diskModules (ValueTree::fromXml (*xml).getChildWithName (Ids::MODULES));
        return liveModules.isEquivalentTo (diskModules);
    }
    static bool areAnyModulesMissing (Project& project)
    {
        OwnedArray<LibraryModule> modules;
        project.getModules().createRequiredModules (modules);
        for (auto* module : modules)
            if (! module->getFolder().isDirectory())
                return true;
        return false;
    }
    StringArray getUserIncludes()
    {
        StringArray paths;
        paths.add (project.getGeneratedCodeFolder().getFullPathName());
        paths.addArray (getSearchPathsFromString (ProjectProperties::getUserHeaderPathString (project)));
        return convertSearchPathsToAbsolute (paths);
    }
    StringArray getSystemIncludePaths()
    {
        StringArray paths;
        paths.addArray (getSearchPathsFromString (ProjectProperties::getSystemHeaderPathString (project)));
        if (project.getProjectType().isAudioPlugin())
        {
            paths.add (getAppSettings().getGlobalPath (Ids::vst3Path, TargetOS::getThisOS()).toString());
        }
        OwnedArray<LibraryModule> modules;
        project.getModules().createRequiredModules (modules);
        for (auto* module : modules)
            paths.addIfNotAlreadyThere (module->getFolder().getParentDirectory().getFullPathName());
        return convertSearchPathsToAbsolute (paths);
    }
    StringArray convertSearchPathsToAbsolute (const StringArray& paths) const
    {
        StringArray s;
        const File root (project.getProjectFolder());
        for (String p : paths)
            s.add (root.getChildFile (p).getFullPathName());
        return s;
    }
    StringArray getExtraDLLs()
    {
        StringArray dlls;
        dlls.addTokens (ProjectProperties::getExtraDLLsString (project), "\n\r,", StringRef());
        dlls.trim();
        dlls.removeEmptyStrings();
        return dlls;
    }
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcess)
};
//==============================================================================
CompileEngineChildProcess::CompileEngineChildProcess (Project& p)
    : project (p),
      continuousRebuild (false)
{
    ProjucerApplication::getApp().openDocumentManager.addListener (this);
    createProcess();
    errorList.setWarningsEnabled (! LiveBuildProjectSettings::areWarningsDisabled (project));
}
CompileEngineChildProcess::~CompileEngineChildProcess()
{
    ProjucerApplication::getApp().openDocumentManager.removeListener (this);
    process = nullptr;
    lastComponentList.clear();
}
void CompileEngineChildProcess::createProcess()
{
    jassert (process == nullptr);
    process = new ChildProcess (*this, project);
    if (! process->openedOk)
        process = nullptr;
    updateAllEditors();
}
void CompileEngineChildProcess::cleanAll()
{
    if (process != nullptr)
        process->cleanAll();
}
void CompileEngineChildProcess::openPreview (const ClassDatabase::Class& comp)
{
    if (process != nullptr)
    {
        MainWindow* projectWindow = nullptr;
        OwnedArray<MainWindow>& windows = ProjucerApplication::getApp().mainWindowList.windows;
        for (int i = 0; i < windows.size(); ++i)
        {
            if (MainWindow* w = windows[i])
            {
                if (w->getProject() == &project)
                {
                    projectWindow = w;
                    break;
                }
            }
        }
        Rectangle<int> mainWindowRect;
        if (projectWindow != nullptr)
            mainWindowRect = projectWindow->getBounds();
        MessageTypes::sendOpenPreview (*process->server, comp, mainWindowRect);
    }
}
void CompileEngineChildProcess::reinstantiatePreviews()
{
    if (process != nullptr)
        process->reinstantiatePreviews();
}
void CompileEngineChildProcess::processActivationChanged (bool isForeground)
{
    if (process != nullptr)
        MessageTypes::sendProcessActivationState (*process->server, isForeground);
}
//==============================================================================
bool CompileEngineChildProcess::canLaunchApp() const
{
    return process != nullptr
            && runningAppProcess == nullptr
            && activityList.getNumActivities() == 0
            && errorList.getNumErrors() == 0;
}
void CompileEngineChildProcess::launchApp()
{
    if (process != nullptr)
        process->launchApp();
}
bool CompileEngineChildProcess::canKillApp() const
{
    return runningAppProcess != nullptr;
}
void CompileEngineChildProcess::killApp()
{
    runningAppProcess = nullptr;
}
void CompileEngineChildProcess::handleAppLaunched()
{
    runningAppProcess = process;
    runningAppProcess->isRunningApp = true;
    createProcess();
}
void CompileEngineChildProcess::handleAppQuit()
{
    DBG ("handleAppQuit");
    runningAppProcess = nullptr;
}
//==============================================================================
struct CompileEngineChildProcess::Editor  : private CodeDocument::Listener,
                                            private Timer
{
    Editor (CompileEngineChildProcess& ccp, const File& f, CodeDocument& doc)
        : owner (ccp), file (f), document (doc), transactionTimer (doc)
    {
        sendFullUpdate();
        document.addListener (this);
    }
    ~Editor()
    {
        document.removeListener (this);
    }
    void codeDocumentTextInserted (const String& newText, int insertIndex) override
    {
        CodeChange (Range<int> (insertIndex, insertIndex), newText).addToList (pendingChanges);
        startEditorChangeTimer();
        transactionTimer.stopTimer();
        owner.lastComponentList.globalNamespace
           .nudgeAllCodeRanges (file.getFullPathName(), insertIndex, newText.length());
    }
    void codeDocumentTextDeleted (int start, int end) override
    {
        CodeChange (Range<int> (start, end), String()).addToList (pendingChanges);
        startEditorChangeTimer();
        transactionTimer.stopTimer();
        owner.lastComponentList.globalNamespace
           .nudgeAllCodeRanges (file.getFullPathName(), start, start - end);
    }
    void sendFullUpdate()
    {
        reset();
        if (owner.process != nullptr)
            MessageTypes::sendFileContentFullUpdate (*owner.process->server, file, document.getAllContent());
    }
    bool flushEditorChanges()
    {
        if (pendingChanges.size() > 0)
        {
            if (owner.process != nullptr && owner.process->server != nullptr)
                MessageTypes::sendFileChanges (*owner.process->server, pendingChanges, file);
            reset();
            return true;
        }
        stopTimer();
        return false;
    }
    void reset()
    {
        stopTimer();
        pendingChanges.clear();
    }
    void startTransactionTimer()
    {
        transactionTimer.startTimer (1000);
    }
    void startEditorChangeTimer()
    {
        startTimer (200);
    }
    CompileEngineChildProcess& owner;
    File file;
    CodeDocument& document;
private:
    Array<CodeChange> pendingChanges;
    void timerCallback() override
    {
        if (owner.continuousRebuild)
            flushEditorChanges();
        else
            stopTimer();
    }
    struct TransactionTimer   : public Timer
    {
        TransactionTimer (CodeDocument& doc) : document (doc) {}
        void timerCallback() override
        {
            stopTimer();
            document.newTransaction();
        }
        CodeDocument& document;
    };
    TransactionTimer transactionTimer;
};
void CompileEngineChildProcess::editorOpened (const File& file, CodeDocument& document)
{
    editors.add (new Editor (*this, file, document));
}
bool CompileEngineChildProcess::documentAboutToClose (OpenDocumentManager::Document* document)
{
    for (int i = editors.size(); --i >= 0;)
    {
        if (document->getFile() == editors.getUnchecked(i)->file)
        {
            const File f (editors.getUnchecked(i)->file);
            editors.remove (i);
            if (process != nullptr)
                MessageTypes::sendHandleFileReset (*process->server, f);
        }
    }
    return true;
}
void CompileEngineChildProcess::updateAllEditors()
{
    for (int i = editors.size(); --i >= 0;)
        editors.getUnchecked(i)->sendFullUpdate();
}
//==============================================================================
void CompileEngineChildProcess::handleCrash (const String& message)
{
    Logger::writeToLog ("*** Child process crashed: " + message);
    if (crashHandler != nullptr)
        crashHandler (message);
}
void CompileEngineChildProcess::handleNewDiagnosticList (const ValueTree& l)         { errorList.setList (l); }
void CompileEngineChildProcess::handleActivityListChanged (const StringArray& l)     { activityList.setList (l); }
void CompileEngineChildProcess::handleCloseIDE()
{
    if (JUCEApplication* app = JUCEApplication::getInstance())
        app->systemRequestedQuit();
}
void CompileEngineChildProcess::handleMissingSystemHeaders()
{
    if (ProjectContentComponent* p = findProjectContentComponent())
        p->handleMissingSystemHeaders();
}
void CompileEngineChildProcess::handleKeyPress (const String& className, const KeyPress& key)
{
    ApplicationCommandManager& commandManager = ProjucerApplication::getCommandManager();
    CommandID command = commandManager.getKeyMappings()->findCommandForKeyPress (key);
    if (command == StandardApplicationCommandIDs::undo)
    {
        handleUndoInEditor (className);
    }
    else if (command == StandardApplicationCommandIDs::redo)
    {
        handleRedoInEditor (className);
    }
    else if (ApplicationCommandTarget* const target = ApplicationCommandManager::findTargetForComponent (findProjectContentComponent()))
    {
        commandManager.setFirstCommandTarget (target);
        commandManager.getKeyMappings()->keyPressed (key, findProjectContentComponent());
        commandManager.setFirstCommandTarget (nullptr);
    }
}
void CompileEngineChildProcess::handleUndoInEditor (const String& /*className*/)
{
}
void CompileEngineChildProcess::handleRedoInEditor (const String& /*className*/)
{
}
void CompileEngineChildProcess::handleClassListChanged (const ValueTree& newList)
{
    lastComponentList = ClassDatabase::ClassList::fromValueTree (newList);
    activityList.sendClassListChangedMessage (lastComponentList);
}
void CompileEngineChildProcess::handleBuildFailed()
{
    if (errorList.getNumErrors() > 0)
        ProjucerApplication::getCommandManager().invokeDirectly (CommandIDs::showBuildTab, true);
    ProjucerApplication::getCommandManager().commandStatusChanged();
}
void CompileEngineChildProcess::handleChangeCode (const SourceCodeRange& location, const String& newText)
{
    if (Editor* ed = getOrOpenEditorFor (location.file))
    {
        if (ed->flushEditorChanges())
            return; // client-side editor changes were pending, so deal with them first, and discard
                    // the incoming change, whose position may now be wrong.
        ed->document.deleteSection (location.range.getStart(), location.range.getEnd());
        ed->document.insertText (location.range.getStart(), newText);
        // deliberately clear the messages that we just added, to avoid these changes being
        // sent to the server (which will already have processed the same ones locally)
        ed->reset();
        ed->startTransactionTimer();
    }
}
void CompileEngineChildProcess::handlePing()
{
}
//==============================================================================
void CompileEngineChildProcess::setContinuousRebuild (bool b)
{
    continuousRebuild = b;
}
void CompileEngineChildProcess::flushEditorChanges()
{
    for (Editor* ed : editors)
        ed->flushEditorChanges();
}
ProjectContentComponent* CompileEngineChildProcess::findProjectContentComponent() const
{
    for (MainWindow* mw : ProjucerApplication::getApp().mainWindowList.windows)
        if (mw->getProject() == &project)
            return mw->getProjectContentComponent();
    return nullptr;
}
CompileEngineChildProcess::Editor* CompileEngineChildProcess::getOrOpenEditorFor (const File& file)
{
    for (Editor* ed : editors)
        if (ed->file == file)
            return ed;
    if (ProjectContentComponent* pcc = findProjectContentComponent())
        if (pcc->showEditorForFile (file, false))
            return getOrOpenEditorFor (file);
    return nullptr;
}
void CompileEngineChildProcess::handleHighlightCode (const SourceCodeRange& location)
{
    ProjectContentComponent* pcc = findProjectContentComponent();
    if (pcc != nullptr && pcc->showEditorForFile (location.file, false))
    {
        SourceCodeEditor* sce = dynamic_cast <SourceCodeEditor*> (pcc->getEditorComponent());
        if (sce != nullptr && sce->editor != nullptr)
        {
            sce->highlight (location.range, true);
            Process::makeForegroundProcess();
            CodeEditorComponent& ed = *sce->editor;
            ed.getTopLevelComponent()->toFront (false);
            ed.grabKeyboardFocus();
        }
    }
}
void CompileEngineChildProcess::cleanAllCachedFilesForProject (Project& p)
{
    File cacheFolder (ProjectProperties::getCacheLocation (p));
    if (cacheFolder.isDirectory())
        cacheFolder.deleteRecursively();
}
 |