The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

895 lines
27KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. #include "../Application/jucer_Headers.h"
  14. #include "../Application/jucer_Application.h"
  15. #include "../ProjectSaving/jucer_ProjectExporter.h"
  16. #include "jucer_MessageIDs.h"
  17. #include "jucer_CppHelpers.h"
  18. #include "jucer_SourceCodeRange.h"
  19. #include "jucer_ClassDatabase.h"
  20. #include "jucer_DiagnosticMessage.h"
  21. #include "jucer_ProjectBuildInfo.h"
  22. #include "jucer_ClientServerMessages.h"
  23. #include "jucer_CompileEngineClient.h"
  24. #include "jucer_CompileEngineServer.h"
  25. #include "jucer_CompileEngineSettings.h"
  26. #ifndef RUN_CLANG_IN_CHILD_PROCESS
  27. #error
  28. #endif
  29. //==============================================================================
  30. static File getProjucerTempFolder() noexcept
  31. {
  32. #if JUCE_MAC
  33. return { "~/Library/Caches/com.juce.projucer" };
  34. #else
  35. return File::getSpecialLocation (File::tempDirectory).getChildFile ("com.juce.projucer");
  36. #endif
  37. }
  38. static File getCacheLocationForProject (Project& project) noexcept
  39. {
  40. auto cacheFolderName = project.getProjectFilenameRootString() + "_" + project.getProjectUIDString();
  41. #if JUCE_DEBUG
  42. cacheFolderName += "_debug";
  43. #endif
  44. return getProjucerTempFolder().getChildFile ("Intermediate Files").getChildFile (cacheFolderName);
  45. }
  46. //==============================================================================
  47. class ClientIPC : public MessageHandler,
  48. private InterprocessConnection,
  49. private Timer
  50. {
  51. public:
  52. ClientIPC (CompileEngineChildProcess& cp)
  53. : InterprocessConnection (true), owner (cp)
  54. {
  55. launchServer();
  56. }
  57. ~ClientIPC()
  58. {
  59. #if RUN_CLANG_IN_CHILD_PROCESS
  60. if (childProcess.isRunning())
  61. {
  62. #if JUCE_DEBUG
  63. killServerPolitely();
  64. #else
  65. // in release builds we don't want to wait
  66. // for the server to clean up and shut down
  67. killServerWithoutMercy();
  68. #endif
  69. }
  70. #endif
  71. }
  72. void launchServer()
  73. {
  74. DBG ("Client: Launching Server...");
  75. auto pipeName = "ipc_" + String::toHexString (Random().nextInt64());
  76. auto command = createCommandLineForLaunchingServer (pipeName, owner.project.getProjectUIDString(),
  77. getCacheLocationForProject (owner.project));
  78. #if RUN_CLANG_IN_CHILD_PROCESS
  79. if (! childProcess.start (command))
  80. jassertfalse;
  81. #else
  82. server = createClangServer (command);
  83. #endif
  84. for (int i = 0; i < 20; ++i)
  85. {
  86. if (connectToPipe (pipeName, 10000))
  87. {
  88. MessageTypes::sendPing (*this);
  89. break;
  90. }
  91. Thread::sleep (50);
  92. }
  93. jassert (isConnected());
  94. startTimer (serverKeepAliveTimeout);
  95. }
  96. void killServerPolitely()
  97. {
  98. DBG ("Client: Killing Server...");
  99. MessageTypes::sendQuit (*this);
  100. disconnect();
  101. stopTimer();
  102. #if RUN_CLANG_IN_CHILD_PROCESS
  103. childProcess.waitForProcessToFinish (5000);
  104. #endif
  105. killServerWithoutMercy();
  106. }
  107. void killServerWithoutMercy()
  108. {
  109. disconnect();
  110. stopTimer();
  111. #if RUN_CLANG_IN_CHILD_PROCESS
  112. childProcess.kill();
  113. #else
  114. destroyClangServer (server);
  115. server = nullptr;
  116. #endif
  117. }
  118. void connectionMade()
  119. {
  120. DBG ("Client: connected");
  121. stopTimer();
  122. }
  123. void connectionLost()
  124. {
  125. DBG ("Client: disconnected");
  126. startTimer (100);
  127. }
  128. bool sendMessage (const ValueTree& m)
  129. {
  130. return InterprocessConnection::sendMessage (MessageHandler::convertMessage (m));
  131. }
  132. void messageReceived (const MemoryBlock& message)
  133. {
  134. #if RUN_CLANG_IN_CHILD_PROCESS
  135. startTimer (serverKeepAliveTimeout);
  136. #else
  137. stopTimer();
  138. #endif
  139. MessageTypes::dispatchToClient (owner, MessageHandler::convertMessage (message));
  140. }
  141. enum { serverKeepAliveTimeout = 10000 };
  142. private:
  143. CompileEngineChildProcess& owner;
  144. #if RUN_CLANG_IN_CHILD_PROCESS
  145. ChildProcess childProcess;
  146. #else
  147. void* server;
  148. #endif
  149. void timerCallback()
  150. {
  151. stopTimer();
  152. owner.handleCrash (String());
  153. }
  154. };
  155. //==============================================================================
  156. class CompileEngineChildProcess::ChildProcess : private ValueTree::Listener,
  157. private Timer
  158. {
  159. public:
  160. ChildProcess (CompileEngineChildProcess& proc, Project& p)
  161. : owner (proc), project (p)
  162. {
  163. projectRoot = project.getProjectRoot();
  164. restartServer();
  165. projectRoot.addListener (this);
  166. openedOk = true;
  167. }
  168. ~ChildProcess() override
  169. {
  170. projectRoot.removeListener (this);
  171. if (isRunningApp && server != nullptr)
  172. server->killServerWithoutMercy();
  173. server.reset();
  174. }
  175. void restartServer()
  176. {
  177. server.reset (new ClientIPC (owner));
  178. sendRebuild();
  179. }
  180. void sendRebuild()
  181. {
  182. stopTimer();
  183. ProjectBuildInfo build;
  184. if (! doesProjectMatchSavedHeaderState (project))
  185. {
  186. MessageTypes::sendNewBuild (*server, build);
  187. owner.errorList.resetToError ("Project structure does not match the saved headers! "
  188. "Please re-save your project to enable compilation");
  189. return;
  190. }
  191. if (areAnyModulesMissing (project))
  192. {
  193. MessageTypes::sendNewBuild (*server, build);
  194. owner.errorList.resetToError ("Some of your JUCE modules can't be found! "
  195. "Please check that all the module paths are correct");
  196. return;
  197. }
  198. build.setSystemIncludes (getSystemIncludePaths());
  199. build.setUserIncludes (getUserIncludes());
  200. build.setGlobalDefs (getGlobalDefs());
  201. build.setCompileFlags (project.getCompileEngineSettings().getExtraCompilerFlagsString());
  202. build.setExtraDLLs (getExtraDLLs());
  203. build.setJuceModulesFolder (project.getEnabledModules().getDefaultModulesFolder().getFullPathName());
  204. build.setUtilsCppInclude (project.getAppIncludeFile().getFullPathName());
  205. build.setWindowsTargetPlatformVersion (project.getCompileEngineSettings().getWindowsTargetPlatformVersionString());
  206. scanForProjectFiles (project, build);
  207. owner.updateAllEditors();
  208. MessageTypes::sendNewBuild (*server, build);
  209. }
  210. void cleanAll()
  211. {
  212. MessageTypes::sendCleanAll (*server);
  213. sendRebuild();
  214. }
  215. void reinstantiatePreviews()
  216. {
  217. MessageTypes::sendReinstantiate (*server);
  218. }
  219. bool launchApp()
  220. {
  221. MessageTypes::sendLaunchApp (*server);
  222. return true;
  223. }
  224. std::unique_ptr<ClientIPC> server;
  225. bool openedOk = false;
  226. bool isRunningApp = false;
  227. private:
  228. CompileEngineChildProcess& owner;
  229. Project& project;
  230. ValueTree projectRoot;
  231. void projectStructureChanged()
  232. {
  233. startTimer (100);
  234. }
  235. void timerCallback() override
  236. {
  237. sendRebuild();
  238. }
  239. void valueTreePropertyChanged (ValueTree&, const Identifier&) override { projectStructureChanged(); }
  240. void valueTreeChildAdded (ValueTree&, ValueTree&) override { projectStructureChanged(); }
  241. void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { projectStructureChanged(); }
  242. void valueTreeParentChanged (ValueTree&) override { projectStructureChanged(); }
  243. String getGlobalDefs()
  244. {
  245. StringArray defs;
  246. defs.add (project.getCompileEngineSettings().getExtraPreprocessorDefsString());
  247. {
  248. auto projectDefines = project.getPreprocessorDefs();
  249. for (int i = 0; i < projectDefines.size(); ++i)
  250. {
  251. auto def = projectDefines.getAllKeys()[i];
  252. auto value = projectDefines.getAllValues()[i];
  253. if (value.isNotEmpty())
  254. def << "=" << value;
  255. defs.add (def);
  256. }
  257. }
  258. for (Project::ExporterIterator exporter (project); exporter.next();)
  259. if (exporter->canLaunchProject())
  260. defs.add (exporter->getExporterIdentifierMacro() + "=1");
  261. defs.removeEmptyStrings();
  262. return defs.joinIntoString (" ");
  263. }
  264. static void scanProjectItem (const Project::Item& projectItem, Array<File>& compileUnits, Array<File>& userFiles)
  265. {
  266. if (projectItem.isGroup())
  267. {
  268. for (int i = 0; i < projectItem.getNumChildren(); ++i)
  269. scanProjectItem (projectItem.getChild(i), compileUnits, userFiles);
  270. return;
  271. }
  272. if (projectItem.shouldBeCompiled())
  273. {
  274. auto f = projectItem.getFile();
  275. if (f.exists())
  276. compileUnits.add (f);
  277. }
  278. if (projectItem.shouldBeAddedToTargetProject() && ! projectItem.shouldBeAddedToBinaryResources())
  279. {
  280. auto f = projectItem.getFile();
  281. if (f.exists())
  282. userFiles.add (f);
  283. }
  284. }
  285. void scanForProjectFiles (Project& proj, ProjectBuildInfo& build)
  286. {
  287. Array<File> compileUnits, userFiles;
  288. scanProjectItem (proj.getMainGroup(), compileUnits, userFiles);
  289. {
  290. auto isVSTHost = project.getEnabledModules().isModuleEnabled ("juce_audio_processors")
  291. && (project.isConfigFlagEnabled ("JUCE_PLUGINHOST_VST3", false) || project.isConfigFlagEnabled ("JUCE_PLUGINHOST_VST", false));
  292. auto isPluginProject = proj.isAudioPluginProject();
  293. OwnedArray<LibraryModule> modules;
  294. proj.getEnabledModules().createRequiredModules (modules);
  295. for (Project::ExporterIterator exporter (proj); exporter.next();)
  296. {
  297. if (exporter->canLaunchProject())
  298. {
  299. for (auto* m : modules)
  300. {
  301. auto copyLocally = proj.getEnabledModules().shouldCopyModuleFilesLocally (m->moduleInfo.getID());
  302. auto localModuleFolder = copyLocally ? proj.getLocalModuleFolder (m->moduleInfo.getID())
  303. : m->moduleInfo.getFolder();
  304. m->findAndAddCompiledUnits (*exporter, nullptr, compileUnits,
  305. isPluginProject || isVSTHost ? build_tools::ProjectType::Target::SharedCodeTarget
  306. : build_tools::ProjectType::Target::unspecified);
  307. if (isPluginProject || isVSTHost)
  308. m->findAndAddCompiledUnits (*exporter, nullptr, compileUnits,
  309. build_tools::ProjectType::Target::StandalonePlugIn);
  310. }
  311. break;
  312. }
  313. }
  314. }
  315. for (int i = 0; ; ++i)
  316. {
  317. auto binaryDataCpp = proj.getBinaryDataCppFile (i);
  318. if (! binaryDataCpp.exists())
  319. break;
  320. compileUnits.add (binaryDataCpp);
  321. }
  322. for (auto i = compileUnits.size(); --i >= 0;)
  323. if (compileUnits.getReference(i).hasFileExtension (".r"))
  324. compileUnits.remove (i);
  325. build.setFiles (compileUnits, userFiles);
  326. }
  327. static bool doesProjectMatchSavedHeaderState (Project& project)
  328. {
  329. auto liveModules = project.getProjectRoot().getChildWithName (Ids::MODULES);
  330. if (auto xml = parseXMLIfTagMatches (project.getFile(), Ids::JUCERPROJECT.toString()))
  331. {
  332. auto diskModules = ValueTree::fromXml (*xml).getChildWithName (Ids::MODULES);
  333. return liveModules.isEquivalentTo (diskModules);
  334. }
  335. return false;
  336. }
  337. static bool areAnyModulesMissing (Project& project)
  338. {
  339. OwnedArray<LibraryModule> modules;
  340. project.getEnabledModules().createRequiredModules (modules);
  341. for (auto* module : modules)
  342. if (! module->getFolder().isDirectory())
  343. return true;
  344. return false;
  345. }
  346. StringArray getUserIncludes()
  347. {
  348. StringArray paths;
  349. paths.add (project.getGeneratedCodeFolder().getFullPathName());
  350. paths.addArray (getSearchPathsFromString (project.getCompileEngineSettings().getUserHeaderPathString()));
  351. return convertSearchPathsToAbsolute (paths);
  352. }
  353. StringArray getSystemIncludePaths()
  354. {
  355. StringArray paths;
  356. paths.add (project.getGeneratedCodeFolder().getFullPathName());
  357. paths.addArray (getSearchPathsFromString (project.getCompileEngineSettings().getSystemHeaderPathString()));
  358. auto isVSTHost = project.getEnabledModules().isModuleEnabled ("juce_audio_processors")
  359. && (project.isConfigFlagEnabled ("JUCE_PLUGINHOST_VST3", false)
  360. || project.isConfigFlagEnabled ("JUCE_PLUGINHOST_VST", false));
  361. OwnedArray<LibraryModule> modules;
  362. project.getEnabledModules().createRequiredModules (modules);
  363. for (auto* module : modules)
  364. {
  365. paths.addIfNotAlreadyThere (module->getFolder().getParentDirectory().getFullPathName());
  366. if (module->getID() == "juce_audio_processors" && (project.isAudioPluginProject() || isVSTHost))
  367. paths.addIfNotAlreadyThere (module->getFolder().getChildFile ("format_types").getChildFile ("VST3_SDK").getFullPathName());
  368. }
  369. return convertSearchPathsToAbsolute (paths);
  370. }
  371. StringArray convertSearchPathsToAbsolute (const StringArray& paths) const
  372. {
  373. StringArray s;
  374. const File root (project.getProjectFolder());
  375. for (String p : paths)
  376. s.add (root.getChildFile (p).getFullPathName());
  377. return s;
  378. }
  379. StringArray getExtraDLLs()
  380. {
  381. auto dlls = StringArray::fromTokens (project.getCompileEngineSettings().getExtraDLLsString(), "\n\r,", {});
  382. dlls.trim();
  383. dlls.removeEmptyStrings();
  384. return dlls;
  385. }
  386. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcess)
  387. };
  388. //==============================================================================
  389. CompileEngineChildProcess::CompileEngineChildProcess (Project& p)
  390. : project (p)
  391. {
  392. ProjucerApplication::getApp().openDocumentManager.addListener (this);
  393. createProcess();
  394. errorList.setWarningsEnabled (project.getCompileEngineSettings().areWarningsEnabled());
  395. }
  396. CompileEngineChildProcess::~CompileEngineChildProcess()
  397. {
  398. ProjucerApplication::getApp().openDocumentManager.removeListener (this);
  399. process.reset();
  400. lastComponentList.clear();
  401. }
  402. void CompileEngineChildProcess::createProcess()
  403. {
  404. jassert (process == nullptr);
  405. process.reset (new ChildProcess (*this, project));
  406. if (! process->openedOk)
  407. process.reset();
  408. updateAllEditors();
  409. }
  410. void CompileEngineChildProcess::cleanAll()
  411. {
  412. if (process != nullptr)
  413. process->cleanAll();
  414. }
  415. void CompileEngineChildProcess::openPreview (const ClassDatabase::Class& comp)
  416. {
  417. if (process != nullptr)
  418. {
  419. MainWindow* projectWindow = nullptr;
  420. OwnedArray<MainWindow>& windows = ProjucerApplication::getApp().mainWindowList.windows;
  421. for (int i = 0; i < windows.size(); ++i)
  422. {
  423. if (MainWindow* w = windows[i])
  424. {
  425. if (w->getProject() == &project)
  426. {
  427. projectWindow = w;
  428. break;
  429. }
  430. }
  431. }
  432. Rectangle<int> mainWindowRect;
  433. if (projectWindow != nullptr)
  434. mainWindowRect = projectWindow->getBounds();
  435. MessageTypes::sendOpenPreview (*process->server, comp, mainWindowRect);
  436. }
  437. }
  438. void CompileEngineChildProcess::reinstantiatePreviews()
  439. {
  440. if (process != nullptr)
  441. process->reinstantiatePreviews();
  442. }
  443. void CompileEngineChildProcess::processActivationChanged (bool isForeground)
  444. {
  445. if (process != nullptr)
  446. MessageTypes::sendProcessActivationState (*process->server, isForeground);
  447. }
  448. //==============================================================================
  449. bool CompileEngineChildProcess::canLaunchApp() const
  450. {
  451. return process != nullptr
  452. && runningAppProcess == nullptr
  453. && activityList.getNumActivities() == 0
  454. && errorList.getNumErrors() == 0
  455. && project.getProjectType().isGUIApplication();
  456. }
  457. void CompileEngineChildProcess::launchApp()
  458. {
  459. if (process != nullptr)
  460. process->launchApp();
  461. }
  462. bool CompileEngineChildProcess::canKillApp() const
  463. {
  464. return runningAppProcess != nullptr;
  465. }
  466. void CompileEngineChildProcess::killApp()
  467. {
  468. runningAppProcess.reset();
  469. }
  470. void CompileEngineChildProcess::handleAppLaunched()
  471. {
  472. runningAppProcess.reset (process.release());
  473. runningAppProcess->isRunningApp = true;
  474. createProcess();
  475. }
  476. void CompileEngineChildProcess::handleAppQuit()
  477. {
  478. DBG ("handleAppQuit");
  479. runningAppProcess.reset();
  480. }
  481. bool CompileEngineChildProcess::isAppRunning() const noexcept
  482. {
  483. return runningAppProcess != nullptr && runningAppProcess->isRunningApp;
  484. }
  485. //==============================================================================
  486. struct CompileEngineChildProcess::Editor : private CodeDocument::Listener,
  487. private Timer
  488. {
  489. Editor (CompileEngineChildProcess& ccp, const File& f, CodeDocument& doc)
  490. : owner (ccp), file (f), document (doc), transactionTimer (doc)
  491. {
  492. sendFullUpdate();
  493. document.addListener (this);
  494. }
  495. ~Editor() override
  496. {
  497. document.removeListener (this);
  498. }
  499. void codeDocumentTextInserted (const String& newText, int insertIndex) override
  500. {
  501. CodeChange (Range<int> (insertIndex, insertIndex), newText).addToList (pendingChanges);
  502. startEditorChangeTimer();
  503. transactionTimer.stopTimer();
  504. owner.lastComponentList.globalNamespace
  505. .nudgeAllCodeRanges (file.getFullPathName(), insertIndex, newText.length());
  506. }
  507. void codeDocumentTextDeleted (int start, int end) override
  508. {
  509. CodeChange (Range<int> (start, end), String()).addToList (pendingChanges);
  510. startEditorChangeTimer();
  511. transactionTimer.stopTimer();
  512. owner.lastComponentList.globalNamespace
  513. .nudgeAllCodeRanges (file.getFullPathName(), start, start - end);
  514. }
  515. void sendFullUpdate()
  516. {
  517. reset();
  518. if (owner.process != nullptr)
  519. MessageTypes::sendFileContentFullUpdate (*owner.process->server, file, document.getAllContent());
  520. }
  521. bool flushEditorChanges()
  522. {
  523. if (pendingChanges.size() > 0)
  524. {
  525. if (owner.process != nullptr && owner.process->server != nullptr)
  526. MessageTypes::sendFileChanges (*owner.process->server, pendingChanges, file);
  527. reset();
  528. return true;
  529. }
  530. stopTimer();
  531. return false;
  532. }
  533. void reset()
  534. {
  535. stopTimer();
  536. pendingChanges.clear();
  537. }
  538. void startTransactionTimer()
  539. {
  540. transactionTimer.startTimer (1000);
  541. }
  542. void startEditorChangeTimer()
  543. {
  544. startTimer (200);
  545. }
  546. CompileEngineChildProcess& owner;
  547. File file;
  548. CodeDocument& document;
  549. private:
  550. Array<CodeChange> pendingChanges;
  551. void timerCallback() override
  552. {
  553. if (owner.project.getCompileEngineSettings().isContinuousRebuildEnabled())
  554. flushEditorChanges();
  555. else
  556. stopTimer();
  557. }
  558. struct TransactionTimer : public Timer
  559. {
  560. TransactionTimer (CodeDocument& doc) : document (doc) {}
  561. void timerCallback() override
  562. {
  563. stopTimer();
  564. document.newTransaction();
  565. }
  566. CodeDocument& document;
  567. };
  568. TransactionTimer transactionTimer;
  569. };
  570. void CompileEngineChildProcess::editorOpened (const File& file, CodeDocument& document)
  571. {
  572. editors.add (new Editor (*this, file, document));
  573. }
  574. bool CompileEngineChildProcess::documentAboutToClose (OpenDocumentManager::Document* document)
  575. {
  576. for (int i = editors.size(); --i >= 0;)
  577. {
  578. if (document->getFile() == editors.getUnchecked(i)->file)
  579. {
  580. const File f (editors.getUnchecked(i)->file);
  581. editors.remove (i);
  582. if (process != nullptr)
  583. MessageTypes::sendHandleFileReset (*process->server, f);
  584. }
  585. }
  586. return true;
  587. }
  588. void CompileEngineChildProcess::updateAllEditors()
  589. {
  590. for (int i = editors.size(); --i >= 0;)
  591. editors.getUnchecked(i)->sendFullUpdate();
  592. }
  593. //==============================================================================
  594. void CompileEngineChildProcess::handleCrash (const String& message)
  595. {
  596. Logger::writeToLog ("*** Child process crashed: " + message);
  597. if (crashHandler != nullptr)
  598. crashHandler (message);
  599. }
  600. void CompileEngineChildProcess::handleNewDiagnosticList (const ValueTree& l) { errorList.setList (l); }
  601. void CompileEngineChildProcess::handleActivityListChanged (const StringArray& l) { activityList.setList (l); }
  602. void CompileEngineChildProcess::handleCloseIDE()
  603. {
  604. if (JUCEApplication* app = JUCEApplication::getInstance())
  605. app->systemRequestedQuit();
  606. }
  607. void CompileEngineChildProcess::handleMissingSystemHeaders()
  608. {
  609. if (ProjectContentComponent* p = findProjectContentComponent())
  610. p->handleMissingSystemHeaders();
  611. }
  612. void CompileEngineChildProcess::handleKeyPress (const String& className, const KeyPress& key)
  613. {
  614. ApplicationCommandManager& commandManager = ProjucerApplication::getCommandManager();
  615. CommandID command = commandManager.getKeyMappings()->findCommandForKeyPress (key);
  616. if (command == StandardApplicationCommandIDs::undo)
  617. {
  618. handleUndoInEditor (className);
  619. }
  620. else if (command == StandardApplicationCommandIDs::redo)
  621. {
  622. handleRedoInEditor (className);
  623. }
  624. else if (ApplicationCommandTarget* const target = ApplicationCommandManager::findTargetForComponent (findProjectContentComponent()))
  625. {
  626. commandManager.setFirstCommandTarget (target);
  627. commandManager.getKeyMappings()->keyPressed (key, findProjectContentComponent());
  628. commandManager.setFirstCommandTarget (nullptr);
  629. }
  630. }
  631. void CompileEngineChildProcess::handleUndoInEditor (const String& /*className*/)
  632. {
  633. }
  634. void CompileEngineChildProcess::handleRedoInEditor (const String& /*className*/)
  635. {
  636. }
  637. void CompileEngineChildProcess::handleClassListChanged (const ValueTree& newList)
  638. {
  639. lastComponentList = ClassDatabase::ClassList::fromValueTree (newList);
  640. activityList.sendClassListChangedMessage (lastComponentList);
  641. }
  642. void CompileEngineChildProcess::handleBuildFailed()
  643. {
  644. ProjucerApplication::getCommandManager().commandStatusChanged();
  645. }
  646. void CompileEngineChildProcess::handleChangeCode (const SourceCodeRange& location, const String& newText)
  647. {
  648. if (Editor* ed = getOrOpenEditorFor (location.file))
  649. {
  650. if (ed->flushEditorChanges())
  651. return; // client-side editor changes were pending, so deal with them first, and discard
  652. // the incoming change, whose position may now be wrong.
  653. ed->document.deleteSection (location.range.getStart(), location.range.getEnd());
  654. ed->document.insertText (location.range.getStart(), newText);
  655. // deliberately clear the messages that we just added, to avoid these changes being
  656. // sent to the server (which will already have processed the same ones locally)
  657. ed->reset();
  658. ed->startTransactionTimer();
  659. }
  660. }
  661. void CompileEngineChildProcess::handlePing()
  662. {
  663. }
  664. //==============================================================================
  665. void CompileEngineChildProcess::flushEditorChanges()
  666. {
  667. for (Editor* ed : editors)
  668. ed->flushEditorChanges();
  669. }
  670. ProjectContentComponent* CompileEngineChildProcess::findProjectContentComponent() const
  671. {
  672. for (MainWindow* mw : ProjucerApplication::getApp().mainWindowList.windows)
  673. if (mw->getProject() == &project)
  674. return mw->getProjectContentComponent();
  675. return nullptr;
  676. }
  677. CompileEngineChildProcess::Editor* CompileEngineChildProcess::getOrOpenEditorFor (const File& file)
  678. {
  679. for (Editor* ed : editors)
  680. if (ed->file == file)
  681. return ed;
  682. if (ProjectContentComponent* pcc = findProjectContentComponent())
  683. if (pcc->showEditorForFile (file, false))
  684. return getOrOpenEditorFor (file);
  685. return nullptr;
  686. }
  687. void CompileEngineChildProcess::handleHighlightCode (const SourceCodeRange& location)
  688. {
  689. ProjectContentComponent* pcc = findProjectContentComponent();
  690. if (pcc != nullptr && pcc->showEditorForFile (location.file, false))
  691. {
  692. SourceCodeEditor* sce = dynamic_cast <SourceCodeEditor*> (pcc->getEditorComponent());
  693. if (sce != nullptr && sce->editor != nullptr)
  694. {
  695. sce->highlight (location.range, true);
  696. Process::makeForegroundProcess();
  697. CodeEditorComponent& ed = *sce->editor;
  698. ed.getTopLevelComponent()->toFront (false);
  699. ed.grabKeyboardFocus();
  700. }
  701. }
  702. }
  703. void CompileEngineChildProcess::cleanAllCachedFilesForProject (Project& p)
  704. {
  705. File cacheFolder (getCacheLocationForProject (p));
  706. if (cacheFolder.isDirectory())
  707. cacheFolder.deleteRecursively();
  708. }