├── JavaScript_Editor.cpp ├── JavaScript_Editor.h └── README.md /JavaScript_Editor.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 Ken Crossen, bugs corrected, code cleaned up 4 | ** 5 | ** "Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are 7 | ** met: 8 | ** * Redistributions of source code must retain the above copyright 9 | ** notice, this list of conditions and the following disclaimer. 10 | ** * Redistributions in binary form must reproduce the above copyright 11 | ** notice, this list of conditions and the following disclaimer in 12 | ** the documentation and/or other materials provided with the 13 | ** distribution. 14 | ** * Redistributions in source code or binary form may not be sold. 15 | ** 16 | ****************************************************************************/ 17 | 18 | /* 19 | This file is part of the Ofi Labs X2 project. 20 | 21 | Copyright (C) 2011 Ariya Hidayat 22 | Copyright (C) 2010 Ariya Hidayat 23 | 24 | Redistribution and use in source and binary forms, with or without 25 | modification, are permitted provided that the following conditions are met: 26 | 27 | * Redistributions of source code must retain the above copyright 28 | notice, this list of conditions and the following disclaimer. 29 | * Redistributions in binary form must reproduce the above copyright 30 | notice, this list of conditions and the following disclaimer in the 31 | documentation and/or other materials provided with the distribution. 32 | * Neither the name of the nor the 33 | names of its contributors may be used to endorse or promote products 34 | derived from this software without specific prior written permission. 35 | 36 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 37 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 38 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 39 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 40 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 41 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 42 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 43 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 45 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 46 | */ 47 | 48 | // For explaination, see "D-Pointer" on Qt Wiki. Cliff's Notes version follows: 49 | // To compile JSEdit as a distributed binary library that can be upgraded w/o ... 50 | // ... breaking code designed for old binary when using a newer upgraded binary, ... 51 | // ... i.e., not compiling JSEdit source code directly into executable. 52 | // For binary library distribution, uncomment line below to #define use_d_pointer. 53 | // #define use_d_pointer 54 | // Otherwise, JSEditPrivate should be compiled from the source code and ... 55 | // ... static linked into the executable as a class (essentially a pretentious STRUCT). 56 | // For static linking into executable, comment line above to #undef use_d_pointer. 57 | 58 | #ifdef use_d_pointer 59 | // For distribution as binary library 60 | #define JSEditPrivate_ref(JSEdit_Private_Class_Member) d_ptr->JSEdit_Private_Class_Member 61 | #else 62 | // For static linking into executable 63 | #define JSEditPrivate_ref(JSEdit_Private_Class_Member) JSEdit_Private.JSEdit_Private_Class_Member 64 | #endif 65 | 66 | #include "JavaScript_Editor.h" 67 | 68 | #include 69 | #include 70 | 71 | QString Test_JS_RegEx = 72 | R"~~~( 73 | 74 | function strip() { 75 | return this.replace(/^\s+/, '').replace(/\s+$/, ''); 76 | } 77 | 78 | function stripTags() { 79 | return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); 80 | } 81 | 82 | function stripScripts() { 83 | return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); 84 | } 85 | 86 | /=(?:[^\[|]*?(?:\[\[[^|\]]*(?:\|(?:[^|\]]*))?\]\])?)+(?:\||\}\})/ 87 | 88 | )~~~"; 89 | 90 | class JSBlockData: public QTextBlockUserData 91 | { 92 | public: 93 | QList bracketPositions; 94 | }; 95 | 96 | class JSHighlighter : public QSyntaxHighlighter { 97 | public: 98 | JSHighlighter ( QTextDocument *parent = 0 ); 99 | void setColor(JSEdit::ColorComponent component, const QColor &color); 100 | void mark(const QString &str, Qt::CaseSensitivity caseSensitivity); 101 | 102 | QStringList keywords() const; 103 | void setKeywords(const QStringList &keywords); 104 | 105 | protected: 106 | void highlightBlock(const QString &text); 107 | 108 | private: 109 | QSet m_keywords; 110 | QSet m_knownIds; 111 | QHash m_colors; 112 | QString m_markString; 113 | Qt::CaseSensitivity m_markCaseSensitivity; 114 | }; 115 | 116 | JSHighlighter::JSHighlighter ( QTextDocument *parent ) : QSyntaxHighlighter ( parent ), 117 | m_markCaseSensitivity(Qt::CaseInsensitive) { 118 | // default color scheme 119 | m_colors[JSEdit::Normal] = QColor(0, 0, 0); // QColor("#000000"); 120 | m_colors[JSEdit::Comment] = QColor(128, 128, 128); // QColor("#808080"); 121 | m_colors[JSEdit::Number] = QColor(192, 0, 0); // QColor("#008000"); 122 | m_colors[JSEdit::String] = QColor(0, 128, 0); // QColor("#800000"); 123 | m_colors[JSEdit::Operator] = QColor(0, 0, 0); // QColor("#808000"); 124 | m_colors[JSEdit::Identifier] = QColor(128, 0, 128); // QColor("#000020"); 125 | m_colors[JSEdit::Keyword] = QColor(0, 0, 192); // QColor("#000080"); 126 | m_colors[JSEdit::BuiltIn] = QColor(128, 0, 192); // QColor("#008080"); 127 | m_colors[JSEdit::Marker] = QColor(255, 255, 0); // QColor("#ffff00"); 128 | 129 | // https://developer.mozilla.org/en/JavaScript/Reference/Reserved_Words 130 | m_keywords << "break"; 131 | m_keywords << "case"; 132 | m_keywords << "catch"; 133 | m_keywords << "continue"; 134 | m_keywords << "default"; 135 | m_keywords << "delete"; 136 | m_keywords << "do"; 137 | m_keywords << "else"; 138 | m_keywords << "finally"; 139 | m_keywords << "for"; 140 | m_keywords << "function"; 141 | m_keywords << "if"; 142 | m_keywords << "in"; 143 | m_keywords << "instanceof"; 144 | m_keywords << "new"; 145 | m_keywords << "return"; 146 | m_keywords << "switch"; 147 | m_keywords << "this"; 148 | m_keywords << "throw"; 149 | m_keywords << "try"; 150 | m_keywords << "typeof"; 151 | m_keywords << "var"; 152 | m_keywords << "void"; 153 | m_keywords << "while"; 154 | m_keywords << "with"; 155 | 156 | m_keywords << "true"; 157 | m_keywords << "false"; 158 | m_keywords << "null"; 159 | 160 | // built-in and other popular objects + properties 161 | m_knownIds << "Object"; 162 | m_knownIds << "prototype"; 163 | m_knownIds << "create"; 164 | m_knownIds << "defineProperty"; 165 | m_knownIds << "defineProperties"; 166 | m_knownIds << "getOwnPropertyDescriptor"; 167 | m_knownIds << "keys"; 168 | m_knownIds << "getOwnPropertyNames"; 169 | m_knownIds << "constructor"; 170 | m_knownIds << "__parent__"; 171 | m_knownIds << "__proto__"; 172 | m_knownIds << "__defineGetter__"; 173 | m_knownIds << "__defineSetter__"; 174 | m_knownIds << "eval"; 175 | m_knownIds << "hasOwnProperty"; 176 | m_knownIds << "isPrototypeOf"; 177 | m_knownIds << "__lookupGetter__"; 178 | m_knownIds << "__lookupSetter__"; 179 | m_knownIds << "__noSuchMethod__"; 180 | m_knownIds << "propertyIsEnumerable"; 181 | m_knownIds << "toSource"; 182 | m_knownIds << "toLocaleString"; 183 | m_knownIds << "toString"; 184 | m_knownIds << "unwatch"; 185 | m_knownIds << "valueOf"; 186 | m_knownIds << "watch"; 187 | 188 | m_knownIds << "Function"; 189 | m_knownIds << "arguments"; 190 | m_knownIds << "arity"; 191 | m_knownIds << "caller"; 192 | m_knownIds << "constructor"; 193 | m_knownIds << "length"; 194 | m_knownIds << "name"; 195 | m_knownIds << "apply"; 196 | m_knownIds << "bind"; 197 | m_knownIds << "call"; 198 | 199 | m_knownIds << "String"; 200 | m_knownIds << "fromCharCode"; 201 | m_knownIds << "length"; 202 | m_knownIds << "charAt"; 203 | m_knownIds << "charCodeAt"; 204 | m_knownIds << "concat"; 205 | m_knownIds << "indexOf"; 206 | m_knownIds << "lastIndexOf"; 207 | m_knownIds << "localCompare"; 208 | m_knownIds << "match"; 209 | m_knownIds << "quote"; 210 | m_knownIds << "replace"; 211 | m_knownIds << "search"; 212 | m_knownIds << "slice"; 213 | m_knownIds << "split"; 214 | m_knownIds << "substr"; 215 | m_knownIds << "substring"; 216 | m_knownIds << "toLocaleLowerCase"; 217 | m_knownIds << "toLocaleUpperCase"; 218 | m_knownIds << "toLowerCase"; 219 | m_knownIds << "toUpperCase"; 220 | m_knownIds << "trim"; 221 | m_knownIds << "trimLeft"; 222 | m_knownIds << "trimRight"; 223 | 224 | m_knownIds << "Array"; 225 | m_knownIds << "isArray"; 226 | m_knownIds << "index"; 227 | m_knownIds << "input"; 228 | m_knownIds << "pop"; 229 | m_knownIds << "push"; 230 | m_knownIds << "reverse"; 231 | m_knownIds << "shift"; 232 | m_knownIds << "sort"; 233 | m_knownIds << "splice"; 234 | m_knownIds << "unshift"; 235 | m_knownIds << "concat"; 236 | m_knownIds << "join"; 237 | m_knownIds << "filter"; 238 | m_knownIds << "forEach"; 239 | m_knownIds << "every"; 240 | m_knownIds << "map"; 241 | m_knownIds << "some"; 242 | m_knownIds << "reduce"; 243 | m_knownIds << "reduceRight"; 244 | 245 | m_knownIds << "RegExp"; 246 | m_knownIds << "global"; 247 | m_knownIds << "ignoreCase"; 248 | m_knownIds << "lastIndex"; 249 | m_knownIds << "multiline"; 250 | m_knownIds << "source"; 251 | m_knownIds << "exec"; 252 | m_knownIds << "test"; 253 | 254 | m_knownIds << "JSON"; 255 | m_knownIds << "parse"; 256 | m_knownIds << "stringify"; 257 | 258 | m_knownIds << "decodeURI"; 259 | m_knownIds << "decodeURIComponent"; 260 | m_knownIds << "encodeURI"; 261 | m_knownIds << "encodeURIComponent"; 262 | m_knownIds << "eval"; 263 | m_knownIds << "isFinite"; 264 | m_knownIds << "isNaN"; 265 | m_knownIds << "parseFloat"; 266 | m_knownIds << "parseInt"; 267 | m_knownIds << "Infinity"; 268 | m_knownIds << "NaN"; 269 | m_knownIds << "undefined"; 270 | 271 | m_knownIds << "Math"; 272 | m_knownIds << "E"; 273 | m_knownIds << "LN2"; 274 | m_knownIds << "LN10"; 275 | m_knownIds << "LOG2E"; 276 | m_knownIds << "LOG10E"; 277 | m_knownIds << "PI"; 278 | m_knownIds << "SQRT1_2"; 279 | m_knownIds << "SQRT2"; 280 | m_knownIds << "abs"; 281 | m_knownIds << "acos"; 282 | m_knownIds << "asin"; 283 | m_knownIds << "atan"; 284 | m_knownIds << "atan2"; 285 | m_knownIds << "ceil"; 286 | m_knownIds << "cos"; 287 | m_knownIds << "exp"; 288 | m_knownIds << "floor"; 289 | m_knownIds << "log"; 290 | m_knownIds << "max"; 291 | m_knownIds << "min"; 292 | m_knownIds << "pow"; 293 | m_knownIds << "random"; 294 | m_knownIds << "round"; 295 | m_knownIds << "sin"; 296 | m_knownIds << "sqrt"; 297 | m_knownIds << "tan"; 298 | 299 | m_knownIds << "document"; 300 | m_knownIds << "window"; 301 | m_knownIds << "navigator"; 302 | m_knownIds << "userAgent"; 303 | } 304 | 305 | void 306 | JSHighlighter::setColor ( JSEdit::ColorComponent component, const QColor &color ) { 307 | m_colors[component] = color; 308 | rehighlight(); 309 | } 310 | 311 | void 312 | JSHighlighter::highlightBlock ( const QString &text ) { 313 | // parsing state 314 | enum { 315 | Start = 0, 316 | Number = 1, 317 | Identifier = 2, 318 | String = 3, 319 | Comment = 4, 320 | Regex = 5 321 | }; 322 | 323 | QList bracketPositions; 324 | 325 | int blockState = previousBlockState(); 326 | int bracketLevel = blockState >> 4; 327 | int state = blockState & 15; 328 | if (blockState < 0) { 329 | bracketLevel = 0; 330 | state = Start; 331 | } 332 | 333 | int start = 0; 334 | int i = 0; 335 | while (i <= text.length()) { 336 | QChar ch = (i < text.length()) ? text.at(i) : QChar(); 337 | QChar next_ch = (i < text.length() - 1) ? text.at(i + 1) : QChar(); 338 | 339 | switch (state) { 340 | 341 | case Start: 342 | start = i; 343 | if (ch.isSpace()) { 344 | ++i; 345 | } else if (ch.isDigit()) { 346 | ++i; 347 | state = Number; 348 | } else if (ch.isLetter() or (ch == '_') or (ch == '$')) { 349 | ++i; 350 | state = Identifier; 351 | } else if ((ch == '\'') or (ch == '\"')) { 352 | ++i; 353 | state = String; 354 | } else if ((ch == '/') and (next_ch == '*')) { 355 | ++i; 356 | ++i; 357 | state = Comment; 358 | } else if ((ch == '/') and (next_ch == '/')) { 359 | i = text.length(); 360 | setFormat(start, text.length(), m_colors[JSEdit::Comment]); 361 | } else if ((ch == '/') and (next_ch != '*')) { 362 | ++i; 363 | state = Regex; 364 | } else { 365 | if (not QString("(){}[]").contains(ch)) 366 | setFormat(start, 1, m_colors[JSEdit::Operator]); 367 | if ((ch =='{') or (ch == '}')) { 368 | bracketPositions += i; 369 | if (ch == '{') 370 | bracketLevel++; 371 | else 372 | bracketLevel--; 373 | } 374 | ++i; 375 | state = Start; 376 | } 377 | break; 378 | 379 | case Number: 380 | if (ch.isSpace() or (not ch.isDigit())) { 381 | setFormat(start, i - start, m_colors[JSEdit::Number]); 382 | state = Start; 383 | } else { 384 | ++i; 385 | } 386 | break; 387 | 388 | case Identifier: 389 | if (ch.isSpace() or 390 | (not (ch.isDigit() or ch.isLetter() or (ch == '_') or (ch == '$')))) { 391 | QString token = text.mid(start, i - start).trimmed(); 392 | if (m_keywords.contains(token)) 393 | setFormat(start, i - start, m_colors[JSEdit::Keyword]); 394 | else if (m_knownIds.contains(token)) 395 | setFormat(start, i - start, m_colors[JSEdit::BuiltIn]); 396 | else 397 | setFormat(start, i - start, m_colors[JSEdit::Identifier]); 398 | state = Start; 399 | } else { 400 | ++i; 401 | } 402 | break; 403 | 404 | case String: 405 | if ((ch == '\\') and 406 | ((next_ch == '\\') or (next_ch == '\'') or (next_ch == '\"') or 407 | (next_ch == 'b') or (next_ch == 'r') or (next_ch == 'f') or 408 | (next_ch == 't') or (next_ch == 'v'))) { 409 | // Accept all valid escapes as part of string 410 | ++i; 411 | ++i; 412 | } 413 | else if (ch == text.at(start)) { 414 | QChar prev_prev = (i > 1) ? text.at(i - 2) : QChar(); 415 | QChar prev = (i > 0) ? text.at(i - 1) : QChar(); 416 | if ((not (prev == '\\')) or ((prev_prev == '\\') and (prev == '\\'))) { 417 | ++i; 418 | setFormat(start, i - start, m_colors[JSEdit::String]); 419 | state = Start; 420 | } 421 | else { 422 | // If (ch == '\\') and we are here, there's an error. 423 | // For example, an invalid escape sequence. 424 | ++i; 425 | } 426 | } else { 427 | // If (ch == '\\') and we are here, there's an error 428 | // For example, an invalid escape sequence. 429 | ++i; 430 | } 431 | break; 432 | 433 | case Comment: 434 | if ((ch == '*') and (next_ch == '/')) { 435 | ++i; 436 | ++i; 437 | setFormat(start, i - start, m_colors[JSEdit::Comment]); 438 | state = Start; 439 | } else { 440 | ++i; 441 | } 442 | break; 443 | 444 | case Regex: 445 | if (ch == '\\') { 446 | // Accept all escapes as part of regex 447 | ++i; 448 | ++i; 449 | } 450 | else if (ch == '/') { 451 | QChar prev = (i > 0) ? text.at(i - 1) : QChar(); 452 | if (not (prev == '\\')) { 453 | // Get the modifiers also 454 | while (QString("igm").contains(next_ch)) { 455 | ++i; 456 | next_ch = (i < text.length() - 1) ? text.at(i + 1) : QChar(); 457 | } 458 | ++i; 459 | setFormat(start, i - start, m_colors[JSEdit::String]); 460 | state = Start; 461 | } else { 462 | ++i; 463 | } 464 | } else { 465 | ++i; 466 | } 467 | break; 468 | 469 | default: 470 | state = Start; 471 | break; 472 | } 473 | } 474 | 475 | if (state == Comment) 476 | setFormat(start, text.length(), m_colors[JSEdit::Comment]); 477 | else 478 | state = Start; 479 | 480 | if (!m_markString.isEmpty()) { 481 | int pos = 0; 482 | int len = m_markString.length(); 483 | QTextCharFormat markerFormat; 484 | markerFormat.setBackground(m_colors[JSEdit::Marker]); 485 | markerFormat.setForeground(m_colors[JSEdit::Normal]); 486 | for (;;) { 487 | pos = text.indexOf(m_markString, pos, m_markCaseSensitivity); 488 | if (pos < 0) 489 | break; 490 | setFormat(pos, len, markerFormat); 491 | ++pos; 492 | } 493 | } 494 | 495 | if (!bracketPositions.isEmpty()) { 496 | JSBlockData *blockData = reinterpret_cast(currentBlock().userData()); 497 | if (!blockData) { 498 | blockData = new JSBlockData; 499 | currentBlock().setUserData(blockData); 500 | } 501 | blockData->bracketPositions = bracketPositions; 502 | } 503 | 504 | blockState = (state & 15) | (bracketLevel << 4); 505 | setCurrentBlockState(blockState); 506 | } 507 | 508 | void 509 | JSHighlighter::mark ( const QString &str, Qt::CaseSensitivity caseSensitivity ) { 510 | m_markString = str; 511 | m_markCaseSensitivity = caseSensitivity; 512 | rehighlight(); 513 | } 514 | 515 | QStringList 516 | JSHighlighter::keywords ( ) const { 517 | return m_keywords.toList(); 518 | } 519 | 520 | void 521 | JSHighlighter::setKeywords ( const QStringList &keywords ) { 522 | m_keywords = QSet::fromList(keywords); 523 | rehighlight(); 524 | } 525 | 526 | struct BlockInfo { 527 | int position; 528 | int number; 529 | bool foldable: 1; 530 | bool folded : 1; 531 | }; 532 | 533 | Q_DECLARE_TYPEINFO(BlockInfo, Q_PRIMITIVE_TYPE); 534 | 535 | class SidebarWidget : public QWidget { 536 | public: 537 | SidebarWidget(JSEdit *editor); 538 | QVector lineNumbers; 539 | QColor backgroundColor; 540 | QColor lineNumberColor; 541 | QColor indicatorColor; 542 | QColor foldIndicatorColor; 543 | QFont font; 544 | int foldIndicatorWidth; 545 | QPixmap rightArrowIcon; 546 | QPixmap downArrowIcon; 547 | protected: 548 | void mousePressEvent(QMouseEvent *event); 549 | void paintEvent(QPaintEvent *event); 550 | }; 551 | 552 | SidebarWidget::SidebarWidget ( JSEdit *editor ) : QWidget ( editor ), 553 | foldIndicatorWidth ( 0 ) { 554 | backgroundColor = QColor(200, 200, 200); 555 | lineNumberColor = Qt::black; 556 | indicatorColor = Qt::white; 557 | foldIndicatorColor = Qt::lightGray; 558 | } 559 | 560 | void 561 | SidebarWidget::mousePressEvent ( QMouseEvent *event ) { 562 | if (foldIndicatorWidth > 0) { 563 | int xofs = width() - foldIndicatorWidth; 564 | int lineNo = -1; 565 | int fh = fontMetrics().lineSpacing(); 566 | int ys = event->pos().y(); 567 | if (event->pos().x() > xofs) { 568 | foreach (BlockInfo ln, lineNumbers) 569 | if (ln.position < ys && (ln.position + fh) > ys) { 570 | if (ln.foldable) 571 | lineNo = ln.number; 572 | break; 573 | } 574 | } 575 | if (lineNo >= 0) { 576 | JSEdit *editor = qobject_cast(parent()); 577 | if (editor) 578 | editor->toggleFold(lineNo); 579 | } 580 | } 581 | } 582 | 583 | void 584 | SidebarWidget::paintEvent ( QPaintEvent *event ) { 585 | QPainter p(this); 586 | p.fillRect(event->rect(), backgroundColor); 587 | p.setPen(lineNumberColor); 588 | p.setFont(font); 589 | int fh = QFontMetrics(font).height(); 590 | foreach (BlockInfo ln, lineNumbers) 591 | p.drawText(0, ln.position, width() - 4 - foldIndicatorWidth, fh, Qt::AlignRight, QString::number(ln.number)); 592 | 593 | if (foldIndicatorWidth > 0) { 594 | int xofs = width() - foldIndicatorWidth; 595 | p.fillRect(xofs, 0, foldIndicatorWidth, height(), indicatorColor); 596 | 597 | // initialize (or recreate) the arrow icons whenever necessary 598 | if (foldIndicatorWidth != rightArrowIcon.width()) { 599 | QPainter iconPainter; 600 | QPolygonF polygon; 601 | 602 | int dim = foldIndicatorWidth; 603 | rightArrowIcon = QPixmap(dim, dim); 604 | rightArrowIcon.fill(Qt::transparent); 605 | downArrowIcon = rightArrowIcon; 606 | 607 | polygon << QPointF(dim * 0.4, dim * 0.25); 608 | polygon << QPointF(dim * 0.4, dim * 0.75); 609 | polygon << QPointF(dim * 0.8, dim * 0.5); 610 | iconPainter.begin(&rightArrowIcon); 611 | iconPainter.setRenderHint(QPainter::Antialiasing); 612 | iconPainter.setPen(Qt::NoPen); 613 | iconPainter.setBrush(foldIndicatorColor); 614 | iconPainter.drawPolygon(polygon); 615 | iconPainter.end(); 616 | 617 | polygon.clear(); 618 | polygon << QPointF(dim * 0.25, dim * 0.4); 619 | polygon << QPointF(dim * 0.75, dim * 0.4); 620 | polygon << QPointF(dim * 0.5, dim * 0.8); 621 | iconPainter.begin(&downArrowIcon); 622 | iconPainter.setRenderHint(QPainter::Antialiasing); 623 | iconPainter.setPen(Qt::NoPen); 624 | iconPainter.setBrush(foldIndicatorColor); 625 | iconPainter.drawPolygon(polygon); 626 | iconPainter.end(); 627 | } 628 | 629 | foreach (BlockInfo ln, lineNumbers) 630 | if (ln.foldable) { 631 | if (ln.folded) 632 | p.drawPixmap(xofs, ln.position, rightArrowIcon); 633 | else 634 | p.drawPixmap(xofs, ln.position, downArrowIcon); 635 | } 636 | } 637 | } 638 | 639 | class JSDocLayout: public QPlainTextDocumentLayout { 640 | public: 641 | JSDocLayout ( QTextDocument *doc ); 642 | void forceUpdate(); 643 | }; 644 | 645 | JSDocLayout::JSDocLayout ( QTextDocument *doc ) : QPlainTextDocumentLayout ( doc ) { 646 | } 647 | 648 | void 649 | JSDocLayout::forceUpdate ( ) { 650 | emit documentSizeChanged(documentSize()); 651 | } 652 | 653 | #ifdef use_d_pointer 654 | JSEdit::JSEdit ( QWidget *parent ) : QPlainTextEdit( parent ), 655 | d_ptr ( new JSEditPrivate ) { 656 | #else 657 | JSEdit::JSEdit ( QWidget *parent ) : QPlainTextEdit( parent ) { 658 | #endif 659 | JSEditPrivate_ref(editor) = this; 660 | JSEditPrivate_ref(layout) = new JSDocLayout(document()); 661 | JSEditPrivate_ref(highlighter) = new JSHighlighter(document()); 662 | JSEditPrivate_ref(sidebar) = new SidebarWidget(this); 663 | JSEditPrivate_ref(showLineNumbers) = true; 664 | JSEditPrivate_ref(textWrap) = true; 665 | JSEditPrivate_ref(bracketsMatching) = true; 666 | JSEditPrivate_ref(cursorColor) = QColor(255, 255, 192); 667 | JSEditPrivate_ref(bracketMatchColor) = QColor(128, 255, 128); 668 | JSEditPrivate_ref(bracketErrorColor) = QColor(255, 128, 128); 669 | JSEditPrivate_ref(codeFolding) = true; 670 | 671 | JSEditPrivate_ref(AutoIndentEnabled) = true; 672 | JSEditPrivate_ref(Tab_Modulus) = Default_Tab_Modulus; 673 | 674 | JSEditPrivate_ref(Brace_Bracket_Character) = true; 675 | JSEditPrivate_ref(Quote_Bracket_Character) = true; 676 | JSEditPrivate_ref(Post_Select_Bracket_Enclosed_Text) = true; 677 | 678 | QStringList expand_keywords = this->keywords(); 679 | expand_keywords << "const" << "let"; 680 | this->setKeywords(expand_keywords); 681 | 682 | // this->setColor(JSEdit::Background, QColor(255, 255, 255)); // QColor("#0C152B")); 683 | // this->setColor(JSEdit::Normal, QColor(0, 0, 0)); // QColor("#FFFFFF")); 684 | // this->setColor(JSEdit::Comment, QColor(128, 128, 128)); // QColor("#666666")); 685 | // this->setColor(JSEdit::Number, QColor(192, 0, 0)); // QColor("#DBF76C")); 686 | // this->setColor(JSEdit::String, QColor(0, 128, 0)); // QColor("#5ED363")); 687 | // this->setColor(JSEdit::Operator, QColor(0, 0, 0)); // QColor("#FF7729")); 688 | this->setColor(JSEdit::Identifier, QColor(128, 0, 128)); // QColor("#FFFFFF")); 689 | this->setColor(JSEdit::Keyword, QColor(0, 160, 160)); // QColor("#FDE15D")); 690 | this->setColor(JSEdit::BuiltIn, QColor(0, 128, 192)); // QColor("#9CB6D4")); 691 | // this->setColor(JSEdit::Cursor, QColor(255, 255, 192)); // QColor("#1E346B")); 692 | // this->setColor(JSEdit::Marker, QColor(255, 255, 0)); // QColor("#DBF76C")); 693 | // this->setColor(JSEdit::BracketMatch, QColor(128, 255, 128)); // QColor("#1AB0A6")); 694 | // this->setColor(JSEdit::BracketError, QColor(255, 128, 128)); // QColor("#A82224")); 695 | // this->setColor(JSEdit::FoldIndicator, Qt::lightGray); // QColor("#555555")); 696 | 697 | document()->setDocumentLayout(JSEditPrivate_ref(layout)); 698 | 699 | connect(this, SIGNAL(textChanged()), this, SLOT(onTextChanged())); 700 | connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateCursor())); 701 | connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateSidebar())); 702 | connect(this, SIGNAL(updateRequest(QRect, int)), this, SLOT(updateSidebar(QRect, int))); 703 | 704 | QString doublequoted_string = "\"(?:[^\\\\\"]|\\\\.)*\""; 705 | QString singlequoted_string = "'(?:[^\\\\']|\\\\.)*'"; 706 | QString c_style_comment = "/\\*(?:[^*]*|\\*[^/])*\\*/"; 707 | QString doubleslash_comment = "//[^\\n]*\\n"; 708 | // js regex literal must come after comment /* and comment // 709 | QString js_regex_literal = "(/(?:\\\\.|[^/])+/[igm]*)"; 710 | QString bracket_characters = "[{}()]"; 711 | 712 | JSEditPrivate_ref(JavaScript_Bracket_RegEx) = 713 | QRegularExpression(doublequoted_string + "|" + singlequoted_string + "|" + 714 | c_style_comment + "|" + doubleslash_comment + "|" + 715 | js_regex_literal + "|" + bracket_characters); 716 | 717 | QString js_identifier = "([A-Za-z$_][A-Za-z0-9$_]*)"; 718 | js_identifier.clear(); // Don't use 719 | 720 | #if defined(Q_OS_MAC) 721 | QFont textFont = font(); 722 | textFont.setPointSize(15); 723 | textFont.setFamily("Courier"); 724 | setFont(textFont); 725 | #elif defined(Q_OS_UNIX) 726 | QFont textFont = font(); 727 | textFont.setFamily("Monospace"); 728 | setFont(textFont); 729 | #endif 730 | } 731 | 732 | JSEdit::~JSEdit ( ) { 733 | delete JSEditPrivate_ref(layout); 734 | } 735 | 736 | void 737 | JSEdit::Set_PlainText ( QString Set_Plain_Text ) { 738 | // Don't defeat Undo/Redo 739 | QPlainTextEdit::selectAll(); 740 | QPlainTextEdit::insertPlainText(Set_Plain_Text); 741 | QTextCursor txt_cursor = QPlainTextEdit::textCursor(); 742 | txt_cursor.movePosition(QTextCursor::Start); 743 | QPlainTextEdit::setTextCursor(txt_cursor); 744 | } 745 | 746 | QString 747 | JSEdit::Compute_Bracket_Text ( QString Source_Text ) { 748 | QString bracket_list = JavaScript_Bracket_List; 749 | QString bracket_text = QString(" ").repeated(Source_Text.length()); 750 | if (JSEditPrivate_ref(JavaScript_Bracket_RegEx).isValid()) { 751 | QRegularExpressionMatchIterator regex_iterator = 752 | JSEditPrivate_ref(JavaScript_Bracket_RegEx.globalMatch(Source_Text)); 753 | if (regex_iterator.hasNext()) { 754 | // At least one found 755 | while (regex_iterator.hasNext()) { 756 | QRegularExpressionMatch match = regex_iterator.next(); 757 | if ((match.capturedLength() == 1) and 758 | bracket_list.contains(match.captured())) { 759 | bracket_text[match.capturedStart()] = match.captured().at(0); 760 | } 761 | } 762 | } 763 | } 764 | return bracket_text; 765 | } 766 | 767 | int 768 | JSEdit::Bracket_Match_Position ( int Current_Position ) { 769 | QString target_text = this->toPlainText(); 770 | 771 | if (not (target_text.length() == JSEditPrivate_ref(Bracket_Text).length())) onTextChanged(); 772 | if (not (target_text == JSEditPrivate_ref(Bracket_Source_Text))) onTextChanged(); 773 | 774 | QString bracket_list = JavaScript_Bracket_List; 775 | 776 | if ((Current_Position < 0) or (Current_Position >= target_text.length()) or 777 | (Current_Position >= JSEditPrivate_ref(Bracket_Text).length())) return -1; 778 | if (not bracket_list.contains(target_text.at(Current_Position))) return -1; 779 | 780 | // For example, may have found bracket_list character in comment or string. 781 | if (not (JSEditPrivate_ref(Bracket_Text).at(Current_Position) == target_text.at(Current_Position))) return -1; 782 | 783 | int match_position = Current_Position; 784 | int paren_level = 0; 785 | if (JSEditPrivate_ref(Bracket_Text).at(match_position) == '(') { 786 | while (match_position < JSEditPrivate_ref(Bracket_Text).length()) { 787 | if (JSEditPrivate_ref(Bracket_Text).at(match_position) == '(') paren_level += 1; 788 | else if (JSEditPrivate_ref(Bracket_Text).at(match_position) == ')') paren_level -= 1; 789 | if (paren_level == 0) break; 790 | match_position += 1; 791 | } 792 | } 793 | else if (JSEditPrivate_ref(Bracket_Text).at(match_position) == ')') { 794 | while (match_position >= 0) { 795 | if (JSEditPrivate_ref(Bracket_Text).at(match_position) == ')') paren_level += 1; 796 | else if (JSEditPrivate_ref(Bracket_Text).at(match_position) == '(') paren_level -= 1; 797 | if (paren_level == 0) break; 798 | match_position -= 1; 799 | } 800 | } 801 | else if (JSEditPrivate_ref(Bracket_Text).at(match_position) == '[') { 802 | while (match_position < JSEditPrivate_ref(Bracket_Text).length()) { 803 | if (JSEditPrivate_ref(Bracket_Text).at(match_position) == '[') paren_level += 1; 804 | else if (JSEditPrivate_ref(Bracket_Text).at(match_position) == ']') paren_level -= 1; 805 | if (paren_level == 0) break; 806 | match_position += 1; 807 | } 808 | } 809 | else if (JSEditPrivate_ref(Bracket_Text).at(match_position) == ']') { 810 | while (match_position >= 0) { 811 | if (JSEditPrivate_ref(Bracket_Text).at(match_position) == ']') paren_level += 1; 812 | else if (JSEditPrivate_ref(Bracket_Text).at(match_position) == '[') paren_level -= 1; 813 | if (paren_level == 0) break; 814 | match_position -= 1; 815 | } 816 | } 817 | else if (JSEditPrivate_ref(Bracket_Text).at(match_position) == '{') { 818 | while (match_position < JSEditPrivate_ref(Bracket_Text).length()) { 819 | if (JSEditPrivate_ref(Bracket_Text).at(match_position) == '{') paren_level += 1; 820 | else if (JSEditPrivate_ref(Bracket_Text).at(match_position) == '}') paren_level -= 1; 821 | if (paren_level == 0) break; 822 | match_position += 1; 823 | } 824 | } 825 | else if (JSEditPrivate_ref(Bracket_Text).at(match_position) == '}') { 826 | while (match_position >= 0) { 827 | if (JSEditPrivate_ref(Bracket_Text).at(match_position) == '}') paren_level += 1; 828 | else if (JSEditPrivate_ref(Bracket_Text).at(match_position) == '{') paren_level -= 1; 829 | if (paren_level == 0) break; 830 | match_position -= 1; 831 | } 832 | } 833 | 834 | if ((match_position >= 0) and (match_position < JSEditPrivate_ref(Bracket_Text).length())) { 835 | return match_position; 836 | } 837 | 838 | return -1; // No match found 839 | } 840 | 841 | int 842 | JSEdit::Compute_Current_Brace_Indent_Level ( int Current_Position ) { 843 | // Returns brace_indent_level in units of Tab_Modulus 844 | int match_position = Current_Position; 845 | int brace_indent_level = 0; 846 | while (match_position >= 0) { 847 | if (JSEditPrivate_ref(Bracket_Text).at(match_position) == '}') brace_indent_level -= 1; 848 | else if (JSEditPrivate_ref(Bracket_Text).at(match_position) == '{') brace_indent_level += 1; 849 | match_position -= 1; 850 | } 851 | return brace_indent_level; 852 | } 853 | 854 | int 855 | JSEdit::Compute_Current_Brace_Indent ( int Current_Position ) { 856 | // Returns brace_indent in units of spaces 857 | int brace_indent_level = Compute_Current_Brace_Indent_Level(Current_Position); 858 | return (brace_indent_level * JSEditPrivate_ref(Tab_Modulus)); 859 | } 860 | 861 | int 862 | JSEdit::Compute_Current_Paren_Indent ( QString Current_Text, 863 | int Current_Position ) { 864 | // Returns paren_indent in units of spaces 865 | QString bracket_text = Compute_Bracket_Text(Current_Text); 866 | // For example: 867 | // if (((cell_x + 1) == parseInt(x_and_y[0])) && 868 | // Or for example: 869 | // if (((cell_x + 1) == parseInt(x_and_y[0])) && 870 | // ((cell_y + 1) == parseInt(x_and_y[1]))) cells += "X"; 871 | int paren_indent = 0; 872 | // Search back for closest unmatched "(", how is it "indented"? 873 | int paren_level = 0; 874 | int paren_position = Current_Position; 875 | while (paren_position > 0) { 876 | if (bracket_text.at(paren_position) == QChar('{')) break; 877 | else if (bracket_text.at(paren_position) == QChar(')')) paren_level -= 1; 878 | else if (bracket_text.at(paren_position) == QChar('(')) { 879 | paren_level += 1; 880 | if (paren_level == 1) { 881 | // Found closest unmatched "(", now find immediately preceding newline 882 | int newline_position = paren_position; 883 | while ((newline_position > 0) and 884 | (not (Current_Text.at(newline_position) == QChar('\n')))) newline_position -= 1; 885 | // Found immediately preceding newline 886 | if ((newline_position >= 0) and 887 | (Current_Text.at(newline_position) == QChar('\n'))) { 888 | paren_indent = paren_position - newline_position; 889 | break; 890 | } 891 | } 892 | } 893 | paren_position -= 1; 894 | } 895 | 896 | return paren_indent; 897 | } 898 | 899 | void 900 | JSEdit::keyPressEvent ( QKeyEvent* event ) { 901 | Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); 902 | 903 | if (((event->key() == Qt::Key_ParenLeft) or 904 | (event->key() == Qt::Key_BracketLeft) or 905 | ((event->key() == Qt::Key_BraceLeft) and JSEditPrivate_ref(Brace_Bracket_Character)) or 906 | ((event->key() == Qt::Key_QuoteDbl) and JSEditPrivate_ref(Quote_Bracket_Character)) or 907 | ((event->key() == Qt::Key_Apostrophe) and JSEditPrivate_ref(Quote_Bracket_Character))) and 908 | ((modifiers & Qt::ControlModifier) == Qt::NoModifier) and 909 | (this->textCursor().selectedText().count() > 0)) { 910 | event->accept(); 911 | // For these "bracketing" characters, if the "opening" character is typed ... 912 | // ... when text is selected, the selected text will be enclosed by ... 913 | // ... "open" and "close" characters. 914 | QString left_encloser; 915 | QString right_encloser; 916 | if (event->key() == Qt::Key_ParenLeft) { 917 | left_encloser = "("; 918 | right_encloser = ")"; 919 | } 920 | else if (event->key() == Qt::Key_BracketLeft) { 921 | left_encloser = "["; 922 | right_encloser = "]"; 923 | } 924 | else if (event->key() == Qt::Key_BraceLeft) { 925 | left_encloser = "{"; 926 | right_encloser = "}"; 927 | } 928 | else if (event->key() == Qt::Key_QuoteDbl) { 929 | left_encloser = "\""; 930 | right_encloser = "\""; 931 | } 932 | else if (event->key() == Qt::Key_Apostrophe) { 933 | left_encloser = "'"; 934 | right_encloser = "'"; 935 | } 936 | 937 | QTextCursor txt_cursor = this->textCursor(); 938 | int sel_begin_pos = txt_cursor.selectionStart(); 939 | int sel_end_pos = txt_cursor.selectionEnd(); 940 | txt_cursor.insertText(left_encloser + txt_cursor.selectedText() + right_encloser); 941 | 942 | if (JSEditPrivate_ref(Post_Select_Bracket_Enclosed_Text)) { 943 | txt_cursor.setPosition((sel_begin_pos + 1)); 944 | txt_cursor.setPosition((sel_end_pos + 1), QTextCursor::KeepAnchor); 945 | this->setTextCursor(txt_cursor); 946 | } 947 | 948 | return; 949 | } 950 | else if (event->key() == Qt::Key_BraceLeft) { 951 | // Insert formatted brace pair, ready for 'braced' code 952 | int initial_position = QPlainTextEdit::textCursor().position(); 953 | QPlainTextEdit::insertPlainText("{\n}"); 954 | QTextCursor txt_cursor = QPlainTextEdit::textCursor(); 955 | 956 | txt_cursor.setPosition(initial_position, QTextCursor::KeepAnchor); 957 | QPlainTextEdit::setTextCursor(txt_cursor); 958 | // Use general formatting code 959 | Format_Selected_Text_Lines(); 960 | 961 | // Put cursor right after the 'keyboarded' left brace, as expected 962 | txt_cursor.setPosition(initial_position + 1); 963 | QPlainTextEdit::setTextCursor(txt_cursor); 964 | } 965 | else if ((event->key() == Qt::Key_Return) and JSEditPrivate_ref(AutoIndentEnabled)) { 966 | QTextCursor txt_cursor = QPlainTextEdit::textCursor(); 967 | QString before_cursor_text = QPlainTextEdit::toPlainText(); 968 | before_cursor_text.truncate(txt_cursor.position()); 969 | int indent_levels = Compute_Current_Brace_Indent_Level(txt_cursor.position()); 970 | 971 | // For example: 972 | // if (((cell_x + 1) == parseInt(x_and_y[0])) && 973 | int paren_indent = Compute_Current_Paren_Indent(before_cursor_text, 974 | (before_cursor_text.length() - 1)); 975 | 976 | QString whitespace = QString(" ").repeated(indent_levels * JSEditPrivate_ref(Tab_Modulus)); 977 | // paren_indent in units of spaces, indent_levels in units of Tab_Modulus 978 | paren_indent = paren_indent - (indent_levels * JSEditPrivate_ref(Tab_Modulus)); 979 | if (paren_indent > 0) whitespace += QString(" ").repeated(paren_indent); 980 | 981 | QPlainTextEdit::insertPlainText("\n" + whitespace); 982 | } 983 | else if (event->key() == Qt::Key_Tab) { 984 | QTextCursor txt_cursor = QPlainTextEdit::textCursor(); 985 | if (txt_cursor.selectedText().length() == 0) { 986 | txt_cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); 987 | QString line_text_before_cursor = txt_cursor.selectedText(); 988 | if (line_text_before_cursor.indexOf(QRegExp("[^\\s]")) < 0) { 989 | // Only whitespace before cursor 990 | txt_cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); 991 | QString line_text_after_cursor = txt_cursor.selectedText(); 992 | int first_non_whitespace_idx = line_text_after_cursor.indexOf(QRegExp("[^\\s]")); 993 | QString whitespace = line_text_before_cursor; 994 | if (first_non_whitespace_idx > 0) { 995 | QTextCursor new_txt_cursor = QPlainTextEdit::textCursor(); 996 | new_txt_cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, first_non_whitespace_idx); 997 | // Move cursor to just before first non-whitespace character 998 | QPlainTextEdit::setTextCursor(new_txt_cursor); 999 | line_text_after_cursor.truncate(first_non_whitespace_idx); 1000 | whitespace += line_text_after_cursor; 1001 | } 1002 | // Add enough whitespace to bring first non-whitespace character to just after next tab modulus 1003 | QPlainTextEdit::insertPlainText(QString(" ").repeated(JSEditPrivate_ref(Tab_Modulus) - (whitespace.length() % JSEditPrivate_ref(Tab_Modulus)))); 1004 | } 1005 | else { 1006 | // Non-whitespace before cursor, just add tab modulus spaces 1007 | QPlainTextEdit::insertPlainText(QString(" ").repeated(JSEditPrivate_ref(Tab_Modulus))); 1008 | } 1009 | } 1010 | else if ((modifiers & Qt::AltModifier) == Qt::NoModifier) { 1011 | Indent_Selected_Text_Lines(JSEditPrivate_ref(Tab_Modulus)); 1012 | } 1013 | } 1014 | else if ((event->key() == Qt::Key_BracketLeft) and 1015 | ((modifiers & Qt::ControlModifier) == Qt::ControlModifier) and 1016 | ((modifiers & Qt::ShiftModifier) == Qt::NoModifier)) { 1017 | Indent_Selected_Text_Lines(-2); 1018 | } 1019 | else if ((event->key() == Qt::Key_BracketRight) and 1020 | ((modifiers & Qt::ControlModifier) == Qt::ControlModifier) and 1021 | ((modifiers & Qt::ShiftModifier) == Qt::NoModifier)) { 1022 | Indent_Selected_Text_Lines(2); 1023 | } 1024 | else if ((event->key() == Qt::Key_BracketLeft) and 1025 | ((modifiers & Qt::ControlModifier) == Qt::ControlModifier) and 1026 | ((modifiers & Qt::ShiftModifier) == Qt::ShiftModifier)) { 1027 | Indent_Selected_Text_Lines(-1); 1028 | } 1029 | else if ((event->key() == Qt::Key_BracketRight) and 1030 | ((modifiers & Qt::ControlModifier) == Qt::ControlModifier) and 1031 | ((modifiers & Qt::ShiftModifier) == Qt::ShiftModifier)) { 1032 | Indent_Selected_Text_Lines(1); 1033 | } 1034 | else if ((event->key() == Qt::Key_Equal) and 1035 | ((modifiers & Qt::ControlModifier) == Qt::ControlModifier) and 1036 | ((modifiers & Qt::ShiftModifier) == Qt::NoModifier)) { 1037 | Format_Selected_Text_Lines(); 1038 | } 1039 | else { 1040 | // Allow parent class (normal) handling of key 1041 | QPlainTextEdit::keyPressEvent(event); 1042 | } 1043 | } 1044 | 1045 | QTextCursor 1046 | JSEdit::Select_Selected_Text_Lines ( ) { 1047 | QTextCursor begin_txt_cursor = QPlainTextEdit::textCursor(); 1048 | begin_txt_cursor.setPosition(begin_txt_cursor.selectionStart()); 1049 | begin_txt_cursor.select(QTextCursor::LineUnderCursor); 1050 | 1051 | QTextCursor end_txt_cursor = QPlainTextEdit::textCursor(); 1052 | end_txt_cursor.setPosition(end_txt_cursor.selectionEnd()); 1053 | end_txt_cursor.select(QTextCursor::LineUnderCursor); 1054 | 1055 | QTextCursor new_txt_cursor = QPlainTextEdit::textCursor(); 1056 | new_txt_cursor.setPosition(qMin(qMin(begin_txt_cursor.selectionStart(), 1057 | begin_txt_cursor.selectionEnd()), 1058 | qMin(end_txt_cursor.selectionStart(), 1059 | end_txt_cursor.selectionEnd()))); 1060 | new_txt_cursor.setPosition(qMax(qMax(begin_txt_cursor.selectionStart(), 1061 | begin_txt_cursor.selectionEnd()), 1062 | qMax(end_txt_cursor.selectionStart(), 1063 | end_txt_cursor.selectionEnd())), 1064 | QTextCursor::KeepAnchor); 1065 | QPlainTextEdit::setTextCursor(new_txt_cursor); 1066 | 1067 | return new_txt_cursor; 1068 | } 1069 | 1070 | void 1071 | JSEdit::Format_Selected_Text_Lines ( ) { 1072 | QTextCursor new_txt_cursor = Select_Selected_Text_Lines(); 1073 | QString before_selected_text = QPlainTextEdit::toPlainText(); 1074 | before_selected_text.truncate(new_txt_cursor.selectionStart()); 1075 | QString selected_text = new_txt_cursor.selectedText(); 1076 | selected_text = selected_text.replace(QChar(0x2029), QChar('\n')); 1077 | QString bracket_text = Compute_Bracket_Text(selected_text); 1078 | // Selection (when extended to include all of any line w/ any text selected), ... 1079 | // ... together with the text preceding the current selection, ... 1080 | // ... must be syntactically "complete" in the upward direction, ... 1081 | // ... i.e. no unmatched "}" or ")" but unmatched "{" or "(" are accepted. 1082 | // In other words, "…{…{…}…}…" and "…{…{…}…" are fine but "…{…}…}…" is not. 1083 | 1084 | int position = bracket_text.length() - 1; 1085 | while (position >= 0) { 1086 | if (bracket_text.at(position) == '{') { 1087 | int insert_position = position + 1; 1088 | while (insert_position < selected_text.length()) { 1089 | QChar ch = selected_text.at(insert_position); 1090 | if (ch.isSpace() and (not (ch == QChar('\n')))) insert_position += 1; 1091 | else break; 1092 | } 1093 | if (insert_position < selected_text.length()) { 1094 | if (not (selected_text.at(insert_position) == QChar('\n'))) 1095 | selected_text.insert(insert_position, "\n"); 1096 | } 1097 | } 1098 | else if (bracket_text.at(position) == '}') { 1099 | int insert_position = position + 1; 1100 | while (insert_position < selected_text.length()) { 1101 | QChar ch = selected_text.at(insert_position); 1102 | if (ch.isSpace() and (not (ch == QChar('\n')))) insert_position += 1; 1103 | else break; 1104 | } 1105 | if (insert_position < selected_text.length()) { 1106 | if (not (selected_text.at(insert_position) == QChar('\n'))) 1107 | selected_text.insert(insert_position, "\n"); 1108 | } 1109 | } 1110 | position -= 1; 1111 | } 1112 | 1113 | int current_indent_level = Compute_Current_Brace_Indent_Level(new_txt_cursor.selectionStart()); 1114 | // Indenting whitespace computed for position at beginning of selection (extended). 1115 | // QString whitespace = QString(" ").repeated(indent_levels * Tab_Modulus); 1116 | // Split into individual lines that can be indented independently as syntax requires 1117 | QStringList selected_lines = selected_text.split("\n"); 1118 | QString indented_text = ""; 1119 | int bracket_level = 0; 1120 | for (int line_idx = 0; line_idx < selected_lines.count(); line_idx += 1) { 1121 | QString line_text = selected_lines[line_idx]; 1122 | // Remove all whitespace and replace with computed whitespace 1123 | line_text = line_text.trimmed(); 1124 | int bracket_indent_level = bracket_level; 1125 | 1126 | // For example: 1127 | // if (((cell_x + 1) == parseInt(x_and_y[0])) && 1128 | // ((cell_y + 1) == parseInt(x_and_y[1]))) cells += "X"; 1129 | QString before_current_text = before_selected_text + indented_text; 1130 | int paren_indent = Compute_Current_Paren_Indent(before_current_text, 1131 | (before_current_text.length() - 1)); 1132 | 1133 | if ((line_text.startsWith('}')) and (line_text.endsWith('}'))) bracket_indent_level -= 1; 1134 | indented_text += QString(" ").repeated((current_indent_level + bracket_indent_level) * JSEditPrivate_ref(Tab_Modulus)); 1135 | // paren_indent is count of individual speces, while *_indent_level are in units of Tab_Modulus 1136 | paren_indent = paren_indent - ((current_indent_level + bracket_indent_level) * JSEditPrivate_ref(Tab_Modulus)); 1137 | indented_text += QString(" ").repeated(paren_indent); 1138 | indented_text += line_text; 1139 | if (line_idx < (selected_lines.count() - 1)) indented_text += "\n"; 1140 | if (line_text.endsWith('{')) bracket_level += 1; 1141 | else if (line_text.endsWith('}')) bracket_level -= 1; 1142 | } 1143 | QPlainTextEdit::insertPlainText(indented_text); 1144 | } 1145 | 1146 | void 1147 | JSEdit::Indent_Selected_Text_Lines ( int Indent_Tab_Modulus ) { 1148 | if (Indent_Tab_Modulus == 0) return; 1149 | 1150 | QTextCursor new_txt_cursor = Select_Selected_Text_Lines(); 1151 | int start_position = new_txt_cursor.selectionStart(); 1152 | QString selected_text = new_txt_cursor.selectedText(); 1153 | QStringList selected_text_lines = selected_text.split(QChar(0x2029)); 1154 | QString indented_text = ""; 1155 | if (Indent_Tab_Modulus > 0) { 1156 | for (int line_idx = 0; line_idx < selected_text_lines.count(); line_idx += 1) { 1157 | indented_text += QString(" ").repeated(Indent_Tab_Modulus) + selected_text_lines.at(line_idx); 1158 | if (line_idx < (selected_text_lines.count() - 1)) indented_text += "\n"; 1159 | } 1160 | } 1161 | else if (Indent_Tab_Modulus < 0) { 1162 | for (int line_idx = 0; line_idx < selected_text_lines.count(); line_idx += 1) { 1163 | QString line_text = selected_text_lines.at(line_idx); 1164 | if (line_text.startsWith(QString(" ").repeated(-Indent_Tab_Modulus))) { 1165 | line_text = line_text.mid(-Indent_Tab_Modulus); 1166 | } 1167 | indented_text += line_text; 1168 | if (line_idx < (selected_text_lines.count() - 1)) indented_text += "\n"; 1169 | } 1170 | } 1171 | QPlainTextEdit::insertPlainText(indented_text); 1172 | new_txt_cursor.setPosition(start_position, QTextCursor::KeepAnchor); 1173 | QPlainTextEdit::setTextCursor(new_txt_cursor); 1174 | } 1175 | 1176 | void 1177 | JSEdit::insertFromMimeData ( const QMimeData* source ) { 1178 | if (source->hasText()) { 1179 | QString paste_text = source->text(); 1180 | // Convert tab characters into tab modulus spaces 1181 | paste_text = paste_text.replace("\t", QString(" ").repeated(JSEditPrivate_ref(Tab_Modulus))); 1182 | QStringList paste_lines = paste_text.split("\n"); 1183 | if (paste_lines.count() == 1) { 1184 | QPlainTextEdit::insertPlainText(paste_text); 1185 | } 1186 | else { 1187 | // Indent pasted text intelligently ... 1188 | int initial_position = QPlainTextEdit::textCursor().position(); 1189 | QPlainTextEdit::insertPlainText(paste_text); 1190 | QTextCursor txt_cursor = QPlainTextEdit::textCursor(); 1191 | 1192 | txt_cursor.setPosition(initial_position, QTextCursor::KeepAnchor); 1193 | QPlainTextEdit::setTextCursor(txt_cursor); 1194 | // ... using general formatting code 1195 | Format_Selected_Text_Lines(); 1196 | } 1197 | } 1198 | } 1199 | 1200 | void 1201 | JSEdit::onTextChanged ( ) { 1202 | JSEditPrivate_ref(Bracket_Source_Text) = this->toPlainText(); 1203 | JSEditPrivate_ref(Bracket_Text) = Compute_Bracket_Text(JSEditPrivate_ref(Bracket_Source_Text)); 1204 | } 1205 | 1206 | void 1207 | JSEdit::setColor ( ColorComponent component, const QColor &color ) { 1208 | if (component == Background) { 1209 | QPalette pal = palette(); 1210 | pal.setColor(QPalette::Base, color); 1211 | setPalette(pal); 1212 | JSEditPrivate_ref(sidebar)->indicatorColor = color; 1213 | updateSidebar(); 1214 | } else if (component == Normal) { 1215 | QPalette pal = palette(); 1216 | pal.setColor(QPalette::Text, color); 1217 | setPalette(pal); 1218 | } else if (component == Sidebar) { 1219 | JSEditPrivate_ref(sidebar)->backgroundColor = color; 1220 | updateSidebar(); 1221 | } else if (component == LineNumber) { 1222 | JSEditPrivate_ref(sidebar)->lineNumberColor = color; 1223 | updateSidebar(); 1224 | } else if (component == Cursor) { 1225 | JSEditPrivate_ref(cursorColor) = color; 1226 | updateCursor(); 1227 | } else if (component == BracketMatch) { 1228 | JSEditPrivate_ref(bracketMatchColor) = color; 1229 | updateCursor(); 1230 | } else if (component == BracketError) { 1231 | JSEditPrivate_ref(bracketErrorColor) = color; 1232 | updateCursor(); 1233 | } else if (component == FoldIndicator) { 1234 | JSEditPrivate_ref(sidebar)->foldIndicatorColor = color; 1235 | updateSidebar(); 1236 | } else { 1237 | JSEditPrivate_ref(highlighter)->setColor(component, color); 1238 | updateCursor(); 1239 | } 1240 | } 1241 | 1242 | void 1243 | JSEdit::setKeywords ( const QStringList &keywords ) { 1244 | JSEditPrivate_ref(highlighter)->setKeywords(keywords); 1245 | } 1246 | 1247 | QStringList 1248 | JSEdit::keywords ( ) const { 1249 | return JSEditPrivate_ref(highlighter)->keywords(); 1250 | } 1251 | 1252 | void 1253 | JSEdit::setLineNumbersVisible ( bool visible ) { 1254 | JSEditPrivate_ref(showLineNumbers) = visible; 1255 | updateSidebar(); 1256 | } 1257 | 1258 | bool 1259 | JSEdit::isLineNumbersVisible ( ) const { 1260 | return JSEditPrivate_ref(showLineNumbers); 1261 | } 1262 | 1263 | void 1264 | JSEdit::setTextWrapEnabled ( bool enable ) { 1265 | JSEditPrivate_ref(textWrap) = enable; 1266 | setLineWrapMode(enable ? WidgetWidth : NoWrap); 1267 | } 1268 | 1269 | bool 1270 | JSEdit::isTextWrapEnabled ( ) const { 1271 | return JSEditPrivate_ref(textWrap); 1272 | } 1273 | 1274 | void 1275 | JSEdit::setBracketsMatchingEnabled ( bool enable ) { 1276 | JSEditPrivate_ref(bracketsMatching) = enable; 1277 | updateCursor(); 1278 | } 1279 | 1280 | bool 1281 | JSEdit::isBracketsMatchingEnabled ( ) const { 1282 | return JSEditPrivate_ref(bracketsMatching); 1283 | } 1284 | 1285 | void 1286 | JSEdit::setAutoIndentEnabled ( bool enable ) { 1287 | JSEditPrivate_ref(AutoIndentEnabled) = enable; 1288 | } 1289 | 1290 | bool 1291 | JSEdit::isAutoIndentEnabled ( ) const { 1292 | return JSEditPrivate_ref(AutoIndentEnabled); 1293 | } 1294 | 1295 | void 1296 | JSEdit::Set_Tab_Modulus ( int New_Tab_Modulus ) { 1297 | JSEditPrivate_ref(Tab_Modulus) = New_Tab_Modulus; 1298 | } 1299 | 1300 | void 1301 | JSEdit::setCodeFoldingEnabled ( bool enable ) { 1302 | JSEditPrivate_ref(codeFolding) = enable; 1303 | updateSidebar(); 1304 | } 1305 | 1306 | bool 1307 | JSEdit::isCodeFoldingEnabled ( ) const { 1308 | return JSEditPrivate_ref(codeFolding); 1309 | } 1310 | 1311 | bool 1312 | JSEdit::isFoldable ( int line ) { 1313 | int matchPos = findClosingConstruct(document()->findBlockByNumber(line - 1)); 1314 | if (matchPos >= 0) { 1315 | QTextBlock matchBlock = document()->findBlock(matchPos); 1316 | if (matchBlock.isValid() && matchBlock.blockNumber() > line) 1317 | return true; 1318 | } 1319 | return false; 1320 | } 1321 | 1322 | bool 1323 | JSEdit::isFolded ( int line ) const { 1324 | QTextBlock block = document()->findBlockByNumber(line - 1); 1325 | if (!block.isValid()) 1326 | return false; 1327 | block = block.next(); 1328 | if (!block.isValid()) 1329 | return false; 1330 | return !block.isVisible(); 1331 | } 1332 | 1333 | void 1334 | JSEdit::fold ( int line ) { 1335 | QTextBlock startBlock = document()->findBlockByNumber(line - 1); 1336 | int endPos = findClosingConstruct(startBlock); 1337 | if (endPos < 0) 1338 | return; 1339 | QTextBlock endBlock = document()->findBlock(endPos); 1340 | 1341 | QTextBlock block = startBlock.next(); 1342 | while (block.isValid() && block != endBlock) { 1343 | block.setVisible(false); 1344 | block.setLineCount(0); 1345 | block = block.next(); 1346 | } 1347 | 1348 | document()->markContentsDirty(startBlock.position(), endPos - startBlock.position() + 1); 1349 | updateSidebar(); 1350 | update(); 1351 | 1352 | JSDocLayout *layout = reinterpret_cast(document()->documentLayout()); 1353 | layout->forceUpdate(); 1354 | } 1355 | 1356 | void 1357 | JSEdit::unfold ( int line ) { 1358 | QTextBlock startBlock = document()->findBlockByNumber(line - 1); 1359 | int endPos = findClosingConstruct(startBlock); 1360 | 1361 | QTextBlock block = startBlock.next(); 1362 | while (block.isValid() && !block.isVisible()) { 1363 | block.setVisible(true); 1364 | block.setLineCount(block.layout()->lineCount()); 1365 | endPos = block.position() + block.length(); 1366 | block = block.next(); 1367 | } 1368 | 1369 | document()->markContentsDirty(startBlock.position(), endPos - startBlock.position() + 1); 1370 | updateSidebar(); 1371 | update(); 1372 | 1373 | JSDocLayout *layout = reinterpret_cast(document()->documentLayout()); 1374 | layout->forceUpdate(); 1375 | } 1376 | 1377 | void 1378 | JSEdit::toggleFold ( int line ) { 1379 | if (isFolded(line)) 1380 | unfold(line); 1381 | else 1382 | fold(line); 1383 | } 1384 | 1385 | int 1386 | JSEdit::findClosingConstruct ( const QTextBlock &block ) { 1387 | if (!block.isValid()) 1388 | return -1; 1389 | JSBlockData *blockData = reinterpret_cast(block.userData()); 1390 | if (!blockData) 1391 | return -1; 1392 | if (blockData->bracketPositions.isEmpty()) 1393 | return -1; 1394 | const QTextDocument *doc = block.document(); 1395 | int offset = block.position(); 1396 | foreach (int pos, blockData->bracketPositions) { 1397 | int absPos = offset + pos; 1398 | if (doc->characterAt(absPos) == '{') { 1399 | int matchPos = Bracket_Match_Position(absPos); // findClosingMatch(doc, absPos); 1400 | if (matchPos >= 0) 1401 | return matchPos; 1402 | } 1403 | } 1404 | return -1; 1405 | } 1406 | 1407 | void 1408 | JSEdit::resizeEvent ( QResizeEvent *event ) { 1409 | QPlainTextEdit::resizeEvent(event); 1410 | updateSidebar(); 1411 | } 1412 | 1413 | void 1414 | JSEdit::wheelEvent ( QWheelEvent *event ) { 1415 | if (event->modifiers() == Qt::ControlModifier) { 1416 | int steps = event->delta() / 20; 1417 | steps = qBound(-3, steps, 3); 1418 | QFont textFont = font(); 1419 | int pointSize = textFont.pointSize() + steps; 1420 | pointSize = qBound(10, pointSize, 40); 1421 | textFont.setPointSize(pointSize); 1422 | setFont(textFont); 1423 | updateSidebar(); 1424 | event->accept(); 1425 | return; 1426 | } 1427 | QPlainTextEdit::wheelEvent(event); 1428 | } 1429 | 1430 | 1431 | void 1432 | JSEdit::updateCursor ( ) { 1433 | if (isReadOnly()) { 1434 | setExtraSelections(QList()); 1435 | } else { 1436 | 1437 | JSEditPrivate_ref(matchPositions).clear(); 1438 | JSEditPrivate_ref(errorPositions).clear(); 1439 | 1440 | if (JSEditPrivate_ref(bracketsMatching) /* && textCursor().block().userData() */ ) { 1441 | QTextCursor cursor = textCursor(); 1442 | int cursorPosition = cursor.position(); 1443 | 1444 | if ((document()->characterAt(cursorPosition) == '{') or 1445 | (document()->characterAt(cursorPosition) == '(')) { 1446 | int matchPos = Bracket_Match_Position(cursorPosition); // findClosingMatch(document(), cursorPosition); 1447 | if (matchPos < 0) { 1448 | JSEditPrivate_ref(errorPositions) += cursorPosition; 1449 | } else { 1450 | JSEditPrivate_ref(matchPositions) += cursorPosition; 1451 | JSEditPrivate_ref(matchPositions) += matchPos; 1452 | } 1453 | } 1454 | 1455 | if ((document()->characterAt(cursorPosition - 1) == '}') or 1456 | (document()->characterAt(cursorPosition - 1) == ')')) { 1457 | int matchPos = Bracket_Match_Position(cursorPosition - 1); // findOpeningMatch(document(), cursorPosition); 1458 | if (matchPos < 0) { 1459 | JSEditPrivate_ref(errorPositions) += cursorPosition - 1; 1460 | } else { 1461 | JSEditPrivate_ref(matchPositions) += cursorPosition - 1; 1462 | JSEditPrivate_ref(matchPositions) += matchPos; 1463 | } 1464 | } 1465 | } 1466 | 1467 | QTextEdit::ExtraSelection highlight; 1468 | highlight.format.setBackground(JSEditPrivate_ref(cursorColor)); 1469 | highlight.format.setProperty(QTextFormat::FullWidthSelection, true); 1470 | highlight.cursor = textCursor(); 1471 | highlight.cursor.clearSelection(); 1472 | 1473 | QList extraSelections; 1474 | extraSelections.append(highlight); 1475 | 1476 | for (int i = 0; i < JSEditPrivate_ref(matchPositions).count(); ++i) { 1477 | int pos = JSEditPrivate_ref(matchPositions).at(i); 1478 | QTextEdit::ExtraSelection matchHighlight; 1479 | matchHighlight.format.setBackground(JSEditPrivate_ref(bracketMatchColor)); 1480 | matchHighlight.cursor = textCursor(); 1481 | matchHighlight.cursor.setPosition(pos); 1482 | matchHighlight.cursor.setPosition(pos + 1, QTextCursor::KeepAnchor); 1483 | extraSelections.append(matchHighlight); 1484 | } 1485 | 1486 | for (int i = 0; i < JSEditPrivate_ref(errorPositions).count(); ++i) { 1487 | int pos = JSEditPrivate_ref(errorPositions).at(i); 1488 | QTextEdit::ExtraSelection errorHighlight; 1489 | errorHighlight.format.setBackground(JSEditPrivate_ref(bracketErrorColor)); 1490 | errorHighlight.cursor = textCursor(); 1491 | errorHighlight.cursor.setPosition(pos); 1492 | errorHighlight.cursor.setPosition(pos + 1, QTextCursor::KeepAnchor); 1493 | extraSelections.append(errorHighlight); 1494 | } 1495 | 1496 | setExtraSelections(extraSelections); 1497 | } 1498 | } 1499 | 1500 | void 1501 | JSEdit::updateSidebar ( const QRect &rect, int d ) { 1502 | Q_UNUSED(rect) 1503 | if (d != 0) 1504 | updateSidebar(); 1505 | } 1506 | 1507 | void 1508 | JSEdit::updateSidebar ( ) { 1509 | if (!JSEditPrivate_ref(showLineNumbers) && !JSEditPrivate_ref(codeFolding)) { 1510 | JSEditPrivate_ref(sidebar)->hide(); 1511 | setViewportMargins(0, 0, 0, 0); 1512 | JSEditPrivate_ref(sidebar)->setGeometry(3, 0, 0, height()); 1513 | return; 1514 | } 1515 | 1516 | JSEditPrivate_ref(sidebar)->foldIndicatorWidth = 0; 1517 | JSEditPrivate_ref(sidebar)->font = this->font(); 1518 | JSEditPrivate_ref(sidebar)->show(); 1519 | 1520 | int sw = 0; 1521 | if (JSEditPrivate_ref(showLineNumbers)) { 1522 | int digits = 2; 1523 | int maxLines = blockCount(); 1524 | for (int number = 10; number < maxLines; number *= 10) 1525 | ++digits; 1526 | sw += fontMetrics().width('w') * digits; 1527 | } 1528 | if (JSEditPrivate_ref(codeFolding)) { 1529 | int fh = fontMetrics().lineSpacing(); 1530 | int fw = fontMetrics().width('w'); 1531 | JSEditPrivate_ref(sidebar)->foldIndicatorWidth = qMax(fw, fh); 1532 | sw += JSEditPrivate_ref(sidebar)->foldIndicatorWidth; 1533 | } 1534 | setViewportMargins(sw, 0, 0, 0); 1535 | 1536 | JSEditPrivate_ref(sidebar)->setGeometry(0, 0, sw, height()); 1537 | QRectF sidebarRect(0, 0, sw, height()); 1538 | 1539 | QTextBlock block = firstVisibleBlock(); 1540 | int index = 0; 1541 | while (block.isValid()) { 1542 | if (block.isVisible()) { 1543 | QRectF rect = blockBoundingGeometry(block).translated(contentOffset()); 1544 | if (sidebarRect.intersects(rect)) { 1545 | if (JSEditPrivate_ref(sidebar)->lineNumbers.count() >= index) 1546 | JSEditPrivate_ref(sidebar)->lineNumbers.resize(index + 1); 1547 | JSEditPrivate_ref(sidebar)->lineNumbers[index].position = rect.top(); 1548 | JSEditPrivate_ref(sidebar)->lineNumbers[index].number = block.blockNumber() + 1; 1549 | JSEditPrivate_ref(sidebar)->lineNumbers[index].foldable = 1550 | JSEditPrivate_ref(codeFolding) ? isFoldable(block.blockNumber() + 1) : false; 1551 | JSEditPrivate_ref(sidebar)->lineNumbers[index].folded = 1552 | JSEditPrivate_ref(codeFolding) ? isFolded(block.blockNumber() + 1) : false; 1553 | ++index; 1554 | } 1555 | if (rect.top() > sidebarRect.bottom()) 1556 | break; 1557 | } 1558 | block = block.next(); 1559 | } 1560 | JSEditPrivate_ref(sidebar)->lineNumbers.resize(index); 1561 | JSEditPrivate_ref(sidebar)->update(); 1562 | } 1563 | 1564 | void 1565 | JSEdit::mark ( const QString &str, Qt::CaseSensitivity sens ) { 1566 | JSEditPrivate_ref(highlighter)->mark(str, sens); 1567 | } 1568 | -------------------------------------------------------------------------------- /JavaScript_Editor.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 Ken Crossen, bugs corrected, code cleaned up 4 | ** 5 | ** "Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are 7 | ** met: 8 | ** * Redistributions of source code must retain the above copyright 9 | ** notice, this list of conditions and the following disclaimer. 10 | ** * Redistributions in binary form must reproduce the above copyright 11 | ** notice, this list of conditions and the following disclaimer in 12 | ** the documentation and/or other materials provided with the 13 | ** distribution. 14 | ** * Redistributions in source code or binary form may not be sold. 15 | ** 16 | ****************************************************************************/ 17 | 18 | /* 19 | This file is part of the Ofi Labs X2 project. 20 | 21 | Copyright (C) 2011 Ariya Hidayat 22 | Copyright (C) 2010 Ariya Hidayat 23 | 24 | Redistribution and use in source and binary forms, with or without 25 | modification, are permitted provided that the following conditions are met: 26 | 27 | * Redistributions of source code must retain the above copyright 28 | notice, this list of conditions and the following disclaimer. 29 | * Redistributions in binary form must reproduce the above copyright 30 | notice, this list of conditions and the following disclaimer in the 31 | documentation and/or other materials provided with the distribution. 32 | * Neither the name of the nor the 33 | names of its contributors may be used to endorse or promote products 34 | derived from this software without specific prior written permission. 35 | 36 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 37 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 38 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 39 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 40 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 41 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 42 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 43 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 45 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 46 | */ 47 | 48 | #ifndef JAVASCRIPTEDIT_H 49 | #define JAVASCRIPTEDIT_H 50 | 51 | #include 52 | #include 53 | 54 | #include 55 | #include 56 | #include 57 | 58 | class JSEdit; 59 | class JSDocLayout; 60 | class SidebarWidget; 61 | class JSHighlighter; 62 | 63 | // class JSEditPrivate; 64 | class JSEditPrivate { 65 | public: 66 | JSEdit *editor; 67 | JSDocLayout *layout; 68 | JSHighlighter *highlighter; 69 | SidebarWidget *sidebar; 70 | bool showLineNumbers; 71 | bool textWrap; 72 | QColor cursorColor; 73 | bool bracketsMatching; 74 | QList matchPositions; 75 | QColor bracketMatchColor; 76 | QList errorPositions; 77 | QColor bracketErrorColor; 78 | bool codeFolding : 1; 79 | 80 | bool Brace_Bracket_Character; 81 | bool Quote_Bracket_Character; 82 | bool Post_Select_Bracket_Enclosed_Text; 83 | 84 | bool AutoIndentEnabled; 85 | int Tab_Modulus; 86 | 87 | QRegularExpression JavaScript_Bracket_RegEx; 88 | QString Bracket_Source_Text; 89 | QString Bracket_Text; 90 | 91 | // If this is to be a distributed library, new data members ... 92 | // ... should be added at the end of this "structure" (class). 93 | }; 94 | 95 | class JSEdit: public QPlainTextEdit 96 | { 97 | Q_OBJECT 98 | Q_PROPERTY(bool bracketsMatchingEnabled READ isBracketsMatchingEnabled WRITE setBracketsMatchingEnabled) 99 | Q_PROPERTY(bool codeFoldingEnabled READ isCodeFoldingEnabled WRITE setCodeFoldingEnabled) 100 | Q_PROPERTY(bool lineNumbersVisible READ isLineNumbersVisible WRITE setLineNumbersVisible) 101 | Q_PROPERTY(bool textWrapEnabled READ isTextWrapEnabled WRITE setTextWrapEnabled) 102 | Q_PROPERTY(bool AutoIndentEnabled READ isAutoIndentEnabled WRITE setAutoIndentEnabled) 103 | 104 | public: 105 | typedef enum { 106 | Background, 107 | Normal, 108 | Comment, 109 | Number, 110 | String, 111 | Operator, 112 | Identifier, 113 | Keyword, 114 | BuiltIn, 115 | Sidebar, 116 | LineNumber, 117 | Cursor, 118 | Marker, 119 | BracketMatch, 120 | BracketError, 121 | FoldIndicator 122 | } ColorComponent; 123 | 124 | JSEdit ( QWidget *parent = 0 ); 125 | ~JSEdit ( ); 126 | 127 | #ifdef use_d_pointer 128 | #else 129 | JSEditPrivate JSEdit_Private; 130 | #endif 131 | 132 | void 133 | Set_PlainText ( QString Set_Plain_Text ); 134 | 135 | void 136 | setColor ( ColorComponent component, 137 | const QColor &color ); 138 | 139 | QStringList keywords ( ) const; 140 | 141 | void 142 | setKeywords ( const QStringList &keywords ); 143 | 144 | public slots: 145 | void 146 | setBracketsMatchingEnabled ( bool enable ); 147 | 148 | public: 149 | bool 150 | isBracketsMatchingEnabled ( ) const; 151 | 152 | public slots: 153 | void 154 | setLineNumbersVisible ( bool visible ); 155 | 156 | public: 157 | bool 158 | isLineNumbersVisible ( ) const; 159 | 160 | public slots: 161 | void 162 | setTextWrapEnabled ( bool enable ); 163 | 164 | public: 165 | bool 166 | isTextWrapEnabled ( ) const; 167 | 168 | public slots: 169 | void 170 | setAutoIndentEnabled ( bool enable ); 171 | 172 | public: 173 | bool 174 | isAutoIndentEnabled ( ) const; 175 | 176 | public: 177 | void 178 | Set_Tab_Modulus ( int New_Tab_modulus ); 179 | 180 | public slots: 181 | void 182 | setCodeFoldingEnabled ( bool enable ); 183 | 184 | public: 185 | bool 186 | isCodeFoldingEnabled ( ) const; 187 | 188 | bool 189 | isFoldable ( int line ); 190 | 191 | bool 192 | isFolded ( int line ) const; 193 | 194 | public slots: 195 | void 196 | fold ( int line ); 197 | 198 | void 199 | unfold ( int line ); 200 | 201 | void 202 | toggleFold ( int line ); 203 | 204 | public slots: 205 | void 206 | mark ( const QString &str, 207 | Qt::CaseSensitivity sens = Qt::CaseInsensitive ); 208 | 209 | protected: 210 | void 211 | keyPressEvent ( QKeyEvent* event ); 212 | 213 | void 214 | resizeEvent ( QResizeEvent* event ); 215 | 216 | void 217 | wheelEvent ( QWheelEvent* event ); 218 | 219 | void 220 | insertFromMimeData ( const QMimeData* source ); 221 | 222 | private: 223 | #define JavaScript_Bracket_List "{}()" 224 | #define Default_Tab_Modulus 4 225 | 226 | QString 227 | Compute_Bracket_Text ( QString Source_Text ); 228 | 229 | int 230 | Bracket_Match_Position ( int Current_Position ); 231 | 232 | int 233 | Compute_Current_Brace_Indent_Level ( int Current_Position ); 234 | 235 | int 236 | Compute_Current_Brace_Indent ( int Current_Position ); 237 | 238 | int 239 | Compute_Current_Paren_Indent ( QString Current_Text, 240 | int Current_Position ); 241 | 242 | QTextCursor 243 | Select_Selected_Text_Lines ( ); 244 | 245 | void 246 | Format_Selected_Text_Lines ( ); 247 | 248 | void 249 | Indent_Selected_Text_Lines ( int Indent_Tab_Modulus ); 250 | 251 | private slots: 252 | void 253 | onTextChanged ( ); 254 | 255 | private: 256 | // int 257 | // findClosingMatch ( const QTextDocument *doc, int cursorPosition ); 258 | 259 | int 260 | findClosingConstruct ( const QTextBlock &block ); 261 | 262 | private slots: 263 | void 264 | updateCursor ( ); 265 | 266 | public slots: 267 | void 268 | updateSidebar ( ); 269 | 270 | private slots: 271 | void 272 | updateSidebar ( const QRect &rect, 273 | int d ); 274 | 275 | private: 276 | #ifdef use_d_pointer 277 | QScopedPointer d_ptr; 278 | #endif 279 | Q_DECLARE_PRIVATE(JSEdit); 280 | Q_DISABLE_COPY(JSEdit); 281 | }; 282 | 283 | #endif // JAVASCRIPTEDIT_H 284 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qt5.6-JavaScript-Editor-Widget 2 | Qt5.6 C++ JavaScript editor widget w/ syntax highlighting, auto-formatting, code folding, paren and brace matching, plus other typical IDE code editor features. To see how this is used, see RegExIDE repository, which also includes a duktape-based execution environment. 3 | --------------------------------------------------------------------------------