rewrite large bit of syntax highlighter, add 'manual' test instructions

felipesanches-svg
don bright 2013-01-12 14:05:10 -06:00
parent 2834e4265b
commit 3b7bc56bde
3 changed files with 149 additions and 109 deletions

View File

@ -24,118 +24,174 @@
*
*/
// Syntax Highlight code by Chris Olah
// based on Syntax Highlight code by Chris Olah
/* test suite
1. action: open example001, remove first {, hit f5
expected result: red highlight appears on last }, cursor moves there
action: replace first {, hit f5
expected result: red highlight disappears
2. action: type a=b
expected result: '=' is highlighted as appropriate
3. action: open example001, put '===' after first ;
expected result: red highlight appears in ===
action: remove '==='
expected result: red highlight disappears
4. action: open example001, remove last ';' but not trailing whitespace/\n
expected result: red highlight appears on last line
action: replace last ';'
expected result: red highlight disappears
5. action: open file, type in a multi-line comment
expected result: multiline comment should be highlighted appropriately
6. action: open example001, put a single '=' after first {
expected result: red highlight of '=' you added
*/
#include "highlighter.h"
#include "parsersettings.h" // extern int parser_error_pos;
#include <QTextDocument>
Highlighter::Highlighter(QTextDocument *parent, mode_e mode)
#include <iostream>
Highlighter::Highlighter(QTextDocument *parent)
: QSyntaxHighlighter(parent)
{
this->mode = mode;
operators << "!" << "&&" << "||" << "+" << "-" << "*" << "/" << "%" << "!" << "#" << ";";
KeyWords << "for" << "intersection_for" << "if" << "assign"
<< "module" << "function"
<< "$children" << "child" << "$fn" << "$fa" << "$fb" // Lump special variables in here
<< "union" << "intersection" << "difference" << "render"; //Lump CSG in here
Primitives3D << "cube" << "cylinder" << "sphere" << "polyhedron";
Primitives2D << "square" << "polygon" << "circle";
Transforms << "scale" << "translate" << "rotate" << "multmatrix" << "color"
<< "linear_extrude" << "rotate_extrude"; // Lump extrudes in here.
Imports << "include" << "use" << "import_stl";
QMap<QString,QStringList> tokentypes;
QMap<QString,QTextCharFormat> typeformats;
//this->OperatorStyle.setForeground
KeyWordStyle.setForeground(Qt::darkGreen);
TransformStyle.setForeground(Qt::darkGreen);
PrimitiveStyle3D.setForeground(Qt::darkBlue);
PrimitiveStyle2D.setForeground(Qt::blue);
ImportStyle.setForeground(Qt::darkYellow);
QuoteStyle.setForeground(Qt::darkMagenta);
CommentStyle.setForeground(Qt::darkCyan);
ErrorStyle.setForeground(Qt::red);
tokentypes["operator"] << "=" << "!" << "&&" << "||" << "+" << "-" << "*" << "/" << "%" << "!" << "#" << ";";
typeformats["operator"].setForeground(Qt::blue);
tokentypes["keyword"] << "for" << "intersection_for" << "if" << "assign" << "module" << "function";
typeformats["keyword"].setForeground(Qt::darkGreen);
tokentypes["prim3d"] << "cube" << "cylinder" << "sphere" << "polyhedron";
typeformats["prim3d"].setForeground(Qt::darkBlue);
tokentypes["prim2d"] << "square" << "polygon" << "circle";
typeformats["prim2d"].setForeground(Qt::blue);
tokentypes["transform"] << "scale" << "translate" << "rotate" << "multmatrix" << "color" << "projection";
typeformats["transform"].setForeground(Qt::darkGreen);
tokentypes["import"] << "include" << "use" << "import_stl";
typeformats["import"].setForeground(Qt::darkYellow);
tokentypes["special"] << "$children" << "child" << "$fn" << "$fa" << "$fb";
typeformats["special"].setForeground(Qt::darkGreen);
tokentypes["csgop"] << "union" << "intersection" << "difference" << "render";
typeformats["csgop"].setForeground(Qt::darkGreen);
tokentypes["extrude"] << "linear_extrude" << "rotate_extrude";
typeformats["extrude"].setForeground(Qt::darkGreen);
// for speed - put all tokens into single QHash, mapped to their format
QList<QString>::iterator ki;
QList<QString> toktypes = tokentypes.keys();
for ( ki=toktypes.begin(); ki!=toktypes.end(); ++ki ) {
QString toktype = *ki;
QStringList::iterator it;
for ( it = tokentypes[toktype].begin(); it < tokentypes[toktype].end(); ++it) {
QString token = *it;
//std::cout << token.toStdString() << "\n";
formatMap[ token ] = typeformats [ toktype ];
}
}
quoteFormat.setForeground(Qt::darkMagenta);
commentFormat.setForeground(Qt::darkCyan);
errorFormat.setBackground(Qt::red);
// format tweaks
formatMap[ "%" ].setFontWeight(QFont::Bold);
separators << tokentypes["operator"];
separators << "(" << ")" << "[" << "]";
lastErrorBlock = parent->begin();
}
void Highlighter::highlightError(int error_pos)
{
QTextBlock err_block = document()->findBlock(error_pos);
std::cout << "error pos: " << error_pos << " doc len: " << document()->characterCount() << "\n";
errorPos = error_pos;
errorState = true;
//if (errorPos == document()->characterCount()-1) errorPos--;
rehighlightBlock( err_block ); // QT 4.6
errorState = false;
lastErrorBlock = err_block;
}
void Highlighter::unhighlightLastError()
{
rehighlightBlock( lastErrorBlock );
}
#include <iostream>
void Highlighter::highlightBlock(const QString &text)
{
if ( mode == NORMAL_MODE) {
state_e state = (state_e) previousBlockState();
//Key words and Primitives
QStringList::iterator it;
std::cout << "block[" << currentBlock().position() << ":"
<< currentBlock().length() + currentBlock().position() << "]"
<< ", err:" << errorPos << ", text:'" << text.toStdString() << "'\n";
for (it = KeyWords.begin(); it != KeyWords.end(); ++it){
for (int i = 0; i < text.count(*it); ++i){
setFormat(text.indexOf(*it),it->size(),KeyWordStyle);
}
// Split the block into pieces and highlight each as appropriate
QString newtext = text;
QStringList::iterator sep, token;
int tokindex = -1; // deals w duplicate tokens in a single block
for ( sep = separators.begin(); sep!=separators.end(); ++sep ) {
// so a=b will have '=' highlighted
newtext = newtext.replace( *sep, " " + *sep + " ");
}
for (it = Primitives3D.begin(); it != Primitives3D.end(); ++it){
for (int i = 0; i < text.count(*it); ++i){
setFormat(text.indexOf(*it),it->size(),PrimitiveStyle3D);
}
}
for (it = Primitives2D.begin(); it != Primitives2D.end(); ++it){
for (int i = 0; i < text.count(*it); ++i){
setFormat(text.indexOf(*it),it->size(),PrimitiveStyle2D);
}
}
for (it = Transforms.begin(); it != Transforms.end(); ++it){
for (int i = 0; i < text.count(*it); ++i){
setFormat(text.indexOf(*it),it->size(),TransformStyle);
}
}
for (it = Imports.begin(); it != Imports.end(); ++it){
for (int i = 0; i < text.count(*it); ++i){
setFormat(text.indexOf(*it),it->size(),ImportStyle);
QStringList tokens = newtext.split(QRegExp("\\s"));
for ( token = tokens.begin(); token!=tokens.end(); ++token ){
if ( formatMap.contains( *token ) ) {
tokindex = text.indexOf( *token, tokindex+1 );
// Speed note: setFormat() is the big slowdown in all of this code
setFormat( tokindex, token->size(), formatMap[ *token ]);
// std::cout << "found tok '" << (*token).toStdString() << "' at " << tokindex << "\n";
}
}
// Quoting and Comments.
// fixme multiline coments dont work
state_e state = (state_e) previousBlockState();
for (int n = 0; n < text.size(); ++n){
if (state == NORMAL){
if (text[n] == '"'){
state = QUOTE;
setFormat(n,1,QuoteStyle);
setFormat(n,1,quoteFormat);
} else if (text[n] == '/'){
if (text[n+1] == '/'){
setFormat(n,text.size(),CommentStyle);
setFormat(n,text.size(),commentFormat);
break;
} else if (text[n+1] == '*'){
setFormat(n++,2,CommentStyle);
setFormat(n++,2,commentFormat);
state = COMMENT;
}
}
} else if (state == QUOTE){
setFormat(n,1,QuoteStyle);
setFormat(n,1,quoteFormat);
if (text[n] == '"' && text[n-1] != '\\')
state = NORMAL;
} else if (state == COMMENT){
setFormat(n,1,CommentStyle);
setFormat(n,1,commentFormat);
if (text[n] == '*' && text[n+1] == '/'){
setFormat(++n,1,CommentStyle);
setFormat(++n,1,commentFormat);
state = NORMAL;
}
}
}
} // not ErrorMode (syntax highlighting)
// Errors
else if (mode == ERROR_MODE) {
int n = previousBlockState();
if (n < 0)
n = 0;
int k = n + text.size() + 1;
setCurrentBlockState(k);
if (parser_error_pos >= n && parser_error_pos < k) {
QTextCharFormat style;
style.setBackground(Qt::red);
setFormat(0, text.size(), style);
#if 0
style.setBackground(Qt::black);
style.setForeground(Qt::white);
setFormat(parser_error_pos - n, 1, style);
#endif
// Highlight an error. Do it last to 'overwrite' other formatting.
if (errorState) {
setFormat( errorPos - currentBlock().position() - 1, 1, errorFormat);
}
} // if errormode
}

View File

@ -7,26 +7,17 @@ class Highlighter : public QSyntaxHighlighter
{
public:
enum state_e {NORMAL=-1,QUOTE,COMMENT};
enum mode_e {NORMAL_MODE, ERROR_MODE};
mode_e mode;
QStringList operators;
QStringList KeyWords;
QStringList Primitives3D;
QStringList Primitives2D;
QStringList Transforms;
QStringList Imports;
QTextCharFormat ErrorStyle;
QTextCharFormat OperatorStyle;
QTextCharFormat CommentStyle;
QTextCharFormat QuoteStyle;
QTextCharFormat KeyWordStyle;
QTextCharFormat PrimitiveStyle3D;
QTextCharFormat PrimitiveStyle2D;
QTextCharFormat TransformStyle;
QTextCharFormat ImportStyle;
Highlighter(QTextDocument *parent, mode_e mode);
QHash<QString, QTextCharFormat> formatMap;
QTextCharFormat errorFormat, commentFormat, quoteFormat;
Highlighter(QTextDocument *parent);
void highlightBlock(const QString &text);
void highlightError(int error_pos);
void unhighlightLastError();
private:
QTextBlock lastErrorBlock;
int errorPos = -1;
bool errorState = false;
QStringList separators;
};
#endif

View File

@ -180,7 +180,7 @@ MainWindow::MainWindow(const QString &filename)
fps = 0;
fsteps = 1;
highlighter = new Highlighter(editor->document(), Highlighter::NORMAL_MODE);
highlighter = new Highlighter(editor->document());
editor->setTabStopWidth(30);
editor->setLineWrapping(true); // Not designable
@ -1036,22 +1036,15 @@ bool MainWindow::compileTopLevelDocument(bool reload)
QFileInfo(this->fileName).absolutePath().toLocal8Bit(),
false);
// Syntax & Error highlighting
if (!this->root_module) {
if (highlighter->mode==Highlighter::NORMAL_MODE) {
delete this->highlighter;
highlighter = new Highlighter(editor->document(), Highlighter::ERROR_MODE);
}
if (!animate_panel->isVisible()) {
if (!animate_panel->isVisible()) {
if (!this->root_module) {
QTextCursor cursor = editor->textCursor();
cursor.setPosition(parser_error_pos);
editor->setTextCursor(cursor);
cursor.setPosition( parser_error_pos );
editor->setTextCursor( cursor );
highlighter->highlightError( parser_error_pos );
} else {
highlighter->unhighlightLastError();
}
} else if (highlighter->mode==Highlighter::ERROR_MODE) {
delete this->highlighter;
this->highlighter = new Highlighter(editor->document(), Highlighter::NORMAL_MODE);
}
}