fb2code.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. #include "fb2code.hpp"
  2. #include <QApplication>
  3. #include <QXmlSchema>
  4. #include <QAbstractMessageHandler>
  5. #include <QXmlSchemaValidator>
  6. #include "fb2dlgs.hpp"
  7. //---------------------------------------------------------------------------
  8. // FbHighlighter
  9. //---------------------------------------------------------------------------
  10. class FbSchemaHandler : public QAbstractMessageHandler
  11. {
  12. public:
  13. FbSchemaHandler()
  14. : QAbstractMessageHandler(0)
  15. {
  16. }
  17. QString statusMessage() const
  18. {
  19. return m_description;
  20. }
  21. int line() const
  22. {
  23. return m_sourceLocation.line();
  24. }
  25. int column() const
  26. {
  27. return m_sourceLocation.column();
  28. }
  29. protected:
  30. virtual void handleMessage(QtMsgType type, const QString &description,
  31. const QUrl &identifier, const QSourceLocation &sourceLocation)
  32. {
  33. Q_UNUSED(type);
  34. Q_UNUSED(identifier);
  35. m_messageType = type;
  36. m_description = description;
  37. m_sourceLocation = sourceLocation;
  38. }
  39. private:
  40. QtMsgType m_messageType;
  41. QString m_description;
  42. QSourceLocation m_sourceLocation;
  43. };
  44. //---------------------------------------------------------------------------
  45. // FbHighlighter
  46. //---------------------------------------------------------------------------
  47. #include <QXmlInputSource>
  48. #include <QtGui>
  49. #include <QSyntaxHighlighter>
  50. class FbHighlighter : public QSyntaxHighlighter
  51. {
  52. public:
  53. FbHighlighter(QObject* parent);
  54. FbHighlighter(QTextDocument* parent);
  55. FbHighlighter(QTextEdit* parent);
  56. ~FbHighlighter();
  57. enum HighlightType
  58. {
  59. SyntaxChar,
  60. ElementName,
  61. Comment,
  62. AttributeName,
  63. AttributeValue,
  64. Error,
  65. Other
  66. };
  67. void setHighlightColor(HighlightType type, QColor color, bool foreground = true);
  68. void setHighlightFormat(HighlightType type, QTextCharFormat format);
  69. protected:
  70. void highlightBlock(const QString& rstrText);
  71. int processDefaultText(int i, const QString& rstrText);
  72. private:
  73. void init();
  74. QTextCharFormat fmtSyntaxChar;
  75. QTextCharFormat fmtElementName;
  76. QTextCharFormat fmtComment;
  77. QTextCharFormat fmtAttributeName;
  78. QTextCharFormat fmtAttributeValue;
  79. QTextCharFormat fmtError;
  80. QTextCharFormat fmtOther;
  81. enum ParsingState
  82. {
  83. NoState = 0,
  84. ExpectElementNameOrSlash,
  85. ExpectElementName,
  86. ExpectAttributeOrEndOfElement,
  87. ExpectEqual,
  88. ExpectAttributeValue
  89. };
  90. enum BlockState
  91. {
  92. NoBlock = -1,
  93. InComment,
  94. InElement
  95. };
  96. ParsingState state;
  97. };
  98. static const QColor DEFAULT_SYNTAX_CHAR = Qt::blue;
  99. static const QColor DEFAULT_ELEMENT_NAME = Qt::darkRed;
  100. static const QColor DEFAULT_COMMENT = Qt::darkGray;
  101. static const QColor DEFAULT_ATTRIBUTE_NAME = Qt::red;
  102. static const QColor DEFAULT_ATTRIBUTE_VALUE = Qt::darkGreen;
  103. static const QColor DEFAULT_ERROR = Qt::darkMagenta;
  104. static const QColor DEFAULT_OTHER = Qt::black;
  105. // Regular expressions for parsing XML borrowed from:
  106. // http://www.cs.sfu.ca/~cameron/REX.html
  107. static const QString EXPR_COMMENT = "<!--[^-]*-([^-][^-]*-)*->";
  108. static const QString EXPR_COMMENT_BEGIN = "<!--";
  109. static const QString EXPR_COMMENT_END = "[^-]*-([^-][^-]*-)*->";
  110. static const QString EXPR_ATTRIBUTE_VALUE = "\"[^<\"]*\"|'[^<']*'";
  111. static const QString EXPR_NAME = "([A-Za-z_:]|[^\\x00-\\x7F])([A-Za-z0-9_:.-]|[^\\x00-\\x7F])*";
  112. FbHighlighter::FbHighlighter(QObject* parent)
  113. : QSyntaxHighlighter(parent)
  114. {
  115. init();
  116. }
  117. FbHighlighter::FbHighlighter(QTextDocument* parent)
  118. : QSyntaxHighlighter(parent)
  119. {
  120. init();
  121. }
  122. FbHighlighter::FbHighlighter(QTextEdit* parent)
  123. : QSyntaxHighlighter(parent)
  124. {
  125. init();
  126. }
  127. FbHighlighter::~FbHighlighter()
  128. {
  129. }
  130. void FbHighlighter::init()
  131. {
  132. fmtSyntaxChar.setForeground(DEFAULT_SYNTAX_CHAR);
  133. fmtElementName.setForeground(DEFAULT_ELEMENT_NAME);
  134. fmtComment.setForeground(DEFAULT_COMMENT);
  135. fmtAttributeName.setForeground(DEFAULT_ATTRIBUTE_NAME);
  136. fmtAttributeValue.setForeground(DEFAULT_ATTRIBUTE_VALUE);
  137. fmtError.setForeground(DEFAULT_ERROR);
  138. fmtOther.setForeground(DEFAULT_OTHER);
  139. }
  140. void FbHighlighter::setHighlightColor(HighlightType type, QColor color, bool foreground)
  141. {
  142. QTextCharFormat format;
  143. if (foreground)
  144. format.setForeground(color);
  145. else
  146. format.setBackground(color);
  147. setHighlightFormat(type, format);
  148. }
  149. void FbHighlighter::setHighlightFormat(HighlightType type, QTextCharFormat format)
  150. {
  151. switch (type)
  152. {
  153. case SyntaxChar:
  154. fmtSyntaxChar = format;
  155. break;
  156. case ElementName:
  157. fmtElementName = format;
  158. break;
  159. case Comment:
  160. fmtComment = format;
  161. break;
  162. case AttributeName:
  163. fmtAttributeName = format;
  164. break;
  165. case AttributeValue:
  166. fmtAttributeValue = format;
  167. break;
  168. case Error:
  169. fmtError = format;
  170. break;
  171. case Other:
  172. fmtOther = format;
  173. break;
  174. }
  175. rehighlight();
  176. }
  177. void FbHighlighter::highlightBlock(const QString& text)
  178. {
  179. int i = 0;
  180. int pos = 0;
  181. int brackets = 0;
  182. state = (previousBlockState() == InElement ? ExpectAttributeOrEndOfElement : NoState);
  183. if (previousBlockState() == InComment)
  184. {
  185. // search for the end of the comment
  186. QRegExp expression(EXPR_COMMENT_END);
  187. pos = expression.indexIn(text, i);
  188. if (pos >= 0)
  189. {
  190. // end comment found
  191. const int iLength = expression.matchedLength();
  192. setFormat(0, iLength - 3, fmtComment);
  193. setFormat(iLength - 3, 3, fmtSyntaxChar);
  194. i += iLength; // skip comment
  195. }
  196. else
  197. {
  198. // in comment
  199. setFormat(0, text.length(), fmtComment);
  200. setCurrentBlockState(InComment);
  201. return;
  202. }
  203. }
  204. const int len = text.length();
  205. for (; i < len; ++i)
  206. {
  207. switch (text.at(i).toLatin1())
  208. {
  209. case '<':
  210. ++brackets;
  211. if (brackets == 1)
  212. {
  213. setFormat(i, 1, fmtSyntaxChar);
  214. state = ExpectElementNameOrSlash;
  215. }
  216. else
  217. {
  218. // wrong bracket nesting
  219. setFormat(i, 1, fmtError);
  220. }
  221. break;
  222. case '>':
  223. --brackets;
  224. if (brackets == 0)
  225. {
  226. setFormat(i, 1, fmtSyntaxChar);
  227. }
  228. else
  229. {
  230. // wrong bracket nesting
  231. setFormat( i, 1, fmtError);
  232. }
  233. state = NoState;
  234. break;
  235. case '/':
  236. if (state == ExpectElementNameOrSlash)
  237. {
  238. state = ExpectElementName;
  239. setFormat(i, 1, fmtSyntaxChar);
  240. }
  241. else
  242. {
  243. if (state == ExpectAttributeOrEndOfElement)
  244. {
  245. setFormat(i, 1, fmtSyntaxChar);
  246. }
  247. else
  248. {
  249. processDefaultText(i, text);
  250. }
  251. }
  252. break;
  253. case '=':
  254. if (state == ExpectEqual)
  255. {
  256. state = ExpectAttributeValue;
  257. setFormat(i, 1, fmtOther);
  258. }
  259. else
  260. {
  261. processDefaultText(i, text);
  262. }
  263. break;
  264. case '\'':
  265. case '\"':
  266. if (state == ExpectAttributeValue)
  267. {
  268. // search attribute value
  269. QRegExp expression(EXPR_ATTRIBUTE_VALUE);
  270. pos = expression.indexIn(text, i);
  271. if (pos == i) // attribute value found ?
  272. {
  273. const int iLength = expression.matchedLength();
  274. setFormat(i, 1, fmtOther);
  275. setFormat(i + 1, iLength - 2, fmtAttributeValue);
  276. setFormat(i + iLength - 1, 1, fmtOther);
  277. i += iLength - 1; // skip attribute value
  278. state = ExpectAttributeOrEndOfElement;
  279. }
  280. else
  281. {
  282. processDefaultText(i, text);
  283. }
  284. }
  285. else
  286. {
  287. processDefaultText(i, text);
  288. }
  289. break;
  290. case '!':
  291. if (state == ExpectElementNameOrSlash)
  292. {
  293. // search comment
  294. QRegExp expression(EXPR_COMMENT);
  295. pos = expression.indexIn(text, i - 1);
  296. if (pos == i - 1) // comment found ?
  297. {
  298. const int iLength = expression.matchedLength();
  299. setFormat(pos, 4, fmtSyntaxChar);
  300. setFormat(pos + 4, iLength - 7, fmtComment);
  301. setFormat(iLength - 3, 3, fmtSyntaxChar);
  302. i += iLength - 2; // skip comment
  303. state = NoState;
  304. --brackets;
  305. }
  306. else
  307. {
  308. // Try find multiline comment
  309. QRegExp expression(EXPR_COMMENT_BEGIN); // search comment start
  310. pos = expression.indexIn(text, i - 1);
  311. //if (pos == i - 1) // comment found ?
  312. if (pos >= i - 1)
  313. {
  314. setFormat(i, 3, fmtSyntaxChar);
  315. setFormat(i + 3, text.length() - i - 3, fmtComment);
  316. setCurrentBlockState(InComment);
  317. return;
  318. }
  319. else
  320. {
  321. processDefaultText(i, text);
  322. }
  323. }
  324. }
  325. else
  326. {
  327. processDefaultText(i, text);
  328. }
  329. break;
  330. default:
  331. const int iLength = processDefaultText(i, text);
  332. if (iLength > 0)
  333. i += iLength - 1;
  334. break;
  335. }
  336. }
  337. if (state == ExpectAttributeOrEndOfElement)
  338. {
  339. setCurrentBlockState(InElement);
  340. }
  341. }
  342. int FbHighlighter::processDefaultText(int i, const QString& text)
  343. {
  344. // length of matched text
  345. int iLength = 0;
  346. switch(state)
  347. {
  348. case ExpectElementNameOrSlash:
  349. case ExpectElementName:
  350. {
  351. // search element name
  352. QRegExp expression(EXPR_NAME);
  353. const int pos = expression.indexIn(text, i);
  354. if (pos == i) // found ?
  355. {
  356. iLength = expression.matchedLength();
  357. setFormat(pos, iLength, fmtElementName);
  358. state = ExpectAttributeOrEndOfElement;
  359. }
  360. else
  361. {
  362. setFormat(i, 1, fmtOther);
  363. }
  364. }
  365. break;
  366. case ExpectAttributeOrEndOfElement:
  367. {
  368. // search attribute name
  369. QRegExp expression(EXPR_NAME);
  370. const int pos = expression.indexIn(text, i);
  371. if (pos == i) // found ?
  372. {
  373. iLength = expression.matchedLength();
  374. setFormat(pos, iLength, fmtAttributeName);
  375. state = ExpectEqual;
  376. }
  377. else
  378. {
  379. setFormat(i, 1, fmtOther);
  380. }
  381. }
  382. break;
  383. default:
  384. setFormat(i, 1, fmtOther);
  385. break;
  386. }
  387. return iLength;
  388. }
  389. //---------------------------------------------------------------------------
  390. // FbCodeEdit
  391. //---------------------------------------------------------------------------
  392. qreal FbCodeEdit::baseFontSize = 10;
  393. qreal FbCodeEdit::zoomRatioMin = 0.2;
  394. qreal FbCodeEdit::zoomRatioMax = 5.0;
  395. FbCodeEdit::FbCodeEdit(QWidget *parent) : QPlainTextEdit(parent)
  396. {
  397. lineNumberArea = new LineNumberArea(this);
  398. FbHighlighter *highlighter = new FbHighlighter(this);
  399. highlighter->setDocument( document() );
  400. connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
  401. connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int)));
  402. connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
  403. zoomRatio = 1;
  404. QFont f("Monospace", baseFontSize);
  405. f.setStyleHint(f.TypeWriter, f.PreferDefault);
  406. setFont(f);
  407. updateLineNumberAreaWidth(0);
  408. highlightCurrentLine();
  409. }
  410. QAction * FbCodeEdit::act(Fb::Actions index) const
  411. {
  412. return m_actions[index];
  413. }
  414. void FbCodeEdit::setAction(Fb::Actions index, QAction *action)
  415. {
  416. m_actions[index] = action;
  417. }
  418. void FbCodeEdit::connectActions(QToolBar *tool)
  419. {
  420. act(Fb::EditUndo)->setEnabled(document()->isUndoAvailable());
  421. act(Fb::EditRedo)->setEnabled(document()->isRedoAvailable());
  422. act(Fb::EditFind)->setEnabled(true);
  423. act(Fb::CheckText)->setEnabled(true);
  424. act(Fb::ZoomIn)->setEnabled(true);
  425. act(Fb::ZoomOut)->setEnabled(true);
  426. act(Fb::ZoomReset)->setEnabled(true);
  427. connect(act(Fb::EditUndo), SIGNAL(triggered()), SLOT(undo()));
  428. connect(act(Fb::EditRedo), SIGNAL(triggered()), SLOT(redo()));
  429. connect(this, SIGNAL(undoAvailable(bool)), act(Fb::EditUndo), SLOT(setEnabled(bool)));
  430. connect(this, SIGNAL(redoAvailable(bool)), act(Fb::EditRedo), SLOT(setEnabled(bool)));
  431. connect(act(Fb::EditCut), SIGNAL(triggered()), SLOT(cut()));
  432. connect(act(Fb::EditCopy), SIGNAL(triggered()), SLOT(copy()));
  433. connect(act(Fb::EditPaste), SIGNAL(triggered()), SLOT(paste()));
  434. connect(act(Fb::EditFind), SIGNAL(triggered()), SLOT(find()));
  435. connect(act(Fb::CheckText), SIGNAL(triggered()), SLOT(validate()));
  436. connect(this, SIGNAL(copyAvailable(bool)), act(Fb::EditCut), SLOT(setEnabled(bool)));
  437. connect(this, SIGNAL(copyAvailable(bool)), act(Fb::EditCopy), SLOT(setEnabled(bool)));
  438. connect(qApp->clipboard(), SIGNAL(dataChanged()), SLOT(clipboardDataChanged()));
  439. clipboardDataChanged();
  440. connect(act(Fb::ZoomIn), SIGNAL(triggered()), SLOT(zoomIn()));
  441. connect(act(Fb::ZoomOut), SIGNAL(triggered()), SLOT(zoomOut()));
  442. connect(act(Fb::ZoomReset), SIGNAL(triggered()), SLOT(zoomReset()));
  443. tool->clear();
  444. tool->addSeparator();
  445. tool->addAction(act(Fb::EditUndo));
  446. tool->addAction(act(Fb::EditRedo));
  447. tool->addSeparator();
  448. tool->addAction(act(Fb::EditCut));
  449. tool->addAction(act(Fb::EditCopy));
  450. tool->addAction(act(Fb::EditPaste));
  451. tool->addSeparator();
  452. tool->addAction(act(Fb::ZoomIn));
  453. tool->addAction(act(Fb::ZoomOut));
  454. tool->addAction(act(Fb::ZoomReset));
  455. }
  456. void FbCodeEdit::disconnectActions()
  457. {
  458. m_actions.disconnect();
  459. }
  460. void FbCodeEdit::clipboardDataChanged()
  461. {
  462. if (const QMimeData *md = QApplication::clipboard()->mimeData()) {
  463. act(Fb::EditPaste)->setEnabled(md->hasText());
  464. }
  465. }
  466. bool FbCodeEdit::read(QIODevice *device)
  467. {
  468. QByteArray data = device->readAll();
  469. delete device;
  470. QXmlInputSource source;
  471. source.setData(data);
  472. setPlainText(source.data());
  473. return true;
  474. }
  475. int FbCodeEdit::lineNumberAreaWidth()
  476. {
  477. int digits = 1;
  478. int max = qMax(1, blockCount());
  479. while (max >= 10) {
  480. max /= 10;
  481. ++digits;
  482. }
  483. int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits;
  484. return space;
  485. }
  486. void FbCodeEdit::updateLineNumberAreaWidth(int /* newBlockCount */)
  487. {
  488. setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
  489. }
  490. void FbCodeEdit::updateLineNumberArea(const QRect &rect, int dy)
  491. {
  492. if (dy)
  493. lineNumberArea->scroll(0, dy);
  494. else
  495. lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
  496. if (rect.contains(viewport()->rect()))
  497. updateLineNumberAreaWidth(0);
  498. }
  499. void FbCodeEdit::resizeEvent(QResizeEvent *e)
  500. {
  501. QPlainTextEdit::resizeEvent(e);
  502. QRect cr = contentsRect();
  503. lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
  504. }
  505. void FbCodeEdit::highlightCurrentLine()
  506. {
  507. QList<QTextEdit::ExtraSelection> extraSelections;
  508. if (!isReadOnly()) {
  509. QTextEdit::ExtraSelection selection;
  510. QColor lineColor = QColor(Qt::yellow).lighter(160);
  511. selection.format.setBackground(lineColor);
  512. selection.format.setProperty(QTextFormat::FullWidthSelection, true);
  513. selection.cursor = textCursor();
  514. selection.cursor.clearSelection();
  515. extraSelections.append(selection);
  516. }
  517. setExtraSelections(extraSelections);
  518. }
  519. void FbCodeEdit::lineNumberAreaPaintEvent(QPaintEvent *event)
  520. {
  521. QPainter painter(lineNumberArea);
  522. painter.fillRect(event->rect(), Qt::lightGray);
  523. QTextBlock block = firstVisibleBlock();
  524. int blockNumber = block.blockNumber();
  525. int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
  526. int bottom = top + (int) blockBoundingRect(block).height();
  527. while (block.isValid() && top <= event->rect().bottom()) {
  528. if (block.isVisible() && bottom >= event->rect().top()) {
  529. QString number = QString::number(blockNumber + 1);
  530. painter.setPen(Qt::black);
  531. painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
  532. Qt::AlignRight, number);
  533. }
  534. block = block.next();
  535. top = bottom;
  536. bottom = top + (int) blockBoundingRect(block).height();
  537. ++blockNumber;
  538. }
  539. }
  540. bool FbCodeEdit::findText(const QString &exp, QTextDocument::FindFlags options)
  541. {
  542. return QPlainTextEdit::find(exp, options);
  543. }
  544. void FbCodeEdit::find()
  545. {
  546. FbCodeFindDlg dlg(*this);
  547. dlg.exec();
  548. }
  549. void FbCodeEdit::zoomIn()
  550. {
  551. qreal ratio = zoomRatio * 1.1;
  552. ratio = qMin(ratio, zoomRatioMax);
  553. setZoomRatio(ratio);
  554. }
  555. void FbCodeEdit::zoomOut()
  556. {
  557. qreal ratio = zoomRatio / 1.1;
  558. ratio = qMax(ratio, zoomRatioMin);
  559. setZoomRatio(ratio);
  560. }
  561. void FbCodeEdit::zoomReset()
  562. {
  563. setZoomRatio(1.0);
  564. }
  565. void FbCodeEdit::setZoomRatio(qreal ratio)
  566. {
  567. if (!qFuzzyCompare(1 + zoomRatio, 1 + ratio)) {
  568. zoomRatio = ratio;
  569. QFont f = font();
  570. f.setPointSizeF(baseFontSize * zoomRatio);
  571. setFont(f);
  572. }
  573. }
  574. void FbCodeEdit::validate()
  575. {
  576. FbSchemaHandler handler;
  577. QXmlSchema schema;
  578. schema.setMessageHandler(&handler);
  579. QUrl url("qrc:/fb2/FictionBook2.1.xsd");
  580. schema.load(url);
  581. if (!schema.isValid()) {
  582. status(tr("Schema is not valid: ") + handler.statusMessage());
  583. return;
  584. }
  585. const QByteArray data = toPlainText().toUtf8();
  586. QXmlSchemaValidator validator(schema);
  587. if (!validator.validate(data)) {
  588. setCursor(handler.line(), handler.column());
  589. status(handler.statusMessage());
  590. } else {
  591. status(tr("Validation successful"));
  592. }
  593. }
  594. void FbCodeEdit::setCursor(int line, int column)
  595. {
  596. moveCursor(QTextCursor::Start);
  597. for (int i = 1; i < line; ++i)
  598. moveCursor(QTextCursor::Down);
  599. for (int i = 1; i < column; ++i)
  600. moveCursor(QTextCursor::Right);
  601. QList<QTextEdit::ExtraSelection> extraSelections;
  602. QTextEdit::ExtraSelection selection;
  603. const QColor lineColor = QColor(Qt::red).lighter(160);
  604. selection.format.setBackground(lineColor);
  605. selection.format.setProperty(QTextFormat::FullWidthSelection, true);
  606. selection.cursor = textCursor();
  607. selection.cursor.clearSelection();
  608. extraSelections.append(selection);
  609. setExtraSelections(extraSelections);
  610. setFocus();
  611. }