fb2code.cpp 19 KB

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