fb2code.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  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. for (; i < text.length(); i++)
  204. {
  205. switch (text.at(i).toAscii())
  206. {
  207. case '<':
  208. brackets++;
  209. if (brackets == 1)
  210. {
  211. setFormat(i, 1, fmtSyntaxChar);
  212. state = ExpectElementNameOrSlash;
  213. }
  214. else
  215. {
  216. // wrong bracket nesting
  217. setFormat(i, 1, fmtError);
  218. }
  219. break;
  220. case '>':
  221. brackets--;
  222. if (brackets == 0)
  223. {
  224. setFormat(i, 1, fmtSyntaxChar);
  225. }
  226. else
  227. {
  228. // wrong bracket nesting
  229. setFormat( i, 1, fmtError);
  230. }
  231. state = NoState;
  232. break;
  233. case '/':
  234. if (state == ExpectElementNameOrSlash)
  235. {
  236. state = ExpectElementName;
  237. setFormat(i, 1, fmtSyntaxChar);
  238. }
  239. else
  240. {
  241. if (state == ExpectAttributeOrEndOfElement)
  242. {
  243. setFormat(i, 1, fmtSyntaxChar);
  244. }
  245. else
  246. {
  247. processDefaultText(i, text);
  248. }
  249. }
  250. break;
  251. case '=':
  252. if (state == ExpectEqual)
  253. {
  254. state = ExpectAttributeValue;
  255. setFormat(i, 1, fmtOther);
  256. }
  257. else
  258. {
  259. processDefaultText(i, text);
  260. }
  261. break;
  262. case '\'':
  263. case '\"':
  264. if (state == ExpectAttributeValue)
  265. {
  266. // search attribute value
  267. QRegExp expression(EXPR_ATTRIBUTE_VALUE);
  268. pos = expression.indexIn(text, i);
  269. if (pos == i) // attribute value found ?
  270. {
  271. const int iLength = expression.matchedLength();
  272. setFormat(i, 1, fmtOther);
  273. setFormat(i + 1, iLength - 2, fmtAttributeValue);
  274. setFormat(i + iLength - 1, 1, fmtOther);
  275. i += iLength - 1; // skip attribute value
  276. state = ExpectAttributeOrEndOfElement;
  277. }
  278. else
  279. {
  280. processDefaultText(i, text);
  281. }
  282. }
  283. else
  284. {
  285. processDefaultText(i, text);
  286. }
  287. break;
  288. case '!':
  289. if (state == ExpectElementNameOrSlash)
  290. {
  291. // search comment
  292. QRegExp expression(EXPR_COMMENT);
  293. pos = expression.indexIn(text, i - 1);
  294. if (pos == i - 1) // comment found ?
  295. {
  296. const int iLength = expression.matchedLength();
  297. setFormat(pos, 4, fmtSyntaxChar);
  298. setFormat(pos + 4, iLength - 7, fmtComment);
  299. setFormat(iLength - 3, 3, fmtSyntaxChar);
  300. i += iLength - 2; // skip comment
  301. state = NoState;
  302. brackets--;
  303. }
  304. else
  305. {
  306. // Try find multiline comment
  307. QRegExp expression(EXPR_COMMENT_BEGIN); // search comment start
  308. pos = expression.indexIn(text, i - 1);
  309. //if (pos == i - 1) // comment found ?
  310. if (pos >= i - 1)
  311. {
  312. setFormat(i, 3, fmtSyntaxChar);
  313. setFormat(i + 3, text.length() - i - 3, fmtComment);
  314. setCurrentBlockState(InComment);
  315. return;
  316. }
  317. else
  318. {
  319. processDefaultText(i, text);
  320. }
  321. }
  322. }
  323. else
  324. {
  325. processDefaultText(i, text);
  326. }
  327. break;
  328. default:
  329. const int iLength = processDefaultText(i, text);
  330. if (iLength > 0)
  331. i += iLength - 1;
  332. break;
  333. }
  334. }
  335. if (state == ExpectAttributeOrEndOfElement)
  336. {
  337. setCurrentBlockState(InElement);
  338. }
  339. }
  340. int FbHighlighter::processDefaultText(int i, const QString& text)
  341. {
  342. // length of matched text
  343. int iLength = 0;
  344. switch(state)
  345. {
  346. case ExpectElementNameOrSlash:
  347. case ExpectElementName:
  348. {
  349. // search element name
  350. QRegExp expression(EXPR_NAME);
  351. const int pos = expression.indexIn(text, i);
  352. if (pos == i) // found ?
  353. {
  354. iLength = expression.matchedLength();
  355. setFormat(pos, iLength, fmtElementName);
  356. state = ExpectAttributeOrEndOfElement;
  357. }
  358. else
  359. {
  360. setFormat(i, 1, fmtOther);
  361. }
  362. }
  363. break;
  364. case ExpectAttributeOrEndOfElement:
  365. {
  366. // search attribute name
  367. QRegExp expression(EXPR_NAME);
  368. const int pos = expression.indexIn(text, i);
  369. if (pos == i) // found ?
  370. {
  371. iLength = expression.matchedLength();
  372. setFormat(pos, iLength, fmtAttributeName);
  373. state = ExpectEqual;
  374. }
  375. else
  376. {
  377. setFormat(i, 1, fmtOther);
  378. }
  379. }
  380. break;
  381. default:
  382. setFormat(i, 1, fmtOther);
  383. break;
  384. }
  385. return iLength;
  386. }
  387. //---------------------------------------------------------------------------
  388. // FbCodeEdit
  389. //---------------------------------------------------------------------------
  390. qreal FbCodeEdit::baseFontSize = 10;
  391. qreal FbCodeEdit::zoomRatioMin = 0.2;
  392. qreal FbCodeEdit::zoomRatioMax = 5.0;
  393. FbCodeEdit::FbCodeEdit(QWidget *parent) : QPlainTextEdit(parent)
  394. {
  395. lineNumberArea = new LineNumberArea(this);
  396. FbHighlighter *highlighter = new FbHighlighter(this);
  397. highlighter->setDocument( document() );
  398. connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
  399. connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int)));
  400. connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
  401. zoomRatio = 1;
  402. #ifdef Q_WS_WIN
  403. setFont(QFont("Courier New", baseFontSize));
  404. #else
  405. setFont(QFont("Monospace", baseFontSize));
  406. #endif
  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. }