diff --git a/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.cpp b/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.cpp index c2a1f5c8e8..0926637998 100644 --- a/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.cpp +++ b/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.cpp @@ -158,3 +158,143 @@ void SourceCodeEditor::valueTreeChildRemoved (ValueTree&, ValueTree&) void SourceCodeEditor::valueTreeChildOrderChanged (ValueTree&) { updateColourScheme(); } void SourceCodeEditor::valueTreeParentChanged (ValueTree&) { updateColourScheme(); } void SourceCodeEditor::valueTreeRedirected (ValueTree&) { updateColourScheme(); } + + +//============================================================================== +namespace CppUtils +{ + 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 bool getIndentForCurrentBlock (CodeDocument::Position pos, String& whitespace) + { + int braceCount = 0; + + while (pos.getLineNumber() > 0) + { + pos = pos.movedByLines (-1); + + const String line (pos.getLineText()); + const String trimmedLine (line.trimStart()); + + String::CharPointerType l (trimmedLine.getCharPointer()); + + for (;;) + { + const juce_wchar c = l.getAndAdvance(); + + if (c == 0) + break; + + if (c == '}') + ++braceCount; + + if (c == '{') + { + if (--braceCount < 0) + { + whitespace = getLeadingWhitespace (line); + return true; + } + } + + if (c == '"' || c == '\'') + { + while (! (l.isEmpty() || l.getAndAdvance() == c)) + {} + } + + if (c == '/' && *l == '/') + break; + } + } + + return false; + } +} + +CppCodeEditorComponent::CppCodeEditorComponent (CodeDocument& codeDocument) + : CodeEditorComponent (codeDocument, CppUtils::getCppTokeniser()) +{ +} + +void CppCodeEditorComponent::handleReturnKey() +{ + CodeEditorComponent::handleReturnKey(); + + const CodeDocument::Position pos (getCaretPos()); + + if (pos.getLineNumber() > 0 && pos.getLineText().trim().isEmpty()) + { + String indent; + CppUtils::getIndentForCurrentBlock (pos, indent); + + const String previousLine (pos.movedByLines (-1).getLineText()); + const String trimmedPreviousLine (previousLine.trim()); + const String leadingWhitespace (CppUtils::getLeadingWhitespace (previousLine)); + + insertTextAtCaret (leadingWhitespace); + + if (trimmedPreviousLine.endsWithChar ('{') + || ((trimmedPreviousLine.startsWith ("if ") + || trimmedPreviousLine.startsWith ("for ") + || trimmedPreviousLine.startsWith ("while ")) + && trimmedPreviousLine.endsWithChar (')'))) + insertTabAtCaret(); + } +} + +void CppCodeEditorComponent::insertTextAtCaret (const String& newText) +{ + if (getHighlightedRegion().isEmpty()) + { + const CodeDocument::Position pos (getCaretPos()); + + if ((newText == "{" || newText == "}") + && pos.getLineNumber() > 0 + && pos.getLineText().trim().isEmpty()) + { + moveCaretToStartOfLine (true); + + String whitespace; + if (CppUtils::getIndentForCurrentBlock (pos, whitespace)) + { + CodeEditorComponent::insertTextAtCaret (whitespace); + + if (newText == "{") + insertTabAtCaret(); + } + } + else if (newText == getDocument().getNewLineCharacters() + && pos.getLineNumber() > 0) + { + const String remainderOfLine (pos.getLineText().substring (pos.getIndexInLine())); + + if (remainderOfLine.startsWithChar ('{') || remainderOfLine.startsWithChar ('}')) + { + String whitespace; + if (CppUtils::getIndentForCurrentBlock (pos, whitespace)) + { + CodeEditorComponent::insertTextAtCaret (newText + whitespace); + + if (remainderOfLine.startsWithChar ('{')) + insertTabAtCaret(); + + return; + } + } + } + } + + CodeEditorComponent::insertTextAtCaret (newText); +} diff --git a/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.h b/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.h index 4470d63044..7cf55980f4 100644 --- a/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.h +++ b/extras/Introjucer/Source/Code Editor/jucer_SourceCodeEditor.h @@ -34,7 +34,6 @@ class SourceCodeDocument : public OpenDocumentManager::Document { public: - //============================================================================== SourceCodeDocument (Project*, const File&); bool loadedOk() const { return true; } @@ -114,128 +113,13 @@ private: class CppCodeEditorComponent : public CodeEditorComponent { public: - CppCodeEditorComponent (CodeDocument& codeDocument) - : CodeEditorComponent (codeDocument, getCppTokeniser()) - { - } + CppCodeEditorComponent (CodeDocument& codeDocument); - void handleReturnKey() - { - CodeEditorComponent::handleReturnKey(); - - const CodeDocument::Position pos (getCaretPos()); - - if (pos.getLineNumber() > 0 && pos.getLineText().trim().isEmpty()) - { - String indent; - getIndentForCurrentBlock (pos, indent); - - 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 - && pos.getLineText().trim().isEmpty()) - { - moveCaretToStartOfLine (true); - - String whitespace; - if (getIndentForCurrentBlock (pos, whitespace)) - { - CodeEditorComponent::insertTextAtCaret (whitespace); - - if (newText == "{") - insertTabAtCaret(); - } - } - else if (newText == getDocument().getNewLineCharacters() - && pos.getLineNumber() > 0) - { - const String remainderOfLine (pos.getLineText().substring (pos.getIndexInLine())); - - if (remainderOfLine.startsWithChar ('{') || remainderOfLine.startsWithChar ('}')) - { - String whitespace; - if (getIndentForCurrentBlock (pos, whitespace)) - { - CodeEditorComponent::insertTextAtCaret (newText + whitespace); - - if (remainderOfLine.startsWithChar ('{')) - insertTabAtCaret(); - - return; - } - } - } - } - - CodeEditorComponent::insertTextAtCaret (newText); - } + void handleReturnKey(); + void insertTextAtCaret (const String& 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 bool getIndentForCurrentBlock (CodeDocument::Position pos, String& whitespace) - { - int braceCount = 0; - - while (pos.getLineNumber() > 0) - { - pos = pos.movedByLines (-1); - - const String line (pos.getLineText()); - const String trimmedLine (line.trimStart()); - - StringArray tokens; - tokens.addTokens (trimmedLine, true); - - for (int i = tokens.size(); --i >= 0;) - { - if (tokens[i] == "}") - ++braceCount; - - if (tokens[i] == "{") - { - if (--braceCount < 0) - { - whitespace = getLeadingWhitespace (line); - return true; - } - } - } - } - - return false; - } + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CppCodeEditorComponent); }; diff --git a/extras/Introjucer/Source/Utility/jucer_PresetIDs.h b/extras/Introjucer/Source/Utility/jucer_PresetIDs.h index fecaf68148..d60b0c1620 100644 --- a/extras/Introjucer/Source/Utility/jucer_PresetIDs.h +++ b/extras/Introjucer/Source/Utility/jucer_PresetIDs.h @@ -52,6 +52,7 @@ namespace Ids DECLARE_ID (line); DECLARE_ID (index); DECLARE_ID (type); + DECLARE_ID (time); DECLARE_ID (extraCompilerFlags); DECLARE_ID (extraLinkerFlags); DECLARE_ID (extraDefs); diff --git a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp index 04f9292b1c..4a1055a18c 100644 --- a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp +++ b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp @@ -260,7 +260,7 @@ namespace CodeEditorHelpers class CodeEditorComponent::GutterComponent : public Component { public: - GutterComponent() {} + GutterComponent() : lastNumLines (0) {} void paint (Graphics& g) { @@ -272,7 +272,8 @@ public: const Rectangle clip (g.getClipBounds()); const int lineHeight = editor.lineHeight; const int firstLineToDraw = jmax (0, clip.getY() / lineHeight); - const int lastLineToDraw = jmin (editor.lines.size(), clip.getBottom() / lineHeight + 1); + const int lastLineToDraw = jmin (editor.lines.size(), clip.getBottom() / lineHeight + 1, + lastNumLines - editor.firstLineOnScreen); const Font lineNumberFont (editor.getFont().withHeight (jmin (13.0f, lineHeight * 0.8f))); const float y = lineHeight - editor.getFont().getDescent(); @@ -286,6 +287,19 @@ public: g.setColour (editor.findColour (lineNumberTextId)); ga.draw (g); } + + void documentChanged (CodeDocument& doc) + { + const int newNumLines = doc.getNumLines(); + if (newNumLines != lastNumLines) + { + lastNumLines = newNumLines; + repaint(); + } + } + +private: + int lastNumLines; }; @@ -417,8 +431,9 @@ void CodeEditorComponent::codeDocumentChanged (const CodeDocument::Position& aff void CodeEditorComponent::resized() { + const int visibleWidth = getWidth() - scrollbarThickness - getGutterSize(); linesOnScreen = jmax (1, (getHeight() - scrollbarThickness) / lineHeight); - columnsOnScreen = jmax (1, (int) ((getWidth() - scrollbarThickness) / charWidth)); + columnsOnScreen = jmax (1, (int) (visibleWidth / charWidth)); lines.clear(); rebuildLineTokens(); updateCaretPosition(); @@ -430,7 +445,7 @@ void CodeEditorComponent::resized() scrollbarThickness, getHeight() - scrollbarThickness); horizontalScrollBar.setBounds (getGutterSize(), getHeight() - scrollbarThickness, - getWidth() - scrollbarThickness - getGutterSize(), scrollbarThickness); + visibleWidth, scrollbarThickness); updateScrollBars(); } @@ -510,6 +525,9 @@ void CodeEditorComponent::rebuildLineTokens() if (minLineToRepaint <= maxLineToRepaint) repaint (0, lineHeight * minLineToRepaint - 1, verticalScrollBar.getX(), lineHeight * (1 + maxLineToRepaint - minLineToRepaint) + 2); + + if (gutter != nullptr) + gutter->documentChanged (document); } //============================================================================== @@ -639,10 +657,12 @@ void CodeEditorComponent::scrollToKeepCaretOnScreen() { if (getWidth() > 0 && getHeight() > 0) { - if (caretPos.getLineNumber() < firstLineOnScreen) - scrollBy (caretPos.getLineNumber() - firstLineOnScreen); - else if (caretPos.getLineNumber() >= firstLineOnScreen + linesOnScreen) - scrollBy (caretPos.getLineNumber() - (firstLineOnScreen + linesOnScreen - 1)); + const int caretLine = caretPos.getLineNumber(); + + if (caretLine < firstLineOnScreen) + scrollBy (caretLine - firstLineOnScreen); + else if (caretLine >= firstLineOnScreen + linesOnScreen) + scrollBy (caretLine - (firstLineOnScreen + linesOnScreen - 1)); const int column = indexToColumn (caretPos.getLineNumber(), caretPos.getIndexInLine()); if (column >= xOffset + columnsOnScreen - 1) @@ -957,13 +977,38 @@ bool CodeEditorComponent::deleteBackwards (const bool moveInWholeWordSteps) else { if (selectionStart == selectionEnd) - selectionStart.moveBy (-1); + { + if (! skipBackwardsToPreviousTab()) + selectionStart.moveBy (-1); + } } cut(); return true; } +bool CodeEditorComponent::skipBackwardsToPreviousTab() +{ + const String currentLineText (caretPos.getLineText().removeCharacters ("\r\n")); + const int currentIndex = caretPos.getIndexInLine(); + + if (currentLineText.isNotEmpty() && currentLineText.length() == currentIndex) + { + const int currentLine = caretPos.getLineNumber(); + const int currentColumn = indexToColumn (currentLine, currentIndex); + const int previousTabColumn = (currentColumn - 1) - ((currentColumn - 1) % spacesPerTab); + const int previousTabIndex = columnToIndex (currentLine, previousTabColumn); + + if (currentLineText.substring (previousTabIndex, currentIndex).trim().isEmpty()) + { + selectionStart.moveBy (previousTabIndex - currentIndex); + return true; + } + } + + return false; +} + bool CodeEditorComponent::deleteForwards (const bool moveInWholeWordSteps) { if (moveInWholeWordSteps) @@ -1445,8 +1490,8 @@ CodeEditorComponent::State::State (const String& s) StringArray tokens; tokens.addTokens (s, ":", String::empty); - lastTopLine = tokens[0].getIntValue(); - lastCaretPos = tokens[1].getIntValue(); + lastTopLine = tokens[0].getIntValue(); + lastCaretPos = tokens[1].getIntValue(); lastSelectionEnd = tokens[2].getIntValue(); } diff --git a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h index f328fbc2c5..124394dddb 100644 --- a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h +++ b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.h @@ -389,6 +389,7 @@ private: void newTransaction(); void cut(); void indentSelectedLines (int spacesToAdd); + bool skipBackwardsToPreviousTab(); int indexToColumn (int line, int index) const noexcept; int columnToIndex (int line, int column) const noexcept;