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