1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - 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 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-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 
26 namespace juce
27 {
28 
29 class CodeTokeniser;
30 
31 
32 //==============================================================================
33 /**
34     A text editor component designed specifically for source code.
35 
36     This is designed to handle syntax highlighting and fast editing of very large
37     files.
38 
39     @tags{GUI}
40 */
41 class JUCE_API  CodeEditorComponent   : public Component,
42                                         public ApplicationCommandTarget,
43                                         public TextInputTarget
44 {
45 public:
46     //==============================================================================
47     /** Creates an editor for a document.
48 
49         The tokeniser object is optional - pass nullptr to disable syntax highlighting.
50         The object that you pass in is not owned or deleted by the editor - you must
51         make sure that it doesn't get deleted while this component is still using it.
52 
53         @see CodeDocument
54     */
55     CodeEditorComponent (CodeDocument& document,
56                          CodeTokeniser* codeTokeniser);
57 
58     /** Destructor. */
59     ~CodeEditorComponent() override;
60 
61     //==============================================================================
62     /** Returns the code document that this component is editing. */
getDocument()63     CodeDocument& getDocument() const noexcept          { return document; }
64 
65     /** Loads the given content into the document.
66         This will completely reset the CodeDocument object, clear its undo history,
67         and fill it with this text.
68     */
69     void loadContent (const String& newContent);
70 
71     //==============================================================================
72     /** Returns the standard character width. */
getCharWidth()73     float getCharWidth() const noexcept                         { return charWidth; }
74 
75     /** Returns the height of a line of text, in pixels. */
getLineHeight()76     int getLineHeight() const noexcept                          { return lineHeight; }
77 
78     /** Returns the number of whole lines visible on the screen,
79         This doesn't include a cut-off line that might be visible at the bottom if the
80         component's height isn't an exact multiple of the line-height.
81     */
getNumLinesOnScreen()82     int getNumLinesOnScreen() const noexcept                    { return linesOnScreen; }
83 
84     /** Returns the index of the first line that's visible at the top of the editor. */
getFirstLineOnScreen()85     int getFirstLineOnScreen() const noexcept                   { return firstLineOnScreen; }
86 
87     /** Returns the number of whole columns visible on the screen.
88         This doesn't include any cut-off columns at the right-hand edge.
89     */
getNumColumnsOnScreen()90     int getNumColumnsOnScreen() const noexcept                  { return columnsOnScreen; }
91 
92     /** Returns the current caret position. */
getCaretPos()93     CodeDocument::Position getCaretPos() const                  { return caretPos; }
94 
95     /** Returns the position of the caret, relative to the editor's origin. */
96     Rectangle<int> getCaretRectangle() override;
97 
98     /** Moves the caret.
99         If selecting is true, the section of the document between the current
100         caret position and the new one will become selected. If false, any currently
101         selected region will be deselected.
102     */
103     void moveCaretTo (const CodeDocument::Position& newPos, bool selecting);
104 
105     /** Returns the on-screen position of a character in the document.
106         The rectangle returned is relative to this component's top-left origin.
107     */
108     Rectangle<int> getCharacterBounds (const CodeDocument::Position& pos) const;
109 
110     /** Finds the character at a given on-screen position.
111         The coordinates are relative to this component's top-left origin.
112     */
113     CodeDocument::Position getPositionAt (int x, int y);
114 
115     /** Returns the start of the selection as a position. */
getSelectionStart()116     CodeDocument::Position getSelectionStart() const            { return selectionStart; }
117 
118     /** Returns the end of the selection as a position. */
getSelectionEnd()119     CodeDocument::Position getSelectionEnd() const              { return selectionEnd; }
120 
121     /** Enables or disables the line-number display in the gutter. */
122     void setLineNumbersShown (bool shouldBeShown);
123 
124     //==============================================================================
125     bool moveCaretLeft (bool moveInWholeWordSteps, bool selecting);
126     bool moveCaretRight (bool moveInWholeWordSteps, bool selecting);
127     bool moveCaretUp (bool selecting);
128     bool moveCaretDown (bool selecting);
129     bool scrollDown();
130     bool scrollUp();
131     bool pageUp (bool selecting);
132     bool pageDown (bool selecting);
133     bool moveCaretToTop (bool selecting);
134     bool moveCaretToStartOfLine (bool selecting);
135     bool moveCaretToEnd (bool selecting);
136     bool moveCaretToEndOfLine (bool selecting);
137     bool deleteBackwards (bool moveInWholeWordSteps);
138     bool deleteForwards (bool moveInWholeWordSteps);
139     bool deleteWhitespaceBackwardsToTabStop();
140     virtual bool copyToClipboard();
141     virtual bool cutToClipboard();
142     virtual bool pasteFromClipboard();
143     bool undo();
144     bool redo();
145 
146     void selectRegion (const CodeDocument::Position& start, const CodeDocument::Position& end);
147     bool selectAll();
148     void deselectAll();
149 
150     void scrollToLine (int newFirstLineOnScreen);
151     void scrollBy (int deltaLines);
152     void scrollToColumn (int newFirstColumnOnScreen);
153     void scrollToKeepCaretOnScreen();
154     void scrollToKeepLinesOnScreen (Range<int> linesToShow);
155 
156     void insertTextAtCaret (const String& textToInsert) override;
157     void insertTabAtCaret();
158 
159     void indentSelection();
160     void unindentSelection();
161 
162     //==============================================================================
163     Range<int> getHighlightedRegion() const override;
164     bool isHighlightActive() const noexcept;
165     void setHighlightedRegion (const Range<int>& newRange) override;
166     String getTextInRange (const Range<int>& range) const override;
167 
168     //==============================================================================
169     /** Can be used to save and restore the editor's caret position, selection state, etc. */
170     struct State
171     {
172         /** Creates an object containing the state of the given editor. */
173         State (const CodeEditorComponent&);
174         /** Creates a state object from a string that was previously created with toString(). */
175         State (const String& stringifiedVersion);
176         State (const State&) noexcept;
177 
178         /** Updates the given editor with this saved state. */
179         void restoreState (CodeEditorComponent&) const;
180 
181         /** Returns a stringified version of this state that can be used to recreate it later. */
182         String toString() const;
183 
184     private:
185         int lastTopLine, lastCaretPos, lastSelectionEnd;
186     };
187 
188     //==============================================================================
189     /** Changes the current tab settings.
190         This lets you change the tab size and whether pressing the tab key inserts a
191         tab character, or its equivalent number of spaces.
192     */
193     void setTabSize (int numSpacesPerTab, bool insertSpacesInsteadOfTabCharacters);
194 
195     /** Returns the current number of spaces per tab.
196         @see setTabSize
197     */
getTabSize()198     int getTabSize() const noexcept                     { return spacesPerTab; }
199 
200     /** Returns true if the tab key will insert spaces instead of actual tab characters.
201         @see setTabSize
202     */
areSpacesInsertedForTabs()203     bool areSpacesInsertedForTabs() const               { return useSpacesForTabs; }
204 
205     /** Returns a string containing spaces or tab characters to generate the given number of spaces. */
206     String getTabString (int numSpaces) const;
207 
208     /** Changes the font.
209         Make sure you only use a fixed-width font, or this component will look pretty nasty!
210     */
211     void setFont (const Font& newFont);
212 
213     /** Returns the font that the editor is using. */
getFont()214     const Font& getFont() const noexcept                { return font; }
215 
216     /** Makes the editor read-only. */
217     void setReadOnly (bool shouldBeReadOnly) noexcept;
218 
219     /** Returns true if the editor is set to be read-only. */
isReadOnly()220     bool isReadOnly() const noexcept                    { return readOnly; }
221 
222     //==============================================================================
223     /** Defines a syntax highlighting colour scheme */
224     struct JUCE_API  ColourScheme
225     {
226         /** Defines a colour for a token type */
227         struct TokenType
228         {
229             String name;
230             Colour colour;
231         };
232 
233         Array<TokenType> types;
234 
235         void set (const String& name, Colour colour);
236     };
237 
238     /** Changes the syntax highlighting scheme.
239         The token type values are dependent on the tokeniser being used - use
240         CodeTokeniser::getTokenTypes() to get a list of the token types.
241         @see getColourForTokenType
242     */
243     void setColourScheme (const ColourScheme& scheme);
244 
245     /** Returns the current syntax highlighting colour scheme. */
getColourScheme()246     const ColourScheme& getColourScheme() const noexcept    { return colourScheme; }
247 
248     /** Returns one the syntax highlighting colour for the given token.
249         The token type values are dependent on the tokeniser being used.
250         @see setColourScheme
251     */
252     Colour getColourForTokenType (int tokenType) const;
253 
254     /** Rebuilds the syntax highlighting for a section of text.
255 
256         This happens automatically any time the CodeDocument is edited, but this
257         method lets you change text colours even when the CodeDocument hasn't changed.
258 
259         For example, you could use this to highlight tokens as the cursor moves.
260         To do so you'll need to tell your custom CodeTokeniser where the token you
261         want to highlight is, and make it return a special type of token. Then you
262         should call this method supplying the range of the highlighted text.
263         @see CodeTokeniser
264      */
265     void retokenise (int startIndex, int endIndex);
266 
267     //==============================================================================
268     /** A set of colour IDs to use to change the colour of various aspects of the editor.
269 
270         These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
271         methods.
272 
273         @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
274     */
275     enum ColourIds
276     {
277         backgroundColourId          = 0x1004500,  /**< A colour to use to fill the editor's background. */
278         highlightColourId           = 0x1004502,  /**< The colour to use for the highlighted background under selected text. */
279         defaultTextColourId         = 0x1004503,  /**< The colour to use for text when no syntax colouring is enabled. */
280         lineNumberBackgroundId      = 0x1004504,  /**< The colour to use for filling the background of the line-number gutter. */
281         lineNumberTextId            = 0x1004505,  /**< The colour to use for drawing the line numbers. */
282     };
283 
284     //==============================================================================
285     /** Changes the size of the scrollbars. */
286     void setScrollbarThickness (int thickness);
287 
288     /** Returns the thickness of the scrollbars. */
getScrollbarThickness()289     int getScrollbarThickness() const noexcept          { return scrollbarThickness; }
290 
291     //==============================================================================
292     /** Called when the return key is pressed - this can be overridden for custom behaviour. */
293     virtual void handleReturnKey();
294     /** Called when the tab key is pressed - this can be overridden for custom behaviour. */
295     virtual void handleTabKey();
296     /** Called when the escape key is pressed - this can be overridden for custom behaviour. */
297     virtual void handleEscapeKey();
298 
299     /** Called when the view position is scrolled horizontally or vertically. */
300     virtual void editorViewportPositionChanged();
301 
302     /** Called when the caret position moves. */
303     virtual void caretPositionMoved();
304 
305     //==============================================================================
306     /** This adds the items to the popup menu.
307 
308         By default it adds the cut/copy/paste items, but you can override this if
309         you need to replace these with your own items.
310 
311         If you want to add your own items to the existing ones, you can override this,
312         call the base class's addPopupMenuItems() method, then append your own items.
313 
314         When the menu has been shown, performPopupMenuAction() will be called to
315         perform the item that the user has chosen.
316 
317         If this was triggered by a mouse-click, the mouseClickEvent parameter will be
318         a pointer to the info about it, or may be null if the menu is being triggered
319         by some other means.
320 
321         @see performPopupMenuAction, setPopupMenuEnabled, isPopupMenuEnabled
322     */
323     virtual void addPopupMenuItems (PopupMenu& menuToAddTo,
324                                     const MouseEvent* mouseClickEvent);
325 
326     /** This is called to perform one of the items that was shown on the popup menu.
327 
328         If you've overridden addPopupMenuItems(), you should also override this
329         to perform the actions that you've added.
330 
331         If you've overridden addPopupMenuItems() but have still left the default items
332         on the menu, remember to call the superclass's performPopupMenuAction()
333         so that it can perform the default actions if that's what the user clicked on.
334 
335         @see addPopupMenuItems, setPopupMenuEnabled, isPopupMenuEnabled
336     */
337     virtual void performPopupMenuAction (int menuItemID);
338 
339     /** Specifies a command-manager which the editor will notify whenever the state
340         of any of its commands changes.
341         If you're making use of the editor's ApplicationCommandTarget interface, then
342         you should also use this to tell it which command manager it should use. Make
343         sure that the manager does not go out of scope while the editor is using it. You
344         can pass a nullptr here to disable this.
345     */
346     void setCommandManager (ApplicationCommandManager* newManager) noexcept;
347 
348     //==============================================================================
349     /** @internal */
350     void paint (Graphics&) override;
351     /** @internal */
352     void resized() override;
353     /** @internal */
354     bool keyPressed (const KeyPress&) override;
355     /** @internal */
356     void mouseDown (const MouseEvent&) override;
357     /** @internal */
358     void mouseDrag (const MouseEvent&) override;
359     /** @internal */
360     void mouseUp (const MouseEvent&) override;
361     /** @internal */
362     void mouseDoubleClick (const MouseEvent&) override;
363     /** @internal */
364     void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
365     /** @internal */
366     void focusGained (FocusChangeType) override;
367     /** @internal */
368     void focusLost (FocusChangeType) override;
369     /** @internal */
370     bool isTextInputActive() const override;
371     /** @internal */
372     void setTemporaryUnderlining (const Array<Range<int>>&) override;
373     /** @internal */
374     ApplicationCommandTarget* getNextCommandTarget() override;
375     /** @internal */
376     void getAllCommands (Array<CommandID>&) override;
377     /** @internal */
378     void getCommandInfo (CommandID, ApplicationCommandInfo&) override;
379     /** @internal */
380     bool perform (const InvocationInfo&) override;
381     /** @internal */
382     void lookAndFeelChanged() override;
383 
384 private:
385     //==============================================================================
386     CodeDocument& document;
387 
388     Font font;
389     int firstLineOnScreen = 0, spacesPerTab = 4;
390     float charWidth = 0;
391     int lineHeight = 0, linesOnScreen = 0, columnsOnScreen = 0;
392     int scrollbarThickness = 16, columnToTryToMaintain = -1;
393     bool readOnly = false, useSpacesForTabs = true, showLineNumbers = false, shouldFollowDocumentChanges = false;
394     double xOffset = 0;
395     CodeDocument::Position caretPos, selectionStart, selectionEnd;
396 
397     std::unique_ptr<CaretComponent> caret;
398     ScrollBar verticalScrollBar { true }, horizontalScrollBar { false };
399     ApplicationCommandManager* appCommandManager = nullptr;
400 
401     class Pimpl;
402     std::unique_ptr<Pimpl> pimpl;
403 
404     class GutterComponent;
405     std::unique_ptr<GutterComponent> gutter;
406 
407     enum DragType
408     {
409         notDragging,
410         draggingSelectionStart,
411         draggingSelectionEnd
412     };
413 
414     DragType dragType = notDragging;
415 
416     //==============================================================================
417     CodeTokeniser* codeTokeniser;
418     ColourScheme colourScheme;
419 
420     class CodeEditorLine;
421     OwnedArray<CodeEditorLine> lines;
422     void rebuildLineTokens();
423     void rebuildLineTokensAsync();
424     void codeDocumentChanged (int start, int end);
425 
426     Array<CodeDocument::Iterator> cachedIterators;
427     void clearCachedIterators (int firstLineToBeInvalid);
428     void updateCachedIterators (int maxLineNum);
429     void getIteratorForPosition (int position, CodeDocument::Iterator&);
430 
431     void moveLineDelta (int delta, bool selecting);
432     int getGutterSize() const noexcept;
433 
434     //==============================================================================
435     void insertText (const String&);
436     virtual void updateCaretPosition();
437     void updateScrollBars();
438     void scrollToLineInternal (int line);
439     void scrollToColumnInternal (double column);
440     void newTransaction();
441     void cut();
442     void indentSelectedLines (int spacesToAdd);
443     bool skipBackwardsToPreviousTab();
444     bool performCommand (CommandID);
445 
446     int indexToColumn (int line, int index) const noexcept;
447     int columnToIndex (int line, int column) const noexcept;
448 
449     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CodeEditorComponent)
450 };
451 
452 } // namespace juce
453