summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/editor.cc2
-rw-r--r--src/editor.h16
-rw-r--r--src/func.cc47
-rw-r--r--src/highlighter.cc299
-rw-r--r--src/highlighter.h23
-rw-r--r--src/mainwin.cc55
6 files changed, 338 insertions, 104 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/func.cc b/src/func.cc
index e427bf2..5dcb3e9 100644
--- a/src/func.cc
+++ b/src/func.cc
@@ -35,6 +35,28 @@
#include "stl-utils.h"
#include "printutils.h"
+/*
+ Random numbers
+
+ Newer versions of boost/C++ include a non-deterministic random_device and
+ auto/bind()s for random function objects, but we are supporting older systems.
+*/
+
+#include <boost/random/mersenne_twister.hpp>
+#include <boost/random/uniform_real_distribution.hpp>
+
+#ifdef __WIN32__
+#include <process.h>
+int process_id = _getpid();
+#else
+#include <sys/types.h>
+#include <unistd.h>
+int process_id = getpid();
+#endif
+
+boost::random::mt19937 deterministic_rng;
+boost::random::mt19937 lessdeterministic_rng( std::time(0) + process_id );
+
AbstractFunction::~AbstractFunction()
{
}
@@ -119,24 +141,15 @@ Value builtin_sign(const Context *, const std::vector<std::string>&, const std::
return Value();
}
-double frand()
-{
- return rand()/(double(RAND_MAX)+1);
-}
-
-double frand(double min, double max)
-{
- return (min>max) ? frand()*(min-max)+max : frand()*(max-min)+min;
-}
-
Value builtin_rands(const Context *, const std::vector<std::string>&, const std::vector<Value> &args)
{
+ bool deterministic = false;
if (args.size() == 3 &&
args[0].type() == Value::NUMBER &&
args[1].type() == Value::NUMBER &&
args[2].type() == Value::NUMBER)
{
- srand((unsigned int)time(0));
+ deterministic = false;
}
else if (args.size() == 4 &&
args[0].type() == Value::NUMBER &&
@@ -144,16 +157,24 @@ Value builtin_rands(const Context *, const std::vector<std::string>&, const std:
args[2].type() == Value::NUMBER &&
args[3].type() == Value::NUMBER)
{
- srand((unsigned int)args[3].toDouble());
+ deterministic_rng.seed( (unsigned int) args[3].toDouble() );
+ deterministic = true;
}
else
{
return Value();
}
+ double min = std::min( args[0].toDouble(), args[1].toDouble() );
+ double max = std::max( args[0].toDouble(), args[1].toDouble() );
+ boost::random::uniform_real_distribution<> distributor( min, max );
Value::VectorType vec;
for (int i=0; i<args[2].toDouble(); i++) {
- vec.push_back(Value(frand(args[0].toDouble(), args[1].toDouble())));
+ if ( deterministic ) {
+ vec.push_back( Value( distributor( deterministic_rng ) ) );
+ } else {
+ vec.push_back( Value( distributor( lessdeterministic_rng ) ) );
+ }
}
return Value(vec);
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);
+ }
+
}
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 5c18b17..251c6e1 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>
@@ -115,7 +110,7 @@ static char helptitle[] =
#endif
"\nhttp://www.openscad.org\n\n";
static char copyrighttext[] =
- "Copyright (C) 2009-2012 Marius Kintel <marius@kintel.net> and Clifford Wolf <clifford@clifford.at>\n"
+ "Copyright (C) 2009-2013 Marius Kintel <marius@kintel.net> and Clifford Wolf <clifford@clifford.at>\n"
"\n"
"This program is free software; you can redistribute it and/or modify "
"it under the terms of the GNU General Public License as published by "
@@ -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()));
@@ -462,6 +444,7 @@ void MainWindow::report_func(const class AbstractNode*, void *vp, int mark)
QApplication::processEvents();
}
+ // FIXME: Check if cancel was requested by e.g. Application quit
if (thisp->progresswidget->wasCanceled()) throw ProgressCancelException();
}
@@ -483,12 +466,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 +935,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 +943,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 +1037,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;
@@ -1847,6 +1809,7 @@ void MainWindow::quit()
QCloseEvent ev;
QApplication::sendEvent(QApplication::instance(), &ev);
if (ev.isAccepted()) QApplication::instance()->quit();
+ // FIXME: Cancel any CGAL calculations
}
void MainWindow::consoleOutput(const std::string &msg, void *userdata)
contact: Jan Huwald // Impressum