JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_CodeEditorComponent.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26namespace juce
27{
28
29//==============================================================================
31{
32public:
35 codeEditorComponentToWrap.isReadOnly() ? AccessibilityRole::staticText
36 : AccessibilityRole::editableText,
37 {},
38 { std::make_unique<CodeEditorComponentTextInterface> (codeEditorComponentToWrap) })
39 {
40 }
41
42private:
43 class CodeEditorComponentTextInterface final : public AccessibilityTextInterface
44 {
45 public:
46 explicit CodeEditorComponentTextInterface (CodeEditorComponent& codeEditorComponentToWrap)
47 : codeEditorComponent (codeEditorComponentToWrap)
48 {
49 }
50
51 bool isDisplayingProtectedText() const override
52 {
53 return false;
54 }
55
56 bool isReadOnly() const override
57 {
58 return codeEditorComponent.isReadOnly();
59 }
60
61 int getTotalNumCharacters() const override
62 {
63 return codeEditorComponent.document.getAllContent().length();
64 }
65
66 Range<int> getSelection() const override
67 {
68 return { codeEditorComponent.selectionStart.getPosition(),
69 codeEditorComponent.selectionEnd.getPosition() };
70 }
71
72 void setSelection (Range<int> r) override
73 {
74 codeEditorComponent.setHighlightedRegion (r);
75 }
76
77 String getText (Range<int> r) const override
78 {
79 auto& doc = codeEditorComponent.document;
80
81 return doc.getTextBetween (CodeDocument::Position (doc, r.getStart()),
83 }
84
85 void setText (const String& newText) override
86 {
87 codeEditorComponent.document.replaceAllContent (newText);
88 }
89
90 int getTextInsertionOffset() const override
91 {
92 return codeEditorComponent.caretPos.getPosition();
93 }
94
95 RectangleList<int> getTextBounds (Range<int> textRange) const override
96 {
97 const auto localRects = codeEditorComponent.getTextBounds (textRange);
98
100
101 for (auto r : localRects)
102 globalRects.add (codeEditorComponent.localAreaToGlobal (r));
103
104 return globalRects;
105 }
106
107 int getOffsetAtPoint (Point<int> point) const override
108 {
109 return codeEditorComponent.getPositionAt (point.x, point.y).getPosition();
110 }
111
112 private:
113 CodeEditorComponent& codeEditorComponent;
114 };
115
116 //==============================================================================
118};
119
120//==============================================================================
122{
123public:
124 CodeEditorLine() noexcept {}
125
126 bool update (CodeDocument& codeDoc, int lineNum,
131 {
134
135 if (tokeniser == nullptr)
136 {
137 auto line = codeDoc.getLine (lineNum);
138 addToken (newTokens, line, line.length(), -1);
139 }
140 else if (lineNum < codeDoc.getNumLines())
141 {
142 const CodeDocument::Position pos (codeDoc, lineNum, 0);
143 createTokens (pos.getPosition(), pos.getLineText(),
144 source, *tokeniser, newTokens);
145 }
146
147 replaceTabsWithSpaces (newTokens, tabSpaces);
148
149 int newHighlightStart = 0;
150 int newHighlightEnd = 0;
151
152 if (selStart.getLineNumber() <= lineNum && selEnd.getLineNumber() >= lineNum)
153 {
154 auto line = codeDoc.getLine (lineNum);
155
157 newHighlightStart = indexToColumn (jmax (0, selStart.getPosition() - lineStart.getPosition()),
158 line, tabSpaces);
159 newHighlightEnd = indexToColumn (jmin (lineEnd.getPosition() - lineStart.getPosition(), selEnd.getPosition() - lineStart.getPosition()),
160 line, tabSpaces);
161 }
162
163 if (newHighlightStart != highlightColumnStart || newHighlightEnd != highlightColumnEnd)
164 {
165 highlightColumnStart = newHighlightStart;
166 highlightColumnEnd = newHighlightEnd;
167 }
168 else if (tokens == newTokens)
169 {
170 return false;
171 }
172
173 tokens.swapWith (newTokens);
174 return true;
175 }
176
177 Optional<Rectangle<float>> getHighlightArea (float x, int y, int lineH, float characterWidth) const
178 {
179 return getHighlightArea (x, y, lineH, characterWidth, { highlightColumnStart, highlightColumnEnd });
180 }
181
182 Optional<Rectangle<float>> getHighlightArea (float x,
183 int y,
184 int lineH,
185 float characterWidth,
187 {
188 if (highlightColumns.isEmpty())
189 return {};
190
191 return Rectangle<float> (x + (float) highlightColumns.getStart() * characterWidth - 1.0f, (float) y - 0.5f,
192 (float) (highlightColumns.getEnd() - highlightColumns.getStart()) * characterWidth + 1.5f, (float) lineH + 1.0f);
193
194 }
195
196 void draw (CodeEditorComponent& owner, Graphics& g, const Font& fontToUse,
197 const float rightClip, const float x, const int y,
198 const int lineH, const float characterWidth) const
199 {
202
203 int column = 0;
204
205 for (auto& token : tokens)
206 {
207 const float tokenX = x + (float) column * characterWidth;
208 if (tokenX > rightClip)
209 break;
210
211 as.append (token.text.initialSectionNotContaining ("\r\n"), fontToUse, owner.getColourForTokenType (token.tokenType));
212 column += token.length;
213 }
214
215 as.draw (g, { x, (float) y, (float) column * characterWidth + 10.0f, (float) lineH });
216 }
217
218private:
219 struct SyntaxToken
220 {
221 SyntaxToken (const String& t, const int len, const int type) noexcept
222 : text (t), length (len), tokenType (type)
223 {}
224
225 bool operator== (const SyntaxToken& other) const noexcept
226 {
227 return tokenType == other.tokenType
228 && length == other.length
229 && text == other.text;
230 }
231
232 String text;
233 int length;
234 int tokenType;
235 };
236
237 Array<SyntaxToken> tokens;
238 int highlightColumnStart = 0, highlightColumnEnd = 0;
239
240 static void createTokens (int startPosition, const String& lineText,
244 {
246 const int lineLength = lineText.length();
247
248 for (;;)
249 {
250 int tokenType = tokeniser.readNextToken (source);
251 int tokenStart = lastIterator.getPosition();
252 int tokenEnd = source.getPosition();
253
254 if (tokenEnd <= tokenStart)
255 break;
256
258
259 if (tokenEnd > 0)
260 {
262 const int start = jmax (0, tokenStart);
263 addToken (newTokens, lineText.substring (start, tokenEnd),
264 tokenEnd - start, tokenType);
265
266 if (tokenEnd >= lineLength)
267 break;
268 }
269
270 lastIterator = source;
271 }
272
273 source = lastIterator;
274 }
275
276 static void replaceTabsWithSpaces (Array<SyntaxToken>& tokens, const int spacesPerTab)
277 {
278 int x = 0;
279
280 for (auto& t : tokens)
281 {
282 for (;;)
283 {
284 const int tabPos = t.text.indexOfChar ('\t');
285 if (tabPos < 0)
286 break;
287
288 const int spacesNeeded = spacesPerTab - ((tabPos + x) % spacesPerTab);
289 t.text = t.text.replaceSection (tabPos, 1, String::repeatedString (" ", spacesNeeded));
290 t.length = t.text.length();
291 }
292
293 x += t.length;
294 }
295 }
296
297 int indexToColumn (int index, const String& line, int tabSpaces) const noexcept
298 {
299 jassert (index <= line.length());
300
301 auto t = line.getCharPointer();
302 int col = 0;
303
304 for (int i = 0; i < index; ++i)
305 {
306 if (t.getAndAdvance() != '\t')
307 ++col;
308 else
309 col += tabSpaces - (col % tabSpaces);
310 }
311
312 return col;
313 }
314
315 static void addToken (Array<SyntaxToken>& dest, const String& text, int length, int type)
316 {
317 if (length > 1000)
318 {
319 // subdivide very long tokens to avoid unwieldy glyph sequences
320 addToken (dest, text.substring (0, length / 2), length / 2, type);
321 addToken (dest, text.substring (length / 2), length - length / 2, type);
322 }
323 else
324 {
325 dest.add (SyntaxToken (text, length, type));
326 }
327 }
328};
329
330namespace CodeEditorHelpers
331{
332 static int findFirstNonWhitespaceChar (StringRef line) noexcept
333 {
334 auto t = line.text;
335 int i = 0;
336
337 while (! t.isEmpty())
338 {
339 if (! t.isWhitespace())
340 return i;
341
342 ++t;
343 ++i;
344 }
345
346 return 0;
347 }
348}
349
350//==============================================================================
352 public AsyncUpdater,
353 public ScrollBar::Listener,
355{
356public:
357 Pimpl (CodeEditorComponent& ed) : owner (ed) {}
358
359private:
360 CodeEditorComponent& owner;
361
362 void timerCallback() override { owner.newTransaction(); }
363 void handleAsyncUpdate() override { owner.rebuildLineTokens(); }
364
365 void scrollBarMoved (ScrollBar* scrollBarThatHasMoved, double newRangeStart) override
366 {
367 if (scrollBarThatHasMoved->isVertical())
368 owner.scrollToLineInternal ((int) newRangeStart);
369 else
370 owner.scrollToColumnInternal (newRangeStart);
371 }
372
373 void codeDocumentTextInserted (const String& newText, int pos) override
374 {
375 owner.codeDocumentChanged (pos, pos + newText.length());
376 }
377
378 void codeDocumentTextDeleted (int start, int end) override
379 {
380 owner.codeDocumentChanged (start, end);
381 }
382
384};
385
386//==============================================================================
388{
389public:
390 GutterComponent() {}
391
392 void paint (Graphics& g) override
393 {
394 jassert (dynamic_cast<CodeEditorComponent*> (getParentComponent()) != nullptr);
395 auto& editor = *static_cast<CodeEditorComponent*> (getParentComponent());
396
398 .overlaidWith (editor.findColour (lineNumberBackgroundId)));
399
400 auto clip = g.getClipBounds();
401 const int lineH = editor.lineHeight;
402 const float lineHeightFloat = (float) lineH;
403 const int firstLineToDraw = jmax (0, clip.getY() / lineH);
404 const int lastLineToDraw = jmin (editor.lines.size(), clip.getBottom() / lineH + 1,
405 lastNumLines - editor.firstLineOnScreen);
406
407 auto lineNumberFont = editor.getFont().withHeight (jmin (13.0f, lineHeightFloat * 0.8f));
408 auto w = (float) getWidth() - 2.0f;
410
411 for (int i = firstLineToDraw; i < lastLineToDraw; ++i)
412 ga.addFittedText (lineNumberFont, String (editor.firstLineOnScreen + i + 1),
413 0, (float) (lineH * i), w, lineHeightFloat,
415
416 g.setColour (editor.findColour (lineNumberTextId));
417 ga.draw (g);
418 }
419
420 void documentChanged (CodeDocument& doc, int newFirstLine)
421 {
422 auto newNumLines = doc.getNumLines();
423
424 if (newNumLines != lastNumLines || firstLine != newFirstLine)
425 {
426 firstLine = newFirstLine;
427 lastNumLines = newNumLines;
428 repaint();
429 }
430 }
431
432private:
433 int firstLine = 0, lastNumLines = 0;
434};
435
436
437//==============================================================================
439 : document (doc),
440 caretPos (doc, 0, 0),
441 selectionStart (doc, 0, 0),
442 selectionEnd (doc, 0, 0),
443 codeTokeniser (tokeniser)
444{
445 pimpl.reset (new Pimpl (*this));
446
447 caretPos.setPositionMaintained (true);
448 selectionStart.setPositionMaintained (true);
449 selectionEnd.setPositionMaintained (true);
450
451 setOpaque (true);
454
455 addAndMakeVisible (verticalScrollBar);
456 verticalScrollBar.setSingleStepSize (1.0);
457
458 addAndMakeVisible (horizontalScrollBar);
459 horizontalScrollBar.setSingleStepSize (1.0);
460
461 Font f (12.0f);
463 setFont (f);
464
465 if (codeTokeniser != nullptr)
466 setColourScheme (codeTokeniser->getDefaultColourScheme());
467
468 setLineNumbersShown (true);
469
470 verticalScrollBar.addListener (pimpl.get());
471 horizontalScrollBar.addListener (pimpl.get());
472 document.addListener (pimpl.get());
473
475}
476
478{
479 if (auto* peer = getPeer())
480 peer->refreshTextInputTarget();
481
482 document.removeListener (pimpl.get());
483}
484
485int CodeEditorComponent::getGutterSize() const noexcept
486{
487 return showLineNumbers ? 35 : 5;
488}
489
491{
492 clearCachedIterators (0);
493 document.replaceAllContent (newContent);
494 document.clearUndoHistory();
495 document.setSavePoint();
496 caretPos.setPosition (0);
497 selectionStart.setPosition (0);
498 selectionEnd.setPosition (0);
499 scrollToLine (0);
500}
501
503{
504 return true;
505}
506
508{
509 // TODO IME composition ranges not yet supported for this component
510}
511
512void CodeEditorComponent::setLineNumbersShown (const bool shouldBeShown)
513{
514 if (showLineNumbers != shouldBeShown)
515 {
516 showLineNumbers = shouldBeShown;
517 gutter.reset();
518
519 if (shouldBeShown)
520 {
521 gutter.reset (new GutterComponent());
522 addAndMakeVisible (gutter.get());
523 }
524
525 resized();
526 }
527}
528
530{
531 if (readOnly != b)
532 {
533 readOnly = b;
534
535 if (b)
536 removeChildComponent (caret.get());
537 else
538 addAndMakeVisible (caret.get());
539
540 invalidateAccessibilityHandler();
541 }
542}
543
544//==============================================================================
546{
547 auto visibleWidth = getWidth() - scrollbarThickness - getGutterSize();
548 linesOnScreen = jmax (1, (getHeight() - scrollbarThickness) / lineHeight);
549 columnsOnScreen = jmax (1, (int) ((float) visibleWidth / charWidth));
550 lines.clear();
551 rebuildLineTokens();
552 updateCaretPosition();
553
554 if (gutter != nullptr)
555 gutter->setBounds (0, 0, getGutterSize() - 2, getHeight());
556
557 verticalScrollBar.setBounds (getWidth() - scrollbarThickness, 0,
558 scrollbarThickness, getHeight() - scrollbarThickness);
559
560 horizontalScrollBar.setBounds (getGutterSize(), getHeight() - scrollbarThickness,
561 visibleWidth, scrollbarThickness);
562 updateScrollBars();
563}
564
566{
568
569 const auto gutterSize = getGutterSize();
570 const auto bottom = horizontalScrollBar.isVisible() ? horizontalScrollBar.getY() : getHeight();
571 const auto right = verticalScrollBar.isVisible() ? verticalScrollBar.getX() : getWidth();
572
573 g.reduceClipRegion (gutterSize, 0, right - gutterSize, bottom);
574
575 g.setFont (font);
576
577 const auto clip = g.getClipBounds();
578 const auto firstLineToDraw = jmax (0, clip.getY() / lineHeight);
579 const auto lastLineToDraw = jmin (lines.size(), clip.getBottom() / lineHeight + 1);
580 const auto x = (float) (gutterSize - xOffset * charWidth);
581 const auto rightClip = (float) clip.getRight();
582
583 {
585
586 for (int i = firstLineToDraw; i < lastLineToDraw; ++i)
587 if (const auto area = lines.getUnchecked (i)->getHighlightArea (x, lineHeight * i, lineHeight, charWidth))
588 highlightArea.add (*area);
589
592 }
593
594 for (int i = firstLineToDraw; i < lastLineToDraw; ++i)
595 lines.getUnchecked (i)->draw (*this, g, font, rightClip, x, lineHeight * i, lineHeight, charWidth);
596}
597
599{
600 if (scrollbarThickness != thickness)
601 {
602 scrollbarThickness = thickness;
603 resized();
604 }
605}
606
607void CodeEditorComponent::rebuildLineTokensAsync()
608{
609 pimpl->triggerAsyncUpdate();
610}
611
612void CodeEditorComponent::rebuildLineTokens()
613{
614 pimpl->cancelPendingUpdate();
615
616 auto numNeeded = linesOnScreen + 1;
618 int maxLineToRepaint = 0;
619
620 if (numNeeded != lines.size())
621 {
622 lines.clear();
623
624 for (int i = numNeeded; --i >= 0;)
625 lines.add (new CodeEditorLine());
626
629 }
630
631 jassert (numNeeded == lines.size());
632
633 CodeDocument::Iterator source (document);
634 getIteratorForPosition (CodeDocument::Position (document, firstLineOnScreen, 0).getPosition(), source);
635
636 for (int i = 0; i < numNeeded; ++i)
637 {
638 if (lines.getUnchecked (i)->update (document, firstLineOnScreen + i, source, codeTokeniser,
639 spacesPerTab, selectionStart, selectionEnd))
640 {
643 }
644 }
645
647 repaint (0, lineHeight * minLineToRepaint - 1,
648 verticalScrollBar.getX(), lineHeight * (1 + maxLineToRepaint - minLineToRepaint) + 2);
649
650 if (gutter != nullptr)
651 gutter->documentChanged (document, firstLineOnScreen);
652}
653
654void CodeEditorComponent::codeDocumentChanged (const int startIndex, const int endIndex)
655{
656 const CodeDocument::Position affectedTextStart (document, startIndex);
657 const CodeDocument::Position affectedTextEnd (document, endIndex);
658
659 retokenise (startIndex, endIndex);
660
661 updateCaretPosition();
662 columnToTryToMaintain = -1;
663
664 if (affectedTextEnd.getPosition() >= selectionStart.getPosition()
665 && affectedTextStart.getPosition() <= selectionEnd.getPosition())
666 deselectAll();
667
668 if (shouldFollowDocumentChanges)
669 if (caretPos.getPosition() > affectedTextEnd.getPosition()
670 || caretPos.getPosition() < affectedTextStart.getPosition())
672
673 updateScrollBars();
674}
675
676void CodeEditorComponent::retokenise (int startIndex, [[maybe_unused]] int endIndex)
677{
678 const CodeDocument::Position affectedTextStart (document, startIndex);
679
680 clearCachedIterators (affectedTextStart.getLineNumber());
681
682 rebuildLineTokensAsync();
683}
684
685//==============================================================================
686void CodeEditorComponent::updateCaretPosition()
687{
688 if (caret != nullptr)
689 {
690 caret->setCaretPosition (getCharacterBounds (getCaretPos()));
691
692 if (auto* handler = getAccessibilityHandler())
693 handler->notifyAccessibilityEvent (AccessibilityEvent::textSelectionChanged);
694 }
695}
696
698{
699 caretPos = newPos;
700 columnToTryToMaintain = -1;
701 bool selectionWasActive = isHighlightActive();
702
703 if (highlighting)
704 {
705 if (dragType == notDragging)
706 {
707 auto oldCaretPos = caretPos.getPosition();
708 auto isStart = std::abs (oldCaretPos - selectionStart.getPosition())
709 < std::abs (oldCaretPos - selectionEnd.getPosition());
710
711 dragType = isStart ? draggingSelectionStart : draggingSelectionEnd;
712 }
713
714 if (dragType == draggingSelectionStart)
715 {
716 if (selectionEnd.getPosition() < caretPos.getPosition())
717 {
718 setSelection (selectionEnd, caretPos);
719 dragType = draggingSelectionEnd;
720 }
721 else
722 {
723 setSelection (caretPos, selectionEnd);
724 }
725 }
726 else
727 {
728 if (caretPos.getPosition() < selectionStart.getPosition())
729 {
730 setSelection (caretPos, selectionStart);
731 dragType = draggingSelectionStart;
732 }
733 else
734 {
735 setSelection (selectionStart, caretPos);
736 }
737 }
738
739 rebuildLineTokensAsync();
740 }
741 else
742 {
743 deselectAll();
744 }
745
746 updateCaretPosition();
747 scrollToKeepCaretOnScreen();
748 updateScrollBars();
750
751 if (auto* handler = getAccessibilityHandler())
752 handler->notifyAccessibilityEvent (AccessibilityEvent::textChanged);
753
754 if (appCommandManager != nullptr && selectionWasActive != isHighlightActive())
755 appCommandManager->commandStatusChanged();
756}
757
758void CodeEditorComponent::deselectAll()
759{
760 if (isHighlightActive())
761 rebuildLineTokensAsync();
762
763 setSelection (caretPos, caretPos);
764 dragType = notDragging;
765}
766
767void CodeEditorComponent::updateScrollBars()
768{
769 verticalScrollBar.setRangeLimits (0, jmax (document.getNumLines(), firstLineOnScreen + linesOnScreen));
770 verticalScrollBar.setCurrentRange (firstLineOnScreen, linesOnScreen);
771
772 horizontalScrollBar.setRangeLimits (0, jmax ((double) document.getMaximumLineLength(), xOffset + columnsOnScreen));
773 horizontalScrollBar.setCurrentRange (xOffset, columnsOnScreen);
774}
775
776void CodeEditorComponent::scrollToLineInternal (int newFirstLineOnScreen)
777{
778 newFirstLineOnScreen = jlimit (0, jmax (0, document.getNumLines() - 1),
780
781 if (newFirstLineOnScreen != firstLineOnScreen)
782 {
783 firstLineOnScreen = newFirstLineOnScreen;
784 updateCaretPosition();
785
786 updateCachedIterators (firstLineOnScreen);
787 rebuildLineTokensAsync();
788 pimpl->handleUpdateNowIfNeeded();
789
791 }
792}
793
794void CodeEditorComponent::scrollToColumnInternal (double column)
795{
796 const double newOffset = jlimit (0.0, document.getMaximumLineLength() + 3.0, column);
797
798 if (! approximatelyEqual (xOffset, newOffset))
799 {
800 xOffset = newOffset;
801 updateCaretPosition();
802 repaint();
803 }
804}
805
806void CodeEditorComponent::scrollToLine (int newFirstLineOnScreen)
807{
808 scrollToLineInternal (newFirstLineOnScreen);
809 updateScrollBars();
810}
811
812void CodeEditorComponent::scrollToColumn (int newFirstColumnOnScreen)
813{
814 scrollToColumnInternal (newFirstColumnOnScreen);
815 updateScrollBars();
816}
817
818void CodeEditorComponent::scrollBy (int deltaLines)
819{
820 scrollToLine (firstLineOnScreen + deltaLines);
821}
822
823void CodeEditorComponent::scrollToKeepLinesOnScreen (Range<int> rangeToShow)
824{
825 if (rangeToShow.getStart() < firstLineOnScreen)
826 scrollBy (rangeToShow.getStart() - firstLineOnScreen);
827 else if (rangeToShow.getEnd() >= firstLineOnScreen + linesOnScreen)
828 scrollBy (rangeToShow.getEnd() - (firstLineOnScreen + linesOnScreen - 1));
829}
830
831void CodeEditorComponent::scrollToKeepCaretOnScreen()
832{
833 if (getWidth() > 0 && getHeight() > 0)
834 {
835 auto caretLine = caretPos.getLineNumber();
836 scrollToKeepLinesOnScreen ({ caretLine, caretLine });
837
838 auto column = indexToColumn (caretPos.getLineNumber(), caretPos.getIndexInLine());
839
840 if (column >= xOffset + columnsOnScreen - 1)
841 scrollToColumn (column + 1 - columnsOnScreen);
842 else if (column < xOffset)
843 scrollToColumn (column);
844 }
845}
846
848{
849 return { roundToInt ((getGutterSize() - xOffset * charWidth) + (float) indexToColumn (pos.getLineNumber(), pos.getIndexInLine()) * charWidth),
850 (pos.getLineNumber() - firstLineOnScreen) * lineHeight,
851 roundToInt (charWidth),
852 lineHeight };
853}
854
856{
857 const int line = y / lineHeight + firstLineOnScreen;
858 const int column = roundToInt ((x - (getGutterSize() - xOffset * charWidth)) / charWidth);
859 const int index = columnToIndex (line, column);
860
861 return CodeDocument::Position (document, line, index);
862}
863
865{
866 return getPositionAt (point.x, point.y).getPosition();
867}
868
870{
872
873 const CodeDocument::Position startPosition (document, textRange.getStart());
874 const CodeDocument::Position endPosition (document, textRange.getEnd());
875
876 for (int line = startPosition.getLineNumber(); line <= endPosition.getLineNumber(); ++line)
877 {
878 const CodeDocument::Position lineStartColumn0 { document, line, 0 };
879
880 const auto lineStart = line == startPosition.getLineNumber() ? lineStartColumn0.movedBy (startPosition.getIndexInLine())
882
883 const CodeDocument::Position lineEnd { document, line, line == endPosition.getLineNumber() ? endPosition.getIndexInLine()
884 : document.getLine (line).length() };
885
886 const auto startPos = getCharacterBounds (lineStart).getTopLeft();
887 const auto endPos = getCharacterBounds (lineEnd) .getTopLeft();
888
889 localRects.add (startPos.x, startPos.y, jmax (1, endPos.x - startPos.x), getLineHeight());
890 }
891
892 return localRects;
893}
894
895//==============================================================================
897{
898 insertText (newText);
899}
900
901void CodeEditorComponent::insertText (const String& newText)
902{
903 if (! readOnly)
904 {
905 document.deleteSection (selectionStart, selectionEnd);
906
907 if (newText.isNotEmpty())
908 document.insertText (caretPos, newText);
909
910 scrollToKeepCaretOnScreen();
912
913 if (auto* handler = getAccessibilityHandler())
914 handler->notifyAccessibilityEvent (AccessibilityEvent::textChanged);
915 }
916}
917
918void CodeEditorComponent::insertTabAtCaret()
919{
920 if (! readOnly)
921 {
923 && caretPos.getLineNumber() == caretPos.movedBy (1).getLineNumber())
924 {
925 moveCaretTo (document.findWordBreakAfter (caretPos), false);
926 }
927
928 if (useSpacesForTabs)
929 {
930 auto caretCol = indexToColumn (caretPos.getLineNumber(), caretPos.getIndexInLine());
931 auto spacesNeeded = spacesPerTab - (caretCol % spacesPerTab);
933 }
934 else
935 {
936 insertTextAtCaret ("\t");
937 }
938 }
939}
940
941bool CodeEditorComponent::deleteWhitespaceBackwardsToTabStop()
942{
943 if (getHighlightedRegion().isEmpty() && ! readOnly)
944 {
945 for (;;)
946 {
947 auto currentColumn = indexToColumn (caretPos.getLineNumber(), caretPos.getIndexInLine());
948
949 if (currentColumn <= 0 || (currentColumn % spacesPerTab) == 0)
950 break;
951
952 moveCaretLeft (false, true);
953 }
954
955 auto selected = getTextInRange (getHighlightedRegion());
956
957 if (selected.isNotEmpty() && selected.trim().isEmpty())
958 {
959 cut();
960 return true;
961 }
962 }
963
964 return false;
965}
966
967void CodeEditorComponent::indentSelection() { indentSelectedLines ( spacesPerTab); }
968void CodeEditorComponent::unindentSelection() { indentSelectedLines (-spacesPerTab); }
969
970void CodeEditorComponent::indentSelectedLines (const int spacesToAdd)
971{
972 if (! readOnly)
973 {
974 newTransaction();
975
976 CodeDocument::Position oldSelectionStart (selectionStart), oldSelectionEnd (selectionEnd), oldCaret (caretPos);
977 oldSelectionStart.setPositionMaintained (true);
978 oldSelectionEnd.setPositionMaintained (true);
979 oldCaret.setPositionMaintained (true);
980
981 const int lineStart = selectionStart.getLineNumber();
982 int lineEnd = selectionEnd.getLineNumber();
983
984 if (lineEnd > lineStart && selectionEnd.getIndexInLine() == 0)
985 --lineEnd;
986
987 for (int line = lineStart; line <= lineEnd; ++line)
988 {
989 auto lineText = document.getLine (line);
990 auto nonWhitespaceStart = CodeEditorHelpers::findFirstNonWhitespaceChar (lineText);
991
992 if (nonWhitespaceStart > 0 || lineText.trimStart().isNotEmpty())
993 {
994 const CodeDocument::Position wsStart (document, line, 0);
995 const CodeDocument::Position wsEnd (document, line, nonWhitespaceStart);
996
997 const int numLeadingSpaces = indexToColumn (line, wsEnd.getIndexInLine());
999
1001 {
1002 document.deleteSection (wsStart, wsEnd);
1004 }
1005 }
1006 }
1007
1008 setSelection (oldSelectionStart, oldSelectionEnd);
1009
1010 if (caretPos != oldCaret)
1011 {
1012 caretPos = oldCaret;
1013
1014 if (auto* handler = getAccessibilityHandler())
1015 handler->notifyAccessibilityEvent (AccessibilityEvent::textChanged);
1016 }
1017 }
1018}
1019
1020void CodeEditorComponent::cut()
1021{
1022 insertText ({});
1023}
1024
1025bool CodeEditorComponent::copyToClipboard()
1026{
1027 newTransaction();
1028 auto selection = document.getTextBetween (selectionStart, selectionEnd);
1029
1030 if (selection.isNotEmpty())
1032
1033 return true;
1034}
1035
1036bool CodeEditorComponent::cutToClipboard()
1037{
1038 copyToClipboard();
1039 cut();
1040 newTransaction();
1041 return true;
1042}
1043
1044bool CodeEditorComponent::pasteFromClipboard()
1045{
1046 newTransaction();
1048
1049 if (clip.isNotEmpty())
1050 insertText (clip);
1051
1052 newTransaction();
1053 return true;
1054}
1055
1056bool CodeEditorComponent::moveCaretLeft (const bool moveInWholeWordSteps, const bool selecting)
1057{
1058 newTransaction();
1059
1060 if (selecting && dragType == notDragging)
1061 {
1062 selectRegion (CodeDocument::Position (selectionEnd), CodeDocument::Position (selectionStart));
1063 dragType = draggingSelectionStart;
1064 }
1065
1066 if (isHighlightActive() && ! (selecting || moveInWholeWordSteps))
1067 {
1068 moveCaretTo (selectionStart, false);
1069 return true;
1070 }
1071
1073 moveCaretTo (document.findWordBreakBefore (caretPos), selecting);
1074 else
1075 moveCaretTo (caretPos.movedBy (-1), selecting);
1076
1077 return true;
1078}
1079
1080bool CodeEditorComponent::moveCaretRight (const bool moveInWholeWordSteps, const bool selecting)
1081{
1082 newTransaction();
1083
1084 if (selecting && dragType == notDragging)
1085 {
1086 selectRegion (CodeDocument::Position (selectionStart), CodeDocument::Position (selectionEnd));
1087 dragType = draggingSelectionEnd;
1088 }
1089
1090 if (isHighlightActive() && ! (selecting || moveInWholeWordSteps))
1091 {
1092 moveCaretTo (selectionEnd, false);
1093 return true;
1094 }
1095
1097 moveCaretTo (document.findWordBreakAfter (caretPos), selecting);
1098 else
1099 moveCaretTo (caretPos.movedBy (1), selecting);
1100
1101 return true;
1102}
1103
1104void CodeEditorComponent::moveLineDelta (const int delta, const bool selecting)
1105{
1106 CodeDocument::Position pos (caretPos);
1107 auto newLineNum = pos.getLineNumber() + delta;
1108
1109 if (columnToTryToMaintain < 0)
1110 columnToTryToMaintain = indexToColumn (pos.getLineNumber(), pos.getIndexInLine());
1111
1112 pos.setLineAndIndex (newLineNum, columnToIndex (newLineNum, columnToTryToMaintain));
1113
1114 auto colToMaintain = columnToTryToMaintain;
1115 moveCaretTo (pos, selecting);
1116 columnToTryToMaintain = colToMaintain;
1117}
1118
1119bool CodeEditorComponent::moveCaretDown (const bool selecting)
1120{
1121 newTransaction();
1122
1123 if (caretPos.getLineNumber() == document.getNumLines() - 1)
1124 moveCaretTo (CodeDocument::Position (document, std::numeric_limits<int>::max(), std::numeric_limits<int>::max()), selecting);
1125 else
1126 moveLineDelta (1, selecting);
1127
1128 return true;
1129}
1130
1131bool CodeEditorComponent::moveCaretUp (const bool selecting)
1132{
1133 newTransaction();
1134
1135 if (caretPos.getLineNumber() == 0)
1136 moveCaretTo (CodeDocument::Position (document, 0, 0), selecting);
1137 else
1138 moveLineDelta (-1, selecting);
1139
1140 return true;
1141}
1142
1143bool CodeEditorComponent::pageDown (const bool selecting)
1144{
1145 newTransaction();
1146 scrollBy (jlimit (0, linesOnScreen, 1 + document.getNumLines() - firstLineOnScreen - linesOnScreen));
1147 moveLineDelta (linesOnScreen, selecting);
1148 return true;
1149}
1150
1151bool CodeEditorComponent::pageUp (const bool selecting)
1152{
1153 newTransaction();
1154 scrollBy (-linesOnScreen);
1155 moveLineDelta (-linesOnScreen, selecting);
1156 return true;
1157}
1158
1159bool CodeEditorComponent::scrollUp()
1160{
1161 newTransaction();
1162 scrollBy (1);
1163
1164 if (caretPos.getLineNumber() < firstLineOnScreen)
1165 moveLineDelta (1, false);
1166
1167 return true;
1168}
1169
1170bool CodeEditorComponent::scrollDown()
1171{
1172 newTransaction();
1173 scrollBy (-1);
1174
1175 if (caretPos.getLineNumber() >= firstLineOnScreen + linesOnScreen)
1176 moveLineDelta (-1, false);
1177
1178 return true;
1179}
1180
1181bool CodeEditorComponent::moveCaretToTop (const bool selecting)
1182{
1183 newTransaction();
1184 moveCaretTo (CodeDocument::Position (document, 0, 0), selecting);
1185 return true;
1186}
1187
1188bool CodeEditorComponent::moveCaretToStartOfLine (const bool selecting)
1189{
1190 newTransaction();
1191
1192 int index = CodeEditorHelpers::findFirstNonWhitespaceChar (caretPos.getLineText());
1193
1194 if (index >= caretPos.getIndexInLine() && caretPos.getIndexInLine() > 0)
1195 index = 0;
1196
1197 moveCaretTo (CodeDocument::Position (document, caretPos.getLineNumber(), index), selecting);
1198 return true;
1199}
1200
1201bool CodeEditorComponent::moveCaretToEnd (const bool selecting)
1202{
1203 newTransaction();
1204 moveCaretTo (CodeDocument::Position (document, std::numeric_limits<int>::max(),
1206 return true;
1207}
1208
1209bool CodeEditorComponent::moveCaretToEndOfLine (const bool selecting)
1210{
1211 newTransaction();
1212 moveCaretTo (CodeDocument::Position (document, caretPos.getLineNumber(),
1214 return true;
1215}
1216
1217bool CodeEditorComponent::deleteBackwards (const bool moveInWholeWordSteps)
1218{
1220 {
1221 cut(); // in case something is already highlighted
1222 moveCaretTo (document.findWordBreakBefore (caretPos), true);
1223 }
1224 else if (selectionStart == selectionEnd && ! skipBackwardsToPreviousTab())
1225 {
1226 selectionStart.moveBy (-1);
1227 }
1228
1229 cut();
1230 return true;
1231}
1232
1233bool CodeEditorComponent::skipBackwardsToPreviousTab()
1234{
1235 auto currentLineText = caretPos.getLineText().removeCharacters ("\r\n");
1236 auto currentIndex = caretPos.getIndexInLine();
1237
1238 if (currentLineText.isNotEmpty() && currentLineText.length() == currentIndex)
1239 {
1240 const int currentLine = caretPos.getLineNumber();
1241 const int currentColumn = indexToColumn (currentLine, currentIndex);
1242 const int previousTabColumn = (currentColumn - 1) - ((currentColumn - 1) % spacesPerTab);
1243 const int previousTabIndex = columnToIndex (currentLine, previousTabColumn);
1244
1245 if (currentLineText.substring (previousTabIndex, currentIndex).trim().isEmpty())
1246 {
1247 selectionStart.moveBy (previousTabIndex - currentIndex);
1248 return true;
1249 }
1250 }
1251
1252 return false;
1253}
1254
1255bool CodeEditorComponent::deleteForwards (const bool moveInWholeWordSteps)
1256{
1258 {
1259 cut(); // in case something is already highlighted
1260 moveCaretTo (document.findWordBreakAfter (caretPos), true);
1261 }
1262 else
1263 {
1264 if (selectionStart == selectionEnd)
1265 selectionEnd.moveBy (1);
1266 else
1267 newTransaction();
1268 }
1269
1270 cut();
1271 return true;
1272}
1273
1274bool CodeEditorComponent::selectAll()
1275{
1276 newTransaction();
1277 selectRegion (CodeDocument::Position (document, std::numeric_limits<int>::max(),
1279 CodeDocument::Position (document, 0, 0));
1280 return true;
1281}
1282
1283void CodeEditorComponent::selectRegion (const CodeDocument::Position& start,
1284 const CodeDocument::Position& end)
1285{
1286 moveCaretTo (start, false);
1287 moveCaretTo (end, true);
1288}
1289
1290//==============================================================================
1291bool CodeEditorComponent::undo()
1292{
1293 if (readOnly)
1294 return false;
1295
1296 ScopedValueSetter<bool> svs (shouldFollowDocumentChanges, true, false);
1297 document.undo();
1298 scrollToKeepCaretOnScreen();
1299 return true;
1300}
1301
1302bool CodeEditorComponent::redo()
1303{
1304 if (readOnly)
1305 return false;
1306
1307 ScopedValueSetter<bool> svs (shouldFollowDocumentChanges, true, false);
1308 document.redo();
1309 scrollToKeepCaretOnScreen();
1310 return true;
1311}
1312
1313void CodeEditorComponent::newTransaction()
1314{
1315 document.newTransaction();
1316 pimpl->startTimer (600);
1317}
1318
1320{
1321 appCommandManager = newManager;
1322}
1323
1324//==============================================================================
1326{
1327 return { selectionStart.getPosition(),
1328 selectionEnd.getPosition() };
1329}
1330
1331bool CodeEditorComponent::isHighlightActive() const noexcept
1332{
1333 return selectionStart != selectionEnd;
1334}
1335
1337{
1339 return;
1340
1341 const auto cursorAtStart = newRange.getEnd() == getHighlightedRegion().getStart()
1342 || newRange.getEnd() == getHighlightedRegion().getEnd();
1343 selectRegion (CodeDocument::Position (document, cursorAtStart ? newRange.getEnd() : newRange.getStart()),
1344 CodeDocument::Position (document, cursorAtStart ? newRange.getStart() : newRange.getEnd()));
1345}
1346
1348{
1349 return document.getTextBetween (CodeDocument::Position (document, range.getStart()),
1350 CodeDocument::Position (document, range.getEnd()));
1351}
1352
1353//==============================================================================
1355{
1357 {
1358 if (readOnly)
1359 return false;
1360
1361 if (key == KeyPress::tabKey || key.getTextCharacter() == '\t') handleTabKey();
1362 else if (key == KeyPress::returnKey) handleReturnKey();
1363 else if (key == KeyPress::escapeKey) handleEscapeKey();
1364 else if (key == KeyPress ('[', ModifierKeys::commandModifier, 0)) unindentSelection();
1365 else if (key == KeyPress (']', ModifierKeys::commandModifier, 0)) indentSelection();
1367 else return false;
1368 }
1369
1370 pimpl->handleUpdateNowIfNeeded();
1371 return true;
1372}
1373
1378
1380{
1381 insertTabAtCaret();
1382}
1383
1385{
1386 newTransaction();
1387}
1388
1392
1396
1397//==============================================================================
1402
1415
1417{
1418 const bool anythingSelected = isHighlightActive();
1419
1420 switch (commandID)
1421 {
1423 result.setInfo (TRANS ("Cut"), TRANS ("Copies the currently selected text to the clipboard and deletes it."), "Editing", 0);
1424 result.setActive (anythingSelected && ! readOnly);
1426 break;
1427
1429 result.setInfo (TRANS ("Copy"), TRANS ("Copies the currently selected text to the clipboard."), "Editing", 0);
1430 result.setActive (anythingSelected);
1432 break;
1433
1435 result.setInfo (TRANS ("Paste"), TRANS ("Inserts text from the clipboard."), "Editing", 0);
1436 result.setActive (! readOnly);
1438 break;
1439
1441 result.setInfo (TRANS ("Delete"), TRANS ("Deletes any selected text."), "Editing", 0);
1442 result.setActive (anythingSelected && ! readOnly);
1443 break;
1444
1446 result.setInfo (TRANS ("Select All"), TRANS ("Selects all the text in the editor."), "Editing", 0);
1448 break;
1449
1451 result.setInfo (TRANS ("Undo"), TRANS ("Undo"), "Editing", 0);
1453 result.setActive (document.getUndoManager().canUndo() && ! readOnly);
1454 break;
1455
1457 result.setInfo (TRANS ("Redo"), TRANS ("Redo"), "Editing", 0);
1459 result.setActive (document.getUndoManager().canRedo() && ! readOnly);
1460 break;
1461
1462 default:
1463 break;
1464 }
1465}
1466
1468{
1469 return performCommand (info.commandID);
1470}
1471
1473{
1474 caret.reset (getLookAndFeel().createCaretComponent (this));
1475 addAndMakeVisible (caret.get());
1476}
1477
1478bool CodeEditorComponent::performCommand (const CommandID commandID)
1479{
1480 switch (commandID)
1481 {
1482 case StandardApplicationCommandIDs::cut: cutToClipboard(); break;
1483 case StandardApplicationCommandIDs::copy: copyToClipboard(); break;
1484 case StandardApplicationCommandIDs::paste: pasteFromClipboard(); break;
1485 case StandardApplicationCommandIDs::del: cut(); break;
1486 case StandardApplicationCommandIDs::selectAll: selectAll(); break;
1487 case StandardApplicationCommandIDs::undo: undo(); break;
1488 case StandardApplicationCommandIDs::redo: redo(); break;
1489 default: return false;
1490 }
1491
1492 return true;
1493}
1494
1495void CodeEditorComponent::setSelection (CodeDocument::Position newSelectionStart,
1496 CodeDocument::Position newSelectionEnd)
1497{
1498 if (selectionStart != newSelectionStart
1499 || selectionEnd != newSelectionEnd)
1500 {
1501 selectionStart = newSelectionStart;
1502 selectionEnd = newSelectionEnd;
1503
1504 if (auto* handler = getAccessibilityHandler())
1505 handler->notifyAccessibilityEvent (AccessibilityEvent::textSelectionChanged);
1506 }
1507}
1508
1509//==============================================================================
1511{
1512 m.addItem (StandardApplicationCommandIDs::cut, TRANS ("Cut"), isHighlightActive() && ! readOnly);
1514 m.addItem (StandardApplicationCommandIDs::paste, TRANS ("Paste"), ! readOnly);
1515 m.addItem (StandardApplicationCommandIDs::del, TRANS ("Delete"), ! readOnly);
1516 m.addSeparator();
1518 m.addSeparator();
1521}
1522
1524{
1525 performCommand (menuItemID);
1526}
1527
1528static void codeEditorMenuCallback (int menuResult, CodeEditorComponent* editor)
1529{
1530 if (editor != nullptr && menuResult != 0)
1532}
1533
1534//==============================================================================
1536{
1537 newTransaction();
1538 dragType = notDragging;
1539
1540 if (e.mods.isPopupMenu())
1541 {
1543
1544 if (getHighlightedRegion().isEmpty())
1545 {
1547 document.findTokenContaining (getPositionAt (e.x, e.y), start, end);
1548
1549 if (start.getPosition() < end.getPosition())
1550 selectRegion (start, end);
1551 }
1552
1553 PopupMenu m;
1555 addPopupMenuItems (m, &e);
1556
1558 ModalCallbackFunction::forComponent (codeEditorMenuCallback, this));
1559 }
1560 else
1561 {
1562 beginDragAutoRepeat (100);
1564 }
1565}
1566
1568{
1569 if (! e.mods.isPopupMenu())
1570 moveCaretTo (getPositionAt (e.x, e.y), true);
1571}
1572
1574{
1575 newTransaction();
1577 dragType = notDragging;
1579}
1580
1582{
1585
1586 if (e.getNumberOfClicks() > 2)
1588 else
1590
1591 selectRegion (tokenStart, tokenEnd);
1592 dragType = notDragging;
1593}
1594
1596{
1597 if ((verticalScrollBar.isVisible() && ! approximatelyEqual (wheel.deltaY, 0.0f))
1598 || (horizontalScrollBar.isVisible() && ! approximatelyEqual (wheel.deltaX, 0.0f)))
1599 {
1600 {
1602 w.deltaX = 0;
1603 verticalScrollBar.mouseWheelMove (e, w);
1604 }
1605
1606 {
1608 w.deltaY = 0;
1609 horizontalScrollBar.mouseWheelMove (e, w);
1610 }
1611 }
1612 else
1613 {
1615 }
1616}
1617
1618//==============================================================================
1619void CodeEditorComponent::focusGained (FocusChangeType) { updateCaretPosition(); }
1620void CodeEditorComponent::focusLost (FocusChangeType) { updateCaretPosition(); }
1621
1622//==============================================================================
1624{
1625 useSpacesForTabs = insertSpaces;
1626
1627 if (spacesPerTab != numSpaces)
1628 {
1629 spacesPerTab = numSpaces;
1630 rebuildLineTokensAsync();
1631 }
1632}
1633
1635{
1636 return String::repeatedString (useSpacesForTabs ? " " : "\t",
1637 useSpacesForTabs ? numSpaces
1638 : (numSpaces / spacesPerTab));
1639}
1640
1641int CodeEditorComponent::indexToColumn (int lineNum, int index) const noexcept
1642{
1643 auto line = document.getLine (lineNum);
1644 auto t = line.getCharPointer();
1645 int col = 0;
1646
1647 for (int i = 0; i < index; ++i)
1648 {
1649 if (t.isEmpty())
1650 {
1652 break;
1653 }
1654
1655 if (t.getAndAdvance() != '\t')
1656 ++col;
1657 else
1658 col += getTabSize() - (col % getTabSize());
1659 }
1660
1661 return col;
1662}
1663
1664int CodeEditorComponent::columnToIndex (int lineNum, int column) const noexcept
1665{
1666 auto line = document.getLine (lineNum);
1667 auto t = line.getCharPointer();
1668 int i = 0, col = 0;
1669
1670 while (! t.isEmpty())
1671 {
1672 if (t.getAndAdvance() != '\t')
1673 ++col;
1674 else
1675 col += getTabSize() - (col % getTabSize());
1676
1677 if (col > column)
1678 break;
1679
1680 ++i;
1681 }
1682
1683 return i;
1684}
1685
1686//==============================================================================
1688{
1689 font = newFont;
1690 charWidth = font.getStringWidthFloat ("0");
1691 lineHeight = roundToInt (font.getHeight());
1692 resized();
1693}
1694
1695void CodeEditorComponent::ColourScheme::set (const String& name, Colour colour)
1696{
1697 for (auto& tt : types)
1698 {
1699 if (tt.name == name)
1700 {
1701 tt.colour = colour;
1702 return;
1703 }
1704 }
1705
1706 TokenType tt;
1707 tt.name = name;
1708 tt.colour = colour;
1709 types.add (tt);
1710}
1711
1713{
1714 colourScheme = scheme;
1715 repaint();
1716}
1717
1719{
1720 return isPositiveAndBelow (tokenType, colourScheme.types.size())
1721 ? colourScheme.types.getReference (tokenType).colour
1723}
1724
1725void CodeEditorComponent::clearCachedIterators (const int firstLineToBeInvalid)
1726{
1727 int i;
1728 for (i = cachedIterators.size(); --i >= 0;)
1729 if (cachedIterators.getUnchecked (i).getLine() < firstLineToBeInvalid)
1730 break;
1731
1732 cachedIterators.removeRange (jmax (0, i - 1), cachedIterators.size());
1733}
1734
1735void CodeEditorComponent::updateCachedIterators (int maxLineNum)
1736{
1737 const int maxNumCachedPositions = 5000;
1738 const int linesBetweenCachedSources = jmax (10, document.getNumLines() / maxNumCachedPositions);
1739
1740 if (cachedIterators.size() == 0)
1741 cachedIterators.add (CodeDocument::Iterator (document));
1742
1743 if (codeTokeniser != nullptr)
1744 {
1745 for (;;)
1746 {
1747 const auto last = cachedIterators.getLast();
1748
1749 if (last.getLine() >= maxLineNum)
1750 break;
1751
1752 cachedIterators.add (CodeDocument::Iterator (last));
1753 auto& t = cachedIterators.getReference (cachedIterators.size() - 1);
1754 const int targetLine = jmin (maxLineNum, last.getLine() + linesBetweenCachedSources);
1755
1756 for (;;)
1757 {
1758 codeTokeniser->readNextToken (t);
1759
1760 if (t.getLine() >= targetLine)
1761 break;
1762
1763 if (t.isEOF())
1764 return;
1765 }
1766 }
1767 }
1768}
1769
1770void CodeEditorComponent::getIteratorForPosition (int position, CodeDocument::Iterator& source)
1771{
1772 if (codeTokeniser != nullptr)
1773 {
1774 for (int i = cachedIterators.size(); --i >= 0;)
1775 {
1776 auto& t = cachedIterators.getReference (i);
1777
1778 if (t.getPosition() <= position)
1779 {
1780 source = t;
1781 break;
1782 }
1783 }
1784
1785 while (source.getPosition() < position)
1786 {
1787 const CodeDocument::Iterator original (source);
1788 codeTokeniser->readNextToken (source);
1789
1790 if (source.getPosition() > position || source.isEOF())
1791 {
1792 source = original;
1793 break;
1794 }
1795 }
1796 }
1797}
1798
1800 : lastTopLine (editor.getFirstLineOnScreen()),
1801 lastCaretPos (editor.getCaretPos().getPosition()),
1802 lastSelectionEnd (lastCaretPos)
1803{
1804 auto selection = editor.getHighlightedRegion();
1805
1806 if (lastCaretPos == selection.getStart())
1807 lastSelectionEnd = selection.getEnd();
1808 else
1809 lastSelectionEnd = selection.getStart();
1810}
1811
1813 : lastTopLine (other.lastTopLine),
1814 lastCaretPos (other.lastCaretPos),
1815 lastSelectionEnd (other.lastSelectionEnd)
1816{
1817}
1818
1820{
1821 editor.selectRegion (CodeDocument::Position (editor.getDocument(), lastSelectionEnd),
1822 CodeDocument::Position (editor.getDocument(), lastCaretPos));
1823
1824 if (lastTopLine > 0 && lastTopLine < editor.getDocument().getNumLines())
1825 editor.scrollToLine (lastTopLine);
1826}
1827
1829{
1830 auto tokens = StringArray::fromTokens (s, ":", {});
1831
1832 lastTopLine = tokens[0].getIntValue();
1833 lastCaretPos = tokens[1].getIntValue();
1834 lastSelectionEnd = tokens[2].getIntValue();
1835}
1836
1838{
1839 return String (lastTopLine) + ":" + String (lastCaretPos) + ":" + String (lastSelectionEnd);
1840}
1841
1842//==============================================================================
1844{
1845 return std::make_unique<CodeEditorAccessibilityHandler> (*this);
1846}
1847
1848} // namespace juce
Base class for accessible Components.
An abstract interface which represents a UI element that supports a text interface.
One of these objects holds a list of all the commands your app can perform, and despatches these comm...
void commandStatusChanged()
This should be called to tell the manager that one of its registered commands may have changed its ac...
A command target publishes a list of command IDs that it can perform.
ApplicationCommandTarget * findFirstTargetParentComponent()
If this object is a Component, this method will search upwards in its current UI hierarchy for the ne...
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:56
void swapWith(OtherArrayType &otherArray) noexcept
This swaps the contents of this array with those of another array.
Definition juce_Array.h:621
void ensureStorageAllocated(int minNumElements)
Increases the array's internal storage to hold a minimum number of elements.
void addArray(const Type *elementsToAdd, int numElementsToAdd)
Adds elements from an array to the end of this array.
Definition juce_Array.h:583
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition juce_Array.h:418
Has a callback method that is triggered asynchronously.
A text string with a set of colour/font settings that are associated with sub-ranges of the text.
void setJustification(Justification newJustification) noexcept
Sets the justification that should be used for laying-out the text.
static bool isWhitespace(char character) noexcept
Checks whether a character is whitespace.
Iterates the text in a CodeDocument.
int getPosition() const noexcept
Returns the position as the number of characters from the start of the document.
An object that receives callbacks from the CodeDocument when its text changes.
A position in a code document.
juce_wchar getCharacter() const
Returns the character in the document at this position.
int getIndexInLine() const noexcept
Returns the number of characters from the start of the line.
int getPosition() const noexcept
Returns the position as the number of characters from the start of the document.
int getLineNumber() const noexcept
Returns the line number of this position.
void moveBy(int characterDelta)
Moves the position forwards or backwards by the specified number of characters.
void setPositionMaintained(bool isMaintained)
Allows the position to be automatically updated when the document changes.
Position movedBy(int characterDelta) const
Returns a position which is the same as this one, moved by the specified number of characters.
void setPosition(int charactersFromStartOfDocument)
Points this object at a new position within the document.
String getLineText() const
Returns the line from the document that this position is within.
A class for storing and manipulating a source code file.
void undo()
Undo the last operation.
Position findWordBreakBefore(const Position &position) const noexcept
Searches for a word-break.
void addListener(Listener *listener)
Registers a listener object to receive callbacks when the document changes.
void replaceAllContent(const String &newContent)
Clears the document and replaces it with some new text.
String getLine(int lineIndex) const noexcept
Returns a line from the document.
void clearUndoHistory()
Clears the undo history.
void newTransaction()
Begins a new undo transaction.
int getNumLines() const noexcept
Returns the number of lines in the document.
UndoManager & getUndoManager() noexcept
Returns the document's UndoManager.
Position findWordBreakAfter(const Position &position) const noexcept
Searches for a word-break.
void findLineContaining(const Position &pos, Position &start, Position &end) const noexcept
Finds the line that contains the given position.
void removeListener(Listener *listener)
Deregisters a listener.
void insertText(const Position &position, const String &text)
Inserts some text into the document at a given position.
void deleteSection(const Position &startPosition, const Position &endPosition)
Deletes a section of the text.
int getMaximumLineLength() noexcept
Returns the number of characters in the longest line of the document.
String getNewLineCharacters() const noexcept
Returns the preferred new-line characters for the document.
void redo()
Redo the last operation.
void findTokenContaining(const Position &pos, Position &start, Position &end) const noexcept
Finds the token that contains the given position.
void setSavePoint() noexcept
Makes a note that the document's current state matches the one that is saved.
String getTextBetween(const Position &start, const Position &end) const
Returns a section of the document's text.
void paint(Graphics &g) override
Components can override this method to draw their content.
A text editor component designed specifically for source code.
int getFirstLineOnScreen() const noexcept
Returns the index of the first line that's visible at the top of the editor.
void getCommandInfo(CommandID, ApplicationCommandInfo &) override
This must provide details about one of the commands that this target can perform.
void lookAndFeelChanged() override
Called to let the component react to a change in the look-and-feel setting.
void mouseUp(const MouseEvent &) override
Called when a mouse button is released.
void mouseDrag(const MouseEvent &) override
Called when the mouse is moved while a button is held down.
~CodeEditorComponent() override
Destructor.
String getTabString(int numSpaces) const
Returns a string containing spaces or tab characters to generate the given number of spaces.
CodeEditorComponent(CodeDocument &document, CodeTokeniser *codeTokeniser)
Creates an editor for a document.
void setTemporaryUnderlining(const Array< Range< int > > &) override
Sets a number of temporarily underlined sections.
int getLineHeight() const noexcept
Returns the height of a line of text, in pixels.
virtual void handleTabKey()
Called when the tab key is pressed - this can be overridden for custom behaviour.
void retokenise(int startIndex, int endIndex)
Rebuilds the syntax highlighting for a section of text.
virtual void handleReturnKey()
Called when the return key is pressed - this can be overridden for custom behaviour.
void setHighlightedRegion(const Range< int > &newRange) override
Sets the currently-selected text region.
Colour getColourForTokenType(int tokenType) const
Returns one the syntax highlighting colour for the given token.
void setReadOnly(bool shouldBeReadOnly) noexcept
Makes the editor read-only.
void loadContent(const String &newContent)
Loads the given content into the document.
void setScrollbarThickness(int thickness)
Changes the size of the scrollbars.
void focusLost(FocusChangeType) override
Called to indicate that this component has just lost the keyboard focus.
void paint(Graphics &) override
Components can override this method to draw their content.
virtual void performPopupMenuAction(int menuItemID)
This is called to perform one of the items that was shown on the popup menu.
CodeDocument::Position getCaretPos() const
Returns the current caret position.
virtual void caretPositionMoved()
Called when the caret position moves.
void setTabSize(int numSpacesPerTab, bool insertSpacesInsteadOfTabCharacters)
Changes the current tab settings.
void setLineNumbersShown(bool shouldBeShown)
Enables or disables the line-number display in the gutter.
Range< int > getHighlightedRegion() const override
Returns the extents of the selected text region, or an empty range if nothing is selected,...
void resized() override
Called when this component's size has been changed.
bool perform(const InvocationInfo &) override
This must actually perform the specified command.
void focusGained(FocusChangeType) override
Called to indicate that this component has just acquired the keyboard focus.
void insertTextAtCaret(const String &textToInsert) override
Inserts some text, overwriting the selected text region, if there is one.
CodeDocument & getDocument() const noexcept
Returns the code document that this component is editing.
void mouseDown(const MouseEvent &) override
Called when a mouse button is pressed.
void setCommandManager(ApplicationCommandManager *newManager) noexcept
Specifies a command-manager which the editor will notify whenever the state of any of its commands ch...
virtual void handleEscapeKey()
Called when the escape key is pressed - this can be overridden for custom behaviour.
void mouseDoubleClick(const MouseEvent &) override
Called when a mouse button has been double-clicked on a component.
void setColourScheme(const ColourScheme &scheme)
Changes the syntax highlighting scheme.
Rectangle< int > getCharacterBounds(const CodeDocument::Position &pos) const
Returns the on-screen position of a character in the document.
String getTextInRange(const Range< int > &range) const override
Returns a specified sub-section of the text.
ApplicationCommandTarget * getNextCommandTarget() override
This must return the next target to try after this one.
bool isTextInputActive() const override
Returns true if this input target is currently accepting input.
void getAllCommands(Array< CommandID > &) override
This must return a complete list of commands that this target can handle.
void setFont(const Font &newFont)
Changes the font.
CodeDocument::Position getPositionAt(int x, int y) const
Finds the character at a given on-screen position.
RectangleList< int > getTextBounds(Range< int > textRange) const override
Returns the bounding box for a range of text in the editor.
std::unique_ptr< AccessibilityHandler > createAccessibilityHandler() override
Override this method to return a custom AccessibilityHandler for this component.
void mouseWheelMove(const MouseEvent &, const MouseWheelDetails &) override
Called when the mouse-wheel is moved.
bool keyPressed(const KeyPress &) override
Called when a key is pressed.
virtual void editorViewportPositionChanged()
Called when the view position is scrolled horizontally or vertically.
virtual void addPopupMenuItems(PopupMenu &menuToAddTo, const MouseEvent *mouseClickEvent)
This adds the items to the popup menu.
@ lineNumberTextId
The colour to use for drawing the line numbers.
@ backgroundColourId
A colour to use to fill the editor's background.
@ highlightColourId
The colour to use for the highlighted background under selected text.
@ lineNumberBackgroundId
The colour to use for filling the background of the line-number gutter.
@ defaultTextColourId
The colour to use for text when no syntax colouring is enabled.
void moveCaretTo(const CodeDocument::Position &newPos, bool selecting)
Moves the caret.
int getCharIndexForPoint(Point< int > point) const override
A base class for tokenising code so that the syntax can be displayed in a code editor.
virtual CodeEditorComponent::ColourScheme getDefaultColourScheme()=0
Returns a suggested syntax highlighting colour scheme.
virtual int readNextToken(CodeDocument::Iterator &source)=0
Reads the next token from the source and returns its token type.
Represents a colour, also including a transparency value.
Definition juce_Colour.h:38
The base class for all JUCE user-interface objects.
bool isVisible() const noexcept
Tests whether the component is visible or not.
Component * getParentComponent() const noexcept
Returns the component which this component is inside.
Point< int > getPosition() const noexcept
Returns the component's top-left position as a Point.
int getHeight() const noexcept
Returns the component's height in pixels.
int getX() const noexcept
Returns the x coordinate of the component's left edge.
void addAndMakeVisible(Component *child, int zOrder=-1)
Adds a child component to this one, and also makes the child visible if it isn't already.
FocusChangeType
Enumeration used by the focusGained() and focusLost() methods.
AccessibilityHandler * getAccessibilityHandler()
Returns the accessibility handler for this component, or nullptr if this component is not accessible.
void setOpaque(bool shouldBeOpaque)
Indicates whether any parts of the component might be transparent.
void setMouseCursor(const MouseCursor &cursorType)
Changes the mouse cursor shape to use when the mouse is over this component.
void repaint()
Marks the whole component as needing to be redrawn.
int getY() const noexcept
Returns the y coordinate of the top of this component.
void setBounds(int x, int y, int width, int height)
Changes the component's position and size.
void setWantsKeyboardFocus(bool wantsFocus) noexcept
Sets a flag to indicate whether this component wants keyboard focus or not.
Colour findColour(int colourID, bool inheritFromParent=false) const
Looks for a colour that has been registered with the given colour ID number.
int getWidth() const noexcept
Returns the component's width in pixels.
void mouseWheelMove(const MouseEvent &event, const MouseWheelDetails &wheel) override
Called when the mouse-wheel is moved.
static void JUCE_CALLTYPE beginDragAutoRepeat(int millisecondsBetweenCallbacks)
Ensures that a non-stop stream of mouse-drag events will be sent during the current mouse-drag operat...
ComponentPeer * getPeer() const
Returns the heavyweight window that contains this component.
LookAndFeel & getLookAndFeel() const noexcept
Finds the appropriate look-and-feel to use for this component.
Represents a particular font, including its size, style, etc.
Definition juce_Font.h:42
float getHeight() const noexcept
Returns the total height of this font, in pixels.
static const String & getDefaultMonospacedFontName()
Returns a typeface font family that represents the default monospaced font.
float getStringWidthFloat(const String &text) const
Returns the total width of a string as it would be drawn using this font.
void setTypefaceName(const String &faceName)
Changes the font family of the typeface.
A set of glyphs, each with a position.
void addFittedText(const Font &font, const String &text, float x, float y, float width, float height, Justification layout, int maximumLinesToUse, float minimumHorizontalScale=0.0f)
Tries to fit some text within a given space.
A graphics context, used for drawing a component or image.
void setFont(const Font &newFont)
Changes the font to use for subsequent text-drawing functions.
void fillRectList(const RectangleList< float > &rectangles) const
Fills a set of rectangles using the current colour or brush.
bool reduceClipRegion(int x, int y, int width, int height)
Intersects the current clipping region with another region.
Rectangle< int > getClipBounds() const
Returns the position of the bounding box for the current clipping region.
void setColour(Colour newColour)
Changes the current drawing colour.
void fillAll() const
Fills the context's entire clip region with the current colour or brush.
@ centredRight
Indicates that the item should be centred vertically but placed on the right hand side.
@ centredLeft
Indicates that the item should be centred vertically but placed on the left hand side.
Represents a key press, including any modifier keys that are needed.
static const int tabKey
key-code for the tab key
juce_wchar getTextCharacter() const noexcept
Returns the character that is associated with this keypress.
static const int escapeKey
key-code for the escape key
static const int returnKey
key-code for the return key
static ModalComponentManager::Callback * forComponent(void(*functionToCall)(int, ComponentType *), ComponentType *component)
This is a utility function to create a ModalComponentManager::Callback that will call a static functi...
bool isPopupMenu() const noexcept
Checks whether the user is trying to launch a pop-up menu.
bool isShiftDown() const noexcept
Checks whether the shift key's flag is set.
@ commandModifier
Command key flag - on windows this is the same as the CTRL key flag.
@ shiftModifier
Shift key flag.
@ NormalCursor
The standard arrow cursor.
@ IBeamCursor
A vertical I-beam for positioning within text.
Contains position and status information about a mouse event.
const ModifierKeys mods
The key modifiers associated with the event.
const int x
The x-position of the mouse when the event occurred.
const int y
The y-position of the mouse when the event occurred.
int getNumberOfClicks() const noexcept
For a click event, the number of times the mouse was clicked in succession.
A simple optional type.
A pair of (x, y) coordinates.
Definition juce_Point.h:42
ValueType y
The point's Y coordinate.
Definition juce_Point.h:252
ValueType x
The point's X coordinate.
Definition juce_Point.h:251
Class used to create a set of options to pass to the show() method.
Creates and displays a popup-menu.
void addSeparator()
Appends a separator to the menu, to help break it up into sections.
void showMenuAsync(const Options &options)
Runs the menu asynchronously.
void setLookAndFeel(LookAndFeel *newLookAndFeel)
Specifies a look-and-feel for the menu and any sub-menus that it has.
void addItem(Item newItem)
Adds an item to the menu.
A general-purpose range object, that simply represents any linear range with a start and end point.
Definition juce_Range.h:40
constexpr ValueType getStart() const noexcept
Returns the start of the range.
Definition juce_Range.h:80
constexpr ValueType getEnd() const noexcept
Returns the end of the range.
Definition juce_Range.h:86
Maintains a set of rectangles as a complex region.
void add(RectangleType rect)
Merges a new rectangle into the list.
Manages a rectangle and allows geometric operations to be performed on it.
Point< ValueType > getTopLeft() const noexcept
Returns the rectangle's top-left position as a Point.
A class for receiving events from a ScrollBar.
A scrollbar component.
bool setCurrentRange(Range< double > newRange, NotificationType notification=sendNotificationAsync)
Changes the position of the scrollbar's 'thumb'.
void addListener(Listener *listener)
Registers a listener that will be called when the scrollbar is moved.
void mouseWheelMove(const MouseEvent &, const MouseWheelDetails &) override
Called when the mouse-wheel is moved.
void setRangeLimits(Range< double > newRangeLimit, NotificationType notification=sendNotificationAsync)
Sets the minimum and maximum values that the bar will move between.
void setSingleStepSize(double newSingleStepSize) noexcept
Sets the amount by which the up and down buttons will move the bar.
static StringArray fromTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
Returns an array containing the tokens in a given string.
A simple class for holding temporary references to a string literal or String.
The JUCE String class!
Definition juce_String.h:53
CharPointerType getCharPointer() const noexcept
Returns the character pointer currently being used to store this string.
static String repeatedString(StringRef stringToRepeat, int numberOfTimesToRepeat)
Creates a string which is a version of a string repeated and joined together.
int length() const noexcept
Returns the number of characters in the string.
String removeCharacters(StringRef charactersToRemove) const
Returns a version of this string with a set of characters removed.
static String charToString(juce_wchar character)
Creates a string from a single character.
String substring(int startIndex, int endIndex) const
Returns a subsection of the string.
static String getTextFromClipboard()
Gets the current clipboard's contents.
static void copyTextToClipboard(const String &text)
Copies a string of text onto the clipboard.
Makes repeated callbacks to a virtual method at a specified time interval.
Definition juce_Timer.h:52
bool canUndo() const
Returns true if there's at least one action in the list to undo.
bool canRedo() const
Returns true if there's at least one action in the list to redo.
T get(T... args)
#define TRANS(stringLiteral)
Uses the LocalisedStrings class to translate the given string literal.
#define jassert(expression)
Platform-independent assertion macro.
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for ...
#define jassertfalse
This will always cause an assertion failure.
typedef float
@ paste
The command ID that should be used to send a "Paste from clipboard" command.
@ del
The command ID that should be used to send a "Delete" command.
@ redo
The command ID that should be used to send a "redo" command.
@ undo
The command ID that should be used to send a "undo" command.
@ cut
The command ID that should be used to send a "Cut" command.
@ copy
The command ID that should be used to send a "Copy to clipboard" command.
@ selectAll
The command ID that should be used to send a "Select all" command.
JUCE Namespace.
constexpr bool approximatelyEqual(Type a, Type b, Tolerance< Type > tolerance=Tolerance< Type >{} .withAbsolute(std::numeric_limits< Type >::min()) .withRelative(std::numeric_limits< Type >::epsilon()))
Returns true if the two floating-point numbers are approximately equal.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
RangedDirectoryIterator end(const RangedDirectoryIterator &)
Returns a default-constructed sentinel value.
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Constrains a value to keep it within a given range.
@ textChanged
Indicates that the visible text of a text element has changed.
@ textSelectionChanged
Indicates that the selection of a text element has changed.
float deltaY
The amount that the wheel has been moved in the Y axis.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
float deltaX
The amount that the wheel has been moved in the X axis.
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
Returns true if a value is at least zero, and also below a specified upper limit.
int CommandID
A type used to hold the unique ID for an application command.
int roundToInt(const FloatType value) noexcept
Fast floating-point-to-integer conversion.
constexpr int numElementsInArray(Type(&)[N]) noexcept
Handy function for getting the number of elements in a simple const C array.
Contains status information about a mouse wheel event.
T reset(T... args)
Holds information describing an application command.
Array< KeyPress > defaultKeypresses
A list of zero or more keypresses that should be used as the default keys for this command.
void setActive(bool isActive) noexcept
An easy way to set or remove the isDisabled bit in the structure's flags field.
void setInfo(const String &shortName, const String &description, const String &categoryName, int flags) noexcept
Sets a number of the structures values at once.
Contains contextual details about the invocation of a command.
CommandID commandID
The UID of the command that should be performed.
Defines a syntax highlighting colour scheme.
Can be used to save and restore the editor's caret position, selection state, etc.
State(const CodeEditorComponent &)
Creates an object containing the state of the given editor.
void restoreState(CodeEditorComponent &) const
Updates the given editor with this saved state.
String toString() const
Returns a stringified version of this state that can be used to recreate it later.
This class is used to invoke a range of text-editor navigation methods on an object,...