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.

975 lines
31KB

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