Galaxis Codebase  1.0.0
Team Galaxis software stack for the Carolo Cup 2023 - ROS2 based software
jsedit.cpp
Go to the documentation of this file.
1 /*
2  This file is part of the Ofi Labs X2 project.
3 
4  Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
5  Copyright (C) 2010 Ariya Hidayat <ariya.hidayat@gmail.com>
6 
7  Redistribution and use in source and binary forms, with or without
8  modification, are permitted provided that the following conditions are met:
9 
10  * Redistributions of source code must retain the above copyright
11  notice, this list of conditions and the following disclaimer.
12  * Redistributions in binary form must reproduce the above copyright
13  notice, this list of conditions and the following disclaimer in the
14  documentation and/or other materials provided with the distribution.
15  * Neither the name of the <organization> nor the
16  names of its contributors may be used to endorse or promote products
17  derived from this software without specific prior written permission.
18 
19  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
23  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30 
31 #include "jsedit.h"
32 
33 JSHighlighter::JSHighlighter(QTextDocument *parent)
34  : QSyntaxHighlighter(parent)
35  , m_markCaseSensitivity(Qt::CaseInsensitive)
36 {
37  // default color scheme
38  m_colors[JSEdit::Normal] = QColor("#000000");
39  m_colors[JSEdit::Comment] = QColor("#808080");
40  m_colors[JSEdit::Number] = QColor("#008000");
41  m_colors[JSEdit::String] = QColor("#800000");
42  m_colors[JSEdit::Operator] = QColor("#808000");
43  m_colors[JSEdit::Identifier] = QColor("#000020");
44  m_colors[JSEdit::Keyword] = QColor("#000080");
45  m_colors[JSEdit::BuiltIn] = QColor("#008080");
46  m_colors[JSEdit::Marker] = QColor("#ffff00");
47 
48  // https://developer.mozilla.org/en/JavaScript/Reference/Reserved_Words
49  m_keywords << "break";
50  m_keywords << "case";
51  m_keywords << "catch";
52  m_keywords << "continue";
53  m_keywords << "default";
54  m_keywords << "delete";
55  m_keywords << "do";
56  m_keywords << "else";
57  m_keywords << "finally";
58  m_keywords << "for";
59  m_keywords << "function";
60  m_keywords << "if";
61  m_keywords << "in";
62  m_keywords << "instanceof";
63  m_keywords << "new";
64  m_keywords << "return";
65  m_keywords << "switch";
66  m_keywords << "this";
67  m_keywords << "throw";
68  m_keywords << "try";
69  m_keywords << "typeof";
70  m_keywords << "var";
71  m_keywords << "void";
72  m_keywords << "while";
73  m_keywords << "with";
74 
75  m_keywords << "true";
76  m_keywords << "false";
77  m_keywords << "null";
78 
79  // built-in and other popular objects + properties
80  m_knownIds << "Object";
81  m_knownIds << "prototype";
82  m_knownIds << "create";
83  m_knownIds << "defineProperty";
84  m_knownIds << "defineProperties";
85  m_knownIds << "getOwnPropertyDescriptor";
86  m_knownIds << "keys";
87  m_knownIds << "getOwnPropertyNames";
88  m_knownIds << "constructor";
89  m_knownIds << "__parent__";
90  m_knownIds << "__proto__";
91  m_knownIds << "__defineGetter__";
92  m_knownIds << "__defineSetter__";
93  m_knownIds << "eval";
94  m_knownIds << "hasOwnProperty";
95  m_knownIds << "isPrototypeOf";
96  m_knownIds << "__lookupGetter__";
97  m_knownIds << "__lookupSetter__";
98  m_knownIds << "__noSuchMethod__";
99  m_knownIds << "propertyIsEnumerable";
100  m_knownIds << "toSource";
101  m_knownIds << "toLocaleString";
102  m_knownIds << "toString";
103  m_knownIds << "unwatch";
104  m_knownIds << "valueOf";
105  m_knownIds << "watch";
106 
107  m_knownIds << "Function";
108  m_knownIds << "arguments";
109  m_knownIds << "arity";
110  m_knownIds << "caller";
111  m_knownIds << "constructor";
112  m_knownIds << "length";
113  m_knownIds << "name";
114  m_knownIds << "apply";
115  m_knownIds << "bind";
116  m_knownIds << "call";
117 
118  m_knownIds << "String";
119  m_knownIds << "fromCharCode";
120  m_knownIds << "length";
121  m_knownIds << "charAt";
122  m_knownIds << "charCodeAt";
123  m_knownIds << "concat";
124  m_knownIds << "indexOf";
125  m_knownIds << "lastIndexOf";
126  m_knownIds << "localCompare";
127  m_knownIds << "match";
128  m_knownIds << "quote";
129  m_knownIds << "replace";
130  m_knownIds << "search";
131  m_knownIds << "slice";
132  m_knownIds << "split";
133  m_knownIds << "substr";
134  m_knownIds << "substring";
135  m_knownIds << "toLocaleLowerCase";
136  m_knownIds << "toLocaleUpperCase";
137  m_knownIds << "toLowerCase";
138  m_knownIds << "toUpperCase";
139  m_knownIds << "trim";
140  m_knownIds << "trimLeft";
141  m_knownIds << "trimRight";
142 
143  m_knownIds << "Array";
144  m_knownIds << "isArray";
145  m_knownIds << "index";
146  m_knownIds << "input";
147  m_knownIds << "pop";
148  m_knownIds << "push";
149  m_knownIds << "reverse";
150  m_knownIds << "shift";
151  m_knownIds << "sort";
152  m_knownIds << "splice";
153  m_knownIds << "unshift";
154  m_knownIds << "concat";
155  m_knownIds << "join";
156  m_knownIds << "filter";
157  m_knownIds << "forEach";
158  m_knownIds << "every";
159  m_knownIds << "map";
160  m_knownIds << "some";
161  m_knownIds << "reduce";
162  m_knownIds << "reduceRight";
163 
164  m_knownIds << "RegExp";
165  m_knownIds << "global";
166  m_knownIds << "ignoreCase";
167  m_knownIds << "lastIndex";
168  m_knownIds << "multiline";
169  m_knownIds << "source";
170  m_knownIds << "exec";
171  m_knownIds << "test";
172 
173  m_knownIds << "JSON";
174  m_knownIds << "parse";
175  m_knownIds << "stringify";
176 
177  m_knownIds << "decodeURI";
178  m_knownIds << "decodeURIComponent";
179  m_knownIds << "encodeURI";
180  m_knownIds << "encodeURIComponent";
181  m_knownIds << "eval";
182  m_knownIds << "isFinite";
183  m_knownIds << "isNaN";
184  m_knownIds << "parseFloat";
185  m_knownIds << "parseInt";
186  m_knownIds << "Infinity";
187  m_knownIds << "NaN";
188  m_knownIds << "undefined";
189 
190  m_knownIds << "Math";
191  m_knownIds << "E";
192  m_knownIds << "LN2";
193  m_knownIds << "LN10";
194  m_knownIds << "LOG2E";
195  m_knownIds << "LOG10E";
196  m_knownIds << "PI";
197  m_knownIds << "SQRT1_2";
198  m_knownIds << "SQRT2";
199  m_knownIds << "abs";
200  m_knownIds << "acos";
201  m_knownIds << "asin";
202  m_knownIds << "atan";
203  m_knownIds << "atan2";
204  m_knownIds << "ceil";
205  m_knownIds << "cos";
206  m_knownIds << "exp";
207  m_knownIds << "floor";
208  m_knownIds << "log";
209  m_knownIds << "max";
210  m_knownIds << "min";
211  m_knownIds << "pow";
212  m_knownIds << "random";
213  m_knownIds << "round";
214  m_knownIds << "sin";
215  m_knownIds << "sqrt";
216  m_knownIds << "tan";
217 
218  m_knownIds << "document";
219  m_knownIds << "window";
220  m_knownIds << "navigator";
221  m_knownIds << "userAgent";
222 }
223 
224 void JSHighlighter::setColor(JSEdit::ColorComponent component, const QColor &color)
225 {
226  m_colors[component] = color;
227  rehighlight();
228 }
229 
230 void JSHighlighter::highlightBlock(const QString &text)
231 {
232  // parsing state
233  enum {
234  Start = 0,
235  Number = 1,
236  Identifier = 2,
237  String = 3,
238  Comment = 4,
239  Regex = 5
240  };
241 
242  QList<int> bracketPositions;
243 
244  int blockState = previousBlockState();
245  int bracketLevel = blockState >> 4;
246  int state = blockState & 15;
247  if (blockState < 0) {
248  bracketLevel = 0;
249  state = Start;
250  }
251 
252  int start = 0;
253  int i = 0;
254  while (i <= text.length()) {
255  QChar ch = (i < text.length()) ? text.at(i) : QChar();
256  QChar next = (i < text.length() - 1) ? text.at(i + 1) : QChar();
257 
258  switch (state) {
259 
260  case Start:
261  start = i;
262  if (ch.isSpace()) {
263  ++i;
264  } else if (ch.isDigit()) {
265  ++i;
266  state = Number;
267  } else if (ch.isLetter() || ch == '_') {
268  ++i;
269  state = Identifier;
270  } else if (ch == '\'' || ch == '\"') {
271  ++i;
272  state = String;
273  } else if (ch == '/' && next == '*') {
274  ++i;
275  ++i;
276  state = Comment;
277  } else if (ch == '/' && next == '/') {
278  i = text.length();
279  setFormat(start, text.length(), m_colors[JSEdit::Comment]);
280  } else if (ch == '/' && next != '*') {
281  ++i;
282  state = Regex;
283  } else {
284  if (!QString("(){}[]").contains(ch))
285  setFormat(start, 1, m_colors[JSEdit::Operator]);
286  if (ch =='{' || ch == '}') {
287  bracketPositions += i;
288  if (ch == '{')
289  bracketLevel++;
290  else
291  bracketLevel--;
292  }
293  ++i;
294  state = Start;
295  }
296  break;
297 
298  case Number:
299  if (ch.isSpace() || !ch.isDigit()) {
300  setFormat(start, i - start, m_colors[JSEdit::Number]);
301  state = Start;
302  } else {
303  ++i;
304  }
305  break;
306 
307  case Identifier:
308  if (ch.isSpace() || !(ch.isDigit() || ch.isLetter() || ch == '_')) {
309  QString token = text.mid(start, i - start).trimmed();
310  if (m_keywords.contains(token))
311  setFormat(start, i - start, m_colors[JSEdit::Keyword]);
312  else if (m_knownIds.contains(token))
313  setFormat(start, i - start, m_colors[JSEdit::BuiltIn]);
314  state = Start;
315  } else {
316  ++i;
317  }
318  break;
319 
320  case String:
321  if (ch == text.at(start)) {
322  QChar prev = (i > 0) ? text.at(i - 1) : QChar();
323  if (prev != '\\') {
324  ++i;
325  setFormat(start, i - start, m_colors[JSEdit::String]);
326  state = Start;
327  } else {
328  ++i;
329  }
330  } else {
331  ++i;
332  }
333  break;
334 
335  case Comment:
336  if (ch == '*' && next == '/') {
337  ++i;
338  ++i;
339  setFormat(start, i - start, m_colors[JSEdit::Comment]);
340  state = Start;
341  } else {
342  ++i;
343  }
344  break;
345 
346  case Regex:
347  if (ch == '/') {
348  QChar prev = (i > 0) ? text.at(i - 1) : QChar();
349  if (prev != '\\') {
350  ++i;
351  setFormat(start, i - start, m_colors[JSEdit::String]);
352  state = Start;
353  } else {
354  ++i;
355  }
356  } else {
357  ++i;
358  }
359  break;
360 
361  default:
362  state = Start;
363  break;
364  }
365  }
366 
367  if (state == Comment)
368  setFormat(start, text.length(), m_colors[JSEdit::Comment]);
369  else
370  state = Start;
371 
372  if (!m_markString.isEmpty()) {
373  int pos = 0;
374  int len = m_markString.length();
375  QTextCharFormat markerFormat;
376  markerFormat.setBackground(m_colors[JSEdit::Marker]);
377  markerFormat.setForeground(m_colors[JSEdit::Normal]);
378  for (;;) {
379  pos = text.indexOf(m_markString, pos, m_markCaseSensitivity);
380  if (pos < 0)
381  break;
382  setFormat(pos, len, markerFormat);
383  ++pos;
384  }
385  }
386 
387  if (!bracketPositions.isEmpty()) {
388  JSBlockData *blockData = reinterpret_cast<JSBlockData*>(currentBlock().userData());
389  if (!blockData) {
390  blockData = new JSBlockData;
391  currentBlock().setUserData(blockData);
392  }
393  blockData->bracketPositions = bracketPositions;
394  }
395 
396  blockState = (state & 15) | (bracketLevel << 4);
397  setCurrentBlockState(blockState);
398 }
399 
400 void JSHighlighter::mark(const QString &str, Qt::CaseSensitivity caseSensitivity)
401 {
402  m_markString = str;
403  m_markCaseSensitivity = caseSensitivity;
404  rehighlight();
405 }
406 
407 QStringList JSHighlighter::keywords() const
408 {
409  return m_keywords.toList();
410 }
411 
412 void JSHighlighter::setKeywords(const QStringList &keywords)
413 {
414  m_keywords = QSet<QString>::fromList(keywords);
415  rehighlight();
416 }
417 
418 struct BlockInfo {
419  int position;
420  int number;
421  bool foldable: 1;
422  bool folded : 1;
423 };
424 
425 Q_DECLARE_TYPEINFO(BlockInfo, Q_PRIMITIVE_TYPE);
426 
427 class SidebarWidget : public QWidget
428 {
429 public:
430  SidebarWidget(JSEdit *editor);
431  QVector<BlockInfo> lineNumbers;
436  QFont font;
438  QPixmap rightArrowIcon;
439  QPixmap downArrowIcon;
440 protected:
441  void mousePressEvent(QMouseEvent *event);
442  void paintEvent(QPaintEvent *event);
443 };
444 
446  : QWidget(editor)
447  , foldIndicatorWidth(0)
448 {
449  backgroundColor = Qt::lightGray;
450  lineNumberColor = Qt::black;
451  indicatorColor = Qt::white;
452  foldIndicatorColor = Qt::lightGray;
453 }
454 
455 void SidebarWidget::mousePressEvent(QMouseEvent *event)
456 {
457  if (foldIndicatorWidth > 0) {
458  int xofs = width() - foldIndicatorWidth;
459  int lineNo = -1;
460  int fh = fontMetrics().lineSpacing();
461  int ys = event->pos().y();
462  if (event->pos().x() > xofs) {
463  foreach (BlockInfo ln, lineNumbers)
464  if (ln.position < ys && (ln.position + fh) > ys) {
465  if (ln.foldable)
466  lineNo = ln.number;
467  break;
468  }
469  }
470  if (lineNo >= 0) {
471  JSEdit *editor = qobject_cast<JSEdit*>(parent());
472  if (editor)
473  editor->toggleFold(lineNo);
474  }
475  }
476 }
477 
478 void SidebarWidget::paintEvent(QPaintEvent *event)
479 {
480  QPainter p(this);
481  p.fillRect(event->rect(), backgroundColor);
482  p.setPen(lineNumberColor);
483  p.setFont(font);
484  int fh = QFontMetrics(font).height();
485  foreach (BlockInfo ln, lineNumbers)
486  p.drawText(0, ln.position, width() - 4 - foldIndicatorWidth, fh, Qt::AlignRight, QString::number(ln.number));
487 
488  if (foldIndicatorWidth > 0) {
489  int xofs = width() - foldIndicatorWidth;
490  p.fillRect(xofs, 0, foldIndicatorWidth, height(), indicatorColor);
491 
492  // initialize (or recreate) the arrow icons whenever necessary
493  if (foldIndicatorWidth != rightArrowIcon.width()) {
494  QPainter iconPainter;
495  QPolygonF polygon;
496 
497  int dim = foldIndicatorWidth;
498  rightArrowIcon = QPixmap(dim, dim);
499  rightArrowIcon.fill(Qt::transparent);
501 
502  polygon << QPointF(dim * 0.4, dim * 0.25);
503  polygon << QPointF(dim * 0.4, dim * 0.75);
504  polygon << QPointF(dim * 0.8, dim * 0.5);
505  iconPainter.begin(&rightArrowIcon);
506  iconPainter.setRenderHint(QPainter::Antialiasing);
507  iconPainter.setPen(Qt::NoPen);
508  iconPainter.setBrush(foldIndicatorColor);
509  iconPainter.drawPolygon(polygon);
510  iconPainter.end();
511 
512  polygon.clear();
513  polygon << QPointF(dim * 0.25, dim * 0.4);
514  polygon << QPointF(dim * 0.75, dim * 0.4);
515  polygon << QPointF(dim * 0.5, dim * 0.8);
516  iconPainter.begin(&downArrowIcon);
517  iconPainter.setRenderHint(QPainter::Antialiasing);
518  iconPainter.setPen(Qt::NoPen);
519  iconPainter.setBrush(foldIndicatorColor);
520  iconPainter.drawPolygon(polygon);
521  iconPainter.end();
522  }
523 
524  foreach (BlockInfo ln, lineNumbers)
525  if (ln.foldable) {
526  if (ln.folded)
527  p.drawPixmap(xofs, ln.position, rightArrowIcon);
528  else
529  p.drawPixmap(xofs, ln.position, downArrowIcon);
530  }
531  }
532 }
533 
534 static int findClosingMatch(const QTextDocument *doc, int cursorPosition)
535 {
536  QTextBlock block = doc->findBlock(cursorPosition);
537  JSBlockData *blockData = reinterpret_cast<JSBlockData*>(block.userData());
538  if (!blockData->bracketPositions.isEmpty()) {
539  int depth = 1;
540  while (block.isValid()) {
541  blockData = reinterpret_cast<JSBlockData*>(block.userData());
542  if (blockData && !blockData->bracketPositions.isEmpty()) {
543  for (int c = 0; c < blockData->bracketPositions.count(); ++c) {
544  int absPos = block.position() + blockData->bracketPositions.at(c);
545  if (absPos <= cursorPosition)
546  continue;
547  if (doc->characterAt(absPos) == '{')
548  depth++;
549  else
550  depth--;
551  if (depth == 0)
552  return absPos;
553  }
554  }
555  block = block.next();
556  }
557  }
558  return -1;
559 }
560 
561 static int findOpeningMatch(const QTextDocument *doc, int cursorPosition)
562 {
563  QTextBlock block = doc->findBlock(cursorPosition);
564  JSBlockData *blockData = reinterpret_cast<JSBlockData*>(block.userData());
565  if (!blockData->bracketPositions.isEmpty()) {
566  int depth = 1;
567  while (block.isValid()) {
568  blockData = reinterpret_cast<JSBlockData*>(block.userData());
569  if (blockData && !blockData->bracketPositions.isEmpty()) {
570  for (int c = blockData->bracketPositions.count() - 1; c >= 0; --c) {
571  int absPos = block.position() + blockData->bracketPositions.at(c);
572  if (absPos >= cursorPosition - 1)
573  continue;
574  if (doc->characterAt(absPos) == '}')
575  depth++;
576  else
577  depth--;
578  if (depth == 0)
579  return absPos;
580  }
581  }
582  block = block.previous();
583  }
584  }
585  return -1;
586 }
587 
588 class JSDocLayout: public QPlainTextDocumentLayout
589 {
590 public:
591  JSDocLayout(QTextDocument *doc);
592  void forceUpdate();
593 };
594 
595 JSDocLayout::JSDocLayout(QTextDocument *doc)
596  : QPlainTextDocumentLayout(doc)
597 {
598 }
599 
601 {
602  emit documentSizeChanged(documentSize());
603 }
604 
606 {
607 public:
613  bool textWrap;
614  QColor cursorColor;
616  QList<int> matchPositions;
618  QList<int> errorPositions;
620  bool codeFolding : 1;
621 };
622 
623 JSEdit::JSEdit(QWidget *parent)
624  : QPlainTextEdit(parent)
625  , d_ptr(new JSEditPrivate)
626 {
627  d_ptr->editor = this;
628  d_ptr->layout = new JSDocLayout(document());
629  d_ptr->highlighter = new JSHighlighter(document());
630  d_ptr->sidebar = new SidebarWidget(this);
631  d_ptr->showLineNumbers = true;
632  d_ptr->textWrap = true;
633  d_ptr->bracketsMatching = true;
634  d_ptr->cursorColor = QColor(255, 255, 192);
635  d_ptr->bracketMatchColor = QColor(180, 238, 180);
636  d_ptr->bracketErrorColor = QColor(224, 128, 128);
637  d_ptr->codeFolding = true;
638 
639  document()->setDocumentLayout(d_ptr->layout);
640 
641  connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateCursor()));
642  connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateSidebar()));
643  connect(this, SIGNAL(updateRequest(QRect, int)), this, SLOT(updateSidebar(QRect, int)));
644 
645 #if defined(Q_OS_MAC)
646  QFont textFont = font();
647  textFont.setPointSize(12);
648  textFont.setFamily("Monaco");
649  setFont(textFont);
650 #elif defined(Q_OS_UNIX)
651  QFont textFont = font();
652  textFont.setFamily("Monospace");
653  setFont(textFont);
654 #endif
655 }
656 
658 {
659  delete d_ptr->layout;
660 }
661 
662 void JSEdit::setColor(ColorComponent component, const QColor &color)
663 {
664  Q_D(JSEdit);
665 
666  if (component == Background) {
667  QPalette pal = palette();
668  pal.setColor(QPalette::Base, color);
669  setPalette(pal);
670  d->sidebar->indicatorColor = color;
671  updateSidebar();
672  } else if (component == Normal) {
673  QPalette pal = palette();
674  pal.setColor(QPalette::Text, color);
675  setPalette(pal);
676  } else if (component == Sidebar) {
677  d->sidebar->backgroundColor = color;
678  updateSidebar();
679  } else if (component == LineNumber) {
680  d->sidebar->lineNumberColor = color;
681  updateSidebar();
682  } else if (component == Cursor) {
683  d->cursorColor = color;
684  updateCursor();
685  } else if (component == BracketMatch) {
686  d->bracketMatchColor = color;
687  updateCursor();
688  } else if (component == BracketError) {
689  d->bracketErrorColor = color;
690  updateCursor();
691  } else if (component == FoldIndicator) {
692  d->sidebar->foldIndicatorColor = color;
693  updateSidebar();
694  } else {
695  d->highlighter->setColor(component, color);
696  updateCursor();
697  }
698 }
699 
700 QStringList JSEdit::keywords() const
701 {
702  return d_ptr->highlighter->keywords();
703 }
704 
705 void JSEdit::setKeywords(const QStringList &keywords)
706 {
707  d_ptr->highlighter->setKeywords(keywords);
708 }
709 
711 {
712  return d_ptr->showLineNumbers;
713 }
714 
716 {
717  d_ptr->showLineNumbers = visible;
718  updateSidebar();
719 }
720 
722 {
723  return d_ptr->textWrap;
724 }
725 
726 void JSEdit::setTextWrapEnabled(bool enable)
727 {
728  d_ptr->textWrap = enable;
729  setLineWrapMode(enable ? WidgetWidth : NoWrap);
730 }
731 
733 {
734  return d_ptr->bracketsMatching;
735 }
736 
738 {
739  d_ptr->bracketsMatching = enable;
740  updateCursor();
741 }
742 
744 {
745  return d_ptr->codeFolding;
746 }
747 
749 {
750  d_ptr->codeFolding = enable;
751  updateSidebar();
752 }
753 
754 static int findClosingConstruct(const QTextBlock &block)
755 {
756  if (!block.isValid())
757  return -1;
758  JSBlockData *blockData = reinterpret_cast<JSBlockData*>(block.userData());
759  if (!blockData)
760  return -1;
761  if (blockData->bracketPositions.isEmpty())
762  return -1;
763  const QTextDocument *doc = block.document();
764  int offset = block.position();
765  foreach (int pos, blockData->bracketPositions) {
766  int absPos = offset + pos;
767  if (doc->characterAt(absPos) == '{') {
768  int matchPos = findClosingMatch(doc, absPos);
769  if (matchPos >= 0)
770  return matchPos;
771  }
772  }
773  return -1;
774 }
775 
776 bool JSEdit::isFoldable(int line) const
777 {
778  int matchPos = findClosingConstruct(document()->findBlockByNumber(line - 1));
779  if (matchPos >= 0) {
780  QTextBlock matchBlock = document()->findBlock(matchPos);
781  if (matchBlock.isValid() && matchBlock.blockNumber() > line)
782  return true;
783  }
784  return false;
785 }
786 
787 bool JSEdit::isFolded(int line) const
788 {
789  QTextBlock block = document()->findBlockByNumber(line - 1);
790  if (!block.isValid())
791  return false;
792  block = block.next();
793  if (!block.isValid())
794  return false;
795  return !block.isVisible();
796 }
797 
798 void JSEdit::fold(int line)
799 {
800  QTextBlock startBlock = document()->findBlockByNumber(line - 1);
801  int endPos = findClosingConstruct(startBlock);
802  if (endPos < 0)
803  return;
804  QTextBlock endBlock = document()->findBlock(endPos);
805 
806  QTextBlock block = startBlock.next();
807  while (block.isValid() && block != endBlock) {
808  block.setVisible(false);
809  block.setLineCount(0);
810  block = block.next();
811  }
812 
813  document()->markContentsDirty(startBlock.position(), endPos - startBlock.position() + 1);
814  updateSidebar();
815  update();
816 
817  JSDocLayout *layout = reinterpret_cast<JSDocLayout*>(document()->documentLayout());
818  layout->forceUpdate();
819 }
820 
821 void JSEdit::unfold(int line)
822 {
823  QTextBlock startBlock = document()->findBlockByNumber(line - 1);
824  int endPos = findClosingConstruct(startBlock);
825 
826  QTextBlock block = startBlock.next();
827  while (block.isValid() && !block.isVisible()) {
828  block.setVisible(true);
829  block.setLineCount(block.layout()->lineCount());
830  endPos = block.position() + block.length();
831  block = block.next();
832  }
833 
834  document()->markContentsDirty(startBlock.position(), endPos - startBlock.position() + 1);
835  updateSidebar();
836  update();
837 
838  JSDocLayout *layout = reinterpret_cast<JSDocLayout*>(document()->documentLayout());
839  layout->forceUpdate();
840 }
841 
842 void JSEdit::toggleFold(int line)
843 {
844  if (isFolded(line))
845  unfold(line);
846  else
847  fold(line);
848 }
849 
850 void JSEdit::resizeEvent(QResizeEvent *e)
851 {
852  QPlainTextEdit::resizeEvent(e);
853  updateSidebar();
854 }
855 
856 void JSEdit::wheelEvent(QWheelEvent *e)
857 {
858  if (e->modifiers() == Qt::ControlModifier) {
859  int steps = e->delta() / 20;
860  steps = qBound(-3, steps, 3);
861  QFont textFont = font();
862  int pointSize = textFont.pointSize() + steps;
863  pointSize = qBound(10, pointSize, 40);
864  textFont.setPointSize(pointSize);
865  setFont(textFont);
866  updateSidebar();
867  e->accept();
868  return;
869  }
870  QPlainTextEdit::wheelEvent(e);
871 }
872 
873 
875 {
876  Q_D(JSEdit);
877 
878  if (isReadOnly()) {
879  setExtraSelections(QList<QTextEdit::ExtraSelection>());
880  } else {
881 
882  d->matchPositions.clear();
883  d->errorPositions.clear();
884 
885  if (d->bracketsMatching && textCursor().block().userData()) {
886  QTextCursor cursor = textCursor();
887  int cursorPosition = cursor.position();
888 
889  if (document()->characterAt(cursorPosition) == '{') {
890  int matchPos = findClosingMatch(document(), cursorPosition);
891  if (matchPos < 0) {
892  d->errorPositions += cursorPosition;
893  } else {
894  d->matchPositions += cursorPosition;
895  d->matchPositions += matchPos;
896  }
897  }
898 
899  if (document()->characterAt(cursorPosition - 1) == '}') {
900  int matchPos = findOpeningMatch(document(), cursorPosition);
901  if (matchPos < 0) {
902  d->errorPositions += cursorPosition - 1;
903  } else {
904  d->matchPositions += cursorPosition - 1;
905  d->matchPositions += matchPos;
906  }
907  }
908  }
909 
910  QTextEdit::ExtraSelection highlight;
911  highlight.format.setBackground(d->cursorColor);
912  highlight.format.setProperty(QTextFormat::FullWidthSelection, true);
913  highlight.cursor = textCursor();
914  highlight.cursor.clearSelection();
915 
916  QList<QTextEdit::ExtraSelection> extraSelections;
917  extraSelections.append(highlight);
918 
919  for (int i = 0; i < d->matchPositions.count(); ++i) {
920  int pos = d->matchPositions.at(i);
921  QTextEdit::ExtraSelection matchHighlight;
922  matchHighlight.format.setBackground(d->bracketMatchColor);
923  matchHighlight.cursor = textCursor();
924  matchHighlight.cursor.setPosition(pos);
925  matchHighlight.cursor.setPosition(pos + 1, QTextCursor::KeepAnchor);
926  extraSelections.append(matchHighlight);
927  }
928 
929  for (int i = 0; i < d->errorPositions.count(); ++i) {
930  int pos = d->errorPositions.at(i);
931  QTextEdit::ExtraSelection errorHighlight;
932  errorHighlight.format.setBackground(d->bracketErrorColor);
933  errorHighlight.cursor = textCursor();
934  errorHighlight.cursor.setPosition(pos);
935  errorHighlight.cursor.setPosition(pos + 1, QTextCursor::KeepAnchor);
936  extraSelections.append(errorHighlight);
937  }
938 
939  setExtraSelections(extraSelections);
940  }
941 }
942 
943 void JSEdit::updateSidebar(const QRect &rect, int d)
944 {
945  Q_UNUSED(rect)
946  if (d != 0)
947  updateSidebar();
948 }
949 
951 {
952  Q_D(JSEdit);
953 
954  if (!d->showLineNumbers && !d->codeFolding) {
955  d->sidebar->hide();
956  setViewportMargins(0, 0, 0, 0);
957  d->sidebar->setGeometry(3, 0, 0, height());
958  return;
959  }
960 
961  d->sidebar->foldIndicatorWidth = 0;
962  d->sidebar->font = this->font();
963  d->sidebar->show();
964 
965  int sw = 0;
966  if (d->showLineNumbers) {
967  int digits = 2;
968  int maxLines = blockCount();
969  for (int number = 10; number < maxLines; number *= 10)
970  ++digits;
971  sw += fontMetrics().width('w') * digits;
972  }
973  if (d->codeFolding) {
974  int fh = fontMetrics().lineSpacing();
975  int fw = fontMetrics().width('w');
976  d->sidebar->foldIndicatorWidth = qMax(fw, fh);
977  sw += d->sidebar->foldIndicatorWidth;
978  }
979  setViewportMargins(sw, 0, 0, 0);
980 
981  d->sidebar->setGeometry(0, 0, sw, height());
982  QRectF sidebarRect(0, 0, sw, height());
983 
984  QTextBlock block = firstVisibleBlock();
985  int index = 0;
986  while (block.isValid()) {
987  if (block.isVisible()) {
988  QRectF rect = blockBoundingGeometry(block).translated(contentOffset());
989  if (sidebarRect.intersects(rect)) {
990  if (d->sidebar->lineNumbers.count() >= index)
991  d->sidebar->lineNumbers.resize(index + 1);
992  d->sidebar->lineNumbers[index].position = rect.top();
993  d->sidebar->lineNumbers[index].number = block.blockNumber() + 1;
994  d->sidebar->lineNumbers[index].foldable = d->codeFolding ? isFoldable(block.blockNumber() + 1) : false;
995  d->sidebar->lineNumbers[index].folded = d->codeFolding ? isFolded(block.blockNumber() + 1) : false;
996  ++index;
997  }
998  if (rect.top() > sidebarRect.bottom())
999  break;
1000  }
1001  block = block.next();
1002  }
1003  d->sidebar->lineNumbers.resize(index);
1004  d->sidebar->update();
1005 }
1006 
1007 void JSEdit::mark(const QString &str, Qt::CaseSensitivity sens)
1008 {
1009  d_ptr->highlighter->mark(str, sens);
1010 }
JSHighlighter::m_markString
QString m_markString
Definition: jsedit.h:138
JSEditPrivate
Definition: jsedit.cpp:605
JSHighlighter::mark
void mark(const QString &str, Qt::CaseSensitivity caseSensitivity)
Definition: jsedit.cpp:400
JSEditPrivate::codeFolding
bool codeFolding
Definition: jsedit.cpp:620
JSHighlighter::setColor
void setColor(JSEdit::ColorComponent component, const QColor &color)
Definition: jsedit.cpp:224
JSEditPrivate::sidebar
SidebarWidget * sidebar
Definition: jsedit.cpp:611
JSEdit::String
@ String
Definition: jsedit.h:65
JSEdit::setCodeFoldingEnabled
void setCodeFoldingEnabled(bool enable)
Definition: jsedit.cpp:748
JSEditPrivate::editor
JSEdit * editor
Definition: jsedit.cpp:608
JSEdit::Background
@ Background
Definition: jsedit.h:61
JSBlockData
Definition: jsedit.h:42
JSEdit::BracketError
@ BracketError
Definition: jsedit.h:75
JSEdit::updateSidebar
void updateSidebar()
Definition: jsedit.cpp:950
JSEdit::setKeywords
void setKeywords(const QStringList &keywords)
Definition: jsedit.cpp:705
JSEdit::BuiltIn
@ BuiltIn
Definition: jsedit.h:69
JSHighlighter::m_markCaseSensitivity
Qt::CaseSensitivity m_markCaseSensitivity
Definition: jsedit.h:139
JSHighlighter
Definition: jsedit.h:121
JSEdit::keywords
QStringList keywords() const
Definition: jsedit.cpp:700
SidebarWidget::foldIndicatorWidth
int foldIndicatorWidth
Definition: jsedit.cpp:437
JSHighlighter::highlightBlock
void highlightBlock(const QString &text)
Definition: jsedit.cpp:230
JSDocLayout::JSDocLayout
JSDocLayout(QTextDocument *doc)
Definition: jsedit.cpp:595
JSEdit::FoldIndicator
@ FoldIndicator
Definition: jsedit.h:76
JSHighlighter::keywords
QStringList keywords() const
Definition: jsedit.cpp:407
JSEditPrivate::errorPositions
QList< int > errorPositions
Definition: jsedit.cpp:618
Q_DECLARE_TYPEINFO
Q_DECLARE_TYPEINFO(BlockInfo, Q_PRIMITIVE_TYPE)
JSEdit::isTextWrapEnabled
bool isTextWrapEnabled() const
Definition: jsedit.cpp:721
SidebarWidget::SidebarWidget
SidebarWidget(JSEdit *editor)
Definition: jsedit.cpp:445
JSEdit::Operator
@ Operator
Definition: jsedit.h:66
BlockInfo::number
int number
Definition: jsedit.cpp:420
JSEdit::Cursor
@ Cursor
Definition: jsedit.h:72
JSDocLayout::forceUpdate
void forceUpdate()
Definition: jsedit.cpp:600
JSEdit::Number
@ Number
Definition: jsedit.h:64
JSEditPrivate::bracketsMatching
bool bracketsMatching
Definition: jsedit.cpp:615
SidebarWidget::lineNumberColor
QColor lineNumberColor
Definition: jsedit.cpp:433
JSEditPrivate::showLineNumbers
bool showLineNumbers
Definition: jsedit.cpp:612
JSEditPrivate::bracketErrorColor
QColor bracketErrorColor
Definition: jsedit.cpp:619
JSEdit::JSEdit
JSEdit(QWidget *parent=0)
Definition: jsedit.cpp:623
JSEdit::Marker
@ Marker
Definition: jsedit.h:73
JSHighlighter::JSHighlighter
JSHighlighter(QTextDocument *parent=0)
Definition: jsedit.cpp:33
JSEditPrivate::bracketMatchColor
QColor bracketMatchColor
Definition: jsedit.cpp:617
JSHighlighter::m_knownIds
QSet< QString > m_knownIds
Definition: jsedit.h:136
JSHighlighter::m_keywords
QSet< QString > m_keywords
Definition: jsedit.h:135
JSEdit::BracketMatch
@ BracketMatch
Definition: jsedit.h:74
JSEdit::isFolded
bool isFolded(int line) const
Definition: jsedit.cpp:787
JSEdit::~JSEdit
~JSEdit()
Definition: jsedit.cpp:657
JSEditPrivate::cursorColor
QColor cursorColor
Definition: jsedit.cpp:614
JSEdit::Identifier
@ Identifier
Definition: jsedit.h:67
BlockInfo::position
int position
Definition: jsedit.cpp:419
BlockInfo
Definition: jsedit.cpp:418
JSDocLayout
Definition: jsedit.cpp:588
JSEdit::setLineNumbersVisible
void setLineNumbersVisible(bool visible)
Definition: jsedit.cpp:715
JSEdit::unfold
void unfold(int line)
Definition: jsedit.cpp:821
BlockInfo::foldable
bool foldable
Definition: jsedit.cpp:421
SidebarWidget::font
QFont font
Definition: jsedit.cpp:436
JSEdit::mark
void mark(const QString &str, Qt::CaseSensitivity sens=Qt::CaseInsensitive)
Definition: jsedit.cpp:1007
JSEdit
Definition: jsedit.h:50
JSEdit::fold
void fold(int line)
Definition: jsedit.cpp:798
SidebarWidget::foldIndicatorColor
QColor foldIndicatorColor
Definition: jsedit.cpp:435
SidebarWidget::indicatorColor
QColor indicatorColor
Definition: jsedit.cpp:434
JSEdit::updateCursor
void updateCursor()
Definition: jsedit.cpp:874
JSEditPrivate::layout
JSDocLayout * layout
Definition: jsedit.cpp:609
SidebarWidget::paintEvent
void paintEvent(QPaintEvent *event)
Definition: jsedit.cpp:478
JSEdit::LineNumber
@ LineNumber
Definition: jsedit.h:71
SidebarWidget::mousePressEvent
void mousePressEvent(QMouseEvent *event)
Definition: jsedit.cpp:455
findClosingConstruct
static int findClosingConstruct(const QTextBlock &block)
Definition: jsedit.cpp:754
JSEdit::isFoldable
bool isFoldable(int line) const
Definition: jsedit.cpp:776
JSEdit::Sidebar
@ Sidebar
Definition: jsedit.h:70
JSEdit::isCodeFoldingEnabled
bool isCodeFoldingEnabled() const
Definition: jsedit.cpp:743
SidebarWidget::downArrowIcon
QPixmap downArrowIcon
Definition: jsedit.cpp:439
findOpeningMatch
static int findOpeningMatch(const QTextDocument *doc, int cursorPosition)
Definition: jsedit.cpp:561
JSEdit::isLineNumbersVisible
bool isLineNumbersVisible() const
Definition: jsedit.cpp:710
SidebarWidget::rightArrowIcon
QPixmap rightArrowIcon
Definition: jsedit.cpp:438
BlockInfo::folded
bool folded
Definition: jsedit.cpp:422
JSEdit::setColor
void setColor(ColorComponent component, const QColor &color)
Definition: jsedit.cpp:662
JSEdit::d_ptr
QScopedPointer< JSEditPrivate > d_ptr
Definition: jsedit.h:116
JSEdit::isBracketsMatchingEnabled
bool isBracketsMatchingEnabled() const
Definition: jsedit.cpp:732
JSHighlighter::setKeywords
void setKeywords(const QStringList &keywords)
Definition: jsedit.cpp:412
JSHighlighter::m_colors
QHash< JSEdit::ColorComponent, QColor > m_colors
Definition: jsedit.h:137
SidebarWidget
Definition: jsedit.cpp:427
SidebarWidget::lineNumbers
QVector< BlockInfo > lineNumbers
Definition: jsedit.cpp:431
JSEdit::resizeEvent
void resizeEvent(QResizeEvent *e)
Definition: jsedit.cpp:850
JSEdit::toggleFold
void toggleFold(int line)
Definition: jsedit.cpp:842
JSEdit::Keyword
@ Keyword
Definition: jsedit.h:68
JSEditPrivate::matchPositions
QList< int > matchPositions
Definition: jsedit.cpp:616
SidebarWidget::backgroundColor
QColor backgroundColor
Definition: jsedit.cpp:432
JSEdit::setTextWrapEnabled
void setTextWrapEnabled(bool enable)
Definition: jsedit.cpp:726
JSEdit::ColorComponent
ColorComponent
Definition: jsedit.h:60
JSEdit::Normal
@ Normal
Definition: jsedit.h:62
JSEdit::setBracketsMatchingEnabled
void setBracketsMatchingEnabled(bool enable)
Definition: jsedit.cpp:737
JSBlockData::bracketPositions
QList< int > bracketPositions
Definition: jsedit.h:45
jsedit.h
JSEdit::Comment
@ Comment
Definition: jsedit.h:63
JSEditPrivate::textWrap
bool textWrap
Definition: jsedit.cpp:613
JSEdit::wheelEvent
void wheelEvent(QWheelEvent *e)
Definition: jsedit.cpp:856
findClosingMatch
static int findClosingMatch(const QTextDocument *doc, int cursorPosition)
Definition: jsedit.cpp:534
JSEditPrivate::highlighter
JSHighlighter * highlighter
Definition: jsedit.cpp:610