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.

536 lines
18KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "jucer_SourceCodeEditor.h"
  19. #include "../Application/jucer_OpenDocumentManager.h"
  20. //==============================================================================
  21. SourceCodeDocument::SourceCodeDocument (Project* p, const File& f)
  22. : modDetector (f), project (p)
  23. {
  24. }
  25. CodeDocument& SourceCodeDocument::getCodeDocument()
  26. {
  27. if (codeDoc == nullptr)
  28. {
  29. codeDoc = new CodeDocument();
  30. reloadInternal();
  31. codeDoc->clearUndoHistory();
  32. }
  33. return *codeDoc;
  34. }
  35. Component* SourceCodeDocument::createEditor()
  36. {
  37. SourceCodeEditor* e = new SourceCodeEditor (this);
  38. e->createEditor (getCodeDocument());
  39. applyLastState (*(e->editor));
  40. return e;
  41. }
  42. void SourceCodeDocument::reloadFromFile()
  43. {
  44. getCodeDocument();
  45. reloadInternal();
  46. }
  47. void SourceCodeDocument::reloadInternal()
  48. {
  49. jassert (codeDoc != nullptr);
  50. modDetector.updateHash();
  51. codeDoc->applyChanges (modDetector.getFile().loadFileAsString());
  52. codeDoc->setSavePoint();
  53. }
  54. bool SourceCodeDocument::save()
  55. {
  56. TemporaryFile temp (modDetector.getFile());
  57. {
  58. FileOutputStream fo (temp.getFile());
  59. if (! (fo.openedOk() && getCodeDocument().writeToStream (fo)))
  60. return false;
  61. }
  62. if (! temp.overwriteTargetFileWithTemporary())
  63. return false;
  64. getCodeDocument().setSavePoint();
  65. modDetector.updateHash();
  66. return true;
  67. }
  68. void SourceCodeDocument::updateLastState (CodeEditorComponent& editor)
  69. {
  70. lastState = new CodeEditorComponent::State (editor);
  71. }
  72. void SourceCodeDocument::applyLastState (CodeEditorComponent& editor) const
  73. {
  74. if (lastState != nullptr)
  75. lastState->restoreState (editor);
  76. }
  77. //==============================================================================
  78. SourceCodeEditor::SourceCodeEditor (OpenDocumentManager::Document* doc)
  79. : DocumentEditorComponent (doc)
  80. {
  81. }
  82. SourceCodeEditor::~SourceCodeEditor()
  83. {
  84. getAppSettings().appearance.settings.removeListener (this);
  85. if (SourceCodeDocument* doc = dynamic_cast <SourceCodeDocument*> (getDocument()))
  86. doc->updateLastState (*editor);
  87. }
  88. void SourceCodeEditor::createEditor (CodeDocument& codeDocument)
  89. {
  90. if (document->getFile().hasFileExtension (sourceOrHeaderFileExtensions))
  91. setEditor (new CppCodeEditorComponent (document->getFile(), codeDocument));
  92. else
  93. setEditor (new GenericCodeEditorComponent (document->getFile(), codeDocument, nullptr));
  94. }
  95. void SourceCodeEditor::setEditor (CodeEditorComponent* newEditor)
  96. {
  97. addAndMakeVisible (editor = newEditor);
  98. editor->setFont (AppearanceSettings::getDefaultCodeFont());
  99. editor->setTabSize (4, true);
  100. updateColourScheme();
  101. getAppSettings().appearance.settings.addListener (this);
  102. }
  103. void SourceCodeEditor::scrollToKeepRangeOnScreen (const Range<int>& range)
  104. {
  105. const int space = jmin (10, editor->getNumLinesOnScreen() / 3);
  106. const CodeDocument::Position start (editor->getDocument(), range.getStart());
  107. const CodeDocument::Position end (editor->getDocument(), range.getEnd());
  108. editor->scrollToKeepLinesOnScreen (Range<int> (start.getLineNumber() - space, end.getLineNumber() + space));
  109. }
  110. void SourceCodeEditor::highlight (const Range<int>& range, bool cursorAtStart)
  111. {
  112. scrollToKeepRangeOnScreen (range);
  113. if (cursorAtStart)
  114. {
  115. editor->moveCaretTo (CodeDocument::Position (editor->getDocument(), range.getEnd()), false);
  116. editor->moveCaretTo (CodeDocument::Position (editor->getDocument(), range.getStart()), true);
  117. }
  118. else
  119. {
  120. editor->setHighlightedRegion (range);
  121. }
  122. }
  123. void SourceCodeEditor::resized()
  124. {
  125. editor->setBounds (getLocalBounds());
  126. }
  127. void SourceCodeEditor::updateColourScheme() { getAppSettings().appearance.applyToCodeEditor (*editor); }
  128. void SourceCodeEditor::valueTreePropertyChanged (ValueTree&, const Identifier&) { updateColourScheme(); }
  129. void SourceCodeEditor::valueTreeChildAdded (ValueTree&, ValueTree&) { updateColourScheme(); }
  130. void SourceCodeEditor::valueTreeChildRemoved (ValueTree&, ValueTree&) { updateColourScheme(); }
  131. void SourceCodeEditor::valueTreeChildOrderChanged (ValueTree&) { updateColourScheme(); }
  132. void SourceCodeEditor::valueTreeParentChanged (ValueTree&) { updateColourScheme(); }
  133. void SourceCodeEditor::valueTreeRedirected (ValueTree&) { updateColourScheme(); }
  134. //==============================================================================
  135. GenericCodeEditorComponent::GenericCodeEditorComponent (const File& f, CodeDocument& codeDocument,
  136. CodeTokeniser* tokeniser)
  137. : CodeEditorComponent (codeDocument, tokeniser), file (f)
  138. {
  139. setCommandManager (commandManager);
  140. }
  141. GenericCodeEditorComponent::~GenericCodeEditorComponent() {}
  142. enum { showInFinderID = 0x2fe821e3 };
  143. void GenericCodeEditorComponent::addPopupMenuItems (PopupMenu& menu, const MouseEvent* e)
  144. {
  145. menu.addItem (showInFinderID,
  146. #if JUCE_MAC
  147. "Reveal " + file.getFileName() + " in Finder");
  148. #else
  149. "Reveal " + file.getFileName() + " in Explorer");
  150. #endif
  151. menu.addSeparator();
  152. CodeEditorComponent::addPopupMenuItems (menu, e);
  153. }
  154. void GenericCodeEditorComponent::performPopupMenuAction (int menuItemID)
  155. {
  156. if (menuItemID == showInFinderID)
  157. file.revealToUser();
  158. else
  159. CodeEditorComponent::performPopupMenuAction (menuItemID);
  160. }
  161. void GenericCodeEditorComponent::getAllCommands (Array <CommandID>& commands)
  162. {
  163. CodeEditorComponent::getAllCommands (commands);
  164. const CommandID ids[] = { CommandIDs::showFindPanel,
  165. CommandIDs::findSelection,
  166. CommandIDs::findNext,
  167. CommandIDs::findPrevious };
  168. commands.addArray (ids, numElementsInArray (ids));
  169. }
  170. void GenericCodeEditorComponent::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
  171. {
  172. const bool anythingSelected = isHighlightActive();
  173. switch (commandID)
  174. {
  175. case CommandIDs::showFindPanel:
  176. result.setInfo (TRANS ("Find"), TRANS ("Searches for text in the current document."), "Editing", 0);
  177. result.defaultKeypresses.add (KeyPress ('f', ModifierKeys::commandModifier, 0));
  178. break;
  179. case CommandIDs::findSelection:
  180. result.setInfo (TRANS ("Find Selection"), TRANS ("Searches for the currently selected text."), "Editing", 0);
  181. result.setActive (anythingSelected);
  182. result.defaultKeypresses.add (KeyPress ('l', ModifierKeys::commandModifier, 0));
  183. break;
  184. case CommandIDs::findNext:
  185. result.setInfo (TRANS ("Find Next"), TRANS ("Searches for the next occurrence of the current search-term."), "Editing", 0);
  186. result.defaultKeypresses.add (KeyPress ('g', ModifierKeys::commandModifier, 0));
  187. break;
  188. case CommandIDs::findPrevious:
  189. result.setInfo (TRANS ("Find Previous"), TRANS ("Searches for the previous occurrence of the current search-term."), "Editing", 0);
  190. result.defaultKeypresses.add (KeyPress ('g', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0));
  191. result.defaultKeypresses.add (KeyPress ('d', ModifierKeys::commandModifier, 0));
  192. break;
  193. default:
  194. CodeEditorComponent::getCommandInfo (commandID, result);
  195. break;
  196. }
  197. }
  198. bool GenericCodeEditorComponent::perform (const InvocationInfo& info)
  199. {
  200. switch (info.commandID)
  201. {
  202. case CommandIDs::showFindPanel: showFindPanel(); return true;
  203. case CommandIDs::findSelection: findSelection(); return true;
  204. case CommandIDs::findNext: findNext (true, true); return true;
  205. case CommandIDs::findPrevious: findNext (false, false); return true;
  206. default: break;
  207. }
  208. return CodeEditorComponent::perform (info);
  209. }
  210. //==============================================================================
  211. class GenericCodeEditorComponent::FindPanel : public Component,
  212. private TextEditor::Listener,
  213. private Button::Listener
  214. {
  215. public:
  216. FindPanel()
  217. : caseButton ("Case-sensitive"),
  218. findPrev ("<"),
  219. findNext (">")
  220. {
  221. editor.setColour (CaretComponent::caretColourId, Colours::black);
  222. addAndMakeVisible (&editor);
  223. label.setText ("Find:", false);
  224. label.setColour (Label::textColourId, Colours::white);
  225. label.attachToComponent (&editor, false);
  226. addAndMakeVisible (&caseButton);
  227. caseButton.setColour (ToggleButton::textColourId, Colours::white);
  228. caseButton.setToggleState (isCaseSensitiveSearch(), false);
  229. caseButton.addListener (this);
  230. findPrev.setConnectedEdges (Button::ConnectedOnRight);
  231. findNext.setConnectedEdges (Button::ConnectedOnLeft);
  232. addAndMakeVisible (&findPrev);
  233. addAndMakeVisible (&findNext);
  234. setWantsKeyboardFocus (false);
  235. setFocusContainer (true);
  236. findPrev.setWantsKeyboardFocus (false);
  237. findNext.setWantsKeyboardFocus (false);
  238. editor.setText (getSearchString());
  239. editor.addListener (this);
  240. }
  241. void setCommandManager (ApplicationCommandManager* cm)
  242. {
  243. findPrev.setCommandToTrigger (cm, CommandIDs::findPrevious, true);
  244. findNext.setCommandToTrigger (cm, CommandIDs::findNext, true);
  245. }
  246. void paint (Graphics& g)
  247. {
  248. Path outline;
  249. outline.addRoundedRectangle (1.0f, 1.0f, getWidth() - 2.0f, getHeight() - 2.0f, 8.0f);
  250. g.setColour (Colours::black.withAlpha (0.6f));
  251. g.fillPath (outline);
  252. g.setColour (Colours::white.withAlpha (0.8f));
  253. g.strokePath (outline, PathStrokeType (1.0f));
  254. }
  255. void resized()
  256. {
  257. int y = 30;
  258. editor.setBounds (10, y, getWidth() - 20, 24);
  259. y += 30;
  260. caseButton.setBounds (10, y, getWidth() / 2 - 10, 22);
  261. findNext.setBounds (getWidth() - 40, y, 30, 22);
  262. findPrev.setBounds (getWidth() - 70, y, 30, 22);
  263. }
  264. void buttonClicked (Button*)
  265. {
  266. setCaseSensitiveSearch (caseButton.getToggleState());
  267. }
  268. void textEditorTextChanged (TextEditor&)
  269. {
  270. setSearchString (editor.getText());
  271. if (CppCodeEditorComponent* ed = getOwner())
  272. ed->findNext (true, false);
  273. }
  274. void textEditorFocusLost (TextEditor&) {}
  275. void textEditorReturnKeyPressed (TextEditor&)
  276. {
  277. commandManager->invokeDirectly (CommandIDs::findNext, true);
  278. }
  279. void textEditorEscapeKeyPressed (TextEditor&)
  280. {
  281. if (CppCodeEditorComponent* ed = getOwner())
  282. ed->hideFindPanel();
  283. }
  284. CppCodeEditorComponent* getOwner() const
  285. {
  286. return findParentComponentOfClass <CppCodeEditorComponent>();
  287. }
  288. TextEditor editor;
  289. Label label;
  290. ToggleButton caseButton;
  291. TextButton findPrev, findNext;
  292. };
  293. void GenericCodeEditorComponent::resized()
  294. {
  295. CodeEditorComponent::resized();
  296. if (findPanel != nullptr)
  297. {
  298. findPanel->setSize (jmin (260, getWidth() - 32), 100);
  299. findPanel->setTopRightPosition (getWidth() - 16, 8);
  300. }
  301. }
  302. void GenericCodeEditorComponent::showFindPanel()
  303. {
  304. if (findPanel == nullptr)
  305. {
  306. findPanel = new FindPanel();
  307. findPanel->setCommandManager (commandManager);
  308. addAndMakeVisible (findPanel);
  309. resized();
  310. }
  311. findPanel->editor.grabKeyboardFocus();
  312. findPanel->editor.selectAll();
  313. }
  314. void GenericCodeEditorComponent::hideFindPanel()
  315. {
  316. findPanel = nullptr;
  317. }
  318. void GenericCodeEditorComponent::findSelection()
  319. {
  320. const String selected (getTextInRange (getHighlightedRegion()));
  321. if (selected.isNotEmpty())
  322. {
  323. setSearchString (selected);
  324. findNext (true, true);
  325. }
  326. }
  327. void GenericCodeEditorComponent::findNext (bool forwards, bool skipCurrentSelection)
  328. {
  329. const Range<int> highlight (getHighlightedRegion());
  330. const CodeDocument::Position startPos (getDocument(), skipCurrentSelection ? highlight.getEnd()
  331. : highlight.getStart());
  332. int lineNum = startPos.getLineNumber();
  333. int linePos = startPos.getIndexInLine();
  334. const int totalLines = getDocument().getNumLines();
  335. const String searchText (getSearchString());
  336. const bool caseSensitive = isCaseSensitiveSearch();
  337. for (int linesToSearch = totalLines; --linesToSearch >= 0;)
  338. {
  339. String line (getDocument().getLine (lineNum));
  340. int index;
  341. if (forwards)
  342. {
  343. index = caseSensitive ? line.indexOf (linePos, searchText)
  344. : line.indexOfIgnoreCase (linePos, searchText);
  345. }
  346. else
  347. {
  348. if (linePos >= 0)
  349. line = line.substring (0, linePos);
  350. index = caseSensitive ? line.lastIndexOf (searchText)
  351. : line.lastIndexOfIgnoreCase (searchText);
  352. }
  353. if (index >= 0)
  354. {
  355. const CodeDocument::Position p (getDocument(), lineNum, index);
  356. selectRegion (p, p.movedBy (searchText.length()));
  357. break;
  358. }
  359. if (forwards)
  360. {
  361. linePos = 0;
  362. lineNum = (lineNum + 1) % totalLines;
  363. }
  364. else
  365. {
  366. if (--lineNum < 0)
  367. lineNum = totalLines - 1;
  368. linePos = -1;
  369. }
  370. }
  371. }
  372. void GenericCodeEditorComponent::handleEscapeKey()
  373. {
  374. CodeEditorComponent::handleEscapeKey();
  375. hideFindPanel();
  376. }
  377. //==============================================================================
  378. static CPlusPlusCodeTokeniser cppTokeniser;
  379. CppCodeEditorComponent::CppCodeEditorComponent (const File& f, CodeDocument& doc)
  380. : GenericCodeEditorComponent (f, doc, &cppTokeniser)
  381. {
  382. }
  383. CppCodeEditorComponent::~CppCodeEditorComponent() {}
  384. void CppCodeEditorComponent::handleReturnKey()
  385. {
  386. GenericCodeEditorComponent::handleReturnKey();
  387. CodeDocument::Position pos (getCaretPos());
  388. String blockIndent, lastLineIndent;
  389. CodeHelpers::getIndentForCurrentBlock (pos, getTabString (getTabSize()), blockIndent, lastLineIndent);
  390. const String remainderOfBrokenLine (pos.getLineText());
  391. const int numLeadingWSChars = CodeHelpers::getLeadingWhitespace (remainderOfBrokenLine).length();
  392. if (numLeadingWSChars > 0)
  393. getDocument().deleteSection (pos, pos.movedBy (numLeadingWSChars));
  394. if (remainderOfBrokenLine.trimStart().startsWithChar ('}'))
  395. insertTextAtCaret (blockIndent);
  396. else
  397. insertTextAtCaret (lastLineIndent);
  398. const String previousLine (pos.movedByLines (-1).getLineText());
  399. const String trimmedPreviousLine (previousLine.trim());
  400. if ((trimmedPreviousLine.startsWith ("if ")
  401. || trimmedPreviousLine.startsWith ("if(")
  402. || trimmedPreviousLine.startsWith ("for ")
  403. || trimmedPreviousLine.startsWith ("for(")
  404. || trimmedPreviousLine.startsWith ("while(")
  405. || trimmedPreviousLine.startsWith ("while "))
  406. && trimmedPreviousLine.endsWithChar (')'))
  407. {
  408. insertTabAtCaret();
  409. }
  410. }
  411. void CppCodeEditorComponent::insertTextAtCaret (const String& newText)
  412. {
  413. if (getHighlightedRegion().isEmpty())
  414. {
  415. const CodeDocument::Position pos (getCaretPos());
  416. if ((newText == "{" || newText == "}")
  417. && pos.getLineNumber() > 0
  418. && pos.getLineText().trim().isEmpty())
  419. {
  420. moveCaretToStartOfLine (true);
  421. String blockIndent, lastLineIndent;
  422. if (CodeHelpers::getIndentForCurrentBlock (pos, getTabString (getTabSize()), blockIndent, lastLineIndent))
  423. {
  424. GenericCodeEditorComponent::insertTextAtCaret (blockIndent);
  425. if (newText == "{")
  426. insertTabAtCaret();
  427. }
  428. }
  429. }
  430. GenericCodeEditorComponent::insertTextAtCaret (newText);
  431. }