summaryrefslogtreecommitdiff
path: root/src/highlighter.cc
diff options
context:
space:
mode:
authordon bright <hugh.m.bright@gmail.com>2013-01-28 02:42:20 (GMT)
committerdon bright <hugh.m.bright@gmail.com>2013-01-28 02:42:20 (GMT)
commit1e64dddf1ea30282c89de7f35854a68614234652 (patch)
tree165d37c1c66f6ff79d48c74794238b3f0bed09da /src/highlighter.cc
parent5c779159c208ca3d88c88479ab29f9cd66574859 (diff)
parentd0856efe6da545693f9c50a8a2514a9f999ab5ef (diff)
Merge branch 'master' of github.com:openscad/openscad into issue159
Diffstat (limited to 'src/highlighter.cc')
-rw-r--r--src/highlighter.cc299
1 files changed, 280 insertions, 19 deletions
diff --git a/src/highlighter.cc b/src/highlighter.cc
index 64ea980..77d1bb8 100644
--- a/src/highlighter.cc
+++ b/src/highlighter.cc
@@ -24,34 +24,295 @@
*
*/
+/*
+ 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, remove first ')'
+ expected result: highlight should appear appropriately
+
+7. action: create a large file (50,000 lines). eg at a bash prompt:
+ for i in {1..2000}; do cat examples/example001.scad >> test5k.scad ; done
+ action: open file in openscad
+ expected result: it should load in a reasonable amount of time
+ 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,
+ run openscad again on the large file. put '=' after last ;
+ expected result: there should be only a small difference in speed.
+
+8. action: open any file, and hold down 'f5' key to repeatedly reparse
+ expected result: no crashing!
+
+9. action: for i in examples/ex* ; do ./openscad $i ; done
+ expected result: make sure the colors look harmonious
+
+10. 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::highlightError(int error_pos)
+{
+ 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::highlightBlock(const QString &text)
+void Highlighter::unhighlightLastError()
{
- 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);
+ 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);
+ }
+
}
contact: Jan Huwald // Impressum