- clean working copy for leaving SVN
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#include "juce_CPlusPlusCodeTokeniserFunctions.h"
|
||||
|
||||
|
||||
//==============================================================================
|
||||
CPlusPlusCodeTokeniser::CPlusPlusCodeTokeniser() {}
|
||||
CPlusPlusCodeTokeniser::~CPlusPlusCodeTokeniser() {}
|
||||
|
||||
int CPlusPlusCodeTokeniser::readNextToken (CodeDocument::Iterator& source)
|
||||
{
|
||||
return CppTokeniserFunctions::readNextToken (source);
|
||||
}
|
||||
|
||||
CodeEditorComponent::ColourScheme CPlusPlusCodeTokeniser::getDefaultColourScheme()
|
||||
{
|
||||
struct Type
|
||||
{
|
||||
const char* name;
|
||||
uint32 colour;
|
||||
};
|
||||
|
||||
const Type types[] =
|
||||
{
|
||||
{ "Error", 0xffcc0000 },
|
||||
{ "Comment", 0xff00aa00 },
|
||||
{ "Keyword", 0xff0000cc },
|
||||
{ "Operator", 0xff225500 },
|
||||
{ "Identifier", 0xff000000 },
|
||||
{ "Integer", 0xff880000 },
|
||||
{ "Float", 0xff885500 },
|
||||
{ "String", 0xff990099 },
|
||||
{ "Bracket", 0xff000055 },
|
||||
{ "Punctuation", 0xff004400 },
|
||||
{ "Preprocessor Text", 0xff660000 }
|
||||
};
|
||||
|
||||
CodeEditorComponent::ColourScheme cs;
|
||||
|
||||
for (unsigned int i = 0; i < sizeof (types) / sizeof (types[0]); ++i) // (NB: numElementsInArray doesn't work here in GCC4.2)
|
||||
cs.set (types[i].name, Colour (types[i].colour));
|
||||
|
||||
return cs;
|
||||
}
|
||||
|
||||
bool CPlusPlusCodeTokeniser::isReservedKeyword (const String& token) noexcept
|
||||
{
|
||||
return CppTokeniserFunctions::isReservedKeyword (token.getCharPointer(), token.length());
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_CPLUSPLUSCODETOKENISER_H_INCLUDED
|
||||
#define JUCE_CPLUSPLUSCODETOKENISER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A simple lexical analyser for syntax colouring of C++ code.
|
||||
|
||||
@see CodeEditorComponent, CodeDocument
|
||||
*/
|
||||
class JUCE_API CPlusPlusCodeTokeniser : public CodeTokeniser
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
CPlusPlusCodeTokeniser();
|
||||
~CPlusPlusCodeTokeniser();
|
||||
|
||||
//==============================================================================
|
||||
int readNextToken (CodeDocument::Iterator&) override;
|
||||
CodeEditorComponent::ColourScheme getDefaultColourScheme() override;
|
||||
|
||||
/** This is a handy method for checking whether a string is a c++ reserved keyword. */
|
||||
static bool isReservedKeyword (const String& token) noexcept;
|
||||
|
||||
/** The token values returned by this tokeniser. */
|
||||
enum TokenType
|
||||
{
|
||||
tokenType_error = 0,
|
||||
tokenType_comment,
|
||||
tokenType_keyword,
|
||||
tokenType_operator,
|
||||
tokenType_identifier,
|
||||
tokenType_integer,
|
||||
tokenType_float,
|
||||
tokenType_string,
|
||||
tokenType_bracket,
|
||||
tokenType_punctuation,
|
||||
tokenType_preprocessor
|
||||
};
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_LEAK_DETECTOR (CPlusPlusCodeTokeniser)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_CPLUSPLUSCODETOKENISER_H_INCLUDED
|
||||
+641
@@ -0,0 +1,641 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_CPLUSPLUSCODETOKENISERFUNCTIONS_H_INCLUDED
|
||||
#define JUCE_CPLUSPLUSCODETOKENISERFUNCTIONS_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Class containing some basic functions for simple tokenising of C++ code.
|
||||
*/
|
||||
struct CppTokeniserFunctions
|
||||
{
|
||||
static bool isIdentifierStart (const juce_wchar c) noexcept
|
||||
{
|
||||
return CharacterFunctions::isLetter (c)
|
||||
|| c == '_' || c == '@';
|
||||
}
|
||||
|
||||
static bool isIdentifierBody (const juce_wchar c) noexcept
|
||||
{
|
||||
return CharacterFunctions::isLetterOrDigit (c)
|
||||
|| c == '_' || c == '@';
|
||||
}
|
||||
|
||||
static bool isReservedKeyword (String::CharPointerType token, const int tokenLength) noexcept
|
||||
{
|
||||
static const char* const keywords2Char[] =
|
||||
{ "if", "do", "or", nullptr };
|
||||
|
||||
static const char* const keywords3Char[] =
|
||||
{ "for", "int", "new", "try", "xor", "and", "asm", "not", nullptr };
|
||||
|
||||
static const char* const keywords4Char[] =
|
||||
{ "bool", "void", "this", "true", "long", "else", "char",
|
||||
"enum", "case", "goto", "auto", nullptr };
|
||||
|
||||
static const char* const keywords5Char[] =
|
||||
{ "float", "const", "while", "break", "false", "catch", "class", "bitor",
|
||||
"compl", "or_eq", "short", "throw", "union", "using", "final", nullptr };
|
||||
|
||||
static const char* const keywords6Char[] =
|
||||
{ "return", "and_eq", "bitand", "delete", "double", "export", "extern",
|
||||
"friend", "inline", "not_eq", "public", "signed", "sizeof", "static",
|
||||
"struct", "switch", "typeid", "xor_eq", nullptr };
|
||||
|
||||
static const char* const keywords7Char[] =
|
||||
{ "nullptr", "alignas", "alignof", "default", "mutable", "private",
|
||||
"typedef", "virtual", "wchar_t", nullptr };
|
||||
|
||||
static const char* const keywordsOther[] =
|
||||
{ "char16_t", "char32_t", "const_cast", "constexpr", "continue", "decltype", "dynamic_cast",
|
||||
"explicit", "namespace", "noexcept", "operator", "protected", "register", "reinterpret_cast",
|
||||
"static_assert", "static_cast", "template", "thread_local", "typename", "unsigned", "volatile",
|
||||
"@class", "@dynamic", "@end", "@implementation", "@interface", "@public", "@private",
|
||||
"@protected", "@property", "@synthesize", nullptr };
|
||||
|
||||
const char* const* k;
|
||||
|
||||
switch (tokenLength)
|
||||
{
|
||||
case 2: k = keywords2Char; break;
|
||||
case 3: k = keywords3Char; break;
|
||||
case 4: k = keywords4Char; break;
|
||||
case 5: k = keywords5Char; break;
|
||||
case 6: k = keywords6Char; break;
|
||||
case 7: k = keywords7Char; break;
|
||||
|
||||
default:
|
||||
if (tokenLength < 2 || tokenLength > 16)
|
||||
return false;
|
||||
|
||||
k = keywordsOther;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; k[i] != 0; ++i)
|
||||
if (token.compare (CharPointer_ASCII (k[i])) == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static int parseIdentifier (Iterator& source) noexcept
|
||||
{
|
||||
int tokenLength = 0;
|
||||
String::CharPointerType::CharType possibleIdentifier [100];
|
||||
String::CharPointerType possible (possibleIdentifier);
|
||||
|
||||
while (isIdentifierBody (source.peekNextChar()))
|
||||
{
|
||||
const juce_wchar c = source.nextChar();
|
||||
|
||||
if (tokenLength < 20)
|
||||
possible.write (c);
|
||||
|
||||
++tokenLength;
|
||||
}
|
||||
|
||||
if (tokenLength > 1 && tokenLength <= 16)
|
||||
{
|
||||
possible.writeNull();
|
||||
|
||||
if (isReservedKeyword (String::CharPointerType (possibleIdentifier), tokenLength))
|
||||
return CPlusPlusCodeTokeniser::tokenType_keyword;
|
||||
}
|
||||
|
||||
return CPlusPlusCodeTokeniser::tokenType_identifier;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static bool skipNumberSuffix (Iterator& source)
|
||||
{
|
||||
const juce_wchar c = source.peekNextChar();
|
||||
if (c == 'l' || c == 'L' || c == 'u' || c == 'U')
|
||||
source.skip();
|
||||
|
||||
if (CharacterFunctions::isLetterOrDigit (source.peekNextChar()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isHexDigit (const juce_wchar c) noexcept
|
||||
{
|
||||
return (c >= '0' && c <= '9')
|
||||
|| (c >= 'a' && c <= 'f')
|
||||
|| (c >= 'A' && c <= 'F');
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static bool parseHexLiteral (Iterator& source) noexcept
|
||||
{
|
||||
if (source.peekNextChar() == '-')
|
||||
source.skip();
|
||||
|
||||
if (source.nextChar() != '0')
|
||||
return false;
|
||||
|
||||
juce_wchar c = source.nextChar();
|
||||
if (c != 'x' && c != 'X')
|
||||
return false;
|
||||
|
||||
int numDigits = 0;
|
||||
while (isHexDigit (source.peekNextChar()))
|
||||
{
|
||||
++numDigits;
|
||||
source.skip();
|
||||
}
|
||||
|
||||
if (numDigits == 0)
|
||||
return false;
|
||||
|
||||
return skipNumberSuffix (source);
|
||||
}
|
||||
|
||||
static bool isOctalDigit (const juce_wchar c) noexcept
|
||||
{
|
||||
return c >= '0' && c <= '7';
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static bool parseOctalLiteral (Iterator& source) noexcept
|
||||
{
|
||||
if (source.peekNextChar() == '-')
|
||||
source.skip();
|
||||
|
||||
if (source.nextChar() != '0')
|
||||
return false;
|
||||
|
||||
if (! isOctalDigit (source.nextChar()))
|
||||
return false;
|
||||
|
||||
while (isOctalDigit (source.peekNextChar()))
|
||||
source.skip();
|
||||
|
||||
return skipNumberSuffix (source);
|
||||
}
|
||||
|
||||
static bool isDecimalDigit (const juce_wchar c) noexcept
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static bool parseDecimalLiteral (Iterator& source) noexcept
|
||||
{
|
||||
if (source.peekNextChar() == '-')
|
||||
source.skip();
|
||||
|
||||
int numChars = 0;
|
||||
while (isDecimalDigit (source.peekNextChar()))
|
||||
{
|
||||
++numChars;
|
||||
source.skip();
|
||||
}
|
||||
|
||||
if (numChars == 0)
|
||||
return false;
|
||||
|
||||
return skipNumberSuffix (source);
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static bool parseFloatLiteral (Iterator& source) noexcept
|
||||
{
|
||||
if (source.peekNextChar() == '-')
|
||||
source.skip();
|
||||
|
||||
int numDigits = 0;
|
||||
|
||||
while (isDecimalDigit (source.peekNextChar()))
|
||||
{
|
||||
source.skip();
|
||||
++numDigits;
|
||||
}
|
||||
|
||||
const bool hasPoint = (source.peekNextChar() == '.');
|
||||
|
||||
if (hasPoint)
|
||||
{
|
||||
source.skip();
|
||||
|
||||
while (isDecimalDigit (source.peekNextChar()))
|
||||
{
|
||||
source.skip();
|
||||
++numDigits;
|
||||
}
|
||||
}
|
||||
|
||||
if (numDigits == 0)
|
||||
return false;
|
||||
|
||||
juce_wchar c = source.peekNextChar();
|
||||
const bool hasExponent = (c == 'e' || c == 'E');
|
||||
|
||||
if (hasExponent)
|
||||
{
|
||||
source.skip();
|
||||
|
||||
c = source.peekNextChar();
|
||||
if (c == '+' || c == '-')
|
||||
source.skip();
|
||||
|
||||
int numExpDigits = 0;
|
||||
while (isDecimalDigit (source.peekNextChar()))
|
||||
{
|
||||
source.skip();
|
||||
++numExpDigits;
|
||||
}
|
||||
|
||||
if (numExpDigits == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
c = source.peekNextChar();
|
||||
if (c == 'f' || c == 'F')
|
||||
source.skip();
|
||||
else if (! (hasExponent || hasPoint))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static int parseNumber (Iterator& source)
|
||||
{
|
||||
const Iterator original (source);
|
||||
|
||||
if (parseFloatLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_float;
|
||||
source = original;
|
||||
|
||||
if (parseHexLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_integer;
|
||||
source = original;
|
||||
|
||||
if (parseOctalLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_integer;
|
||||
source = original;
|
||||
|
||||
if (parseDecimalLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_integer;
|
||||
source = original;
|
||||
|
||||
return CPlusPlusCodeTokeniser::tokenType_error;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static void skipQuotedString (Iterator& source) noexcept
|
||||
{
|
||||
const juce_wchar quote = source.nextChar();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const juce_wchar c = source.nextChar();
|
||||
|
||||
if (c == quote || c == 0)
|
||||
break;
|
||||
|
||||
if (c == '\\')
|
||||
source.skip();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static void skipComment (Iterator& source) noexcept
|
||||
{
|
||||
bool lastWasStar = false;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const juce_wchar c = source.nextChar();
|
||||
|
||||
if (c == 0 || (c == '/' && lastWasStar))
|
||||
break;
|
||||
|
||||
lastWasStar = (c == '*');
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static void skipPreprocessorLine (Iterator& source) noexcept
|
||||
{
|
||||
bool lastWasBackslash = false;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const juce_wchar c = source.peekNextChar();
|
||||
|
||||
if (c == '"')
|
||||
{
|
||||
skipQuotedString (source);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '/')
|
||||
{
|
||||
Iterator next (source);
|
||||
next.skip();
|
||||
const juce_wchar c2 = next.peekNextChar();
|
||||
|
||||
if (c2 == '/' || c2 == '*')
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == 0)
|
||||
break;
|
||||
|
||||
if (c == '\n' || c == '\r')
|
||||
{
|
||||
source.skipToEndOfLine();
|
||||
|
||||
if (lastWasBackslash)
|
||||
skipPreprocessorLine (source);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
lastWasBackslash = (c == '\\');
|
||||
source.skip();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static void skipIfNextCharMatches (Iterator& source, const juce_wchar c) noexcept
|
||||
{
|
||||
if (source.peekNextChar() == c)
|
||||
source.skip();
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static void skipIfNextCharMatches (Iterator& source, const juce_wchar c1, const juce_wchar c2) noexcept
|
||||
{
|
||||
const juce_wchar c = source.peekNextChar();
|
||||
|
||||
if (c == c1 || c == c2)
|
||||
source.skip();
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static int readNextToken (Iterator& source)
|
||||
{
|
||||
source.skipWhitespace();
|
||||
|
||||
const juce_wchar firstChar = source.peekNextChar();
|
||||
|
||||
switch (firstChar)
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
case '.':
|
||||
{
|
||||
int result = parseNumber (source);
|
||||
|
||||
if (result == CPlusPlusCodeTokeniser::tokenType_error)
|
||||
{
|
||||
source.skip();
|
||||
|
||||
if (firstChar == '.')
|
||||
return CPlusPlusCodeTokeniser::tokenType_punctuation;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case ',':
|
||||
case ';':
|
||||
case ':':
|
||||
source.skip();
|
||||
return CPlusPlusCodeTokeniser::tokenType_punctuation;
|
||||
|
||||
case '(': case ')':
|
||||
case '{': case '}':
|
||||
case '[': case ']':
|
||||
source.skip();
|
||||
return CPlusPlusCodeTokeniser::tokenType_bracket;
|
||||
|
||||
case '"':
|
||||
case '\'':
|
||||
skipQuotedString (source);
|
||||
return CPlusPlusCodeTokeniser::tokenType_string;
|
||||
|
||||
case '+':
|
||||
source.skip();
|
||||
skipIfNextCharMatches (source, '+', '=');
|
||||
return CPlusPlusCodeTokeniser::tokenType_operator;
|
||||
|
||||
case '-':
|
||||
{
|
||||
source.skip();
|
||||
int result = parseNumber (source);
|
||||
|
||||
if (result == CPlusPlusCodeTokeniser::tokenType_error)
|
||||
{
|
||||
skipIfNextCharMatches (source, '-', '=');
|
||||
return CPlusPlusCodeTokeniser::tokenType_operator;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case '*': case '%':
|
||||
case '=': case '!':
|
||||
source.skip();
|
||||
skipIfNextCharMatches (source, '=');
|
||||
return CPlusPlusCodeTokeniser::tokenType_operator;
|
||||
|
||||
case '/':
|
||||
{
|
||||
source.skip();
|
||||
juce_wchar nextChar = source.peekNextChar();
|
||||
|
||||
if (nextChar == '/')
|
||||
{
|
||||
source.skipToEndOfLine();
|
||||
return CPlusPlusCodeTokeniser::tokenType_comment;
|
||||
}
|
||||
|
||||
if (nextChar == '*')
|
||||
{
|
||||
source.skip();
|
||||
skipComment (source);
|
||||
return CPlusPlusCodeTokeniser::tokenType_comment;
|
||||
}
|
||||
|
||||
if (nextChar == '=')
|
||||
source.skip();
|
||||
|
||||
return CPlusPlusCodeTokeniser::tokenType_operator;
|
||||
}
|
||||
|
||||
case '?':
|
||||
case '~':
|
||||
source.skip();
|
||||
return CPlusPlusCodeTokeniser::tokenType_operator;
|
||||
|
||||
case '<': case '>':
|
||||
case '|': case '&': case '^':
|
||||
source.skip();
|
||||
skipIfNextCharMatches (source, firstChar);
|
||||
skipIfNextCharMatches (source, '=');
|
||||
return CPlusPlusCodeTokeniser::tokenType_operator;
|
||||
|
||||
case '#':
|
||||
skipPreprocessorLine (source);
|
||||
return CPlusPlusCodeTokeniser::tokenType_preprocessor;
|
||||
|
||||
default:
|
||||
if (isIdentifierStart (firstChar))
|
||||
return parseIdentifier (source);
|
||||
|
||||
source.skip();
|
||||
break;
|
||||
}
|
||||
|
||||
return CPlusPlusCodeTokeniser::tokenType_error;
|
||||
}
|
||||
|
||||
/** A class that can be passed to the CppTokeniserFunctions functions in order to
|
||||
parse a String.
|
||||
*/
|
||||
struct StringIterator
|
||||
{
|
||||
StringIterator (const String& s) noexcept : t (s.getCharPointer()), numChars (0) {}
|
||||
StringIterator (String::CharPointerType s) noexcept : t (s), numChars (0) {}
|
||||
|
||||
juce_wchar nextChar() noexcept { if (isEOF()) return 0; ++numChars; return t.getAndAdvance(); }
|
||||
juce_wchar peekNextChar()noexcept { return *t; }
|
||||
void skip() noexcept { if (! isEOF()) { ++t; ++numChars; } }
|
||||
void skipWhitespace() noexcept { while (t.isWhitespace()) skip(); }
|
||||
void skipToEndOfLine() noexcept { while (*t != '\r' && *t != '\n' && *t != 0) skip(); }
|
||||
bool isEOF() const noexcept { return t.isEmpty(); }
|
||||
|
||||
String::CharPointerType t;
|
||||
int numChars;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Takes a UTF8 string and writes it to a stream using standard C++ escape sequences for any
|
||||
non-ascii bytes.
|
||||
|
||||
Although not strictly a tokenising function, this is still a function that often comes in
|
||||
handy when working with C++ code!
|
||||
|
||||
Note that addEscapeChars() is easier to use than this function if you're working with Strings.
|
||||
|
||||
@see addEscapeChars
|
||||
*/
|
||||
static void writeEscapeChars (OutputStream& out, const char* utf8, const int numBytesToRead,
|
||||
const int maxCharsOnLine, const bool breakAtNewLines,
|
||||
const bool replaceSingleQuotes, const bool allowStringBreaks)
|
||||
{
|
||||
int charsOnLine = 0;
|
||||
bool lastWasHexEscapeCode = false;
|
||||
|
||||
for (int i = 0; i < numBytesToRead || numBytesToRead < 0; ++i)
|
||||
{
|
||||
const unsigned char c = (unsigned char) utf8[i];
|
||||
bool startNewLine = false;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '\t': out << "\\t"; lastWasHexEscapeCode = false; charsOnLine += 2; break;
|
||||
case '\r': out << "\\r"; lastWasHexEscapeCode = false; charsOnLine += 2; break;
|
||||
case '\n': out << "\\n"; lastWasHexEscapeCode = false; charsOnLine += 2; startNewLine = breakAtNewLines; break;
|
||||
case '\\': out << "\\\\"; lastWasHexEscapeCode = false; charsOnLine += 2; break;
|
||||
case '\"': out << "\\\""; lastWasHexEscapeCode = false; charsOnLine += 2; break;
|
||||
|
||||
case 0:
|
||||
if (numBytesToRead < 0)
|
||||
return;
|
||||
|
||||
out << "\\0";
|
||||
lastWasHexEscapeCode = true;
|
||||
charsOnLine += 2;
|
||||
break;
|
||||
|
||||
case '\'':
|
||||
if (replaceSingleQuotes)
|
||||
{
|
||||
out << "\\\'";
|
||||
lastWasHexEscapeCode = false;
|
||||
charsOnLine += 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// deliberate fall-through...
|
||||
|
||||
default:
|
||||
if (c >= 32 && c < 127 && ! (lastWasHexEscapeCode // (have to avoid following a hex escape sequence with a valid hex digit)
|
||||
&& CharacterFunctions::getHexDigitValue (c) >= 0))
|
||||
{
|
||||
out << (char) c;
|
||||
lastWasHexEscapeCode = false;
|
||||
++charsOnLine;
|
||||
}
|
||||
else if (allowStringBreaks && lastWasHexEscapeCode && c >= 32 && c < 127)
|
||||
{
|
||||
out << "\"\"" << (char) c;
|
||||
lastWasHexEscapeCode = false;
|
||||
charsOnLine += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
out << (c < 16 ? "\\x0" : "\\x") << String::toHexString ((int) c);
|
||||
lastWasHexEscapeCode = true;
|
||||
charsOnLine += 4;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ((startNewLine || (maxCharsOnLine > 0 && charsOnLine >= maxCharsOnLine))
|
||||
&& (numBytesToRead < 0 || i < numBytesToRead - 1))
|
||||
{
|
||||
charsOnLine = 0;
|
||||
out << "\"" << newLine << "\"";
|
||||
lastWasHexEscapeCode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Takes a string and returns a version of it where standard C++ escape sequences have been
|
||||
used to replace any non-ascii bytes.
|
||||
|
||||
Although not strictly a tokenising function, this is still a function that often comes in
|
||||
handy when working with C++ code!
|
||||
|
||||
@see writeEscapeChars
|
||||
*/
|
||||
static String addEscapeChars (const String& s)
|
||||
{
|
||||
MemoryOutputStream mo;
|
||||
writeEscapeChars (mo, s.toRawUTF8(), -1, -1, false, true, true);
|
||||
return mo.toString();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_CPLUSPLUSCODETOKENISERFUNCTIONS_H_INCLUDED
|
||||
@@ -0,0 +1,990 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
class CodeDocumentLine
|
||||
{
|
||||
public:
|
||||
CodeDocumentLine (const String::CharPointerType startOfLine,
|
||||
const String::CharPointerType endOfLine,
|
||||
const int lineLen,
|
||||
const int numNewLineChars,
|
||||
const int startInFile)
|
||||
: line (startOfLine, endOfLine),
|
||||
lineStartInFile (startInFile),
|
||||
lineLength (lineLen),
|
||||
lineLengthWithoutNewLines (lineLen - numNewLineChars)
|
||||
{
|
||||
}
|
||||
|
||||
static void createLines (Array<CodeDocumentLine*>& newLines, StringRef text)
|
||||
{
|
||||
String::CharPointerType t (text.text);
|
||||
int charNumInFile = 0;
|
||||
bool finished = false;
|
||||
|
||||
while (! (finished || t.isEmpty()))
|
||||
{
|
||||
String::CharPointerType startOfLine (t);
|
||||
int startOfLineInFile = charNumInFile;
|
||||
int lineLength = 0;
|
||||
int numNewLineChars = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const juce_wchar c = t.getAndAdvance();
|
||||
|
||||
if (c == 0)
|
||||
{
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
|
||||
++charNumInFile;
|
||||
++lineLength;
|
||||
|
||||
if (c == '\r')
|
||||
{
|
||||
++numNewLineChars;
|
||||
|
||||
if (*t == '\n')
|
||||
{
|
||||
++t;
|
||||
++charNumInFile;
|
||||
++lineLength;
|
||||
++numNewLineChars;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == '\n')
|
||||
{
|
||||
++numNewLineChars;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
newLines.add (new CodeDocumentLine (startOfLine, t, lineLength,
|
||||
numNewLineChars, startOfLineInFile));
|
||||
}
|
||||
|
||||
jassert (charNumInFile == text.length());
|
||||
}
|
||||
|
||||
bool endsWithLineBreak() const noexcept
|
||||
{
|
||||
return lineLengthWithoutNewLines != lineLength;
|
||||
}
|
||||
|
||||
void updateLength() noexcept
|
||||
{
|
||||
lineLength = 0;
|
||||
lineLengthWithoutNewLines = 0;
|
||||
|
||||
for (String::CharPointerType t (line.getCharPointer());;)
|
||||
{
|
||||
const juce_wchar c = t.getAndAdvance();
|
||||
|
||||
if (c == 0)
|
||||
break;
|
||||
|
||||
++lineLength;
|
||||
|
||||
if (c != '\n' && c != '\r')
|
||||
lineLengthWithoutNewLines = lineLength;
|
||||
}
|
||||
}
|
||||
|
||||
String line;
|
||||
int lineStartInFile, lineLength, lineLengthWithoutNewLines;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
CodeDocument::Iterator::Iterator (const CodeDocument& doc) noexcept
|
||||
: document (&doc),
|
||||
charPointer (nullptr),
|
||||
line (0),
|
||||
position (0)
|
||||
{
|
||||
}
|
||||
|
||||
CodeDocument::Iterator::Iterator (const CodeDocument::Iterator& other) noexcept
|
||||
: document (other.document),
|
||||
charPointer (other.charPointer),
|
||||
line (other.line),
|
||||
position (other.position)
|
||||
{
|
||||
}
|
||||
|
||||
CodeDocument::Iterator& CodeDocument::Iterator::operator= (const CodeDocument::Iterator& other) noexcept
|
||||
{
|
||||
document = other.document;
|
||||
charPointer = other.charPointer;
|
||||
line = other.line;
|
||||
position = other.position;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CodeDocument::Iterator::~Iterator() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
juce_wchar CodeDocument::Iterator::nextChar() noexcept
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
if (charPointer.getAddress() == nullptr)
|
||||
{
|
||||
if (const CodeDocumentLine* const l = document->lines[line])
|
||||
charPointer = l->line.getCharPointer();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (const juce_wchar result = charPointer.getAndAdvance())
|
||||
{
|
||||
if (charPointer.isEmpty())
|
||||
{
|
||||
++line;
|
||||
charPointer = nullptr;
|
||||
}
|
||||
|
||||
++position;
|
||||
return result;
|
||||
}
|
||||
|
||||
++line;
|
||||
charPointer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CodeDocument::Iterator::skip() noexcept
|
||||
{
|
||||
nextChar();
|
||||
}
|
||||
|
||||
void CodeDocument::Iterator::skipToEndOfLine() noexcept
|
||||
{
|
||||
if (charPointer.getAddress() == nullptr)
|
||||
{
|
||||
const CodeDocumentLine* const l = document->lines[line];
|
||||
|
||||
if (l == nullptr)
|
||||
return;
|
||||
|
||||
charPointer = l->line.getCharPointer();
|
||||
}
|
||||
|
||||
position += (int) charPointer.length();
|
||||
++line;
|
||||
charPointer = nullptr;
|
||||
}
|
||||
|
||||
juce_wchar CodeDocument::Iterator::peekNextChar() const noexcept
|
||||
{
|
||||
if (charPointer.getAddress() == nullptr)
|
||||
{
|
||||
if (const CodeDocumentLine* const l = document->lines[line])
|
||||
charPointer = l->line.getCharPointer();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (const juce_wchar c = *charPointer)
|
||||
return c;
|
||||
|
||||
if (const CodeDocumentLine* const l = document->lines [line + 1])
|
||||
return l->line[0];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CodeDocument::Iterator::skipWhitespace() noexcept
|
||||
{
|
||||
while (CharacterFunctions::isWhitespace (peekNextChar()))
|
||||
skip();
|
||||
}
|
||||
|
||||
bool CodeDocument::Iterator::isEOF() const noexcept
|
||||
{
|
||||
return charPointer.getAddress() == nullptr && line >= document->lines.size();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
CodeDocument::Position::Position() noexcept
|
||||
: owner (nullptr), characterPos (0), line (0),
|
||||
indexInLine (0), positionMaintained (false)
|
||||
{
|
||||
}
|
||||
|
||||
CodeDocument::Position::Position (const CodeDocument& ownerDocument,
|
||||
const int lineNum, const int index) noexcept
|
||||
: owner (const_cast<CodeDocument*> (&ownerDocument)),
|
||||
characterPos (0), line (lineNum),
|
||||
indexInLine (index), positionMaintained (false)
|
||||
{
|
||||
setLineAndIndex (lineNum, index);
|
||||
}
|
||||
|
||||
CodeDocument::Position::Position (const CodeDocument& ownerDocument, const int characterPos_) noexcept
|
||||
: owner (const_cast<CodeDocument*> (&ownerDocument)),
|
||||
positionMaintained (false)
|
||||
{
|
||||
setPosition (characterPos_);
|
||||
}
|
||||
|
||||
CodeDocument::Position::Position (const Position& other) noexcept
|
||||
: owner (other.owner), characterPos (other.characterPos), line (other.line),
|
||||
indexInLine (other.indexInLine), positionMaintained (false)
|
||||
{
|
||||
jassert (*this == other);
|
||||
}
|
||||
|
||||
CodeDocument::Position::~Position()
|
||||
{
|
||||
setPositionMaintained (false);
|
||||
}
|
||||
|
||||
CodeDocument::Position& CodeDocument::Position::operator= (const Position& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
const bool wasPositionMaintained = positionMaintained;
|
||||
if (owner != other.owner)
|
||||
setPositionMaintained (false);
|
||||
|
||||
owner = other.owner;
|
||||
line = other.line;
|
||||
indexInLine = other.indexInLine;
|
||||
characterPos = other.characterPos;
|
||||
setPositionMaintained (wasPositionMaintained);
|
||||
|
||||
jassert (*this == other);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool CodeDocument::Position::operator== (const Position& other) const noexcept
|
||||
{
|
||||
jassert ((characterPos == other.characterPos)
|
||||
== (line == other.line && indexInLine == other.indexInLine));
|
||||
|
||||
return characterPos == other.characterPos
|
||||
&& line == other.line
|
||||
&& indexInLine == other.indexInLine
|
||||
&& owner == other.owner;
|
||||
}
|
||||
|
||||
bool CodeDocument::Position::operator!= (const Position& other) const noexcept
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
void CodeDocument::Position::setLineAndIndex (const int newLineNum, const int newIndexInLine)
|
||||
{
|
||||
jassert (owner != nullptr);
|
||||
|
||||
if (owner->lines.size() == 0)
|
||||
{
|
||||
line = 0;
|
||||
indexInLine = 0;
|
||||
characterPos = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (newLineNum >= owner->lines.size())
|
||||
{
|
||||
line = owner->lines.size() - 1;
|
||||
|
||||
const CodeDocumentLine& l = *owner->lines.getUnchecked (line);
|
||||
indexInLine = l.lineLengthWithoutNewLines;
|
||||
characterPos = l.lineStartInFile + indexInLine;
|
||||
}
|
||||
else
|
||||
{
|
||||
line = jmax (0, newLineNum);
|
||||
|
||||
const CodeDocumentLine& l = *owner->lines.getUnchecked (line);
|
||||
|
||||
if (l.lineLengthWithoutNewLines > 0)
|
||||
indexInLine = jlimit (0, l.lineLengthWithoutNewLines, newIndexInLine);
|
||||
else
|
||||
indexInLine = 0;
|
||||
|
||||
characterPos = l.lineStartInFile + indexInLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CodeDocument::Position::setPosition (const int newPosition)
|
||||
{
|
||||
jassert (owner != nullptr);
|
||||
|
||||
line = 0;
|
||||
indexInLine = 0;
|
||||
characterPos = 0;
|
||||
|
||||
if (newPosition > 0)
|
||||
{
|
||||
int lineStart = 0;
|
||||
int lineEnd = owner->lines.size();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (lineEnd - lineStart < 4)
|
||||
{
|
||||
for (int i = lineStart; i < lineEnd; ++i)
|
||||
{
|
||||
const CodeDocumentLine& l = *owner->lines.getUnchecked (i);
|
||||
const int index = newPosition - l.lineStartInFile;
|
||||
|
||||
if (index >= 0 && (index < l.lineLength || i == lineEnd - 1))
|
||||
{
|
||||
line = i;
|
||||
indexInLine = jmin (l.lineLengthWithoutNewLines, index);
|
||||
characterPos = l.lineStartInFile + indexInLine;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
const int midIndex = (lineStart + lineEnd + 1) / 2;
|
||||
|
||||
if (newPosition >= owner->lines.getUnchecked (midIndex)->lineStartInFile)
|
||||
lineStart = midIndex;
|
||||
else
|
||||
lineEnd = midIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CodeDocument::Position::moveBy (int characterDelta)
|
||||
{
|
||||
jassert (owner != nullptr);
|
||||
|
||||
if (characterDelta == 1)
|
||||
{
|
||||
setPosition (getPosition());
|
||||
|
||||
// If moving right, make sure we don't get stuck between the \r and \n characters..
|
||||
if (line < owner->lines.size())
|
||||
{
|
||||
const CodeDocumentLine& l = *owner->lines.getUnchecked (line);
|
||||
|
||||
if (indexInLine + characterDelta < l.lineLength
|
||||
&& indexInLine + characterDelta >= l.lineLengthWithoutNewLines + 1)
|
||||
++characterDelta;
|
||||
}
|
||||
}
|
||||
|
||||
setPosition (characterPos + characterDelta);
|
||||
}
|
||||
|
||||
CodeDocument::Position CodeDocument::Position::movedBy (const int characterDelta) const
|
||||
{
|
||||
CodeDocument::Position p (*this);
|
||||
p.moveBy (characterDelta);
|
||||
return p;
|
||||
}
|
||||
|
||||
CodeDocument::Position CodeDocument::Position::movedByLines (const int deltaLines) const
|
||||
{
|
||||
CodeDocument::Position p (*this);
|
||||
p.setLineAndIndex (getLineNumber() + deltaLines, getIndexInLine());
|
||||
return p;
|
||||
}
|
||||
|
||||
juce_wchar CodeDocument::Position::getCharacter() const
|
||||
{
|
||||
if (const CodeDocumentLine* const l = owner->lines [line])
|
||||
return l->line [getIndexInLine()];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
String CodeDocument::Position::getLineText() const
|
||||
{
|
||||
if (const CodeDocumentLine* const l = owner->lines [line])
|
||||
return l->line;
|
||||
|
||||
return String::empty;
|
||||
}
|
||||
|
||||
void CodeDocument::Position::setPositionMaintained (const bool isMaintained)
|
||||
{
|
||||
if (isMaintained != positionMaintained)
|
||||
{
|
||||
positionMaintained = isMaintained;
|
||||
|
||||
if (owner != nullptr)
|
||||
{
|
||||
if (isMaintained)
|
||||
{
|
||||
jassert (! owner->positionsToMaintain.contains (this));
|
||||
owner->positionsToMaintain.add (this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If this happens, you may have deleted the document while there are Position objects that are still using it...
|
||||
jassert (owner->positionsToMaintain.contains (this));
|
||||
owner->positionsToMaintain.removeFirstMatchingValue (this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
CodeDocument::CodeDocument()
|
||||
: undoManager (std::numeric_limits<int>::max(), 10000),
|
||||
currentActionIndex (0),
|
||||
indexOfSavedState (-1),
|
||||
maximumLineLength (-1),
|
||||
newLineChars ("\r\n")
|
||||
{
|
||||
}
|
||||
|
||||
CodeDocument::~CodeDocument()
|
||||
{
|
||||
}
|
||||
|
||||
String CodeDocument::getAllContent() const
|
||||
{
|
||||
return getTextBetween (Position (*this, 0),
|
||||
Position (*this, lines.size(), 0));
|
||||
}
|
||||
|
||||
String CodeDocument::getTextBetween (const Position& start, const Position& end) const
|
||||
{
|
||||
if (end.getPosition() <= start.getPosition())
|
||||
return String::empty;
|
||||
|
||||
const int startLine = start.getLineNumber();
|
||||
const int endLine = end.getLineNumber();
|
||||
|
||||
if (startLine == endLine)
|
||||
{
|
||||
if (CodeDocumentLine* const line = lines [startLine])
|
||||
return line->line.substring (start.getIndexInLine(), end.getIndexInLine());
|
||||
|
||||
return String::empty;
|
||||
}
|
||||
|
||||
MemoryOutputStream mo;
|
||||
mo.preallocate ((size_t) (end.getPosition() - start.getPosition() + 4));
|
||||
|
||||
const int maxLine = jmin (lines.size() - 1, endLine);
|
||||
|
||||
for (int i = jmax (0, startLine); i <= maxLine; ++i)
|
||||
{
|
||||
const CodeDocumentLine& line = *lines.getUnchecked(i);
|
||||
int len = line.lineLength;
|
||||
|
||||
if (i == startLine)
|
||||
{
|
||||
const int index = start.getIndexInLine();
|
||||
mo << line.line.substring (index, len);
|
||||
}
|
||||
else if (i == endLine)
|
||||
{
|
||||
len = end.getIndexInLine();
|
||||
mo << line.line.substring (0, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
mo << line.line;
|
||||
}
|
||||
}
|
||||
|
||||
return mo.toUTF8();
|
||||
}
|
||||
|
||||
int CodeDocument::getNumCharacters() const noexcept
|
||||
{
|
||||
if (const CodeDocumentLine* const lastLine = lines.getLast())
|
||||
return lastLine->lineStartInFile + lastLine->lineLength;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
String CodeDocument::getLine (const int lineIndex) const noexcept
|
||||
{
|
||||
if (const CodeDocumentLine* const line = lines [lineIndex])
|
||||
return line->line;
|
||||
|
||||
return String::empty;
|
||||
}
|
||||
|
||||
int CodeDocument::getMaximumLineLength() noexcept
|
||||
{
|
||||
if (maximumLineLength < 0)
|
||||
{
|
||||
maximumLineLength = 0;
|
||||
|
||||
for (int i = lines.size(); --i >= 0;)
|
||||
maximumLineLength = jmax (maximumLineLength, lines.getUnchecked(i)->lineLength);
|
||||
}
|
||||
|
||||
return maximumLineLength;
|
||||
}
|
||||
|
||||
void CodeDocument::deleteSection (const Position& startPosition, const Position& endPosition)
|
||||
{
|
||||
deleteSection (startPosition.getPosition(), endPosition.getPosition());
|
||||
}
|
||||
|
||||
void CodeDocument::deleteSection (const int start, const int end)
|
||||
{
|
||||
remove (start, end, true);
|
||||
}
|
||||
|
||||
void CodeDocument::insertText (const Position& position, const String& text)
|
||||
{
|
||||
insertText (position.getPosition(), text);
|
||||
}
|
||||
|
||||
void CodeDocument::insertText (const int insertIndex, const String& text)
|
||||
{
|
||||
insert (text, insertIndex, true);
|
||||
}
|
||||
|
||||
void CodeDocument::replaceSection (const int start, const int end, const String& newText)
|
||||
{
|
||||
insertText (end, newText);
|
||||
deleteSection (start, end);
|
||||
}
|
||||
|
||||
void CodeDocument::applyChanges (const String& newContent)
|
||||
{
|
||||
const String corrected (StringArray::fromLines (newContent)
|
||||
.joinIntoString (newLineChars));
|
||||
|
||||
TextDiff diff (getAllContent(), corrected);
|
||||
|
||||
for (int i = 0; i < diff.changes.size(); ++i)
|
||||
{
|
||||
const TextDiff::Change& c = diff.changes.getReference(i);
|
||||
|
||||
if (c.isDeletion())
|
||||
remove (c.start, c.start + c.length, true);
|
||||
else
|
||||
insert (c.insertedText, c.start, true);
|
||||
}
|
||||
}
|
||||
|
||||
void CodeDocument::replaceAllContent (const String& newContent)
|
||||
{
|
||||
remove (0, getNumCharacters(), true);
|
||||
insert (newContent, 0, true);
|
||||
}
|
||||
|
||||
bool CodeDocument::loadFromStream (InputStream& stream)
|
||||
{
|
||||
remove (0, getNumCharacters(), false);
|
||||
insert (stream.readEntireStreamAsString(), 0, false);
|
||||
setSavePoint();
|
||||
clearUndoHistory();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CodeDocument::writeToStream (OutputStream& stream)
|
||||
{
|
||||
for (int i = 0; i < lines.size(); ++i)
|
||||
{
|
||||
String temp (lines.getUnchecked(i)->line); // use a copy to avoid bloating the memory footprint of the stored string.
|
||||
const char* utf8 = temp.toUTF8();
|
||||
|
||||
if (! stream.write (utf8, strlen (utf8)))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CodeDocument::setNewLineCharacters (const String& newChars) noexcept
|
||||
{
|
||||
jassert (newChars == "\r\n" || newChars == "\n" || newChars == "\r");
|
||||
newLineChars = newChars;
|
||||
}
|
||||
|
||||
void CodeDocument::newTransaction()
|
||||
{
|
||||
undoManager.beginNewTransaction (String::empty);
|
||||
}
|
||||
|
||||
void CodeDocument::undo()
|
||||
{
|
||||
newTransaction();
|
||||
undoManager.undo();
|
||||
}
|
||||
|
||||
void CodeDocument::redo()
|
||||
{
|
||||
undoManager.redo();
|
||||
}
|
||||
|
||||
void CodeDocument::clearUndoHistory()
|
||||
{
|
||||
undoManager.clearUndoHistory();
|
||||
}
|
||||
|
||||
void CodeDocument::setSavePoint() noexcept
|
||||
{
|
||||
indexOfSavedState = currentActionIndex;
|
||||
}
|
||||
|
||||
bool CodeDocument::hasChangedSinceSavePoint() const noexcept
|
||||
{
|
||||
return currentActionIndex != indexOfSavedState;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
namespace CodeDocumentHelpers
|
||||
{
|
||||
static int getCharacterType (const juce_wchar character) noexcept
|
||||
{
|
||||
return (CharacterFunctions::isLetterOrDigit (character) || character == '_')
|
||||
? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1);
|
||||
}
|
||||
|
||||
static bool isTokenCharacter (const juce_wchar c) noexcept
|
||||
{
|
||||
return CharacterFunctions::isLetterOrDigit (c) || c == '.' || c == '_';
|
||||
}
|
||||
}
|
||||
|
||||
CodeDocument::Position CodeDocument::findWordBreakAfter (const Position& position) const noexcept
|
||||
{
|
||||
Position p (position);
|
||||
const int maxDistance = 256;
|
||||
int i = 0;
|
||||
|
||||
while (i < maxDistance
|
||||
&& CharacterFunctions::isWhitespace (p.getCharacter())
|
||||
&& (i == 0 || (p.getCharacter() != '\n'
|
||||
&& p.getCharacter() != '\r')))
|
||||
{
|
||||
++i;
|
||||
p.moveBy (1);
|
||||
}
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
const int type = CodeDocumentHelpers::getCharacterType (p.getCharacter());
|
||||
|
||||
while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.getCharacter()))
|
||||
{
|
||||
++i;
|
||||
p.moveBy (1);
|
||||
}
|
||||
|
||||
while (i < maxDistance
|
||||
&& CharacterFunctions::isWhitespace (p.getCharacter())
|
||||
&& (i == 0 || (p.getCharacter() != '\n'
|
||||
&& p.getCharacter() != '\r')))
|
||||
{
|
||||
++i;
|
||||
p.moveBy (1);
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
CodeDocument::Position CodeDocument::findWordBreakBefore (const Position& position) const noexcept
|
||||
{
|
||||
Position p (position);
|
||||
const int maxDistance = 256;
|
||||
int i = 0;
|
||||
bool stoppedAtLineStart = false;
|
||||
|
||||
while (i < maxDistance)
|
||||
{
|
||||
const juce_wchar c = p.movedBy (-1).getCharacter();
|
||||
|
||||
if (c == '\r' || c == '\n')
|
||||
{
|
||||
stoppedAtLineStart = true;
|
||||
|
||||
if (i > 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (! CharacterFunctions::isWhitespace (c))
|
||||
break;
|
||||
|
||||
p.moveBy (-1);
|
||||
++i;
|
||||
}
|
||||
|
||||
if (i < maxDistance && ! stoppedAtLineStart)
|
||||
{
|
||||
const int type = CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter());
|
||||
|
||||
while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter()))
|
||||
{
|
||||
p.moveBy (-1);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void CodeDocument::findTokenContaining (const Position& pos, Position& start, Position& end) const noexcept
|
||||
{
|
||||
end = pos;
|
||||
while (CodeDocumentHelpers::isTokenCharacter (end.getCharacter()))
|
||||
end.moveBy (1);
|
||||
|
||||
start = end;
|
||||
while (start.getIndexInLine() > 0
|
||||
&& CodeDocumentHelpers::isTokenCharacter (start.movedBy (-1).getCharacter()))
|
||||
start.moveBy (-1);
|
||||
}
|
||||
|
||||
void CodeDocument::findLineContaining (const Position& pos, Position& s, Position& e) const noexcept
|
||||
{
|
||||
s.setLineAndIndex (pos.getLineNumber(), 0);
|
||||
e.setLineAndIndex (pos.getLineNumber() + 1, 0);
|
||||
}
|
||||
|
||||
void CodeDocument::checkLastLineStatus()
|
||||
{
|
||||
while (lines.size() > 0
|
||||
&& lines.getLast()->lineLength == 0
|
||||
&& (lines.size() == 1 || ! lines.getUnchecked (lines.size() - 2)->endsWithLineBreak()))
|
||||
{
|
||||
// remove any empty lines at the end if the preceding line doesn't end in a newline.
|
||||
lines.removeLast();
|
||||
}
|
||||
|
||||
const CodeDocumentLine* const lastLine = lines.getLast();
|
||||
|
||||
if (lastLine != nullptr && lastLine->endsWithLineBreak())
|
||||
{
|
||||
// check that there's an empty line at the end if the preceding one ends in a newline..
|
||||
lines.add (new CodeDocumentLine (StringRef(), StringRef(), 0, 0,
|
||||
lastLine->lineStartInFile + lastLine->lineLength));
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void CodeDocument::addListener (CodeDocument::Listener* const l) noexcept { listeners.add (l); }
|
||||
void CodeDocument::removeListener (CodeDocument::Listener* const l) noexcept { listeners.remove (l); }
|
||||
|
||||
//==============================================================================
|
||||
class CodeDocumentInsertAction : public UndoableAction
|
||||
{
|
||||
public:
|
||||
CodeDocumentInsertAction (CodeDocument& doc, const String& t, const int pos) noexcept
|
||||
: owner (doc), text (t), insertPos (pos)
|
||||
{
|
||||
}
|
||||
|
||||
bool perform()
|
||||
{
|
||||
owner.currentActionIndex++;
|
||||
owner.insert (text, insertPos, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool undo()
|
||||
{
|
||||
owner.currentActionIndex--;
|
||||
owner.remove (insertPos, insertPos + text.length(), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
int getSizeInUnits() { return text.length() + 32; }
|
||||
|
||||
private:
|
||||
CodeDocument& owner;
|
||||
const String text;
|
||||
const int insertPos;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (CodeDocumentInsertAction)
|
||||
};
|
||||
|
||||
void CodeDocument::insert (const String& text, const int insertPos, const bool undoable)
|
||||
{
|
||||
if (text.isNotEmpty())
|
||||
{
|
||||
if (undoable)
|
||||
{
|
||||
undoManager.perform (new CodeDocumentInsertAction (*this, text, insertPos));
|
||||
}
|
||||
else
|
||||
{
|
||||
Position pos (*this, insertPos);
|
||||
const int firstAffectedLine = pos.getLineNumber();
|
||||
|
||||
CodeDocumentLine* const firstLine = lines [firstAffectedLine];
|
||||
String textInsideOriginalLine (text);
|
||||
|
||||
if (firstLine != nullptr)
|
||||
{
|
||||
const int index = pos.getIndexInLine();
|
||||
textInsideOriginalLine = firstLine->line.substring (0, index)
|
||||
+ textInsideOriginalLine
|
||||
+ firstLine->line.substring (index);
|
||||
}
|
||||
|
||||
maximumLineLength = -1;
|
||||
Array<CodeDocumentLine*> newLines;
|
||||
CodeDocumentLine::createLines (newLines, textInsideOriginalLine);
|
||||
jassert (newLines.size() > 0);
|
||||
|
||||
CodeDocumentLine* const newFirstLine = newLines.getUnchecked (0);
|
||||
newFirstLine->lineStartInFile = firstLine != nullptr ? firstLine->lineStartInFile : 0;
|
||||
lines.set (firstAffectedLine, newFirstLine);
|
||||
|
||||
if (newLines.size() > 1)
|
||||
lines.insertArray (firstAffectedLine + 1, newLines.getRawDataPointer() + 1, newLines.size() - 1);
|
||||
|
||||
int lineStart = newFirstLine->lineStartInFile;
|
||||
for (int i = firstAffectedLine; i < lines.size(); ++i)
|
||||
{
|
||||
CodeDocumentLine& l = *lines.getUnchecked (i);
|
||||
l.lineStartInFile = lineStart;
|
||||
lineStart += l.lineLength;
|
||||
}
|
||||
|
||||
checkLastLineStatus();
|
||||
|
||||
const int newTextLength = text.length();
|
||||
for (int i = 0; i < positionsToMaintain.size(); ++i)
|
||||
{
|
||||
CodeDocument::Position& p = *positionsToMaintain.getUnchecked(i);
|
||||
|
||||
if (p.getPosition() >= insertPos)
|
||||
p.setPosition (p.getPosition() + newTextLength);
|
||||
}
|
||||
|
||||
listeners.call (&CodeDocument::Listener::codeDocumentTextInserted, text, insertPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class CodeDocumentDeleteAction : public UndoableAction
|
||||
{
|
||||
public:
|
||||
CodeDocumentDeleteAction (CodeDocument& doc, const int start, const int end) noexcept
|
||||
: owner (doc), startPos (start), endPos (end),
|
||||
removedText (doc.getTextBetween (CodeDocument::Position (doc, start),
|
||||
CodeDocument::Position (doc, end)))
|
||||
{
|
||||
}
|
||||
|
||||
bool perform()
|
||||
{
|
||||
owner.currentActionIndex++;
|
||||
owner.remove (startPos, endPos, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool undo()
|
||||
{
|
||||
owner.currentActionIndex--;
|
||||
owner.insert (removedText, startPos, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
int getSizeInUnits() { return (endPos - startPos) + 32; }
|
||||
|
||||
private:
|
||||
CodeDocument& owner;
|
||||
const int startPos, endPos;
|
||||
const String removedText;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (CodeDocumentDeleteAction)
|
||||
};
|
||||
|
||||
void CodeDocument::remove (const int startPos, const int endPos, const bool undoable)
|
||||
{
|
||||
if (endPos <= startPos)
|
||||
return;
|
||||
|
||||
if (undoable)
|
||||
{
|
||||
undoManager.perform (new CodeDocumentDeleteAction (*this, startPos, endPos));
|
||||
}
|
||||
else
|
||||
{
|
||||
Position startPosition (*this, startPos);
|
||||
Position endPosition (*this, endPos);
|
||||
|
||||
maximumLineLength = -1;
|
||||
const int firstAffectedLine = startPosition.getLineNumber();
|
||||
const int endLine = endPosition.getLineNumber();
|
||||
CodeDocumentLine& firstLine = *lines.getUnchecked (firstAffectedLine);
|
||||
|
||||
if (firstAffectedLine == endLine)
|
||||
{
|
||||
firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
|
||||
+ firstLine.line.substring (endPosition.getIndexInLine());
|
||||
firstLine.updateLength();
|
||||
}
|
||||
else
|
||||
{
|
||||
CodeDocumentLine& lastLine = *lines.getUnchecked (endLine);
|
||||
|
||||
firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
|
||||
+ lastLine.line.substring (endPosition.getIndexInLine());
|
||||
firstLine.updateLength();
|
||||
|
||||
int numLinesToRemove = endLine - firstAffectedLine;
|
||||
lines.removeRange (firstAffectedLine + 1, numLinesToRemove);
|
||||
}
|
||||
|
||||
for (int i = firstAffectedLine + 1; i < lines.size(); ++i)
|
||||
{
|
||||
CodeDocumentLine& l = *lines.getUnchecked (i);
|
||||
const CodeDocumentLine& previousLine = *lines.getUnchecked (i - 1);
|
||||
l.lineStartInFile = previousLine.lineStartInFile + previousLine.lineLength;
|
||||
}
|
||||
|
||||
checkLastLineStatus();
|
||||
|
||||
const int totalChars = getNumCharacters();
|
||||
|
||||
for (int i = 0; i < positionsToMaintain.size(); ++i)
|
||||
{
|
||||
CodeDocument::Position& p = *positionsToMaintain.getUnchecked(i);
|
||||
|
||||
if (p.getPosition() > startPosition.getPosition())
|
||||
p.setPosition (jmax (startPos, p.getPosition() + startPos - endPos));
|
||||
|
||||
if (p.getPosition() > totalChars)
|
||||
p.setPosition (totalChars);
|
||||
}
|
||||
|
||||
listeners.call (&CodeDocument::Listener::codeDocumentTextDeleted, startPos, endPos);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_CODEDOCUMENT_H_INCLUDED
|
||||
#define JUCE_CODEDOCUMENT_H_INCLUDED
|
||||
|
||||
class CodeDocumentLine;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class for storing and manipulating a source code file.
|
||||
|
||||
When using a CodeEditorComponent, it takes one of these as its source object.
|
||||
|
||||
The CodeDocument stores its content as an array of lines, which makes it
|
||||
quick to insert and delete.
|
||||
|
||||
@see CodeEditorComponent
|
||||
*/
|
||||
class JUCE_API CodeDocument
|
||||
{
|
||||
public:
|
||||
/** Creates a new, empty document. */
|
||||
CodeDocument();
|
||||
|
||||
/** Destructor. */
|
||||
~CodeDocument();
|
||||
|
||||
//==============================================================================
|
||||
/** A position in a code document.
|
||||
|
||||
Using this class you can find a position in a code document and quickly get its
|
||||
character position, line, and index. By calling setPositionMaintained (true), the
|
||||
position is automatically updated when text is inserted or deleted in the document,
|
||||
so that it maintains its original place in the text.
|
||||
*/
|
||||
class JUCE_API Position
|
||||
{
|
||||
public:
|
||||
/** Creates an uninitialised postion.
|
||||
Don't attempt to call any methods on this until you've given it an owner document
|
||||
to refer to!
|
||||
*/
|
||||
Position() noexcept;
|
||||
|
||||
/** Creates a position based on a line and index in a document.
|
||||
|
||||
Note that this index is NOT the column number, it's the number of characters from the
|
||||
start of the line. The "column" number isn't quite the same, because if the line
|
||||
contains any tab characters, the relationship of the index to its visual column depends on
|
||||
the number of spaces per tab being used!
|
||||
|
||||
Lines are numbered from zero, and if the line or index are beyond the bounds of the document,
|
||||
they will be adjusted to keep them within its limits.
|
||||
*/
|
||||
Position (const CodeDocument& ownerDocument,
|
||||
int line, int indexInLine) noexcept;
|
||||
|
||||
/** Creates a position based on a character index in a document.
|
||||
This position is placed at the specified number of characters from the start of the
|
||||
document. The line and column are auto-calculated.
|
||||
|
||||
If the position is beyond the range of the document, it'll be adjusted to keep it
|
||||
inside.
|
||||
*/
|
||||
Position (const CodeDocument& ownerDocument,
|
||||
int charactersFromStartOfDocument) noexcept;
|
||||
|
||||
/** Creates a copy of another position.
|
||||
|
||||
This will copy the position, but the new object will not be set to maintain its position,
|
||||
even if the source object was set to do so.
|
||||
*/
|
||||
Position (const Position&) noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
~Position();
|
||||
|
||||
Position& operator= (const Position&);
|
||||
|
||||
bool operator== (const Position&) const noexcept;
|
||||
bool operator!= (const Position&) const noexcept;
|
||||
|
||||
/** Points this object at a new position within the document.
|
||||
|
||||
If the position is beyond the range of the document, it'll be adjusted to keep it
|
||||
inside.
|
||||
@see getPosition, setLineAndIndex
|
||||
*/
|
||||
void setPosition (int charactersFromStartOfDocument);
|
||||
|
||||
/** Returns the position as the number of characters from the start of the document.
|
||||
@see setPosition, getLineNumber, getIndexInLine
|
||||
*/
|
||||
int getPosition() const noexcept { return characterPos; }
|
||||
|
||||
/** Moves the position to a new line and index within the line.
|
||||
|
||||
Note that the index is NOT the column at which the position appears in an editor.
|
||||
If the line contains any tab characters, the relationship of the index to its
|
||||
visual position depends on the number of spaces per tab being used!
|
||||
|
||||
Lines are numbered from zero, and if the line or index are beyond the bounds of the document,
|
||||
they will be adjusted to keep them within its limits.
|
||||
*/
|
||||
void setLineAndIndex (int newLine, int newIndexInLine);
|
||||
|
||||
/** Returns the line number of this position.
|
||||
The first line in the document is numbered zero, not one!
|
||||
*/
|
||||
int getLineNumber() const noexcept { return line; }
|
||||
|
||||
/** Returns the number of characters from the start of the line.
|
||||
|
||||
Note that this value is NOT the column at which the position appears in an editor.
|
||||
If the line contains any tab characters, the relationship of the index to its
|
||||
visual position depends on the number of spaces per tab being used!
|
||||
*/
|
||||
int getIndexInLine() const noexcept { return indexInLine; }
|
||||
|
||||
/** Allows the position to be automatically updated when the document changes.
|
||||
|
||||
If this is set to true, the positon will register with its document so that
|
||||
when the document has text inserted or deleted, this position will be automatically
|
||||
moved to keep it at the same position in the text.
|
||||
*/
|
||||
void setPositionMaintained (bool isMaintained);
|
||||
|
||||
//==============================================================================
|
||||
/** Moves the position forwards or backwards by the specified number of characters.
|
||||
@see movedBy
|
||||
*/
|
||||
void moveBy (int characterDelta);
|
||||
|
||||
/** Returns a position which is the same as this one, moved by the specified number of
|
||||
characters.
|
||||
@see moveBy
|
||||
*/
|
||||
Position movedBy (int characterDelta) const;
|
||||
|
||||
/** Returns a position which is the same as this one, moved up or down by the specified
|
||||
number of lines.
|
||||
@see movedBy
|
||||
*/
|
||||
Position movedByLines (int deltaLines) const;
|
||||
|
||||
/** Returns the character in the document at this position.
|
||||
@see getLineText
|
||||
*/
|
||||
juce_wchar getCharacter() const;
|
||||
|
||||
/** Returns the line from the document that this position is within.
|
||||
@see getCharacter, getLineNumber
|
||||
*/
|
||||
String getLineText() const;
|
||||
|
||||
private:
|
||||
CodeDocument* owner;
|
||||
int characterPos, line, indexInLine;
|
||||
bool positionMaintained;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the full text of the document. */
|
||||
String getAllContent() const;
|
||||
|
||||
/** Returns a section of the document's text. */
|
||||
String getTextBetween (const Position& start, const Position& end) const;
|
||||
|
||||
/** Returns a line from the document. */
|
||||
String getLine (int lineIndex) const noexcept;
|
||||
|
||||
/** Returns the number of characters in the document. */
|
||||
int getNumCharacters() const noexcept;
|
||||
|
||||
/** Returns the number of lines in the document. */
|
||||
int getNumLines() const noexcept { return lines.size(); }
|
||||
|
||||
/** Returns the number of characters in the longest line of the document. */
|
||||
int getMaximumLineLength() noexcept;
|
||||
|
||||
/** Deletes a section of the text.
|
||||
This operation is undoable.
|
||||
*/
|
||||
void deleteSection (const Position& startPosition, const Position& endPosition);
|
||||
|
||||
/** Deletes a section of the text.
|
||||
This operation is undoable.
|
||||
*/
|
||||
void deleteSection (int startIndex, int endIndex);
|
||||
|
||||
/** Inserts some text into the document at a given position.
|
||||
This operation is undoable.
|
||||
*/
|
||||
void insertText (const Position& position, const String& text);
|
||||
|
||||
/** Inserts some text into the document at a given position.
|
||||
This operation is undoable.
|
||||
*/
|
||||
void insertText (int insertIndex, const String& text);
|
||||
|
||||
/** Replaces a section of the text with a new string.
|
||||
This operation is undoable.
|
||||
*/
|
||||
void replaceSection (int startIndex, int endIndex, const String& newText);
|
||||
|
||||
/** Clears the document and replaces it with some new text.
|
||||
|
||||
This operation is undoable - if you're trying to completely reset the document, you
|
||||
might want to also call clearUndoHistory() and setSavePoint() after using this method.
|
||||
*/
|
||||
void replaceAllContent (const String& newContent);
|
||||
|
||||
/** Analyses the changes between the current content and some new text, and applies
|
||||
those changes.
|
||||
*/
|
||||
void applyChanges (const String& newContent);
|
||||
|
||||
/** Replaces the editor's contents with the contents of a stream.
|
||||
This will also reset the undo history and save point marker.
|
||||
*/
|
||||
bool loadFromStream (InputStream& stream);
|
||||
|
||||
/** Writes the editor's current contents to a stream. */
|
||||
bool writeToStream (OutputStream& stream);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the preferred new-line characters for the document.
|
||||
This will be either "\n", "\r\n", or (rarely) "\r".
|
||||
@see setNewLineCharacters
|
||||
*/
|
||||
String getNewLineCharacters() const noexcept { return newLineChars; }
|
||||
|
||||
/** Sets the new-line characters that the document should use.
|
||||
The string must be either "\n", "\r\n", or (rarely) "\r".
|
||||
@see getNewLineCharacters
|
||||
*/
|
||||
void setNewLineCharacters (const String& newLine) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Begins a new undo transaction.
|
||||
|
||||
The document itself will not call this internally, so relies on whatever is using the
|
||||
document to periodically call this to break up the undo sequence into sensible chunks.
|
||||
@see UndoManager::beginNewTransaction
|
||||
*/
|
||||
void newTransaction();
|
||||
|
||||
/** Undo the last operation.
|
||||
@see UndoManager::undo
|
||||
*/
|
||||
void undo();
|
||||
|
||||
/** Redo the last operation.
|
||||
@see UndoManager::redo
|
||||
*/
|
||||
void redo();
|
||||
|
||||
/** Clears the undo history.
|
||||
@see UndoManager::clearUndoHistory
|
||||
*/
|
||||
void clearUndoHistory();
|
||||
|
||||
/** Returns the document's UndoManager */
|
||||
UndoManager& getUndoManager() noexcept { return undoManager; }
|
||||
|
||||
//==============================================================================
|
||||
/** Makes a note that the document's current state matches the one that is saved.
|
||||
|
||||
After this has been called, hasChangedSinceSavePoint() will return false until
|
||||
the document has been altered, and then it'll start returning true. If the document is
|
||||
altered, but then undone until it gets back to this state, hasChangedSinceSavePoint()
|
||||
will again return false.
|
||||
|
||||
@see hasChangedSinceSavePoint
|
||||
*/
|
||||
void setSavePoint() noexcept;
|
||||
|
||||
/** Returns true if the state of the document differs from the state it was in when
|
||||
setSavePoint() was last called.
|
||||
|
||||
@see setSavePoint
|
||||
*/
|
||||
bool hasChangedSinceSavePoint() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Searches for a word-break. */
|
||||
Position findWordBreakAfter (const Position& position) const noexcept;
|
||||
/** Searches for a word-break. */
|
||||
Position findWordBreakBefore (const Position& position) const noexcept;
|
||||
/** Finds the token that contains the given position. */
|
||||
void findTokenContaining (const Position& pos, Position& start, Position& end) const noexcept;
|
||||
/** Finds the line that contains the given position. */
|
||||
void findLineContaining (const Position& pos, Position& start, Position& end) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** An object that receives callbacks from the CodeDocument when its text changes.
|
||||
@see CodeDocument::addListener, CodeDocument::removeListener
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
Listener() {}
|
||||
virtual ~Listener() {}
|
||||
|
||||
/** Called by a CodeDocument when text is added. */
|
||||
virtual void codeDocumentTextInserted (const String& newText, int insertIndex) = 0;
|
||||
|
||||
/** Called by a CodeDocument when text is deleted. */
|
||||
virtual void codeDocumentTextDeleted (int startIndex, int endIndex) = 0;
|
||||
};
|
||||
|
||||
/** Registers a listener object to receive callbacks when the document changes.
|
||||
If the listener is already registered, this method has no effect.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (Listener* listener) noexcept;
|
||||
|
||||
/** Deregisters a listener.
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (Listener* listener) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Iterates the text in a CodeDocument.
|
||||
|
||||
This class lets you read characters from a CodeDocument. It's designed to be used
|
||||
by a CodeTokeniser object.
|
||||
|
||||
@see CodeDocument
|
||||
*/
|
||||
class JUCE_API Iterator
|
||||
{
|
||||
public:
|
||||
Iterator (const CodeDocument& document) noexcept;
|
||||
Iterator (const Iterator&) noexcept;
|
||||
Iterator& operator= (const Iterator&) noexcept;
|
||||
~Iterator() noexcept;
|
||||
|
||||
/** Reads the next character and returns it.
|
||||
@see peekNextChar
|
||||
*/
|
||||
juce_wchar nextChar() noexcept;
|
||||
|
||||
/** Reads the next character without advancing the current position. */
|
||||
juce_wchar peekNextChar() const noexcept;
|
||||
|
||||
/** Advances the position by one character. */
|
||||
void skip() noexcept;
|
||||
|
||||
/** Returns the position as the number of characters from the start of the document. */
|
||||
int getPosition() const noexcept { return position; }
|
||||
|
||||
/** Skips over any whitespace characters until the next character is non-whitespace. */
|
||||
void skipWhitespace() noexcept;
|
||||
|
||||
/** Skips forward until the next character will be the first character on the next line */
|
||||
void skipToEndOfLine() noexcept;
|
||||
|
||||
/** Returns the line number of the next character. */
|
||||
int getLine() const noexcept { return line; }
|
||||
|
||||
/** Returns true if the iterator has reached the end of the document. */
|
||||
bool isEOF() const noexcept;
|
||||
|
||||
private:
|
||||
const CodeDocument* document;
|
||||
mutable String::CharPointerType charPointer;
|
||||
int line, position;
|
||||
};
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
friend class CodeDocumentInsertAction;
|
||||
friend class CodeDocumentDeleteAction;
|
||||
friend class Iterator;
|
||||
friend class Position;
|
||||
|
||||
OwnedArray <CodeDocumentLine> lines;
|
||||
Array <Position*> positionsToMaintain;
|
||||
UndoManager undoManager;
|
||||
int currentActionIndex, indexOfSavedState;
|
||||
int maximumLineLength;
|
||||
ListenerList <Listener> listeners;
|
||||
String newLineChars;
|
||||
|
||||
void insert (const String& text, int insertPos, bool undoable);
|
||||
void remove (int startPos, int endPos, bool undoable);
|
||||
void checkLastLineStatus();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CodeDocument)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_CODEDOCUMENT_H_INCLUDED
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,432 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_CODEEDITORCOMPONENT_H_INCLUDED
|
||||
#define JUCE_CODEEDITORCOMPONENT_H_INCLUDED
|
||||
|
||||
class CodeTokeniser;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A text editor component designed specifically for source code.
|
||||
|
||||
This is designed to handle syntax highlighting and fast editing of very large
|
||||
files.
|
||||
*/
|
||||
class JUCE_API CodeEditorComponent : public Component,
|
||||
public ApplicationCommandTarget,
|
||||
public TextInputTarget
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an editor for a document.
|
||||
|
||||
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.
|
||||
|
||||
@see CodeDocument
|
||||
*/
|
||||
CodeEditorComponent (CodeDocument& document,
|
||||
CodeTokeniser* codeTokeniser);
|
||||
|
||||
/** Destructor. */
|
||||
~CodeEditorComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the code document that this component is editing. */
|
||||
CodeDocument& getDocument() const noexcept { return document; }
|
||||
|
||||
/** Loads the given content into the document.
|
||||
This will completely reset the CodeDocument object, clear its undo history,
|
||||
and fill it with this text.
|
||||
*/
|
||||
void loadContent (const String& newContent);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the standard character width. */
|
||||
float getCharWidth() const noexcept { return charWidth; }
|
||||
|
||||
/** Returns the height of a line of text, in pixels. */
|
||||
int getLineHeight() const noexcept { return lineHeight; }
|
||||
|
||||
/** Returns the number of whole lines visible on the screen,
|
||||
This doesn't include a cut-off line that might be visible at the bottom if the
|
||||
component's height isn't an exact multiple of the line-height.
|
||||
*/
|
||||
int getNumLinesOnScreen() const noexcept { return linesOnScreen; }
|
||||
|
||||
/** Returns the index of the first line that's visible at the top of the editor. */
|
||||
int getFirstLineOnScreen() const noexcept { return firstLineOnScreen; }
|
||||
|
||||
/** Returns the number of whole columns visible on the screen.
|
||||
This doesn't include any cut-off columns at the right-hand edge.
|
||||
*/
|
||||
int getNumColumnsOnScreen() const noexcept { return columnsOnScreen; }
|
||||
|
||||
/** Returns the current caret position. */
|
||||
CodeDocument::Position getCaretPos() const { return caretPos; }
|
||||
|
||||
/** Returns the position of the caret, relative to the editor's origin. */
|
||||
Rectangle<int> getCaretRectangle() override;
|
||||
|
||||
/** Moves the caret.
|
||||
If selecting is true, the section of the document between the current
|
||||
caret position and the new one will become selected. If false, any currently
|
||||
selected region will be deselected.
|
||||
*/
|
||||
void moveCaretTo (const CodeDocument::Position& newPos, bool selecting);
|
||||
|
||||
/** Returns the on-screen position of a character in the document.
|
||||
The rectangle returned is relative to this component's top-left origin.
|
||||
*/
|
||||
Rectangle<int> getCharacterBounds (const CodeDocument::Position& pos) const;
|
||||
|
||||
/** Finds the character at a given on-screen position.
|
||||
The coordinates are relative to this component's top-left origin.
|
||||
*/
|
||||
CodeDocument::Position getPositionAt (int x, int y);
|
||||
|
||||
/** Returns the start of the selection as a position. */
|
||||
CodeDocument::Position getSelectionStart() const { return selectionStart; }
|
||||
|
||||
/** Returns the end of the selection as a position. */
|
||||
CodeDocument::Position getSelectionEnd() const { return selectionEnd; }
|
||||
|
||||
/** Enables or disables the line-number display in the gutter. */
|
||||
void setLineNumbersShown (bool shouldBeShown);
|
||||
|
||||
//==============================================================================
|
||||
bool moveCaretLeft (bool moveInWholeWordSteps, bool selecting);
|
||||
bool moveCaretRight (bool moveInWholeWordSteps, bool selecting);
|
||||
bool moveCaretUp (bool selecting);
|
||||
bool moveCaretDown (bool selecting);
|
||||
bool scrollDown();
|
||||
bool scrollUp();
|
||||
bool pageUp (bool selecting);
|
||||
bool pageDown (bool selecting);
|
||||
bool moveCaretToTop (bool selecting);
|
||||
bool moveCaretToStartOfLine (bool selecting);
|
||||
bool moveCaretToEnd (bool selecting);
|
||||
bool moveCaretToEndOfLine (bool selecting);
|
||||
bool deleteBackwards (bool moveInWholeWordSteps);
|
||||
bool deleteForwards (bool moveInWholeWordSteps);
|
||||
bool deleteWhitespaceBackwardsToTabStop();
|
||||
virtual bool copyToClipboard();
|
||||
virtual bool cutToClipboard();
|
||||
virtual bool pasteFromClipboard();
|
||||
bool undo();
|
||||
bool redo();
|
||||
|
||||
void selectRegion (const CodeDocument::Position& start, const CodeDocument::Position& end);
|
||||
bool selectAll();
|
||||
void deselectAll();
|
||||
|
||||
void scrollToLine (int newFirstLineOnScreen);
|
||||
void scrollBy (int deltaLines);
|
||||
void scrollToColumn (int newFirstColumnOnScreen);
|
||||
void scrollToKeepCaretOnScreen();
|
||||
void scrollToKeepLinesOnScreen (Range<int> linesToShow);
|
||||
|
||||
void insertTextAtCaret (const String& textToInsert) override;
|
||||
void insertTabAtCaret();
|
||||
|
||||
void indentSelection();
|
||||
void unindentSelection();
|
||||
|
||||
//==============================================================================
|
||||
Range<int> getHighlightedRegion() const override;
|
||||
bool isHighlightActive() const noexcept;
|
||||
void setHighlightedRegion (const Range<int>& newRange) override;
|
||||
String getTextInRange (const Range<int>& range) const override;
|
||||
|
||||
//==============================================================================
|
||||
/** Can be used to save and restore the editor's caret position, selection state, etc. */
|
||||
struct State
|
||||
{
|
||||
/** Creates an object containing the state of the given editor. */
|
||||
State (const CodeEditorComponent&);
|
||||
/** Creates a state object from a string that was previously created with toString(). */
|
||||
State (const String& stringifiedVersion);
|
||||
State (const State&) noexcept;
|
||||
|
||||
/** Updates the given editor with this saved state. */
|
||||
void restoreState (CodeEditorComponent&) const;
|
||||
|
||||
/** Returns a stringified version of this state that can be used to recreate it later. */
|
||||
String toString() const;
|
||||
|
||||
private:
|
||||
int lastTopLine, lastCaretPos, lastSelectionEnd;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the current tab settings.
|
||||
This lets you change the tab size and whether pressing the tab key inserts a
|
||||
tab character, or its equivalent number of spaces.
|
||||
*/
|
||||
void setTabSize (int numSpacesPerTab, bool insertSpacesInsteadOfTabCharacters);
|
||||
|
||||
/** Returns the current number of spaces per tab.
|
||||
@see setTabSize
|
||||
*/
|
||||
int getTabSize() const noexcept { return spacesPerTab; }
|
||||
|
||||
/** Returns true if the tab key will insert spaces instead of actual tab characters.
|
||||
@see setTabSize
|
||||
*/
|
||||
bool areSpacesInsertedForTabs() const { return useSpacesForTabs; }
|
||||
|
||||
/** Returns a string containing spaces or tab characters to generate the given number of spaces. */
|
||||
String getTabString (int numSpaces) const;
|
||||
|
||||
/** Changes the font.
|
||||
Make sure you only use a fixed-width font, or this component will look pretty nasty!
|
||||
*/
|
||||
void setFont (const Font& newFont);
|
||||
|
||||
/** Returns the font that the editor is using. */
|
||||
const Font& getFont() const noexcept { return font; }
|
||||
|
||||
/** Makes the editor read-only. */
|
||||
void setReadOnly (bool shouldBeReadOnly) noexcept;
|
||||
|
||||
/** Returns true if the editor is set to be read-only. */
|
||||
bool isReadOnly() const noexcept { return readOnly; }
|
||||
|
||||
//==============================================================================
|
||||
struct JUCE_API ColourScheme
|
||||
{
|
||||
struct TokenType
|
||||
{
|
||||
String name;
|
||||
Colour colour;
|
||||
};
|
||||
|
||||
Array<TokenType> types;
|
||||
|
||||
void set (const String& name, const Colour colour);
|
||||
};
|
||||
|
||||
/** Changes the syntax highlighting scheme.
|
||||
The token type values are dependent on the tokeniser being used - use
|
||||
CodeTokeniser::getTokenTypes() to get a list of the token types.
|
||||
@see getColourForTokenType
|
||||
*/
|
||||
void setColourScheme (const ColourScheme& scheme);
|
||||
|
||||
/** Returns the current syntax highlighting colour scheme. */
|
||||
const ColourScheme& getColourScheme() const noexcept { return colourScheme; }
|
||||
|
||||
/** Returns one the syntax highlighting colour for the given token.
|
||||
The token type values are dependent on the tokeniser being used.
|
||||
@see setColourScheme
|
||||
*/
|
||||
Colour getColourForTokenType (int tokenType) const;
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the editor.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1004500, /**< A colour to use to fill the editor's background. */
|
||||
highlightColourId = 0x1004502, /**< The colour to use for the highlighted background under selected text. */
|
||||
defaultTextColourId = 0x1004503, /**< The colour to use for text when no syntax colouring is enabled. */
|
||||
lineNumberBackgroundId = 0x1004504, /**< The colour to use for filling the background of the line-number gutter. */
|
||||
lineNumberTextId = 0x1004505, /**< The colour to use for drawing the line numbers. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the size of the scrollbars. */
|
||||
void setScrollbarThickness (int thickness);
|
||||
|
||||
/** Returns the thickness of the scrollbars. */
|
||||
int getScrollbarThickness() const noexcept { return scrollbarThickness; }
|
||||
|
||||
//==============================================================================
|
||||
/** 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);
|
||||
|
||||
/** Specifies a commmand-manager which the editor will notify whenever the state
|
||||
of any of its commands changes.
|
||||
If you're making use of the editor's ApplicationCommandTarget interface, then
|
||||
you should also use this to tell it which command manager it should use. Make
|
||||
sure that the manager does not go out of scope while the editor is using it. You
|
||||
can pass a nullptr here to disable this.
|
||||
*/
|
||||
void setCommandManager (ApplicationCommandManager* newManager) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDoubleClick (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
|
||||
/** @internal */
|
||||
void focusGained (FocusChangeType) override;
|
||||
/** @internal */
|
||||
void focusLost (FocusChangeType) override;
|
||||
/** @internal */
|
||||
bool isTextInputActive() const override;
|
||||
/** @internal */
|
||||
void setTemporaryUnderlining (const Array<Range<int> >&) override;
|
||||
/** @internal */
|
||||
ApplicationCommandTarget* getNextCommandTarget() override;
|
||||
/** @internal */
|
||||
void getAllCommands (Array<CommandID>&) override;
|
||||
/** @internal */
|
||||
void getCommandInfo (CommandID, ApplicationCommandInfo&) override;
|
||||
/** @internal */
|
||||
bool perform (const InvocationInfo&) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
CodeDocument& document;
|
||||
|
||||
Font font;
|
||||
int firstLineOnScreen, spacesPerTab;
|
||||
float charWidth;
|
||||
int lineHeight, linesOnScreen, columnsOnScreen;
|
||||
int scrollbarThickness, columnToTryToMaintain;
|
||||
bool readOnly, useSpacesForTabs, showLineNumbers, shouldFollowDocumentChanges;
|
||||
double xOffset;
|
||||
|
||||
CodeDocument::Position caretPos, selectionStart, selectionEnd;
|
||||
|
||||
ScopedPointer<CaretComponent> caret;
|
||||
ScrollBar verticalScrollBar, horizontalScrollBar;
|
||||
ApplicationCommandManager* appCommandManager;
|
||||
|
||||
class Pimpl;
|
||||
friend class Pimpl;
|
||||
friend struct ContainerDeletePolicy<Pimpl>;
|
||||
ScopedPointer<Pimpl> pimpl;
|
||||
|
||||
class GutterComponent;
|
||||
friend class GutterComponent;
|
||||
friend struct ContainerDeletePolicy<GutterComponent>;
|
||||
ScopedPointer<GutterComponent> gutter;
|
||||
|
||||
enum DragType
|
||||
{
|
||||
notDragging,
|
||||
draggingSelectionStart,
|
||||
draggingSelectionEnd
|
||||
};
|
||||
|
||||
DragType dragType;
|
||||
|
||||
//==============================================================================
|
||||
CodeTokeniser* codeTokeniser;
|
||||
ColourScheme colourScheme;
|
||||
|
||||
class CodeEditorLine;
|
||||
OwnedArray<CodeEditorLine> lines;
|
||||
void rebuildLineTokens();
|
||||
void rebuildLineTokensAsync();
|
||||
void codeDocumentChanged (int start, int end);
|
||||
|
||||
OwnedArray<CodeDocument::Iterator> cachedIterators;
|
||||
void clearCachedIterators (int firstLineToBeInvalid);
|
||||
void updateCachedIterators (int maxLineNum);
|
||||
void getIteratorForPosition (int position, CodeDocument::Iterator&);
|
||||
|
||||
void moveLineDelta (int delta, bool selecting);
|
||||
int getGutterSize() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
void insertText (const String&);
|
||||
virtual void updateCaretPosition();
|
||||
void updateScrollBars();
|
||||
void scrollToLineInternal (int line);
|
||||
void scrollToColumnInternal (double column);
|
||||
void newTransaction();
|
||||
void cut();
|
||||
void indentSelectedLines (int spacesToAdd);
|
||||
bool skipBackwardsToPreviousTab();
|
||||
bool performCommand (CommandID);
|
||||
|
||||
int indexToColumn (int line, int index) const noexcept;
|
||||
int columnToIndex (int line, int column) const noexcept;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CodeEditorComponent)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_CODEEDITORCOMPONENT_H_INCLUDED
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_CODETOKENISER_H_INCLUDED
|
||||
#define JUCE_CODETOKENISER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A base class for tokenising code so that the syntax can be displayed in a
|
||||
code editor.
|
||||
|
||||
@see CodeDocument, CodeEditorComponent
|
||||
*/
|
||||
class JUCE_API CodeTokeniser
|
||||
{
|
||||
public:
|
||||
CodeTokeniser() {}
|
||||
virtual ~CodeTokeniser() {}
|
||||
|
||||
//==============================================================================
|
||||
/** Reads the next token from the source and returns its token type.
|
||||
|
||||
This must leave the source pointing to the first character in the
|
||||
next token.
|
||||
*/
|
||||
virtual int readNextToken (CodeDocument::Iterator& source) = 0;
|
||||
|
||||
/** Returns a suggested syntax highlighting colour scheme. */
|
||||
virtual CodeEditorComponent::ColourScheme getDefaultColourScheme() = 0;
|
||||
|
||||
private:
|
||||
JUCE_LEAK_DETECTOR (CodeTokeniser)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_CODETOKENISER_H_INCLUDED
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
struct LuaTokeniserFunctions
|
||||
{
|
||||
static bool isReservedKeyword (String::CharPointerType token, const int tokenLength) noexcept
|
||||
{
|
||||
static const char* const keywords2Char[] =
|
||||
{ "if", "or", "in", "do", nullptr };
|
||||
|
||||
static const char* const keywords3Char[] =
|
||||
{ "and", "end", "for", "nil", "not", nullptr };
|
||||
|
||||
static const char* const keywords4Char[] =
|
||||
{ "then", "true", "else", nullptr };
|
||||
|
||||
static const char* const keywords5Char[] =
|
||||
{ "false", "local", "until", "while", "break", nullptr };
|
||||
|
||||
static const char* const keywords6Char[] =
|
||||
{ "repeat", "return", "elseif", nullptr};
|
||||
|
||||
static const char* const keywordsOther[] =
|
||||
{ "function", "@interface", "@end", "@synthesize", "@dynamic", "@public",
|
||||
"@private", "@property", "@protected", "@class", nullptr };
|
||||
|
||||
const char* const* k;
|
||||
|
||||
switch (tokenLength)
|
||||
{
|
||||
case 2: k = keywords2Char; break;
|
||||
case 3: k = keywords3Char; break;
|
||||
case 4: k = keywords4Char; break;
|
||||
case 5: k = keywords5Char; break;
|
||||
case 6: k = keywords6Char; break;
|
||||
|
||||
default:
|
||||
if (tokenLength < 2 || tokenLength > 16)
|
||||
return false;
|
||||
|
||||
k = keywordsOther;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; k[i] != 0; ++i)
|
||||
if (token.compare (CharPointer_ASCII (k[i])) == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static int parseIdentifier (Iterator& source) noexcept
|
||||
{
|
||||
int tokenLength = 0;
|
||||
String::CharPointerType::CharType possibleIdentifier [100];
|
||||
String::CharPointerType possible (possibleIdentifier);
|
||||
|
||||
while (CppTokeniserFunctions::isIdentifierBody (source.peekNextChar()))
|
||||
{
|
||||
const juce_wchar c = source.nextChar();
|
||||
|
||||
if (tokenLength < 20)
|
||||
possible.write (c);
|
||||
|
||||
++tokenLength;
|
||||
}
|
||||
|
||||
if (tokenLength > 1 && tokenLength <= 16)
|
||||
{
|
||||
possible.writeNull();
|
||||
|
||||
if (isReservedKeyword (String::CharPointerType (possibleIdentifier), tokenLength))
|
||||
return LuaTokeniser::tokenType_keyword;
|
||||
}
|
||||
|
||||
return LuaTokeniser::tokenType_identifier;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static int readNextToken (Iterator& source)
|
||||
{
|
||||
source.skipWhitespace();
|
||||
|
||||
const juce_wchar firstChar = source.peekNextChar();
|
||||
|
||||
switch (firstChar)
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
case '.':
|
||||
{
|
||||
int result = CppTokeniserFunctions::parseNumber (source);
|
||||
|
||||
if (result == LuaTokeniser::tokenType_error)
|
||||
{
|
||||
source.skip();
|
||||
|
||||
if (firstChar == '.')
|
||||
return LuaTokeniser::tokenType_punctuation;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case ',':
|
||||
case ';':
|
||||
case ':':
|
||||
source.skip();
|
||||
return LuaTokeniser::tokenType_punctuation;
|
||||
|
||||
case '(': case ')':
|
||||
case '{': case '}':
|
||||
case '[': case ']':
|
||||
source.skip();
|
||||
return LuaTokeniser::tokenType_bracket;
|
||||
|
||||
case '"':
|
||||
case '\'':
|
||||
CppTokeniserFunctions::skipQuotedString (source);
|
||||
return LuaTokeniser::tokenType_string;
|
||||
|
||||
case '+':
|
||||
source.skip();
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '+', '=');
|
||||
return LuaTokeniser::tokenType_operator;
|
||||
|
||||
case '-':
|
||||
{
|
||||
source.skip();
|
||||
int result = CppTokeniserFunctions::parseNumber (source);
|
||||
|
||||
if (source.peekNextChar() == '-')
|
||||
{
|
||||
source.skipToEndOfLine();
|
||||
return LuaTokeniser::tokenType_comment;
|
||||
}
|
||||
|
||||
if (result == LuaTokeniser::tokenType_error)
|
||||
{
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '-', '=');
|
||||
return LuaTokeniser::tokenType_operator;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case '*': case '%':
|
||||
case '=': case '!':
|
||||
source.skip();
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '=');
|
||||
return LuaTokeniser::tokenType_operator;
|
||||
|
||||
case '?':
|
||||
case '~':
|
||||
source.skip();
|
||||
return LuaTokeniser::tokenType_operator;
|
||||
|
||||
case '<': case '>':
|
||||
case '|': case '&': case '^':
|
||||
source.skip();
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, firstChar);
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '=');
|
||||
return LuaTokeniser::tokenType_operator;
|
||||
|
||||
default:
|
||||
if (CppTokeniserFunctions::isIdentifierStart (firstChar))
|
||||
return parseIdentifier (source);
|
||||
|
||||
source.skip();
|
||||
break;
|
||||
}
|
||||
|
||||
return LuaTokeniser::tokenType_error;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
LuaTokeniser::LuaTokeniser() {}
|
||||
LuaTokeniser::~LuaTokeniser() {}
|
||||
|
||||
int LuaTokeniser::readNextToken (CodeDocument::Iterator& source)
|
||||
{
|
||||
return LuaTokeniserFunctions::readNextToken (source);
|
||||
}
|
||||
|
||||
CodeEditorComponent::ColourScheme LuaTokeniser::getDefaultColourScheme()
|
||||
{
|
||||
static const CodeEditorComponent::ColourScheme::TokenType types[] =
|
||||
{
|
||||
{ "Error", Colour (0xffcc0000) },
|
||||
{ "Comment", Colour (0xff3c3c3c) },
|
||||
{ "Keyword", Colour (0xff0000cc) },
|
||||
{ "Operator", Colour (0xff225500) },
|
||||
{ "Identifier", Colour (0xff000000) },
|
||||
{ "Integer", Colour (0xff880000) },
|
||||
{ "Float", Colour (0xff885500) },
|
||||
{ "String", Colour (0xff990099) },
|
||||
{ "Bracket", Colour (0xff000055) },
|
||||
{ "Punctuation", Colour (0xff004400) }
|
||||
};
|
||||
|
||||
CodeEditorComponent::ColourScheme cs;
|
||||
|
||||
for (unsigned int i = 0; i < sizeof (types) / sizeof (types[0]); ++i) // (NB: numElementsInArray doesn't work here in GCC4.2)
|
||||
cs.set (types[i].name, types[i].colour);
|
||||
|
||||
return cs;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_LUACODETOKENISER_H_INCLUDED
|
||||
#define JUCE_LUACODETOKENISER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
*/
|
||||
class JUCE_API LuaTokeniser : public CodeTokeniser
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
LuaTokeniser();
|
||||
~LuaTokeniser();
|
||||
|
||||
//==============================================================================
|
||||
int readNextToken (CodeDocument::Iterator&) override;
|
||||
CodeEditorComponent::ColourScheme getDefaultColourScheme() override;
|
||||
|
||||
/** The token values returned by this tokeniser. */
|
||||
enum TokenType
|
||||
{
|
||||
tokenType_error = 0,
|
||||
tokenType_comment,
|
||||
tokenType_keyword,
|
||||
tokenType_operator,
|
||||
tokenType_identifier,
|
||||
tokenType_integer,
|
||||
tokenType_float,
|
||||
tokenType_string,
|
||||
tokenType_bracket,
|
||||
tokenType_punctuation
|
||||
};
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LuaTokeniser)
|
||||
};
|
||||
|
||||
#endif // JUCE_LUACODETOKENISER_H_INCLUDED
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
XmlTokeniser::XmlTokeniser() {}
|
||||
XmlTokeniser::~XmlTokeniser() {}
|
||||
|
||||
CodeEditorComponent::ColourScheme XmlTokeniser::getDefaultColourScheme()
|
||||
{
|
||||
struct Type
|
||||
{
|
||||
const char* name;
|
||||
uint32 colour;
|
||||
};
|
||||
|
||||
const Type types[] =
|
||||
{
|
||||
{ "Error", 0xffcc0000 },
|
||||
{ "Comment", 0xff00aa00 },
|
||||
{ "Keyword", 0xff0000cc },
|
||||
{ "Operator", 0xff225500 },
|
||||
{ "Identifier", 0xff000000 },
|
||||
{ "String", 0xff990099 },
|
||||
{ "Bracket", 0xff000055 },
|
||||
{ "Punctuation", 0xff004400 },
|
||||
{ "Preprocessor Text", 0xff660000 }
|
||||
};
|
||||
|
||||
CodeEditorComponent::ColourScheme cs;
|
||||
|
||||
for (unsigned int i = 0; i < sizeof (types) / sizeof (types[0]); ++i) // (NB: numElementsInArray doesn't work here in GCC4.2)
|
||||
cs.set (types[i].name, Colour (types[i].colour));
|
||||
|
||||
return cs;
|
||||
};
|
||||
|
||||
template <typename Iterator>
|
||||
static void skipToEndOfXmlDTD (Iterator& source) noexcept
|
||||
{
|
||||
bool lastWasQuestionMark = false;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const juce_wchar c = source.nextChar();
|
||||
|
||||
if (c == 0 || (c == '>' && lastWasQuestionMark))
|
||||
break;
|
||||
|
||||
lastWasQuestionMark = (c == '?');
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
static void skipToEndOfXmlComment (Iterator& source) noexcept
|
||||
{
|
||||
juce_wchar last[2] = { 0 };
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const juce_wchar c = source.nextChar();
|
||||
|
||||
if (c == 0 || (c == '>' && last[0] == '-' && last[1] == '-'))
|
||||
break;
|
||||
|
||||
last[1] = last[0];
|
||||
last[0] = c;
|
||||
}
|
||||
}
|
||||
|
||||
int XmlTokeniser::readNextToken (CodeDocument::Iterator& source)
|
||||
{
|
||||
source.skipWhitespace();
|
||||
const juce_wchar firstChar = source.peekNextChar();
|
||||
|
||||
switch (firstChar)
|
||||
{
|
||||
case 0: break;
|
||||
|
||||
case '"':
|
||||
case '\'':
|
||||
CppTokeniserFunctions::skipQuotedString (source);
|
||||
return tokenType_string;
|
||||
|
||||
case '<':
|
||||
{
|
||||
source.skip();
|
||||
source.skipWhitespace();
|
||||
const juce_wchar nextChar = source.peekNextChar();
|
||||
|
||||
if (nextChar == '?')
|
||||
{
|
||||
source.skip();
|
||||
skipToEndOfXmlDTD (source);
|
||||
return tokenType_preprocessor;
|
||||
}
|
||||
|
||||
if (nextChar == '!')
|
||||
{
|
||||
source.skip();
|
||||
|
||||
if (source.peekNextChar() == '-')
|
||||
{
|
||||
source.skip();
|
||||
|
||||
if (source.peekNextChar() == '-')
|
||||
{
|
||||
skipToEndOfXmlComment (source);
|
||||
return tokenType_comment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '/');
|
||||
CppTokeniserFunctions::parseIdentifier (source);
|
||||
source.skipWhitespace();
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '/');
|
||||
source.skipWhitespace();
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '>');
|
||||
return tokenType_keyword;
|
||||
}
|
||||
|
||||
case '>':
|
||||
source.skip();
|
||||
return tokenType_keyword;
|
||||
|
||||
case '/':
|
||||
source.skip();
|
||||
source.skipWhitespace();
|
||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '>');
|
||||
return tokenType_keyword;
|
||||
|
||||
case '=':
|
||||
case ':':
|
||||
source.skip();
|
||||
return tokenType_operator;
|
||||
|
||||
default:
|
||||
if (CppTokeniserFunctions::isIdentifierStart (firstChar))
|
||||
CppTokeniserFunctions::parseIdentifier (source);
|
||||
|
||||
source.skip();
|
||||
break;
|
||||
};
|
||||
|
||||
return tokenType_identifier;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_XMLCODETOKENISER_H_INCLUDED
|
||||
#define JUCE_XMLCODETOKENISER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
*/
|
||||
class JUCE_API XmlTokeniser : public CodeTokeniser
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
XmlTokeniser();
|
||||
~XmlTokeniser();
|
||||
|
||||
//==============================================================================
|
||||
int readNextToken (CodeDocument::Iterator&) override;
|
||||
CodeEditorComponent::ColourScheme getDefaultColourScheme() override;
|
||||
|
||||
/** The token values returned by this tokeniser. */
|
||||
enum TokenType
|
||||
{
|
||||
tokenType_error = 0,
|
||||
tokenType_comment,
|
||||
tokenType_keyword,
|
||||
tokenType_operator,
|
||||
tokenType_identifier,
|
||||
tokenType_string,
|
||||
tokenType_bracket,
|
||||
tokenType_punctuation,
|
||||
tokenType_preprocessor
|
||||
};
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlTokeniser)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_XMLCODETOKENISER_H_INCLUDED
|
||||
Reference in New Issue
Block a user