| @@ -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); | |||
| } | |||
| @@ -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()) | |||
| { | |||
| } | |||
| 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); | |||
| CppCodeEditorComponent (CodeDocument& codeDocument); | |||
| 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); | |||
| }; | |||
| @@ -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); | |||
| @@ -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<int> 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(); | |||
| } | |||
| @@ -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; | |||