diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/editor.cc | 2 | ||||
-rw-r--r-- | src/editor.h | 16 | ||||
-rw-r--r-- | src/highlighter.cc | 301 | ||||
-rw-r--r-- | src/highlighter.h | 23 | ||||
-rw-r--r-- | src/mainwin.cc | 51 |
5 files changed, 303 insertions, 90 deletions
diff --git a/src/editor.cc b/src/editor.cc index 92aeefe..08bf005 100644 --- a/src/editor.cc +++ b/src/editor.cc @@ -1,7 +1,6 @@ #include "editor.h" #include "Preferences.h" -#ifndef _QCODE_EDIT_ void Editor::indentSelection() { QTextCursor cursor = textCursor(); @@ -109,4 +108,3 @@ void Editor::wheelEvent ( QWheelEvent * event ) } } -#endif diff --git a/src/editor.h b/src/editor.h index bb338b0..09484f5 100644 --- a/src/editor.h +++ b/src/editor.h @@ -3,26 +3,11 @@ #include <QWidget> #include <QWheelEvent> -#ifdef _QCODE_EDIT_ -#include <qeditor.h> -class Editor : public QEditor -#else #include <QTextEdit> class Editor : public QTextEdit -#endif { Q_OBJECT public: -#ifdef _QCODE_EDIT_ - Editor(QWidget *parent) : QEditor(parent) {} - QString toPlainText() const { return text(); } - void setPlainText(const QString& text) { setText(text); } -public slots: - //void zoomIn() { zoom(1); } - void zoomIn(int n = 1) { zoom(n); } - //void zoomOut() { zoom(-1); } - void zoomOut(int n = 1) { zoom(-n); } -#else Editor(QWidget *parent) : QTextEdit(parent) { setAcceptRichText(false); } public slots: void zoomIn(); @@ -36,5 +21,4 @@ public slots: void uncommentSelection(); private: void wheelEvent ( QWheelEvent * event ); -#endif }; diff --git a/src/highlighter.cc b/src/highlighter.cc index 64ea980..44ef368 100644 --- a/src/highlighter.cc +++ b/src/highlighter.cc @@ -24,34 +24,297 @@ * */ +/* + Syntax Highlighter for OpenSCAD + based on Syntax Highlight code by Christopher Olah + + Speed Note: + + setFormat() is very slow. normally this doesnt matter because we + only highlight a block or two at once. But when OpenSCAD first starts, + QT automatigically calls 'highlightBlock' on every single textblock in the file + even if it's not visible in the window. On a large file (50,000 lines) this + can take several seconds. + + Also, QT 4.5 and lower do not have rehighlightBlock(), so they will be slow + on large files as well, as they re-highlight everything after each compile. + + The vast majority of OpenSCAD files, however, are not 50,000 lines and + most machines have Qt > 4.5 + + See Also: + + Giles Bathgate's Rapcad lexer-based highlighter: rapcad.org + + Test suite: + +1. action: open example001, remove first {, hit f5 + expected result: error highlight appears on last }, cursor moves there + action: replace first {, hit f5 + expected result: error highlight disappears + +1a. action: open example001, remove first {, hit f5 + expected result: error highlight appears on last }, cursor moves there + action: replace first { with the letter 'P', hit f5 + expected result: error highlight on last } disappears, appears on elsewhere + action: replace first {, hit f5 + expected result: error highlight disappears + +2. action: type a=b into any file + expected result: '=' is highlighted with its appropriate format + +2a. action: type a=b=c=d=e=f= into any file + expected result: each '=' is highlighted with its appropriate format + +3. action: open example001, put '===' after first ; hit f5 + expected result: error highlight appears in === + action: remove '===' + expected result: error highlight disappears + +3a. action: open example001, put '=' after first ; hit f5 + expected result: error highlight appears + action: remove '=' + expected result: error highlight disappears + +3b. action: open example001, put '=' after first { + expected result: error highlight appears + action: remove '=' + expected result: error highlight disappears + +3c. action: open example001, replace first { with '=' + expected result: error highlight appears + action: remove '=', replace with { + expected result: error highlight disappears + +4. action: open example001, remove last ';' but not trailing whitespace/\n + expected result: error highlight appears somewhere near end + action: replace last ';' + expected result: error 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: error highlight of '=' you added + +7. action: open example001, remove first ')' + expected result: highlight should appear appropriately + +8. action: create a large file (50,000 lines). eg at a bash prompt: + for i in {1..1000}; do cat examples/example001.scad >> test5k.scad ; done + action: open file in openscad + expected result: there should not be a slowdown due to highlighting + action: scroll to bottom, put '=' after last ; + expected result: there should be a highlight, and a report of syntax error + action: comment out the highlighter code from mainwin.cc, recompile, put '=' after last ; + expected result: there should be almost no difference in speed + +9. action: open any file, and hold down 'f5' key to repeatedly reparse + expected result: no crashing! + +10. action: for i in examples/ex* ; do ./openscad $i ; done + expected result: make sure the colors look harmonious + +11. action: type random string of [][][][]()()[][90,3904,00,000] + expected result: all should be highlighted correctly + +*/ + #include "highlighter.h" -#include "parsersettings.h" // extern int parser_error_pos; +#include <QTextDocument> +#include <QTextCursor> +#include <QColor> +//#include <iostream> -#ifdef _QCODE_EDIT_ -Highlighter::Highlighter(QDocument *parent) -#else Highlighter::Highlighter(QTextDocument *parent) -#endif : QSyntaxHighlighter(parent) { + tokentypes["operator"] << "=" << "!" << "&&" << "||" << "+" << "-" << "*" << "/" << "%" << "!" << "#" << ";"; + typeformats["operator"].setForeground(Qt::blue); + + tokentypes["keyword"] << "module" << "function" << "for" << "intersection_for" << "if" << "assign"; + typeformats["keyword"].setForeground(QColor("Green")); + typeformats["keyword"].setToolTip("Keyword"); + + tokentypes["transform"] << "scale" << "translate" << "rotate" << "multmatrix" << "color" << "projection" << "hull"; + typeformats["transform"].setForeground(QColor("Indigo")); + + tokentypes["csgop"] << "union" << "intersection" << "difference" << "render"; + typeformats["csgop"].setForeground(QColor("DarkGreen")); + + tokentypes["prim3d"] << "cube" << "cylinder" << "sphere" << "polyhedron"; + typeformats["prim3d"].setForeground(QColor("DarkBlue")); + + tokentypes["prim2d"] << "square" << "polygon" << "circle"; + typeformats["prim2d"].setForeground(QColor("MidnightBlue")); + + tokentypes["import"] << "include" << "use" << "import_stl" << "import" << "import_dxf" << "dxf_dim" << "dxf_cross"; + typeformats["import"].setForeground(Qt::darkYellow); + + tokentypes["special"] << "$children" << "child" << "$fn" << "$fa" << "$fb"; + typeformats["special"].setForeground(Qt::darkGreen); + + tokentypes["extrude"] << "linear_extrude" << "rotate_extrude"; + typeformats["extrude"].setForeground(Qt::darkGreen); + + tokentypes["bracket"] << "[" << "]" << "(" << ")"; + typeformats["bracket"].setForeground(QColor("Green")); + + tokentypes["curlies"] << "{" << "}"; + typeformats["curlies"].setForeground(QColor(32,32,20)); + + tokentypes["bool"] << "true" << "false"; + typeformats["bool"].setForeground(QColor("DarkRed")); + + // Put each tokens into single QHash, mapped to it's 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"; + tokenFormats[ token ] = typeformats [ toktype ]; + } + } + + quoteFormat.setForeground(Qt::darkMagenta); + commentFormat.setForeground(Qt::darkCyan); + errorFormat.setBackground(Qt::red); + numberFormat.setForeground(QColor("DarkRed")); + + errorState = false; + errorPos = -1; + lastErrorBlock = parent->begin(); } -void Highlighter::highlightBlock(const QString &text) +void Highlighter::highlightError(int error_pos) { - 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); + errorState = true; + errorPos = error_pos; + + QTextBlock err_block = document()->findBlock( errorPos ); + //std::cout << "error pos: " << error_pos << " doc len: " << document()->characterCount() << "\n"; + + while (err_block.text().remove(QRegExp("\\s+")).size()==0) { + //std::cout << "special case - errors at end of file w whitespace\n"; + err_block = err_block.previous(); + errorPos = err_block.position()+err_block.length() - 2; + } + if ( errorPos == lastDocumentPos()-1 ) { + errorPos--; + } + + int block_last_pos = err_block.position() + err_block.length() - 1; + if ( errorPos == block_last_pos ) { + //std::cout << "special case - errors at ends of certain blocks\n"; + errorPos--; + } + err_block = document()->findBlock(errorPos); + + portable_rehighlightBlock( err_block ); + + errorState = false; + lastErrorBlock = err_block; +} + +void Highlighter::unhighlightLastError() +{ + portable_rehighlightBlock( lastErrorBlock ); +} + +void Highlighter::portable_rehighlightBlock( const QTextBlock &block ) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)) + rehighlightBlock( block ); +#else + rehighlight(); // slow on very large files #endif +} + +int Highlighter::lastDocumentPos() +{ +#if (QT_VERSION >= QT_VERSION_CHECK(4, 5, 0)) + return document()->characterCount(); +#else + QTextBlock lastblock = document()->lastBlock(); + return lastblock.position() + lastblock.length(); +#endif +} + +void Highlighter::highlightBlock(const QString &text) +{ + int block_first_pos = currentBlock().position(); + //int block_last_pos = block_first_pos + currentBlock().length() - 1; + //std::cout << "block[" << block_first_pos << ":" << block_last_pos << "]" + // << ", err:" << errorPos << "," << errorState + // << ", text:'" << text.toStdString() << "'\n"; + + // Split the block into pieces and highlight each as appropriate + QString newtext = text; + QStringList splitHelpers; + QStringList::iterator sh, token; + // splitHelpers - so "[a+b]" is treated as "[ a + b ]" and formatted + splitHelpers << tokentypes["operator"] << "(" << ")" << "[" << "]" << "," << ":"; + for ( sh = splitHelpers.begin(); sh!=splitHelpers.end(); ++sh ) { + newtext = newtext.replace( *sh, " " + *sh + " "); } + //std::cout << "\nnewtext: " << newtext.toStdString() << "\n"; + QStringList tokens = newtext.split(QRegExp("\\s")); + int tokindex = 0; // tokindex helps w duplicate tokens in a single block + bool numtest; + for ( token = tokens.begin(); token!=tokens.end(); ++token ){ + if ( tokenFormats.contains( *token ) ) { + tokindex = text.indexOf( *token, tokindex ); + setFormat( tokindex, token->size(), tokenFormats[ *token ]); + //std::cout << "found tok '" << (*token).toStdString() << "' at " << tokindex << "\n"; + tokindex += token->size(); + } else { + (*token).toDouble( &numtest ); + if ( numtest ) { + tokindex = text.indexOf( *token, tokindex ); + setFormat( tokindex, token->size(), numberFormat ); + //std::cout << "found num '" << (*token).toStdString() << "' at " << tokindex << "\n"; + tokindex += token->size(); + } + } + } + + // Quoting and Comments. + 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,quoteFormat); + } else if (text[n] == '/'){ + if (text[n+1] == '/'){ + setFormat(n,text.size(),commentFormat); + break; + } else if (text[n+1] == '*'){ + setFormat(n++,2,commentFormat); + state = COMMENT; + } + } + } else if (state == QUOTE){ + setFormat(n,1,quoteFormat); + if (text[n] == '"' && text[n-1] != '\\') + state = NORMAL; + } else if (state == COMMENT){ + setFormat(n,1,commentFormat); + if (text[n] == '*' && text[n+1] == '/'){ + setFormat(++n,1,commentFormat); + state = NORMAL; + } + } + } + setCurrentBlockState((int) state); + + // Highlight an error. Do it last to 'overwrite' other formatting. + if (errorState) { + setFormat( errorPos - block_first_pos, 1, errorFormat); + } + } diff --git a/src/highlighter.h b/src/highlighter.h index 1bd54d2..b4ffae8 100644 --- a/src/highlighter.h +++ b/src/highlighter.h @@ -2,20 +2,27 @@ #define HIGHLIGHTER_H_ #include <QSyntaxHighlighter> - -#ifdef _QCODE_EDIT_ -#include "qdocument.h" -#endif +#include <QTextFormat> +#include <QHash> class Highlighter : public QSyntaxHighlighter { public: -#ifdef _QCODE_EDIT_ - Highlighter(QDocument *parent); -#else + enum state_e {NORMAL=-1,QUOTE,COMMENT}; + QHash<QString, QTextCharFormat> tokenFormats; + QTextCharFormat errorFormat, commentFormat, quoteFormat, numberFormat; Highlighter(QTextDocument *parent); -#endif void highlightBlock(const QString &text); + void highlightError(int error_pos); + void unhighlightLastError(); +private: + QTextBlock lastErrorBlock; + int errorPos; + bool errorState; + QMap<QString,QStringList> tokentypes; + QMap<QString,QTextCharFormat> typeformats; + int lastDocumentPos(); + void portable_rehighlightBlock( const QTextBlock &text ); }; #endif diff --git a/src/mainwin.cc b/src/mainwin.cc index dde6761..6bb43e6 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -70,11 +70,6 @@ #include <QSettings> #include <QProgressDialog> #include <QMutexLocker> -#ifdef _QCODE_EDIT_ -#include "qdocument.h" -#include "qformatscheme.h" -#include "qlanguagefactory.h" -#endif #include <fstream> @@ -185,16 +180,8 @@ MainWindow::MainWindow(const QString &filename) fps = 0; fsteps = 1; - highlighter = NULL; -#ifdef _QCODE_EDIT_ - QFormatScheme *formats = new QFormatScheme("qxs/openscad.qxf"); - QDocument::setDefaultFormatScheme(formats); - QLanguageFactory *languages = new QLanguageFactory(formats,this); - languages->addDefinitionPath("qxs"); - languages->setLanguage(editor, "openscad"); -#else + highlighter = new Highlighter(editor->document()); editor->setTabStopWidth(30); -#endif editor->setLineWrapping(true); // Not designable this->glview->statusLabel = new QLabel(this); @@ -348,13 +335,8 @@ MainWindow::MainWindow(const QString &filename) updateRecentFileActions(); connect(editor->document(), SIGNAL(contentsChanged()), this, SLOT(animateUpdateDocChanged())); -#ifdef _QCODE_EDIT_ - connect(editor, SIGNAL(contentModified(bool)), this, SLOT(setWindowModified(bool))); - connect(editor, SIGNAL(contentModified(bool)), fileActionSave, SLOT(setEnabled(bool))); -#else connect(editor->document(), SIGNAL(modificationChanged(bool)), this, SLOT(setWindowModified(bool))); connect(editor->document(), SIGNAL(modificationChanged(bool)), fileActionSave, SLOT(setEnabled(bool))); -#endif connect(this->glview, SIGNAL(doAnimateUpdate()), this, SLOT(animateUpdate())); connect(Preferences::inst(), SIGNAL(requestRedraw()), this->glview, SLOT(updateGL())); @@ -483,12 +465,7 @@ void MainWindow::openFile(const QString &new_filename) { #ifdef ENABLE_MDI -#ifdef _QCODE_EDIT_ - if (this->editor->document()->lines() > 1 || - !this->editor->document()->text(true, false).trimmed().isEmpty()) { -#else if (!editor->toPlainText().isEmpty()) { -#endif new MainWindow(new_filename); clearCurrentOutput(); return; @@ -957,11 +934,7 @@ void MainWindow::hideEditor() void MainWindow::pasteViewportTranslation() { -#ifdef _QCODE_EDIT_ - QDocumentCursor cursor = editor->cursor(); -#else QTextCursor cursor = editor->textCursor(); -#endif QString txt; txt.sprintf("[ %.2f, %.2f, %.2f ]", -this->glview->object_trans_x, -this->glview->object_trans_y, -this->glview->object_trans_z); cursor.insertText(txt); @@ -969,11 +942,7 @@ void MainWindow::pasteViewportTranslation() void MainWindow::pasteViewportRotation() { -#ifdef _QCODE_EDIT_ - QDocumentCursor cursor = editor->cursor(); -#else QTextCursor cursor = editor->textCursor(); -#endif QString txt; txt.sprintf("[ %.2f, %.2f, %.2f ]", fmodf(360 - this->glview->object_rot_x + 90, 360), fmodf(360 - this->glview->object_rot_y, 360), fmodf(360 - this->glview->object_rot_z, 360)); @@ -1067,24 +1036,16 @@ bool MainWindow::compileTopLevelDocument(bool reload) QFileInfo(this->fileName).absolutePath().toLocal8Bit(), false); - // Error highlighting - delete this->highlighter; - this->highlighter = NULL; - - if (!this->root_module) { - this->highlighter = new Highlighter(editor->document()); - - if (!animate_panel->isVisible()) { -#ifdef _QCODE_EDIT_ - QDocumentCursor cursor = editor->cursor(); - cursor.setPosition(parser_error_pos); -#else + if (!animate_panel->isVisible()) { + highlighter->unhighlightLastError(); + if (!this->root_module) { QTextCursor cursor = editor->textCursor(); cursor.setPosition(parser_error_pos); editor->setTextCursor(cursor); -#endif + highlighter->highlightError( parser_error_pos ); } } + } bool changed = shouldcompiletoplevel; |