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.

973 lines
32KB

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