mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-02-05 03:50:07 +00:00
CodeEditorComponent improvements and TextEditor menu refactoring.
This commit is contained in:
parent
d3bb7fb9cc
commit
6758ce0bc9
10 changed files with 440 additions and 190 deletions
|
|
@ -183,8 +183,8 @@ public:
|
|||
}
|
||||
else if (topLevelMenuIndex == 1) // "Edit" menu
|
||||
{
|
||||
menu.addCommandItem (commandManager, CommandIDs::undo);
|
||||
menu.addCommandItem (commandManager, CommandIDs::redo);
|
||||
menu.addCommandItem (commandManager, StandardApplicationCommandIDs::undo);
|
||||
menu.addCommandItem (commandManager, StandardApplicationCommandIDs::redo);
|
||||
menu.addSeparator();
|
||||
menu.addCommandItem (commandManager, StandardApplicationCommandIDs::cut);
|
||||
menu.addCommandItem (commandManager, StandardApplicationCommandIDs::copy);
|
||||
|
|
|
|||
|
|
@ -43,8 +43,6 @@ namespace CommandIDs
|
|||
static const int showAppearanceSettings = 0x200077;
|
||||
|
||||
static const int saveAll = 0x200080;
|
||||
static const int undo = 0x200090;
|
||||
static const int redo = 0x2000a0;
|
||||
|
||||
static const int closeWindow = 0x201001;
|
||||
static const int closeAllDocuments = 0x201000;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
#include "../Code Editor/jucer_SourceCodeEditor.h"
|
||||
|
||||
//==============================================================================
|
||||
Component* SourceCodeDocument::createEditor() { return SourceCodeEditor::createFor (this, codeDoc); }
|
||||
Component* SourceCodeDocument::createEditor() { return new SourceCodeEditor (this, codeDoc); }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -29,23 +29,21 @@
|
|||
|
||||
//==============================================================================
|
||||
SourceCodeEditor::SourceCodeEditor (OpenDocumentManager::Document* document_,
|
||||
CodeDocument& codeDocument,
|
||||
CodeTokeniser* const codeTokeniser)
|
||||
: DocumentEditorComponent (document_),
|
||||
editor (codeDocument, codeTokeniser)
|
||||
CodeDocument& codeDocument)
|
||||
: DocumentEditorComponent (document_)
|
||||
{
|
||||
addAndMakeVisible (&editor);
|
||||
addAndMakeVisible (editor = createEditor (codeDocument));
|
||||
|
||||
#if JUCE_MAC
|
||||
Font font (13.0f);
|
||||
font.setTypefaceName ("Menlo");
|
||||
#else
|
||||
Font font (10.0f);
|
||||
Font font (12.0f);
|
||||
font.setTypefaceName (Font::getDefaultMonospacedFontName());
|
||||
#endif
|
||||
editor.setFont (font);
|
||||
editor->setFont (font);
|
||||
|
||||
editor.setTabSize (4, true);
|
||||
editor->setTabSize (4, true);
|
||||
|
||||
updateColourScheme();
|
||||
getAppSettings().appearance.settings.addListener (this);
|
||||
|
|
@ -56,27 +54,21 @@ SourceCodeEditor::~SourceCodeEditor()
|
|||
getAppSettings().appearance.settings.removeListener (this);
|
||||
}
|
||||
|
||||
CodeEditorComponent* SourceCodeEditor::createEditor (CodeDocument& codeDocument)
|
||||
{
|
||||
if (document->getFile().hasFileExtension (sourceOrHeaderFileExtensions))
|
||||
return new CppCodeEditorComponent (codeDocument);
|
||||
|
||||
return new CodeEditorComponent (codeDocument, nullptr);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SourceCodeEditor::resized()
|
||||
{
|
||||
editor.setBounds (getLocalBounds());
|
||||
editor->setBounds (getLocalBounds());
|
||||
}
|
||||
|
||||
CodeTokeniser* SourceCodeEditor::getTokeniserFor (const File& file)
|
||||
{
|
||||
if (file.hasFileExtension (sourceOrHeaderFileExtensions))
|
||||
{
|
||||
static CPlusPlusCodeTokeniser cppTokeniser;
|
||||
return &cppTokeniser;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SourceCodeEditor* SourceCodeEditor::createFor (OpenDocumentManager::Document* document,
|
||||
CodeDocument& codeDocument)
|
||||
{
|
||||
return new SourceCodeEditor (document, codeDocument, getTokeniserFor (document->getFile()));
|
||||
}
|
||||
void SourceCodeEditor::updateColourScheme() { getAppSettings().appearance.applyToCodeEditor (*editor); }
|
||||
|
||||
void SourceCodeEditor::valueTreePropertyChanged (ValueTree&, const Identifier&) { updateColourScheme(); }
|
||||
void SourceCodeEditor::valueTreeChildAdded (ValueTree&, ValueTree&) { updateColourScheme(); }
|
||||
|
|
@ -84,8 +76,3 @@ void SourceCodeEditor::valueTreeChildRemoved (ValueTree&, ValueTree&)
|
|||
void SourceCodeEditor::valueTreeChildOrderChanged (ValueTree&) { updateColourScheme(); }
|
||||
void SourceCodeEditor::valueTreeParentChanged (ValueTree&) { updateColourScheme(); }
|
||||
void SourceCodeEditor::valueTreeRedirected (ValueTree&) { updateColourScheme(); }
|
||||
|
||||
void SourceCodeEditor::updateColourScheme()
|
||||
{
|
||||
getAppSettings().appearance.applyToCodeEditor (editor);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,24 +34,16 @@ class SourceCodeEditor : public DocumentEditorComponent,
|
|||
private ValueTree::Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
SourceCodeEditor (OpenDocumentManager::Document* document,
|
||||
CodeDocument& codeDocument,
|
||||
CodeTokeniser* const codeTokeniser);
|
||||
|
||||
CodeDocument& codeDocument);
|
||||
~SourceCodeEditor();
|
||||
|
||||
static SourceCodeEditor* createFor (OpenDocumentManager::Document* document,
|
||||
CodeDocument& codeDocument);
|
||||
private:
|
||||
ScopedPointer<CodeEditorComponent> editor;
|
||||
|
||||
static CodeTokeniser* getTokeniserFor (const File& file);
|
||||
|
||||
//==============================================================================
|
||||
CodeEditorComponent* createEditor (CodeDocument&);
|
||||
void resized();
|
||||
|
||||
CodeEditorComponent editor;
|
||||
|
||||
private:
|
||||
void valueTreePropertyChanged (ValueTree&, const Identifier&);
|
||||
void valueTreeChildAdded (ValueTree&, ValueTree&);
|
||||
void valueTreeChildRemoved (ValueTree&, ValueTree&);
|
||||
|
|
@ -65,4 +57,88 @@ private:
|
|||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
class CppCodeEditorComponent : public CodeEditorComponent
|
||||
{
|
||||
public:
|
||||
CppCodeEditorComponent (CodeDocument& codeDocument)
|
||||
: CodeEditorComponent (codeDocument, getCppTokeniser())
|
||||
{
|
||||
}
|
||||
|
||||
void handleReturnKey()
|
||||
{
|
||||
CodeEditorComponent::handleReturnKey();
|
||||
|
||||
const CodeDocument::Position pos (getCaretPos());
|
||||
|
||||
if (pos.getLineNumber() > 0 && pos.getLineText().trim().isEmpty())
|
||||
{
|
||||
String indent (getIndentForCurrentBlock (pos));
|
||||
const String previousLine (pos.movedByLines (-1).getLineText());
|
||||
const String trimmedPreviousLine (previousLine.trim());
|
||||
const String leadingWhitespace (getLeadingWhitespace (previousLine));
|
||||
|
||||
insertTextAtCaret (leadingWhitespace);
|
||||
|
||||
if (trimmedPreviousLine.endsWithChar ('{')
|
||||
|| ((trimmedPreviousLine.startsWith ("if ")
|
||||
|| trimmedPreviousLine.startsWith ("for ")
|
||||
|| trimmedPreviousLine.startsWith ("while "))
|
||||
&& trimmedPreviousLine.endsWithChar (')')))
|
||||
insertTabAtCaret();
|
||||
}
|
||||
}
|
||||
|
||||
void insertTextAtCaret (const String& newText)
|
||||
{
|
||||
if (getHighlightedRegion().isEmpty())
|
||||
{
|
||||
const CodeDocument::Position pos (getCaretPos());
|
||||
|
||||
if ((newText == "{" || newText == "}") && pos.getLineNumber() > 0)
|
||||
{
|
||||
moveCaretToStartOfLine (true);
|
||||
CodeEditorComponent::insertTextAtCaret (getIndentForCurrentBlock (pos));
|
||||
|
||||
if (newText == "{")
|
||||
insertTabAtCaret();
|
||||
}
|
||||
}
|
||||
|
||||
CodeEditorComponent::insertTextAtCaret (newText);
|
||||
}
|
||||
|
||||
private:
|
||||
static CPlusPlusCodeTokeniser* getCppTokeniser()
|
||||
{
|
||||
static CPlusPlusCodeTokeniser cppTokeniser;
|
||||
return &cppTokeniser;
|
||||
}
|
||||
|
||||
static String getLeadingWhitespace (String line)
|
||||
{
|
||||
line = line.removeCharacters ("\r\n");
|
||||
const String::CharPointerType endOfLeadingWS (line.getCharPointer().findEndOfWhitespace());
|
||||
return String (line.getCharPointer(), endOfLeadingWS);
|
||||
}
|
||||
|
||||
static String getIndentForCurrentBlock (CodeDocument::Position pos)
|
||||
{
|
||||
while (pos.getLineNumber() > 0)
|
||||
{
|
||||
pos = pos.movedByLines (-1);
|
||||
|
||||
const String line (pos.getLineText());
|
||||
const String trimmedLine (line.trimStart());
|
||||
|
||||
if (trimmedLine.startsWithChar ('{'))
|
||||
return getLeadingWhitespace (line);
|
||||
}
|
||||
|
||||
return String::empty;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif // __JUCER_SOURCECODEEDITOR_JUCEHEADER__
|
||||
|
|
|
|||
|
|
@ -77,6 +77,12 @@ namespace StandardApplicationCommandIDs
|
|||
|
||||
/** The command ID that should be used to send a "Deselect all" command. */
|
||||
static const CommandID deselectAll = 0x1007;
|
||||
|
||||
/** The command ID that should be used to send a "undo" command. */
|
||||
static const CommandID undo = 0x1008;
|
||||
|
||||
/** The command ID that should be used to send a "redo" command. */
|
||||
static const CommandID redo = 0x1009;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1755,12 +1755,51 @@ void TextEditor::paintOverChildren (Graphics& g)
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
void TextEditor::addPopupMenuItems (PopupMenu& m, const MouseEvent*)
|
||||
{
|
||||
const bool writable = ! isReadOnly();
|
||||
|
||||
if (passwordCharacter == 0)
|
||||
{
|
||||
m.addItem (StandardApplicationCommandIDs::cut, TRANS("Cut"), writable);
|
||||
m.addItem (StandardApplicationCommandIDs::copy, TRANS("Copy"), ! selection.isEmpty());
|
||||
m.addItem (StandardApplicationCommandIDs::paste, TRANS("Paste"), writable);
|
||||
}
|
||||
|
||||
m.addItem (StandardApplicationCommandIDs::del, TRANS("Delete"), writable);
|
||||
m.addSeparator();
|
||||
m.addItem (StandardApplicationCommandIDs::selectAll, TRANS("Select All"));
|
||||
m.addSeparator();
|
||||
|
||||
if (getUndoManager() != nullptr)
|
||||
{
|
||||
m.addItem (StandardApplicationCommandIDs::undo, TRANS("Undo"), undoManager.canUndo());
|
||||
m.addItem (StandardApplicationCommandIDs::redo, TRANS("Redo"), undoManager.canRedo());
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::performPopupMenuAction (const int menuItemID)
|
||||
{
|
||||
switch (menuItemID)
|
||||
{
|
||||
case StandardApplicationCommandIDs::cut: cutToClipboard(); break;
|
||||
case StandardApplicationCommandIDs::copy: copyToClipboard(); break;
|
||||
case StandardApplicationCommandIDs::paste: pasteFromClipboard(); break;
|
||||
case StandardApplicationCommandIDs::del: cut(); break;
|
||||
case StandardApplicationCommandIDs::selectAll: selectAll(); break;
|
||||
case StandardApplicationCommandIDs::undo: undo(); break;
|
||||
case StandardApplicationCommandIDs::redo: redo(); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
static void textEditorMenuCallback (int menuResult, TextEditor* editor)
|
||||
{
|
||||
if (editor != nullptr && menuResult != 0)
|
||||
editor->performPopupMenuAction (menuResult);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TextEditor::mouseDown (const MouseEvent& e)
|
||||
{
|
||||
beginDragAutoRepeat (100);
|
||||
|
|
@ -1788,12 +1827,8 @@ void TextEditor::mouseDown (const MouseEvent& e)
|
|||
void TextEditor::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
if (wasFocused || ! selectAllTextWhenFocused)
|
||||
{
|
||||
if (! (popupMenuEnabled && e.mods.isPopupMenu()))
|
||||
{
|
||||
moveCaretTo (getTextIndexAt (e.x, e.y), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::mouseUp (const MouseEvent& e)
|
||||
|
|
@ -1802,12 +1837,8 @@ void TextEditor::mouseUp (const MouseEvent& e)
|
|||
textHolder->restartTimer();
|
||||
|
||||
if (wasFocused || ! selectAllTextWhenFocused)
|
||||
{
|
||||
if (e.mouseWasClicked() && ! (popupMenuEnabled && e.mods.isPopupMenu()))
|
||||
{
|
||||
moveCaret (getTextIndexAt (e.x, e.y));
|
||||
}
|
||||
}
|
||||
|
||||
wasFocused = true;
|
||||
}
|
||||
|
|
@ -2094,47 +2125,6 @@ bool TextEditor::keyStateChanged (const bool isKeyDown)
|
|||
return ! ModifierKeys::getCurrentModifiers().isCommandDown();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const int baseMenuItemID = 0x7fff0000;
|
||||
|
||||
void TextEditor::addPopupMenuItems (PopupMenu& m, const MouseEvent*)
|
||||
{
|
||||
const bool writable = ! isReadOnly();
|
||||
|
||||
if (passwordCharacter == 0)
|
||||
{
|
||||
m.addItem (baseMenuItemID + 1, TRANS("cut"), writable);
|
||||
m.addItem (baseMenuItemID + 2, TRANS("copy"), ! selection.isEmpty());
|
||||
m.addItem (baseMenuItemID + 3, TRANS("paste"), writable);
|
||||
}
|
||||
|
||||
m.addItem (baseMenuItemID + 4, TRANS("delete"), writable);
|
||||
m.addSeparator();
|
||||
m.addItem (baseMenuItemID + 5, TRANS("select all"));
|
||||
m.addSeparator();
|
||||
|
||||
if (getUndoManager() != nullptr)
|
||||
{
|
||||
m.addItem (baseMenuItemID + 6, TRANS("undo"), undoManager.canUndo());
|
||||
m.addItem (baseMenuItemID + 7, TRANS("redo"), undoManager.canRedo());
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditor::performPopupMenuAction (const int menuItemID)
|
||||
{
|
||||
switch (menuItemID)
|
||||
{
|
||||
case baseMenuItemID + 1: cutToClipboard(); break;
|
||||
case baseMenuItemID + 2: copyToClipboard(); break;
|
||||
case baseMenuItemID + 3: pasteFromClipboard(); break;
|
||||
case baseMenuItemID + 4: cut(); break;
|
||||
case baseMenuItemID + 5: selectAll(); break;
|
||||
case baseMenuItemID + 6: undo(); break;
|
||||
case baseMenuItemID + 7: redo(); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TextEditor::focusGained (FocusChangeType)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -566,9 +566,8 @@ public:
|
|||
When the menu has been shown, performPopupMenuAction() will be called to
|
||||
perform the item that the user has chosen.
|
||||
|
||||
The default menu items will be added using item IDs in the range
|
||||
0x7fff0000 - 0x7fff1000, so you should avoid those values for your own
|
||||
menu IDs.
|
||||
The default menu items will be added using item IDs from the
|
||||
StandardApplicationCommandIDs namespace.
|
||||
|
||||
If this was triggered by a mouse-click, the mouseClickEvent parameter will be
|
||||
a pointer to the info about it, or may be null if the menu is being triggered
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@
|
|||
class CodeEditorComponent::CodeEditorLine
|
||||
{
|
||||
public:
|
||||
CodeEditorLine() noexcept
|
||||
: highlightColumnStart (0), highlightColumnEnd (0)
|
||||
CodeEditorLine() noexcept : highlightColumnStart (0), highlightColumnEnd (0)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -196,7 +195,7 @@ private:
|
|||
|
||||
for (;;)
|
||||
{
|
||||
int tabPos = t.text.indexOfChar ('\t');
|
||||
const int tabPos = t.text.indexOfChar ('\t');
|
||||
if (tabPos < 0)
|
||||
break;
|
||||
|
||||
|
|
@ -226,6 +225,26 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
namespace CodeEditorHelpers
|
||||
{
|
||||
static int findFirstNonWhitespaceChar (const String& line) noexcept
|
||||
{
|
||||
String::CharPointerType t (line.getCharPointer());
|
||||
int i = 0;
|
||||
|
||||
while (! t.isEmpty())
|
||||
{
|
||||
if (! t.isWhitespace())
|
||||
return i;
|
||||
|
||||
++t;
|
||||
++i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class CodeEditorComponent::GutterComponent : public Component
|
||||
{
|
||||
|
|
@ -640,6 +659,11 @@ CodeDocument::Position CodeEditorComponent::getPositionAt (int x, int y)
|
|||
|
||||
//==============================================================================
|
||||
void CodeEditorComponent::insertTextAtCaret (const String& newText)
|
||||
{
|
||||
insertText (newText);
|
||||
}
|
||||
|
||||
void CodeEditorComponent::insertText (const String& newText)
|
||||
{
|
||||
document.deleteSection (selectionStart, selectionEnd);
|
||||
|
||||
|
|
@ -661,17 +685,89 @@ void CodeEditorComponent::insertTabAtCaret()
|
|||
{
|
||||
const int caretCol = indexToColumn (caretPos.getLineNumber(), caretPos.getIndexInLine());
|
||||
const int spacesNeeded = spacesPerTab - (caretCol % spacesPerTab);
|
||||
insertTextAtCaret (String::repeatedString (" ", spacesNeeded));
|
||||
insertText (String::repeatedString (" ", spacesNeeded));
|
||||
}
|
||||
else
|
||||
{
|
||||
insertTextAtCaret ("\t");
|
||||
insertText ("\t");
|
||||
}
|
||||
}
|
||||
|
||||
bool CodeEditorComponent::deleteWhitespaceBackwardsToTabStop()
|
||||
{
|
||||
if (! getHighlightedRegion().isEmpty())
|
||||
return false;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const int currentColumn = indexToColumn (caretPos.getLineNumber(), caretPos.getIndexInLine());
|
||||
|
||||
if (currentColumn <= 0 || (currentColumn % spacesPerTab) == 0)
|
||||
break;
|
||||
|
||||
moveCaretLeft (false, true);
|
||||
}
|
||||
|
||||
const String selected (getTextInRange (getHighlightedRegion()));
|
||||
|
||||
if (selected.isNotEmpty() && selected.trim().isEmpty())
|
||||
{
|
||||
cut();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CodeEditorComponent::indentSelection() { indentSelectedLines ( spacesPerTab); }
|
||||
void CodeEditorComponent::unindentSelection() { indentSelectedLines (-spacesPerTab); }
|
||||
|
||||
void CodeEditorComponent::indentSelectedLines (const int spacesToAdd)
|
||||
{
|
||||
newTransaction();
|
||||
|
||||
CodeDocument::Position oldSelectionStart (selectionStart), oldSelectionEnd (selectionEnd), oldCaret (caretPos);
|
||||
oldSelectionStart.setPositionMaintained (true);
|
||||
oldSelectionEnd.setPositionMaintained (true);
|
||||
oldCaret.setPositionMaintained (true);
|
||||
|
||||
const int lineStart = selectionStart.getLineNumber();
|
||||
int lineEnd = selectionEnd.getLineNumber();
|
||||
|
||||
if (lineEnd > lineStart && selectionEnd.getIndexInLine() == 0)
|
||||
--lineEnd;
|
||||
|
||||
for (int line = lineStart; line <= lineEnd; ++line)
|
||||
{
|
||||
const String lineText (document.getLine (line));
|
||||
const int nonWhitespaceStart = CodeEditorHelpers::findFirstNonWhitespaceChar (lineText);
|
||||
|
||||
if (nonWhitespaceStart > 0 || lineText.trimStart().isNotEmpty())
|
||||
{
|
||||
const CodeDocument::Position wsStart (&document, line, 0);
|
||||
const CodeDocument::Position wsEnd (&document, line, nonWhitespaceStart);
|
||||
|
||||
const int numLeadingSpaces = indexToColumn (line, wsEnd.getIndexInLine());
|
||||
const int newNumLeadingSpaces = jmax (0, numLeadingSpaces + spacesToAdd);
|
||||
|
||||
if (newNumLeadingSpaces != numLeadingSpaces)
|
||||
{
|
||||
document.deleteSection (wsStart, wsEnd);
|
||||
document.insertText (wsStart, String::repeatedString (useSpacesForTabs ? " " : "\t",
|
||||
useSpacesForTabs ? newNumLeadingSpaces
|
||||
: (newNumLeadingSpaces / spacesPerTab)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectionStart = oldSelectionStart;
|
||||
selectionEnd = oldSelectionEnd;
|
||||
caretPos = oldCaret;
|
||||
}
|
||||
|
||||
void CodeEditorComponent::cut()
|
||||
{
|
||||
insertTextAtCaret (String::empty);
|
||||
insertText (String::empty);
|
||||
}
|
||||
|
||||
bool CodeEditorComponent::copyToClipboard()
|
||||
|
|
@ -700,7 +796,7 @@ bool CodeEditorComponent::pasteFromClipboard()
|
|||
const String clip (SystemClipboard::getTextFromClipboard());
|
||||
|
||||
if (clip.isNotEmpty())
|
||||
insertTextAtCaret (clip);
|
||||
insertText (clip);
|
||||
|
||||
newTransaction();
|
||||
return true;
|
||||
|
|
@ -814,26 +910,6 @@ bool CodeEditorComponent::moveCaretToTop (const bool selecting)
|
|||
return true;
|
||||
}
|
||||
|
||||
namespace CodeEditorHelpers
|
||||
{
|
||||
static int findFirstNonWhitespaceChar (const String& line) noexcept
|
||||
{
|
||||
String::CharPointerType t (line.getCharPointer());
|
||||
int i = 0;
|
||||
|
||||
while (! t.isEmpty())
|
||||
{
|
||||
if (! t.isWhitespace())
|
||||
return i;
|
||||
|
||||
++t;
|
||||
++i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool CodeEditorComponent::moveCaretToStartOfLine (const bool selecting)
|
||||
{
|
||||
newTransaction();
|
||||
|
|
@ -956,21 +1032,28 @@ bool CodeEditorComponent::keyPressed (const KeyPress& key)
|
|||
{
|
||||
if (key == KeyPress::tabKey || key.getTextCharacter() == '\t')
|
||||
{
|
||||
insertTabAtCaret();
|
||||
handleTabKey();
|
||||
}
|
||||
else if (key == KeyPress::returnKey)
|
||||
{
|
||||
newTransaction();
|
||||
insertTextAtCaret (document.getNewLineCharacters());
|
||||
handleReturnKey();
|
||||
}
|
||||
else if (key.isKeyCode (KeyPress::escapeKey))
|
||||
{
|
||||
newTransaction();
|
||||
handleEscapeKey();
|
||||
}
|
||||
else if (key.getTextCharacter() >= ' ')
|
||||
{
|
||||
insertTextAtCaret (String::charToString (key.getTextCharacter()));
|
||||
}
|
||||
else if (key == KeyPress ('[', ModifierKeys::commandModifier, 0))
|
||||
{
|
||||
unindentSelection();
|
||||
}
|
||||
else if (key == KeyPress (']', ModifierKeys::commandModifier, 0))
|
||||
{
|
||||
indentSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
|
|
@ -980,26 +1063,90 @@ bool CodeEditorComponent::keyPressed (const KeyPress& key)
|
|||
return true;
|
||||
}
|
||||
|
||||
void CodeEditorComponent::handleReturnKey()
|
||||
{
|
||||
insertText (document.getNewLineCharacters());
|
||||
}
|
||||
|
||||
void CodeEditorComponent::handleTabKey()
|
||||
{
|
||||
insertTabAtCaret();
|
||||
}
|
||||
|
||||
void CodeEditorComponent::handleEscapeKey()
|
||||
{
|
||||
newTransaction();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void CodeEditorComponent::addPopupMenuItems (PopupMenu& m, const MouseEvent*)
|
||||
{
|
||||
m.addItem (StandardApplicationCommandIDs::cut, TRANS("Cut"));
|
||||
m.addItem (StandardApplicationCommandIDs::copy, TRANS("Copy"), ! getHighlightedRegion().isEmpty());
|
||||
m.addItem (StandardApplicationCommandIDs::paste, TRANS("Paste"));
|
||||
m.addItem (StandardApplicationCommandIDs::del, TRANS("Delete"));
|
||||
m.addSeparator();
|
||||
m.addItem (StandardApplicationCommandIDs::selectAll, TRANS("Select All"));
|
||||
m.addSeparator();
|
||||
m.addItem (StandardApplicationCommandIDs::undo, TRANS("Undo"), document.getUndoManager().canUndo());
|
||||
m.addItem (StandardApplicationCommandIDs::redo, TRANS("Redo"), document.getUndoManager().canRedo());
|
||||
}
|
||||
|
||||
void CodeEditorComponent::performPopupMenuAction (const int menuItemID)
|
||||
{
|
||||
switch (menuItemID)
|
||||
{
|
||||
case StandardApplicationCommandIDs::cut: cutToClipboard(); break;
|
||||
case StandardApplicationCommandIDs::copy: copyToClipboard(); break;
|
||||
case StandardApplicationCommandIDs::paste: pasteFromClipboard(); break;
|
||||
case StandardApplicationCommandIDs::del: cut(); break;
|
||||
case StandardApplicationCommandIDs::selectAll: selectAll(); break;
|
||||
case StandardApplicationCommandIDs::undo: undo(); break;
|
||||
case StandardApplicationCommandIDs::redo: redo(); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
static void codeEditorMenuCallback (int menuResult, CodeEditorComponent* editor)
|
||||
{
|
||||
if (editor != nullptr && menuResult != 0)
|
||||
editor->performPopupMenuAction (menuResult);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void CodeEditorComponent::mouseDown (const MouseEvent& e)
|
||||
{
|
||||
newTransaction();
|
||||
dragType = notDragging;
|
||||
|
||||
if (! e.mods.isPopupMenu())
|
||||
if (e.mods.isPopupMenu())
|
||||
{
|
||||
beginDragAutoRepeat (100);
|
||||
moveCaretTo (getPositionAt (e.x, e.y), e.mods.isShiftDown());
|
||||
if (getHighlightedRegion().isEmpty())
|
||||
{
|
||||
const CodeDocument::Position pos (getPositionAt (e.x, e.y));
|
||||
const String line (pos.getLineText());
|
||||
const int index = pos.getIndexInLine();
|
||||
const int lineLen = line.length();
|
||||
|
||||
if (index > 0 && index < lineLen - 2)
|
||||
{
|
||||
moveCaretTo (pos, false);
|
||||
moveCaretLeft (true, false);
|
||||
moveCaretRight (true, true);
|
||||
}
|
||||
}
|
||||
|
||||
PopupMenu m;
|
||||
m.setLookAndFeel (&getLookAndFeel());
|
||||
addPopupMenuItems (m, &e);
|
||||
|
||||
m.showMenuAsync (PopupMenu::Options(),
|
||||
ModalCallbackFunction::forComponent (codeEditorMenuCallback, this));
|
||||
}
|
||||
else
|
||||
{
|
||||
/*PopupMenu m;
|
||||
addPopupMenuItems (m, &e);
|
||||
|
||||
const int result = m.show();
|
||||
|
||||
if (result != 0)
|
||||
performPopupMenuAction (result);
|
||||
*/
|
||||
beginDragAutoRepeat (100);
|
||||
moveCaretTo (getPositionAt (e.x, e.y), e.mods.isShiftDown());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1147,6 +1294,7 @@ void CodeEditorComponent::ColourScheme::set (const String& name, const Colour& c
|
|||
for (int i = 0; i < types.size(); ++i)
|
||||
{
|
||||
TokenType& tt = types.getReference(i);
|
||||
|
||||
if (tt.name == name)
|
||||
{
|
||||
tt.colour = colour;
|
||||
|
|
@ -1191,57 +1339,57 @@ void CodeEditorComponent::updateCachedIterators (int maxLineNum)
|
|||
if (cachedIterators.size() == 0)
|
||||
cachedIterators.add (new CodeDocument::Iterator (&document));
|
||||
|
||||
if (codeTokeniser == nullptr)
|
||||
return;
|
||||
|
||||
for (;;)
|
||||
if (codeTokeniser != nullptr)
|
||||
{
|
||||
CodeDocument::Iterator* last = cachedIterators.getLast();
|
||||
|
||||
if (last->getLine() >= maxLineNum)
|
||||
break;
|
||||
|
||||
CodeDocument::Iterator* t = new CodeDocument::Iterator (*last);
|
||||
cachedIterators.add (t);
|
||||
const int targetLine = last->getLine() + linesBetweenCachedSources;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
codeTokeniser->readNextToken (*t);
|
||||
CodeDocument::Iterator* const last = cachedIterators.getLast();
|
||||
|
||||
if (t->getLine() >= targetLine)
|
||||
if (last->getLine() >= maxLineNum)
|
||||
break;
|
||||
|
||||
if (t->isEOF())
|
||||
return;
|
||||
CodeDocument::Iterator* t = new CodeDocument::Iterator (*last);
|
||||
cachedIterators.add (t);
|
||||
const int targetLine = last->getLine() + linesBetweenCachedSources;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
codeTokeniser->readNextToken (*t);
|
||||
|
||||
if (t->getLine() >= targetLine)
|
||||
break;
|
||||
|
||||
if (t->isEOF())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CodeEditorComponent::getIteratorForPosition (int position, CodeDocument::Iterator& source)
|
||||
{
|
||||
if (codeTokeniser == nullptr)
|
||||
return;
|
||||
|
||||
for (int i = cachedIterators.size(); --i >= 0;)
|
||||
if (codeTokeniser != nullptr)
|
||||
{
|
||||
CodeDocument::Iterator* t = cachedIterators.getUnchecked (i);
|
||||
if (t->getPosition() <= position)
|
||||
for (int i = cachedIterators.size(); --i >= 0;)
|
||||
{
|
||||
source = *t;
|
||||
break;
|
||||
CodeDocument::Iterator* t = cachedIterators.getUnchecked (i);
|
||||
if (t->getPosition() <= position)
|
||||
{
|
||||
source = *t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (source.getPosition() < position)
|
||||
{
|
||||
const CodeDocument::Iterator original (source);
|
||||
codeTokeniser->readNextToken (source);
|
||||
|
||||
if (source.getPosition() > position || source.isEOF())
|
||||
while (source.getPosition() < position)
|
||||
{
|
||||
source = original;
|
||||
break;
|
||||
const CodeDocument::Iterator original (source);
|
||||
codeTokeniser->readNextToken (source);
|
||||
|
||||
if (source.getPosition() > position || source.isEOF())
|
||||
{
|
||||
source = original;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,16 +38,16 @@ class CodeTokeniser;
|
|||
*/
|
||||
class JUCE_API CodeEditorComponent : public Component,
|
||||
public TextInputTarget,
|
||||
public Timer,
|
||||
public ScrollBar::Listener,
|
||||
public CodeDocument::Listener,
|
||||
public AsyncUpdater
|
||||
private Timer,
|
||||
private ScrollBar::Listener,
|
||||
private CodeDocument::Listener,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an editor for a document.
|
||||
|
||||
The tokeniser object is optional - pass 0 to disable syntax highlighting.
|
||||
The tokeniser object is optional - pass nullptr to disable syntax highlighting.
|
||||
The object that you pass in is not owned or deleted by the editor - you must
|
||||
make sure that it doesn't get deleted while this component is still using it.
|
||||
|
||||
|
|
@ -128,6 +128,7 @@ public:
|
|||
bool moveCaretToEndOfLine (bool selecting);
|
||||
bool deleteBackwards (bool moveInWholeWordSteps);
|
||||
bool deleteForwards (bool moveInWholeWordSteps);
|
||||
bool deleteWhitespaceBackwardsToTabStop();
|
||||
bool copyToClipboard();
|
||||
bool cutToClipboard();
|
||||
bool pasteFromClipboard();
|
||||
|
|
@ -145,6 +146,9 @@ public:
|
|||
void insertTextAtCaret (const String& textToInsert);
|
||||
void insertTabAtCaret();
|
||||
|
||||
void indentSelection();
|
||||
void unindentSelection();
|
||||
|
||||
//==============================================================================
|
||||
Range<int> getHighlightedRegion() const;
|
||||
void setHighlightedRegion (const Range<int>& newRange);
|
||||
|
|
@ -230,11 +234,53 @@ public:
|
|||
int getScrollbarThickness() const noexcept { return scrollbarThickness; }
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void resized();
|
||||
/** Called when the return key is pressed - this can be overridden for custom behaviour. */
|
||||
virtual void handleReturnKey();
|
||||
/** Called when the tab key is pressed - this can be overridden for custom behaviour. */
|
||||
virtual void handleTabKey();
|
||||
/** Called when the escape key is pressed - this can be overridden for custom behaviour. */
|
||||
virtual void handleEscapeKey();
|
||||
|
||||
//==============================================================================
|
||||
/** This adds the items to the popup menu.
|
||||
|
||||
By default it adds the cut/copy/paste items, but you can override this if
|
||||
you need to replace these with your own items.
|
||||
|
||||
If you want to add your own items to the existing ones, you can override this,
|
||||
call the base class's addPopupMenuItems() method, then append your own items.
|
||||
|
||||
When the menu has been shown, performPopupMenuAction() will be called to
|
||||
perform the item that the user has chosen.
|
||||
|
||||
If this was triggered by a mouse-click, the mouseClickEvent parameter will be
|
||||
a pointer to the info about it, or may be null if the menu is being triggered
|
||||
by some other means.
|
||||
|
||||
@see performPopupMenuAction, setPopupMenuEnabled, isPopupMenuEnabled
|
||||
*/
|
||||
virtual void addPopupMenuItems (PopupMenu& menuToAddTo,
|
||||
const MouseEvent* mouseClickEvent);
|
||||
|
||||
/** This is called to perform one of the items that was shown on the popup menu.
|
||||
|
||||
If you've overridden addPopupMenuItems(), you should also override this
|
||||
to perform the actions that you've added.
|
||||
|
||||
If you've overridden addPopupMenuItems() but have still left the default items
|
||||
on the menu, remember to call the superclass's performPopupMenuAction()
|
||||
so that it can perform the default actions if that's what the user clicked on.
|
||||
|
||||
@see addPopupMenuItems, setPopupMenuEnabled, isPopupMenuEnabled
|
||||
*/
|
||||
virtual void performPopupMenuAction (int menuItemID);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&);
|
||||
/** @internal */
|
||||
void resized();
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&);
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&);
|
||||
|
|
@ -251,14 +297,6 @@ public:
|
|||
/** @internal */
|
||||
void focusLost (FocusChangeType);
|
||||
/** @internal */
|
||||
void timerCallback();
|
||||
/** @internal */
|
||||
void scrollBarMoved (ScrollBar*, double);
|
||||
/** @internal */
|
||||
void handleAsyncUpdate();
|
||||
/** @internal */
|
||||
void codeDocumentChanged (const CodeDocument::Position&, const CodeDocument::Position&);
|
||||
/** @internal */
|
||||
bool isTextInputActive() const;
|
||||
/** @internal */
|
||||
void setTemporaryUnderlining (const Array <Range<int> >&);
|
||||
|
|
@ -307,16 +345,24 @@ private:
|
|||
void clearCachedIterators (int firstLineToBeInvalid);
|
||||
void updateCachedIterators (int maxLineNum);
|
||||
void getIteratorForPosition (int position, CodeDocument::Iterator& result);
|
||||
|
||||
void timerCallback();
|
||||
void scrollBarMoved (ScrollBar*, double);
|
||||
void handleAsyncUpdate();
|
||||
void codeDocumentChanged (const CodeDocument::Position&, const CodeDocument::Position&);
|
||||
|
||||
void moveLineDelta (int delta, bool selecting);
|
||||
int getGutterSize() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
void insertText (const String& textToInsert);
|
||||
void updateCaretPosition();
|
||||
void updateScrollBars();
|
||||
void scrollToLineInternal (int line);
|
||||
void scrollToColumnInternal (double column);
|
||||
void newTransaction();
|
||||
void cut();
|
||||
void indentSelectedLines (int spacesToAdd);
|
||||
|
||||
int indexToColumn (int line, int index) const noexcept;
|
||||
int columnToIndex (int line, int column) const noexcept;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue