mirror of https://github.com/vitalif/openscad
rewrite large bit of syntax highlighter, add 'manual' test instructions
parent
2834e4265b
commit
3b7bc56bde
|
@ -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 "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)
|
: QSyntaxHighlighter(parent)
|
||||||
{
|
{
|
||||||
this->mode = mode;
|
QMap<QString,QStringList> tokentypes;
|
||||||
operators << "!" << "&&" << "||" << "+" << "-" << "*" << "/" << "%" << "!" << "#" << ";";
|
QMap<QString,QTextCharFormat> typeformats;
|
||||||
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";
|
|
||||||
|
|
||||||
//this->OperatorStyle.setForeground
|
tokentypes["operator"] << "=" << "!" << "&&" << "||" << "+" << "-" << "*" << "/" << "%" << "!" << "#" << ";";
|
||||||
KeyWordStyle.setForeground(Qt::darkGreen);
|
typeformats["operator"].setForeground(Qt::blue);
|
||||||
TransformStyle.setForeground(Qt::darkGreen);
|
|
||||||
PrimitiveStyle3D.setForeground(Qt::darkBlue);
|
tokentypes["keyword"] << "for" << "intersection_for" << "if" << "assign" << "module" << "function";
|
||||||
PrimitiveStyle2D.setForeground(Qt::blue);
|
typeformats["keyword"].setForeground(Qt::darkGreen);
|
||||||
ImportStyle.setForeground(Qt::darkYellow);
|
|
||||||
QuoteStyle.setForeground(Qt::darkMagenta);
|
tokentypes["prim3d"] << "cube" << "cylinder" << "sphere" << "polyhedron";
|
||||||
CommentStyle.setForeground(Qt::darkCyan);
|
typeformats["prim3d"].setForeground(Qt::darkBlue);
|
||||||
ErrorStyle.setForeground(Qt::red);
|
|
||||||
|
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)
|
void Highlighter::highlightBlock(const QString &text)
|
||||||
{
|
{
|
||||||
if ( mode == NORMAL_MODE) {
|
std::cout << "block[" << currentBlock().position() << ":"
|
||||||
state_e state = (state_e) previousBlockState();
|
<< currentBlock().length() + currentBlock().position() << "]"
|
||||||
//Key words and Primitives
|
<< ", err:" << errorPos << ", text:'" << text.toStdString() << "'\n";
|
||||||
QStringList::iterator it;
|
|
||||||
|
|
||||||
for (it = KeyWords.begin(); it != KeyWords.end(); ++it){
|
// Split the block into pieces and highlight each as appropriate
|
||||||
for (int i = 0; i < text.count(*it); ++i){
|
QString newtext = text;
|
||||||
setFormat(text.indexOf(*it),it->size(),KeyWordStyle);
|
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){
|
QStringList tokens = newtext.split(QRegExp("\\s"));
|
||||||
for (int i = 0; i < text.count(*it); ++i){
|
for ( token = tokens.begin(); token!=tokens.end(); ++token ){
|
||||||
setFormat(text.indexOf(*it),it->size(),PrimitiveStyle3D);
|
if ( formatMap.contains( *token ) ) {
|
||||||
}
|
tokindex = text.indexOf( *token, tokindex+1 );
|
||||||
}
|
// Speed note: setFormat() is the big slowdown in all of this code
|
||||||
for (it = Primitives2D.begin(); it != Primitives2D.end(); ++it){
|
setFormat( tokindex, token->size(), formatMap[ *token ]);
|
||||||
for (int i = 0; i < text.count(*it); ++i){
|
// std::cout << "found tok '" << (*token).toStdString() << "' at " << tokindex << "\n";
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quoting and Comments.
|
// Quoting and Comments.
|
||||||
|
// fixme multiline coments dont work
|
||||||
|
state_e state = (state_e) previousBlockState();
|
||||||
for (int n = 0; n < text.size(); ++n){
|
for (int n = 0; n < text.size(); ++n){
|
||||||
if (state == NORMAL){
|
if (state == NORMAL){
|
||||||
if (text[n] == '"'){
|
if (text[n] == '"'){
|
||||||
state = QUOTE;
|
state = QUOTE;
|
||||||
setFormat(n,1,QuoteStyle);
|
setFormat(n,1,quoteFormat);
|
||||||
} else if (text[n] == '/'){
|
} else if (text[n] == '/'){
|
||||||
if (text[n+1] == '/'){
|
if (text[n+1] == '/'){
|
||||||
setFormat(n,text.size(),CommentStyle);
|
setFormat(n,text.size(),commentFormat);
|
||||||
break;
|
break;
|
||||||
} else if (text[n+1] == '*'){
|
} else if (text[n+1] == '*'){
|
||||||
setFormat(n++,2,CommentStyle);
|
setFormat(n++,2,commentFormat);
|
||||||
state = COMMENT;
|
state = COMMENT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (state == QUOTE){
|
} else if (state == QUOTE){
|
||||||
setFormat(n,1,QuoteStyle);
|
setFormat(n,1,quoteFormat);
|
||||||
if (text[n] == '"' && text[n-1] != '\\')
|
if (text[n] == '"' && text[n-1] != '\\')
|
||||||
state = NORMAL;
|
state = NORMAL;
|
||||||
} else if (state == COMMENT){
|
} else if (state == COMMENT){
|
||||||
setFormat(n,1,CommentStyle);
|
setFormat(n,1,commentFormat);
|
||||||
if (text[n] == '*' && text[n+1] == '/'){
|
if (text[n] == '*' && text[n+1] == '/'){
|
||||||
setFormat(++n,1,CommentStyle);
|
setFormat(++n,1,commentFormat);
|
||||||
state = NORMAL;
|
state = NORMAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // not ErrorMode (syntax highlighting)
|
// Highlight an error. Do it last to 'overwrite' other formatting.
|
||||||
|
if (errorState) {
|
||||||
|
setFormat( errorPos - currentBlock().position() - 1, 1, errorFormat);
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
} // if errormode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,26 +7,17 @@ class Highlighter : public QSyntaxHighlighter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum state_e {NORMAL=-1,QUOTE,COMMENT};
|
enum state_e {NORMAL=-1,QUOTE,COMMENT};
|
||||||
enum mode_e {NORMAL_MODE, ERROR_MODE};
|
QHash<QString, QTextCharFormat> formatMap;
|
||||||
mode_e mode;
|
QTextCharFormat errorFormat, commentFormat, quoteFormat;
|
||||||
|
Highlighter(QTextDocument *parent);
|
||||||
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);
|
|
||||||
void highlightBlock(const QString &text);
|
void highlightBlock(const QString &text);
|
||||||
|
void highlightError(int error_pos);
|
||||||
|
void unhighlightLastError();
|
||||||
|
private:
|
||||||
|
QTextBlock lastErrorBlock;
|
||||||
|
int errorPos = -1;
|
||||||
|
bool errorState = false;
|
||||||
|
QStringList separators;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -180,7 +180,7 @@ MainWindow::MainWindow(const QString &filename)
|
||||||
fps = 0;
|
fps = 0;
|
||||||
fsteps = 1;
|
fsteps = 1;
|
||||||
|
|
||||||
highlighter = new Highlighter(editor->document(), Highlighter::NORMAL_MODE);
|
highlighter = new Highlighter(editor->document());
|
||||||
editor->setTabStopWidth(30);
|
editor->setTabStopWidth(30);
|
||||||
editor->setLineWrapping(true); // Not designable
|
editor->setLineWrapping(true); // Not designable
|
||||||
|
|
||||||
|
@ -1036,22 +1036,15 @@ bool MainWindow::compileTopLevelDocument(bool reload)
|
||||||
QFileInfo(this->fileName).absolutePath().toLocal8Bit(),
|
QFileInfo(this->fileName).absolutePath().toLocal8Bit(),
|
||||||
false);
|
false);
|
||||||
|
|
||||||
// Syntax & Error highlighting
|
if (!animate_panel->isVisible()) {
|
||||||
|
if (!this->root_module) {
|
||||||
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()) {
|
|
||||||
QTextCursor cursor = editor->textCursor();
|
QTextCursor cursor = editor->textCursor();
|
||||||
cursor.setPosition(parser_error_pos);
|
cursor.setPosition( parser_error_pos );
|
||||||
editor->setTextCursor(cursor);
|
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue