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.

947 lines
30KB

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