diff --git a/extras/Introjucer/Source/Application/jucer_Application.h b/extras/Introjucer/Source/Application/jucer_Application.h index c783b23dfa..7173ed290c 100644 --- a/extras/Introjucer/Source/Application/jucer_Application.h +++ b/extras/Introjucer/Source/Application/jucer_Application.h @@ -183,8 +183,8 @@ public: } else if (topLevelMenuIndex == 1) // "Edit" menu { - menu.addCommandItem (commandManager, CommandIDs::undo); - menu.addCommandItem (commandManager, CommandIDs::redo); + menu.addCommandItem (commandManager, StandardApplicationCommandIDs::undo); + menu.addCommandItem (commandManager, StandardApplicationCommandIDs::redo); menu.addSeparator(); menu.addCommandItem (commandManager, StandardApplicationCommandIDs::cut); menu.addCommandItem (commandManager, StandardApplicationCommandIDs::copy); diff --git a/extras/Introjucer/Source/Application/jucer_CommandIDs.h b/extras/Introjucer/Source/Application/jucer_CommandIDs.h index 9bc51cb728..81fd742a67 100644 --- a/extras/Introjucer/Source/Application/jucer_CommandIDs.h +++ b/extras/Introjucer/Source/Application/jucer_CommandIDs.h @@ -43,8 +43,6 @@ namespace CommandIDs static const int showAppearanceSettings = 0x200077; static const int saveAll = 0x200080; - static const int undo = 0x200090; - static const int redo = 0x2000a0; static const int closeWindow = 0x201001; static const int closeAllDocuments = 0x201000; diff --git a/extras/Introjucer/Source/Application/jucer_OpenDocumentManager.cpp b/extras/Introjucer/Source/Application/jucer_OpenDocumentManager.cpp index cea504a456..d418e84376 100644 --- a/extras/Introjucer/Source/Application/jucer_OpenDocumentManager.cpp +++ b/extras/Introjucer/Source/Application/jucer_OpenDocumentManager.cpp @@ -28,7 +28,7 @@ #include "../Code Editor/jucer_SourceCodeEditor.h" //============================================================================== -Component* SourceCodeDocument::createEditor() { return SourceCodeEditor::createFor (this, codeDoc); } +Component* SourceCodeDocument::createEditor() { return new SourceCodeEditor (this, codeDoc); } //============================================================================== diff --git a/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.cpp b/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.cpp index f00a974599..1ec6567adf 100644 --- a/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.cpp +++ b/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.cpp @@ -29,23 +29,21 @@ //============================================================================== SourceCodeEditor::SourceCodeEditor (OpenDocumentManager::Document* document_, - CodeDocument& codeDocument, - CodeTokeniser* const codeTokeniser) - : DocumentEditorComponent (document_), - editor (codeDocument, codeTokeniser) + CodeDocument& codeDocument) + : DocumentEditorComponent (document_) { - addAndMakeVisible (&editor); + addAndMakeVisible (editor = createEditor (codeDocument)); #if JUCE_MAC Font font (13.0f); font.setTypefaceName ("Menlo"); #else - Font font (10.0f); + Font font (12.0f); font.setTypefaceName (Font::getDefaultMonospacedFontName()); #endif - editor.setFont (font); + editor->setFont (font); - editor.setTabSize (4, true); + editor->setTabSize (4, true); updateColourScheme(); getAppSettings().appearance.settings.addListener (this); @@ -56,36 +54,25 @@ SourceCodeEditor::~SourceCodeEditor() getAppSettings().appearance.settings.removeListener (this); } -void SourceCodeEditor::resized() +CodeEditorComponent* SourceCodeEditor::createEditor (CodeDocument& codeDocument) { - editor.setBounds (getLocalBounds()); -} + if (document->getFile().hasFileExtension (sourceOrHeaderFileExtensions)) + return new CppCodeEditorComponent (codeDocument); -CodeTokeniser* SourceCodeEditor::getTokeniserFor (const File& file) -{ - if (file.hasFileExtension (sourceOrHeaderFileExtensions)) - { - static CPlusPlusCodeTokeniser cppTokeniser; - return &cppTokeniser; - } - - return nullptr; + return new CodeEditorComponent (codeDocument, nullptr); } -SourceCodeEditor* SourceCodeEditor::createFor (OpenDocumentManager::Document* document, - CodeDocument& codeDocument) +//============================================================================== +void SourceCodeEditor::resized() { - return new SourceCodeEditor (document, codeDocument, getTokeniserFor (document->getFile())); + editor->setBounds (getLocalBounds()); } +void SourceCodeEditor::updateColourScheme() { getAppSettings().appearance.applyToCodeEditor (*editor); } + void SourceCodeEditor::valueTreePropertyChanged (ValueTree&, const Identifier&) { updateColourScheme(); } void SourceCodeEditor::valueTreeChildAdded (ValueTree&, ValueTree&) { updateColourScheme(); } void SourceCodeEditor::valueTreeChildRemoved (ValueTree&, ValueTree&) { updateColourScheme(); } void SourceCodeEditor::valueTreeChildOrderChanged (ValueTree&) { updateColourScheme(); } void SourceCodeEditor::valueTreeParentChanged (ValueTree&) { updateColourScheme(); } void SourceCodeEditor::valueTreeRedirected (ValueTree&) { updateColourScheme(); } - -void SourceCodeEditor::updateColourScheme() -{ - getAppSettings().appearance.applyToCodeEditor (editor); -} diff --git a/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.h b/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.h index bafd8889aa..119e141ee6 100644 --- a/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.h +++ b/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.h @@ -34,24 +34,16 @@ class SourceCodeEditor : public DocumentEditorComponent, private ValueTree::Listener { public: - //============================================================================== SourceCodeEditor (OpenDocumentManager::Document* document, - CodeDocument& codeDocument, - CodeTokeniser* const codeTokeniser); - + CodeDocument& codeDocument); ~SourceCodeEditor(); - static SourceCodeEditor* createFor (OpenDocumentManager::Document* document, - CodeDocument& codeDocument); - - static CodeTokeniser* getTokeniserFor (const File& file); +private: + ScopedPointer editor; - //============================================================================== + CodeEditorComponent* createEditor (CodeDocument&); void resized(); - CodeEditorComponent editor; - -private: void valueTreePropertyChanged (ValueTree&, const Identifier&); void valueTreeChildAdded (ValueTree&, ValueTree&); void valueTreeChildRemoved (ValueTree&, ValueTree&); @@ -65,4 +57,88 @@ private: }; +//============================================================================== +class CppCodeEditorComponent : public CodeEditorComponent +{ +public: + CppCodeEditorComponent (CodeDocument& codeDocument) + : CodeEditorComponent (codeDocument, getCppTokeniser()) + { + } + + void handleReturnKey() + { + CodeEditorComponent::handleReturnKey(); + + const CodeDocument::Position pos (getCaretPos()); + + if (pos.getLineNumber() > 0 && pos.getLineText().trim().isEmpty()) + { + String indent (getIndentForCurrentBlock (pos)); + const String previousLine (pos.movedByLines (-1).getLineText()); + const String trimmedPreviousLine (previousLine.trim()); + const String leadingWhitespace (getLeadingWhitespace (previousLine)); + + insertTextAtCaret (leadingWhitespace); + + if (trimmedPreviousLine.endsWithChar ('{') + || ((trimmedPreviousLine.startsWith ("if ") + || trimmedPreviousLine.startsWith ("for ") + || trimmedPreviousLine.startsWith ("while ")) + && trimmedPreviousLine.endsWithChar (')'))) + insertTabAtCaret(); + } + } + + void insertTextAtCaret (const String& newText) + { + if (getHighlightedRegion().isEmpty()) + { + const CodeDocument::Position pos (getCaretPos()); + + if ((newText == "{" || newText == "}") && pos.getLineNumber() > 0) + { + moveCaretToStartOfLine (true); + CodeEditorComponent::insertTextAtCaret (getIndentForCurrentBlock (pos)); + + if (newText == "{") + insertTabAtCaret(); + } + } + + CodeEditorComponent::insertTextAtCaret (newText); + } + +private: + static CPlusPlusCodeTokeniser* getCppTokeniser() + { + static CPlusPlusCodeTokeniser cppTokeniser; + return &cppTokeniser; + } + + static String getLeadingWhitespace (String line) + { + line = line.removeCharacters ("\r\n"); + const String::CharPointerType endOfLeadingWS (line.getCharPointer().findEndOfWhitespace()); + return String (line.getCharPointer(), endOfLeadingWS); + } + + static String getIndentForCurrentBlock (CodeDocument::Position pos) + { + while (pos.getLineNumber() > 0) + { + pos = pos.movedByLines (-1); + + const String line (pos.getLineText()); + const String trimmedLine (line.trimStart()); + + if (trimmedLine.startsWithChar ('{')) + return getLeadingWhitespace (line); + } + + return String::empty; + } +}; + + #endif // __JUCER_SOURCECODEEDITOR_JUCEHEADER__ diff --git a/modules/juce_gui_basics/commands/juce_ApplicationCommandID.h b/modules/juce_gui_basics/commands/juce_ApplicationCommandID.h index 8572305d46..9c3d8c9899 100644 --- a/modules/juce_gui_basics/commands/juce_ApplicationCommandID.h +++ b/modules/juce_gui_basics/commands/juce_ApplicationCommandID.h @@ -77,6 +77,12 @@ namespace StandardApplicationCommandIDs /** The command ID that should be used to send a "Deselect all" command. */ static const CommandID deselectAll = 0x1007; + + /** The command ID that should be used to send a "undo" command. */ + static const CommandID undo = 0x1008; + + /** The command ID that should be used to send a "redo" command. */ + static const CommandID redo = 0x1009; } diff --git a/modules/juce_gui_basics/widgets/juce_TextEditor.cpp b/modules/juce_gui_basics/widgets/juce_TextEditor.cpp index 92acbc473c..479824336c 100644 --- a/modules/juce_gui_basics/widgets/juce_TextEditor.cpp +++ b/modules/juce_gui_basics/widgets/juce_TextEditor.cpp @@ -1755,12 +1755,51 @@ void TextEditor::paintOverChildren (Graphics& g) } //============================================================================== +void TextEditor::addPopupMenuItems (PopupMenu& m, const MouseEvent*) +{ + const bool writable = ! isReadOnly(); + + if (passwordCharacter == 0) + { + m.addItem (StandardApplicationCommandIDs::cut, TRANS("Cut"), writable); + m.addItem (StandardApplicationCommandIDs::copy, TRANS("Copy"), ! selection.isEmpty()); + m.addItem (StandardApplicationCommandIDs::paste, TRANS("Paste"), writable); + } + + m.addItem (StandardApplicationCommandIDs::del, TRANS("Delete"), writable); + m.addSeparator(); + m.addItem (StandardApplicationCommandIDs::selectAll, TRANS("Select All")); + m.addSeparator(); + + if (getUndoManager() != nullptr) + { + m.addItem (StandardApplicationCommandIDs::undo, TRANS("Undo"), undoManager.canUndo()); + m.addItem (StandardApplicationCommandIDs::redo, TRANS("Redo"), undoManager.canRedo()); + } +} + +void TextEditor::performPopupMenuAction (const int menuItemID) +{ + switch (menuItemID) + { + case StandardApplicationCommandIDs::cut: cutToClipboard(); break; + case StandardApplicationCommandIDs::copy: copyToClipboard(); break; + case StandardApplicationCommandIDs::paste: pasteFromClipboard(); break; + case StandardApplicationCommandIDs::del: cut(); break; + case StandardApplicationCommandIDs::selectAll: selectAll(); break; + case StandardApplicationCommandIDs::undo: undo(); break; + case StandardApplicationCommandIDs::redo: redo(); break; + default: break; + } +} + static void textEditorMenuCallback (int menuResult, TextEditor* editor) { if (editor != nullptr && menuResult != 0) editor->performPopupMenuAction (menuResult); } +//============================================================================== void TextEditor::mouseDown (const MouseEvent& e) { beginDragAutoRepeat (100); @@ -1788,12 +1827,8 @@ void TextEditor::mouseDown (const MouseEvent& e) void TextEditor::mouseDrag (const MouseEvent& e) { if (wasFocused || ! selectAllTextWhenFocused) - { if (! (popupMenuEnabled && e.mods.isPopupMenu())) - { moveCaretTo (getTextIndexAt (e.x, e.y), true); - } - } } void TextEditor::mouseUp (const MouseEvent& e) @@ -1802,12 +1837,8 @@ void TextEditor::mouseUp (const MouseEvent& e) textHolder->restartTimer(); if (wasFocused || ! selectAllTextWhenFocused) - { if (e.mouseWasClicked() && ! (popupMenuEnabled && e.mods.isPopupMenu())) - { moveCaret (getTextIndexAt (e.x, e.y)); - } - } wasFocused = true; } @@ -2094,47 +2125,6 @@ bool TextEditor::keyStateChanged (const bool isKeyDown) return ! ModifierKeys::getCurrentModifiers().isCommandDown(); } -//============================================================================== -const int baseMenuItemID = 0x7fff0000; - -void TextEditor::addPopupMenuItems (PopupMenu& m, const MouseEvent*) -{ - const bool writable = ! isReadOnly(); - - if (passwordCharacter == 0) - { - m.addItem (baseMenuItemID + 1, TRANS("cut"), writable); - m.addItem (baseMenuItemID + 2, TRANS("copy"), ! selection.isEmpty()); - m.addItem (baseMenuItemID + 3, TRANS("paste"), writable); - } - - m.addItem (baseMenuItemID + 4, TRANS("delete"), writable); - m.addSeparator(); - m.addItem (baseMenuItemID + 5, TRANS("select all")); - m.addSeparator(); - - if (getUndoManager() != nullptr) - { - m.addItem (baseMenuItemID + 6, TRANS("undo"), undoManager.canUndo()); - m.addItem (baseMenuItemID + 7, TRANS("redo"), undoManager.canRedo()); - } -} - -void TextEditor::performPopupMenuAction (const int menuItemID) -{ - switch (menuItemID) - { - case baseMenuItemID + 1: cutToClipboard(); break; - case baseMenuItemID + 2: copyToClipboard(); break; - case baseMenuItemID + 3: pasteFromClipboard(); break; - case baseMenuItemID + 4: cut(); break; - case baseMenuItemID + 5: selectAll(); break; - case baseMenuItemID + 6: undo(); break; - case baseMenuItemID + 7: redo(); break; - default: break; - } -} - //============================================================================== void TextEditor::focusGained (FocusChangeType) { diff --git a/modules/juce_gui_basics/widgets/juce_TextEditor.h b/modules/juce_gui_basics/widgets/juce_TextEditor.h index 9de1c1a812..242343b3be 100644 --- a/modules/juce_gui_basics/widgets/juce_TextEditor.h +++ b/modules/juce_gui_basics/widgets/juce_TextEditor.h @@ -566,9 +566,8 @@ public: When the menu has been shown, performPopupMenuAction() will be called to perform the item that the user has chosen. - The default menu items will be added using item IDs in the range - 0x7fff0000 - 0x7fff1000, so you should avoid those values for your own - menu IDs. + The default menu items will be added using item IDs from the + StandardApplicationCommandIDs namespace. If this was triggered by a mouse-click, the mouseClickEvent parameter will be a pointer to the info about it, or may be null if the menu is being triggered diff --git a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp index db82b5e510..8b3a553915 100644 --- a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp +++ b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp @@ -26,8 +26,7 @@ class CodeEditorComponent::CodeEditorLine { public: - CodeEditorLine() noexcept - : highlightColumnStart (0), highlightColumnEnd (0) + CodeEditorLine() noexcept : highlightColumnStart (0), highlightColumnEnd (0) { } @@ -196,7 +195,7 @@ private: for (;;) { - int tabPos = t.text.indexOfChar ('\t'); + const int tabPos = t.text.indexOfChar ('\t'); if (tabPos < 0) break; @@ -226,6 +225,26 @@ private: } }; +namespace CodeEditorHelpers +{ + static int findFirstNonWhitespaceChar (const String& line) noexcept + { + String::CharPointerType t (line.getCharPointer()); + int i = 0; + + while (! t.isEmpty()) + { + if (! t.isWhitespace()) + return i; + + ++t; + ++i; + } + + return 0; + } +} + //============================================================================== class CodeEditorComponent::GutterComponent : public Component { @@ -640,6 +659,11 @@ CodeDocument::Position CodeEditorComponent::getPositionAt (int x, int y) //============================================================================== void CodeEditorComponent::insertTextAtCaret (const String& newText) +{ + insertText (newText); +} + +void CodeEditorComponent::insertText (const String& newText) { document.deleteSection (selectionStart, selectionEnd); @@ -661,17 +685,89 @@ void CodeEditorComponent::insertTabAtCaret() { const int caretCol = indexToColumn (caretPos.getLineNumber(), caretPos.getIndexInLine()); const int spacesNeeded = spacesPerTab - (caretCol % spacesPerTab); - insertTextAtCaret (String::repeatedString (" ", spacesNeeded)); + insertText (String::repeatedString (" ", spacesNeeded)); } else { - insertTextAtCaret ("\t"); + insertText ("\t"); + } +} + +bool CodeEditorComponent::deleteWhitespaceBackwardsToTabStop() +{ + if (! getHighlightedRegion().isEmpty()) + return false; + + for (;;) + { + const int currentColumn = indexToColumn (caretPos.getLineNumber(), caretPos.getIndexInLine()); + + if (currentColumn <= 0 || (currentColumn % spacesPerTab) == 0) + break; + + moveCaretLeft (false, true); + } + + const String selected (getTextInRange (getHighlightedRegion())); + + if (selected.isNotEmpty() && selected.trim().isEmpty()) + { + cut(); + return true; + } + + return false; +} + +void CodeEditorComponent::indentSelection() { indentSelectedLines ( spacesPerTab); } +void CodeEditorComponent::unindentSelection() { indentSelectedLines (-spacesPerTab); } + +void CodeEditorComponent::indentSelectedLines (const int spacesToAdd) +{ + newTransaction(); + + CodeDocument::Position oldSelectionStart (selectionStart), oldSelectionEnd (selectionEnd), oldCaret (caretPos); + oldSelectionStart.setPositionMaintained (true); + oldSelectionEnd.setPositionMaintained (true); + oldCaret.setPositionMaintained (true); + + const int lineStart = selectionStart.getLineNumber(); + int lineEnd = selectionEnd.getLineNumber(); + + if (lineEnd > lineStart && selectionEnd.getIndexInLine() == 0) + --lineEnd; + + for (int line = lineStart; line <= lineEnd; ++line) + { + const String lineText (document.getLine (line)); + const int nonWhitespaceStart = CodeEditorHelpers::findFirstNonWhitespaceChar (lineText); + + if (nonWhitespaceStart > 0 || lineText.trimStart().isNotEmpty()) + { + const CodeDocument::Position wsStart (&document, line, 0); + const CodeDocument::Position wsEnd (&document, line, nonWhitespaceStart); + + const int numLeadingSpaces = indexToColumn (line, wsEnd.getIndexInLine()); + const int newNumLeadingSpaces = jmax (0, numLeadingSpaces + spacesToAdd); + + if (newNumLeadingSpaces != numLeadingSpaces) + { + document.deleteSection (wsStart, wsEnd); + document.insertText (wsStart, String::repeatedString (useSpacesForTabs ? " " : "\t", + useSpacesForTabs ? newNumLeadingSpaces + : (newNumLeadingSpaces / spacesPerTab))); + } + } } + + selectionStart = oldSelectionStart; + selectionEnd = oldSelectionEnd; + caretPos = oldCaret; } void CodeEditorComponent::cut() { - insertTextAtCaret (String::empty); + insertText (String::empty); } bool CodeEditorComponent::copyToClipboard() @@ -700,7 +796,7 @@ bool CodeEditorComponent::pasteFromClipboard() const String clip (SystemClipboard::getTextFromClipboard()); if (clip.isNotEmpty()) - insertTextAtCaret (clip); + insertText (clip); newTransaction(); return true; @@ -814,26 +910,6 @@ bool CodeEditorComponent::moveCaretToTop (const bool selecting) return true; } -namespace CodeEditorHelpers -{ - static int findFirstNonWhitespaceChar (const String& line) noexcept - { - String::CharPointerType t (line.getCharPointer()); - int i = 0; - - while (! t.isEmpty()) - { - if (! t.isWhitespace()) - return i; - - ++t; - ++i; - } - - return 0; - } -} - bool CodeEditorComponent::moveCaretToStartOfLine (const bool selecting) { newTransaction(); @@ -956,21 +1032,28 @@ bool CodeEditorComponent::keyPressed (const KeyPress& key) { if (key == KeyPress::tabKey || key.getTextCharacter() == '\t') { - insertTabAtCaret(); + handleTabKey(); } else if (key == KeyPress::returnKey) { - newTransaction(); - insertTextAtCaret (document.getNewLineCharacters()); + handleReturnKey(); } else if (key.isKeyCode (KeyPress::escapeKey)) { - newTransaction(); + handleEscapeKey(); } else if (key.getTextCharacter() >= ' ') { insertTextAtCaret (String::charToString (key.getTextCharacter())); } + else if (key == KeyPress ('[', ModifierKeys::commandModifier, 0)) + { + unindentSelection(); + } + else if (key == KeyPress (']', ModifierKeys::commandModifier, 0)) + { + indentSelection(); + } else { return false; @@ -980,26 +1063,90 @@ bool CodeEditorComponent::keyPressed (const KeyPress& key) return true; } +void CodeEditorComponent::handleReturnKey() +{ + insertText (document.getNewLineCharacters()); +} + +void CodeEditorComponent::handleTabKey() +{ + insertTabAtCaret(); +} + +void CodeEditorComponent::handleEscapeKey() +{ + newTransaction(); +} + +//============================================================================== +void CodeEditorComponent::addPopupMenuItems (PopupMenu& m, const MouseEvent*) +{ + m.addItem (StandardApplicationCommandIDs::cut, TRANS("Cut")); + m.addItem (StandardApplicationCommandIDs::copy, TRANS("Copy"), ! getHighlightedRegion().isEmpty()); + m.addItem (StandardApplicationCommandIDs::paste, TRANS("Paste")); + m.addItem (StandardApplicationCommandIDs::del, TRANS("Delete")); + m.addSeparator(); + m.addItem (StandardApplicationCommandIDs::selectAll, TRANS("Select All")); + m.addSeparator(); + m.addItem (StandardApplicationCommandIDs::undo, TRANS("Undo"), document.getUndoManager().canUndo()); + m.addItem (StandardApplicationCommandIDs::redo, TRANS("Redo"), document.getUndoManager().canRedo()); +} + +void CodeEditorComponent::performPopupMenuAction (const int menuItemID) +{ + switch (menuItemID) + { + case StandardApplicationCommandIDs::cut: cutToClipboard(); break; + case StandardApplicationCommandIDs::copy: copyToClipboard(); break; + case StandardApplicationCommandIDs::paste: pasteFromClipboard(); break; + case StandardApplicationCommandIDs::del: cut(); break; + case StandardApplicationCommandIDs::selectAll: selectAll(); break; + case StandardApplicationCommandIDs::undo: undo(); break; + case StandardApplicationCommandIDs::redo: redo(); break; + default: break; + } +} + +static void codeEditorMenuCallback (int menuResult, CodeEditorComponent* editor) +{ + if (editor != nullptr && menuResult != 0) + editor->performPopupMenuAction (menuResult); +} + +//============================================================================== void CodeEditorComponent::mouseDown (const MouseEvent& e) { newTransaction(); dragType = notDragging; - if (! e.mods.isPopupMenu()) + if (e.mods.isPopupMenu()) { - beginDragAutoRepeat (100); - moveCaretTo (getPositionAt (e.x, e.y), e.mods.isShiftDown()); + if (getHighlightedRegion().isEmpty()) + { + const CodeDocument::Position pos (getPositionAt (e.x, e.y)); + const String line (pos.getLineText()); + const int index = pos.getIndexInLine(); + const int lineLen = line.length(); + + if (index > 0 && index < lineLen - 2) + { + moveCaretTo (pos, false); + moveCaretLeft (true, false); + moveCaretRight (true, true); + } + } + + PopupMenu m; + m.setLookAndFeel (&getLookAndFeel()); + addPopupMenuItems (m, &e); + + m.showMenuAsync (PopupMenu::Options(), + ModalCallbackFunction::forComponent (codeEditorMenuCallback, this)); } else { - /*PopupMenu m; - addPopupMenuItems (m, &e); - - const int result = m.show(); - - if (result != 0) - performPopupMenuAction (result); - */ + beginDragAutoRepeat (100); + moveCaretTo (getPositionAt (e.x, e.y), e.mods.isShiftDown()); } } @@ -1147,6 +1294,7 @@ void CodeEditorComponent::ColourScheme::set (const String& name, const Colour& c for (int i = 0; i < types.size(); ++i) { TokenType& tt = types.getReference(i); + if (tt.name == name) { tt.colour = colour; @@ -1191,57 +1339,57 @@ void CodeEditorComponent::updateCachedIterators (int maxLineNum) if (cachedIterators.size() == 0) cachedIterators.add (new CodeDocument::Iterator (&document)); - if (codeTokeniser == nullptr) - return; - - for (;;) + if (codeTokeniser != nullptr) { - CodeDocument::Iterator* last = cachedIterators.getLast(); - - if (last->getLine() >= maxLineNum) - break; - - CodeDocument::Iterator* t = new CodeDocument::Iterator (*last); - cachedIterators.add (t); - const int targetLine = last->getLine() + linesBetweenCachedSources; - for (;;) { - codeTokeniser->readNextToken (*t); + CodeDocument::Iterator* const last = cachedIterators.getLast(); - if (t->getLine() >= targetLine) + if (last->getLine() >= maxLineNum) break; - if (t->isEOF()) - return; + CodeDocument::Iterator* t = new CodeDocument::Iterator (*last); + cachedIterators.add (t); + const int targetLine = last->getLine() + linesBetweenCachedSources; + + for (;;) + { + codeTokeniser->readNextToken (*t); + + if (t->getLine() >= targetLine) + break; + + if (t->isEOF()) + return; + } } } } void CodeEditorComponent::getIteratorForPosition (int position, CodeDocument::Iterator& source) { - if (codeTokeniser == nullptr) - return; - - for (int i = cachedIterators.size(); --i >= 0;) + if (codeTokeniser != nullptr) { - CodeDocument::Iterator* t = cachedIterators.getUnchecked (i); - if (t->getPosition() <= position) + for (int i = cachedIterators.size(); --i >= 0;) { - source = *t; - break; + CodeDocument::Iterator* t = cachedIterators.getUnchecked (i); + if (t->getPosition() <= position) + { + source = *t; + break; + } } - } - while (source.getPosition() < position) - { - const CodeDocument::Iterator original (source); - codeTokeniser->readNextToken (source); - - if (source.getPosition() > position || source.isEOF()) + while (source.getPosition() < position) { - source = original; - break; + const CodeDocument::Iterator original (source); + codeTokeniser->readNextToken (source); + + if (source.getPosition() > position || source.isEOF()) + { + source = original; + break; + } } } } diff --git a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h index c9e2a3f1ae..7ff136f018 100644 --- a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h +++ b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h @@ -38,16 +38,16 @@ class CodeTokeniser; */ class JUCE_API CodeEditorComponent : public Component, public TextInputTarget, - public Timer, - public ScrollBar::Listener, - public CodeDocument::Listener, - public AsyncUpdater + private Timer, + private ScrollBar::Listener, + private CodeDocument::Listener, + private AsyncUpdater { public: //============================================================================== /** Creates an editor for a document. - The tokeniser object is optional - pass 0 to disable syntax highlighting. + The tokeniser object is optional - pass nullptr to disable syntax highlighting. The object that you pass in is not owned or deleted by the editor - you must make sure that it doesn't get deleted while this component is still using it. @@ -128,6 +128,7 @@ public: bool moveCaretToEndOfLine (bool selecting); bool deleteBackwards (bool moveInWholeWordSteps); bool deleteForwards (bool moveInWholeWordSteps); + bool deleteWhitespaceBackwardsToTabStop(); bool copyToClipboard(); bool cutToClipboard(); bool pasteFromClipboard(); @@ -145,6 +146,9 @@ public: void insertTextAtCaret (const String& textToInsert); void insertTabAtCaret(); + void indentSelection(); + void unindentSelection(); + //============================================================================== Range getHighlightedRegion() const; void setHighlightedRegion (const Range& newRange); @@ -230,11 +234,53 @@ public: int getScrollbarThickness() const noexcept { return scrollbarThickness; } //============================================================================== - /** @internal */ - void resized(); + /** Called when the return key is pressed - this can be overridden for custom behaviour. */ + virtual void handleReturnKey(); + /** Called when the tab key is pressed - this can be overridden for custom behaviour. */ + virtual void handleTabKey(); + /** Called when the escape key is pressed - this can be overridden for custom behaviour. */ + virtual void handleEscapeKey(); + + //============================================================================== + /** This adds the items to the popup menu. + + By default it adds the cut/copy/paste items, but you can override this if + you need to replace these with your own items. + + If you want to add your own items to the existing ones, you can override this, + call the base class's addPopupMenuItems() method, then append your own items. + + When the menu has been shown, performPopupMenuAction() will be called to + perform the item that the user has chosen. + + If this was triggered by a mouse-click, the mouseClickEvent parameter will be + a pointer to the info about it, or may be null if the menu is being triggered + by some other means. + + @see performPopupMenuAction, setPopupMenuEnabled, isPopupMenuEnabled + */ + virtual void addPopupMenuItems (PopupMenu& menuToAddTo, + const MouseEvent* mouseClickEvent); + + /** This is called to perform one of the items that was shown on the popup menu. + + If you've overridden addPopupMenuItems(), you should also override this + to perform the actions that you've added. + + If you've overridden addPopupMenuItems() but have still left the default items + on the menu, remember to call the superclass's performPopupMenuAction() + so that it can perform the default actions if that's what the user clicked on. + + @see addPopupMenuItems, setPopupMenuEnabled, isPopupMenuEnabled + */ + virtual void performPopupMenuAction (int menuItemID); + + //============================================================================== /** @internal */ void paint (Graphics&); /** @internal */ + void resized(); + /** @internal */ bool keyPressed (const KeyPress&); /** @internal */ void mouseDown (const MouseEvent&); @@ -251,14 +297,6 @@ public: /** @internal */ void focusLost (FocusChangeType); /** @internal */ - void timerCallback(); - /** @internal */ - void scrollBarMoved (ScrollBar*, double); - /** @internal */ - void handleAsyncUpdate(); - /** @internal */ - void codeDocumentChanged (const CodeDocument::Position&, const CodeDocument::Position&); - /** @internal */ bool isTextInputActive() const; /** @internal */ void setTemporaryUnderlining (const Array >&); @@ -307,16 +345,24 @@ private: void clearCachedIterators (int firstLineToBeInvalid); void updateCachedIterators (int maxLineNum); void getIteratorForPosition (int position, CodeDocument::Iterator& result); + + void timerCallback(); + void scrollBarMoved (ScrollBar*, double); + void handleAsyncUpdate(); + void codeDocumentChanged (const CodeDocument::Position&, const CodeDocument::Position&); + void moveLineDelta (int delta, bool selecting); int getGutterSize() const noexcept; //============================================================================== + void insertText (const String& textToInsert); void updateCaretPosition(); void updateScrollBars(); void scrollToLineInternal (int line); void scrollToColumnInternal (double column); void newTransaction(); void cut(); + void indentSelectedLines (int spacesToAdd); int indexToColumn (int line, int index) const noexcept; int columnToIndex (int line, int column) const noexcept;