vedit.cpp 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232
  1. #include <QtWidgets>
  2. #include <QVector>
  3. #include <QDebug>
  4. #include "vedit.h"
  5. #include "vnote.h"
  6. #include "vconfigmanager.h"
  7. #include "vtoc.h"
  8. #include "utils/vutils.h"
  9. #include "utils/veditutils.h"
  10. #include "veditoperations.h"
  11. #include "vedittab.h"
  12. extern VConfigManager vconfig;
  13. extern VNote *g_vnote;
  14. void VEditConfig::init(const QFontMetrics &p_metric)
  15. {
  16. update(p_metric);
  17. m_enableVimMode = vconfig.getEnableVimMode();
  18. m_highlightWholeBlock = m_enableVimMode;
  19. }
  20. void VEditConfig::update(const QFontMetrics &p_metric)
  21. {
  22. if (vconfig.getTabStopWidth() > 0) {
  23. m_tabStopWidth = vconfig.getTabStopWidth() * p_metric.width(' ');
  24. } else {
  25. m_tabStopWidth = 0;
  26. }
  27. m_expandTab = vconfig.getIsExpandTab();
  28. if (m_expandTab && (vconfig.getTabStopWidth() > 0)) {
  29. m_tabSpaces = QString(vconfig.getTabStopWidth(), ' ');
  30. } else {
  31. m_tabSpaces = "\t";
  32. }
  33. m_cursorLineBg = QColor(vconfig.getEditorCurrentLineBg());
  34. }
  35. VEdit::VEdit(VFile *p_file, QWidget *p_parent)
  36. : QTextEdit(p_parent), m_file(p_file),
  37. m_editOps(NULL), m_enableInputMethod(true)
  38. {
  39. const int labelTimerInterval = 500;
  40. const int extraSelectionHighlightTimer = 500;
  41. const int labelSize = 64;
  42. m_selectedWordColor = QColor(vconfig.getEditorSelectedWordBg());
  43. m_searchedWordColor = QColor(vconfig.getEditorSearchedWordBg());
  44. m_searchedWordCursorColor = QColor(vconfig.getEditorSearchedWordCursorBg());
  45. m_incrementalSearchedWordColor = QColor(vconfig.getEditorIncrementalSearchedWordBg());
  46. m_trailingSpaceColor = QColor(vconfig.getEditorTrailingSpaceBg());
  47. QPixmap wrapPixmap(":/resources/icons/search_wrap.svg");
  48. m_wrapLabel = new QLabel(this);
  49. m_wrapLabel->setPixmap(wrapPixmap.scaled(labelSize, labelSize));
  50. m_wrapLabel->hide();
  51. m_labelTimer = new QTimer(this);
  52. m_labelTimer->setSingleShot(true);
  53. m_labelTimer->setInterval(labelTimerInterval);
  54. connect(m_labelTimer, &QTimer::timeout,
  55. this, &VEdit::labelTimerTimeout);
  56. m_highlightTimer = new QTimer(this);
  57. m_highlightTimer->setSingleShot(true);
  58. m_highlightTimer->setInterval(extraSelectionHighlightTimer);
  59. connect(m_highlightTimer, &QTimer::timeout,
  60. this, &VEdit::doHighlightExtraSelections);
  61. connect(document(), &QTextDocument::modificationChanged,
  62. (VFile *)m_file, &VFile::setModified);
  63. m_extraSelections.resize((int)SelectionId::MaxSelection);
  64. updateFontAndPalette();
  65. m_config.init(QFontMetrics(font()));
  66. updateConfig();
  67. connect(this, &VEdit::cursorPositionChanged,
  68. this, &VEdit::handleCursorPositionChanged);
  69. connect(this, &VEdit::selectionChanged,
  70. this, &VEdit::highlightSelectedWord);
  71. m_lineNumberArea = new LineNumberArea(this);
  72. connect(document(), &QTextDocument::blockCountChanged,
  73. this, &VEdit::updateLineNumberAreaMargin);
  74. connect(this, &QTextEdit::textChanged,
  75. this, &VEdit::updateLineNumberArea);
  76. connect(verticalScrollBar(), &QScrollBar::valueChanged,
  77. this, &VEdit::updateLineNumberArea);
  78. updateLineNumberAreaMargin();
  79. }
  80. VEdit::~VEdit()
  81. {
  82. if (m_file) {
  83. disconnect(document(), &QTextDocument::modificationChanged,
  84. (VFile *)m_file, &VFile::setModified);
  85. }
  86. }
  87. void VEdit::updateConfig()
  88. {
  89. m_config.update(QFontMetrics(font()));
  90. if (m_config.m_tabStopWidth > 0) {
  91. setTabStopWidth(m_config.m_tabStopWidth);
  92. }
  93. emit configUpdated();
  94. }
  95. void VEdit::beginEdit()
  96. {
  97. updateFontAndPalette();
  98. updateConfig();
  99. setReadOnly(false);
  100. setModified(false);
  101. }
  102. void VEdit::endEdit()
  103. {
  104. setReadOnly(true);
  105. }
  106. void VEdit::saveFile()
  107. {
  108. if (!document()->isModified()) {
  109. return;
  110. }
  111. m_file->setContent(toHtml());
  112. document()->setModified(false);
  113. }
  114. void VEdit::reloadFile()
  115. {
  116. setHtml(m_file->getContent());
  117. setModified(false);
  118. }
  119. void VEdit::scrollToLine(int p_lineNumber)
  120. {
  121. Q_ASSERT(p_lineNumber >= 0);
  122. QTextBlock block = document()->findBlockByLineNumber(p_lineNumber);
  123. if (block.isValid()) {
  124. VEditUtils::scrollBlockInPage(this, block.blockNumber(), 0);
  125. moveCursor(QTextCursor::EndOfBlock);
  126. }
  127. }
  128. bool VEdit::isModified() const
  129. {
  130. return document()->isModified();
  131. }
  132. void VEdit::setModified(bool p_modified)
  133. {
  134. document()->setModified(p_modified);
  135. if (m_file) {
  136. m_file->setModified(p_modified);
  137. }
  138. }
  139. void VEdit::insertImage()
  140. {
  141. if (m_editOps) {
  142. m_editOps->insertImage();
  143. }
  144. }
  145. bool VEdit::peekText(const QString &p_text, uint p_options, bool p_forward)
  146. {
  147. if (p_text.isEmpty()) {
  148. makeBlockVisible(document()->findBlock(textCursor().selectionStart()));
  149. highlightIncrementalSearchedWord(QTextCursor());
  150. return false;
  151. }
  152. bool wrapped = false;
  153. QTextCursor retCursor;
  154. bool found = findTextHelper(p_text, p_options, p_forward,
  155. p_forward ? textCursor().position() + 1
  156. : textCursor().position(),
  157. wrapped, retCursor);
  158. if (found) {
  159. makeBlockVisible(document()->findBlock(retCursor.selectionStart()));
  160. highlightIncrementalSearchedWord(retCursor);
  161. }
  162. return found;
  163. }
  164. // Use QTextEdit::find() instead of QTextDocument::find() because the later has
  165. // bugs in searching backward.
  166. bool VEdit::findTextHelper(const QString &p_text, uint p_options,
  167. bool p_forward, int p_start,
  168. bool &p_wrapped, QTextCursor &p_cursor)
  169. {
  170. p_wrapped = false;
  171. bool found = false;
  172. // Options
  173. QTextDocument::FindFlags findFlags;
  174. bool caseSensitive = false;
  175. if (p_options & FindOption::CaseSensitive) {
  176. findFlags |= QTextDocument::FindCaseSensitively;
  177. caseSensitive = true;
  178. }
  179. if (p_options & FindOption::WholeWordOnly) {
  180. findFlags |= QTextDocument::FindWholeWords;
  181. }
  182. if (!p_forward) {
  183. findFlags |= QTextDocument::FindBackward;
  184. }
  185. // Use regular expression
  186. bool useRegExp = false;
  187. QRegExp exp;
  188. if (p_options & FindOption::RegularExpression) {
  189. useRegExp = true;
  190. exp = QRegExp(p_text,
  191. caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
  192. }
  193. // Store current state of the cursor.
  194. QTextCursor cursor = textCursor();
  195. if (cursor.position() != p_start) {
  196. if (p_start < 0) {
  197. p_start = 0;
  198. } else if (p_start > document()->characterCount()) {
  199. p_start = document()->characterCount();
  200. }
  201. QTextCursor startCursor = cursor;
  202. startCursor.setPosition(p_start);
  203. setTextCursor(startCursor);
  204. }
  205. while (!found) {
  206. if (useRegExp) {
  207. found = find(exp, findFlags);
  208. } else {
  209. found = find(p_text, findFlags);
  210. }
  211. if (p_wrapped) {
  212. break;
  213. }
  214. if (!found) {
  215. // Wrap to the other end of the document to search again.
  216. p_wrapped = true;
  217. QTextCursor wrapCursor = textCursor();
  218. if (p_forward) {
  219. wrapCursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
  220. } else {
  221. wrapCursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
  222. }
  223. setTextCursor(wrapCursor);
  224. }
  225. }
  226. if (found) {
  227. p_cursor = textCursor();
  228. }
  229. // Restore the original cursor.
  230. setTextCursor(cursor);
  231. return found;
  232. }
  233. QList<QTextCursor> VEdit::findTextAll(const QString &p_text, uint p_options)
  234. {
  235. QList<QTextCursor> results;
  236. if (p_text.isEmpty()) {
  237. return results;
  238. }
  239. // Options
  240. QTextDocument::FindFlags findFlags;
  241. bool caseSensitive = false;
  242. if (p_options & FindOption::CaseSensitive) {
  243. findFlags |= QTextDocument::FindCaseSensitively;
  244. caseSensitive = true;
  245. }
  246. if (p_options & FindOption::WholeWordOnly) {
  247. findFlags |= QTextDocument::FindWholeWords;
  248. }
  249. // Use regular expression
  250. bool useRegExp = false;
  251. QRegExp exp;
  252. if (p_options & FindOption::RegularExpression) {
  253. useRegExp = true;
  254. exp = QRegExp(p_text,
  255. caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
  256. }
  257. int startPos = 0;
  258. QTextCursor cursor;
  259. QTextDocument *doc = document();
  260. while (true) {
  261. if (useRegExp) {
  262. cursor = doc->find(exp, startPos, findFlags);
  263. } else {
  264. cursor = doc->find(p_text, startPos, findFlags);
  265. }
  266. if (cursor.isNull()) {
  267. break;
  268. } else {
  269. results.append(cursor);
  270. startPos = cursor.selectionEnd();
  271. }
  272. }
  273. return results;
  274. }
  275. bool VEdit::findText(const QString &p_text, uint p_options, bool p_forward,
  276. QTextCursor *p_cursor, QTextCursor::MoveMode p_moveMode)
  277. {
  278. clearIncrementalSearchedWordHighlight();
  279. if (p_text.isEmpty()) {
  280. clearSearchedWordHighlight();
  281. return false;
  282. }
  283. QTextCursor cursor = textCursor();
  284. bool wrapped = false;
  285. QTextCursor retCursor;
  286. int matches = 0;
  287. int start = p_forward ? cursor.position() + 1 : cursor.position();
  288. if (p_cursor) {
  289. start = p_forward ? p_cursor->position() + 1 : p_cursor->position();
  290. }
  291. bool found = findTextHelper(p_text, p_options, p_forward, start,
  292. wrapped, retCursor);
  293. if (found) {
  294. Q_ASSERT(!retCursor.isNull());
  295. if (wrapped) {
  296. showWrapLabel();
  297. }
  298. if (p_cursor) {
  299. p_cursor->setPosition(retCursor.selectionStart(), p_moveMode);
  300. } else {
  301. cursor.setPosition(retCursor.selectionStart(), p_moveMode);
  302. setTextCursor(cursor);
  303. }
  304. highlightSearchedWord(p_text, p_options);
  305. highlightSearchedWordUnderCursor(retCursor);
  306. matches = m_extraSelections[(int)SelectionId::SearchedKeyword].size();
  307. } else {
  308. clearSearchedWordHighlight();
  309. }
  310. if (matches == 0) {
  311. statusMessage(tr("Found no match"));
  312. } else {
  313. statusMessage(tr("Found %1 %2").arg(matches)
  314. .arg(matches > 1 ? tr("matches") : tr("match")));
  315. }
  316. return found;
  317. }
  318. void VEdit::replaceText(const QString &p_text, uint p_options,
  319. const QString &p_replaceText, bool p_findNext)
  320. {
  321. QTextCursor cursor = textCursor();
  322. bool wrapped = false;
  323. QTextCursor retCursor;
  324. bool found = findTextHelper(p_text, p_options, true,
  325. cursor.position(), wrapped, retCursor);
  326. if (found) {
  327. if (retCursor.selectionStart() == cursor.position()) {
  328. // Matched.
  329. retCursor.beginEditBlock();
  330. retCursor.insertText(p_replaceText);
  331. retCursor.endEditBlock();
  332. setTextCursor(retCursor);
  333. }
  334. if (p_findNext) {
  335. findText(p_text, p_options, true);
  336. }
  337. }
  338. }
  339. void VEdit::replaceTextAll(const QString &p_text, uint p_options,
  340. const QString &p_replaceText)
  341. {
  342. // Replace from the start to the end and restore the cursor.
  343. QTextCursor cursor = textCursor();
  344. int nrReplaces = 0;
  345. QTextCursor tmpCursor = cursor;
  346. tmpCursor.setPosition(0);
  347. setTextCursor(tmpCursor);
  348. int start = tmpCursor.position();
  349. while (true) {
  350. bool wrapped = false;
  351. QTextCursor retCursor;
  352. bool found = findTextHelper(p_text, p_options, true,
  353. start, wrapped, retCursor);
  354. if (!found) {
  355. break;
  356. } else {
  357. if (wrapped) {
  358. // Wrap back.
  359. break;
  360. }
  361. nrReplaces++;
  362. retCursor.beginEditBlock();
  363. retCursor.insertText(p_replaceText);
  364. retCursor.endEditBlock();
  365. setTextCursor(retCursor);
  366. start = retCursor.position();
  367. }
  368. }
  369. // Restore cursor position.
  370. cursor.clearSelection();
  371. setTextCursor(cursor);
  372. qDebug() << "replace all" << nrReplaces << "occurences";
  373. emit statusMessage(tr("Replace %1 %2").arg(nrReplaces)
  374. .arg(nrReplaces > 1 ? tr("occurences")
  375. : tr("occurence")));
  376. }
  377. void VEdit::showWrapLabel()
  378. {
  379. int labelW = m_wrapLabel->width();
  380. int labelH = m_wrapLabel->height();
  381. int x = (width() - labelW) / 2;
  382. int y = (height() - labelH) / 2;
  383. if (x < 0) {
  384. x = 0;
  385. }
  386. if (y < 0) {
  387. y = 0;
  388. }
  389. m_wrapLabel->move(x, y);
  390. m_wrapLabel->show();
  391. m_labelTimer->stop();
  392. m_labelTimer->start();
  393. }
  394. void VEdit::labelTimerTimeout()
  395. {
  396. m_wrapLabel->hide();
  397. }
  398. void VEdit::updateFontAndPalette()
  399. {
  400. setFont(vconfig.getBaseEditFont());
  401. setPalette(vconfig.getBaseEditPalette());
  402. }
  403. void VEdit::highlightExtraSelections(bool p_now)
  404. {
  405. m_highlightTimer->stop();
  406. if (p_now) {
  407. doHighlightExtraSelections();
  408. } else {
  409. m_highlightTimer->start();
  410. }
  411. }
  412. void VEdit::doHighlightExtraSelections()
  413. {
  414. int nrExtra = m_extraSelections.size();
  415. Q_ASSERT(nrExtra == (int)SelectionId::MaxSelection);
  416. QList<QTextEdit::ExtraSelection> extraSelects;
  417. for (int i = 0; i < nrExtra; ++i) {
  418. extraSelects.append(m_extraSelections[i]);
  419. }
  420. setExtraSelections(extraSelects);
  421. }
  422. void VEdit::highlightCurrentLine()
  423. {
  424. QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::CurrentLine];
  425. if (vconfig.getHighlightCursorLine() && !isReadOnly()) {
  426. // Need to highlight current line.
  427. selects.clear();
  428. // A long block maybe splited into multiple visual lines.
  429. QTextEdit::ExtraSelection select;
  430. select.format.setBackground(m_config.m_cursorLineBg);
  431. select.format.setProperty(QTextFormat::FullWidthSelection, true);
  432. QTextCursor cursor = textCursor();
  433. if (m_config.m_highlightWholeBlock) {
  434. cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor, 1);
  435. QTextBlock block = cursor.block();
  436. int blockEnd = block.position() + block.length();
  437. int pos = -1;
  438. while (cursor.position() < blockEnd && pos != cursor.position()) {
  439. QTextEdit::ExtraSelection newSelect = select;
  440. newSelect.cursor = cursor;
  441. selects.append(newSelect);
  442. pos = cursor.position();
  443. cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, 1);
  444. }
  445. } else {
  446. cursor.clearSelection();
  447. select.cursor = cursor;
  448. selects.append(select);
  449. }
  450. } else {
  451. // Need to clear current line highlight.
  452. if (selects.isEmpty()) {
  453. return;
  454. }
  455. selects.clear();
  456. }
  457. highlightExtraSelections(true);
  458. }
  459. void VEdit::setReadOnly(bool p_ro)
  460. {
  461. QTextEdit::setReadOnly(p_ro);
  462. highlightCurrentLine();
  463. }
  464. void VEdit::highlightSelectedWord()
  465. {
  466. QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SelectedWord];
  467. if (!vconfig.getHighlightSelectedWord()) {
  468. if (!selects.isEmpty()) {
  469. selects.clear();
  470. highlightExtraSelections(true);
  471. }
  472. return;
  473. }
  474. QString text = textCursor().selectedText().trimmed();
  475. if (text.isEmpty() || wordInSearchedSelection(text)) {
  476. selects.clear();
  477. highlightExtraSelections(true);
  478. return;
  479. }
  480. QTextCharFormat format;
  481. format.setBackground(m_selectedWordColor);
  482. highlightTextAll(text, FindOption::CaseSensitive, SelectionId::SelectedWord,
  483. format);
  484. }
  485. // Do not highlight trailing spaces with current cursor right behind.
  486. static void trailingSpaceFilter(VEdit *p_editor, QList<QTextEdit::ExtraSelection> &p_result)
  487. {
  488. QTextCursor cursor = p_editor->textCursor();
  489. if (!cursor.atBlockEnd()) {
  490. return;
  491. }
  492. int cursorPos = cursor.position();
  493. for (auto it = p_result.begin(); it != p_result.end(); ++it) {
  494. if (it->cursor.selectionEnd() == cursorPos) {
  495. p_result.erase(it);
  496. // There will be only one.
  497. return;
  498. }
  499. }
  500. }
  501. void VEdit::highlightTrailingSpace()
  502. {
  503. if (!vconfig.getEnableTrailingSpaceHighlight()) {
  504. QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::TrailingSapce];
  505. if (!selects.isEmpty()) {
  506. selects.clear();
  507. highlightExtraSelections(true);
  508. }
  509. return;
  510. }
  511. QTextCharFormat format;
  512. format.setBackground(m_trailingSpaceColor);
  513. QString text("\\s+$");
  514. highlightTextAll(text, FindOption::RegularExpression,
  515. SelectionId::TrailingSapce, format,
  516. trailingSpaceFilter);
  517. }
  518. bool VEdit::wordInSearchedSelection(const QString &p_text)
  519. {
  520. QString text = p_text.trimmed();
  521. QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SearchedKeyword];
  522. for (int i = 0; i < selects.size(); ++i) {
  523. QString searchedWord = selects[i].cursor.selectedText();
  524. if (text == searchedWord.trimmed()) {
  525. return true;
  526. }
  527. }
  528. return false;
  529. }
  530. void VEdit::highlightTextAll(const QString &p_text, uint p_options,
  531. SelectionId p_id, QTextCharFormat p_format,
  532. void (*p_filter)(VEdit *, QList<QTextEdit::ExtraSelection> &))
  533. {
  534. QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)p_id];
  535. if (!p_text.isEmpty()) {
  536. selects.clear();
  537. QList<QTextCursor> occurs = findTextAll(p_text, p_options);
  538. for (int i = 0; i < occurs.size(); ++i) {
  539. QTextEdit::ExtraSelection select;
  540. select.format = p_format;
  541. select.cursor = occurs[i];
  542. selects.append(select);
  543. }
  544. } else {
  545. if (selects.isEmpty()) {
  546. return;
  547. }
  548. selects.clear();
  549. }
  550. if (p_filter) {
  551. p_filter(this, selects);
  552. }
  553. highlightExtraSelections();
  554. }
  555. void VEdit::highlightSearchedWord(const QString &p_text, uint p_options)
  556. {
  557. QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SearchedKeyword];
  558. if (!vconfig.getHighlightSearchedWord() || p_text.isEmpty()) {
  559. if (!selects.isEmpty()) {
  560. selects.clear();
  561. highlightExtraSelections(true);
  562. }
  563. return;
  564. }
  565. QTextCharFormat format;
  566. format.setBackground(m_searchedWordColor);
  567. highlightTextAll(p_text, p_options, SelectionId::SearchedKeyword, format);
  568. }
  569. void VEdit::highlightSearchedWordUnderCursor(const QTextCursor &p_cursor)
  570. {
  571. QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SearchedKeywordUnderCursor];
  572. if (!p_cursor.hasSelection()) {
  573. if (!selects.isEmpty()) {
  574. selects.clear();
  575. highlightExtraSelections(true);
  576. }
  577. return;
  578. }
  579. selects.clear();
  580. QTextEdit::ExtraSelection select;
  581. select.format.setBackground(m_searchedWordCursorColor);
  582. select.cursor = p_cursor;
  583. selects.append(select);
  584. highlightExtraSelections(true);
  585. }
  586. void VEdit::highlightIncrementalSearchedWord(const QTextCursor &p_cursor)
  587. {
  588. QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::IncrementalSearchedKeyword];
  589. if (!vconfig.getHighlightSearchedWord() || !p_cursor.hasSelection()) {
  590. if (!selects.isEmpty()) {
  591. selects.clear();
  592. highlightExtraSelections(true);
  593. }
  594. return;
  595. }
  596. selects.clear();
  597. QTextEdit::ExtraSelection select;
  598. select.format.setBackground(m_incrementalSearchedWordColor);
  599. select.cursor = p_cursor;
  600. selects.append(select);
  601. highlightExtraSelections(true);
  602. }
  603. void VEdit::clearSearchedWordHighlight()
  604. {
  605. clearIncrementalSearchedWordHighlight(false);
  606. clearSearchedWordUnderCursorHighlight(false);
  607. QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SearchedKeyword];
  608. if (selects.isEmpty()) {
  609. return;
  610. }
  611. selects.clear();
  612. highlightExtraSelections(true);
  613. }
  614. void VEdit::clearSearchedWordUnderCursorHighlight(bool p_now)
  615. {
  616. QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SearchedKeywordUnderCursor];
  617. if (selects.isEmpty()) {
  618. return;
  619. }
  620. selects.clear();
  621. highlightExtraSelections(p_now);
  622. }
  623. void VEdit::clearIncrementalSearchedWordHighlight(bool p_now)
  624. {
  625. QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::IncrementalSearchedKeyword];
  626. if (selects.isEmpty()) {
  627. return;
  628. }
  629. selects.clear();
  630. highlightExtraSelections(p_now);
  631. }
  632. void VEdit::contextMenuEvent(QContextMenuEvent *p_event)
  633. {
  634. QMenu *menu = createStandardContextMenu();
  635. menu->setToolTipsVisible(true);
  636. const QList<QAction *> actions = menu->actions();
  637. if (!textCursor().hasSelection()) {
  638. VEditTab *editTab = dynamic_cast<VEditTab *>(parent());
  639. V_ASSERT(editTab);
  640. if (editTab->isEditMode()) {
  641. QAction *saveExitAct = new QAction(QIcon(":/resources/icons/save_exit.svg"),
  642. tr("&Save Changes And Read"), this);
  643. saveExitAct->setToolTip(tr("Save changes and exit edit mode"));
  644. connect(saveExitAct, &QAction::triggered,
  645. this, &VEdit::handleSaveExitAct);
  646. QAction *discardExitAct = new QAction(QIcon(":/resources/icons/discard_exit.svg"),
  647. tr("&Discard Changes And Read"), this);
  648. discardExitAct->setToolTip(tr("Discard changes and exit edit mode"));
  649. connect(discardExitAct, &QAction::triggered,
  650. this, &VEdit::handleDiscardExitAct);
  651. menu->insertAction(actions.isEmpty() ? NULL : actions[0], discardExitAct);
  652. menu->insertAction(discardExitAct, saveExitAct);
  653. if (!actions.isEmpty()) {
  654. menu->insertSeparator(actions[0]);
  655. }
  656. } else if (m_file->isModifiable()) {
  657. // HTML.
  658. QAction *editAct= new QAction(QIcon(":/resources/icons/edit_note.svg"),
  659. tr("&Edit"), this);
  660. editAct->setToolTip(tr("Edit current note"));
  661. connect(editAct, &QAction::triggered,
  662. this, &VEdit::handleEditAct);
  663. menu->insertAction(actions.isEmpty() ? NULL : actions[0], editAct);
  664. // actions does not contain editAction.
  665. if (!actions.isEmpty()) {
  666. menu->insertSeparator(actions[0]);
  667. }
  668. }
  669. }
  670. menu->exec(p_event->globalPos());
  671. delete menu;
  672. }
  673. void VEdit::handleSaveExitAct()
  674. {
  675. emit saveAndRead();
  676. }
  677. void VEdit::handleDiscardExitAct()
  678. {
  679. emit discardAndRead();
  680. }
  681. void VEdit::handleEditAct()
  682. {
  683. emit editNote();
  684. }
  685. VFile *VEdit::getFile() const
  686. {
  687. return m_file;
  688. }
  689. void VEdit::handleCursorPositionChanged()
  690. {
  691. static QTextCursor lastCursor;
  692. QTextCursor cursor = textCursor();
  693. if (lastCursor.isNull() || cursor.blockNumber() != lastCursor.blockNumber()) {
  694. highlightCurrentLine();
  695. highlightTrailingSpace();
  696. } else {
  697. // Judge whether we have trailing space at current line.
  698. QString text = cursor.block().text();
  699. if (text.rbegin()->isSpace()) {
  700. highlightTrailingSpace();
  701. }
  702. // Handle word-wrap in one block.
  703. // Highlight current line if in different visual line.
  704. if ((lastCursor.positionInBlock() - lastCursor.columnNumber()) !=
  705. (cursor.positionInBlock() - cursor.columnNumber())) {
  706. highlightCurrentLine();
  707. }
  708. }
  709. lastCursor = cursor;
  710. }
  711. VEditConfig &VEdit::getConfig()
  712. {
  713. return m_config;
  714. }
  715. void VEdit::mousePressEvent(QMouseEvent *p_event)
  716. {
  717. if (p_event->button() == Qt::LeftButton
  718. && p_event->modifiers() == Qt::ControlModifier
  719. && !textCursor().hasSelection()) {
  720. m_oriMouseX = p_event->x();
  721. m_oriMouseY = p_event->y();
  722. m_readyToScroll = true;
  723. m_mouseMoveScrolled = false;
  724. p_event->accept();
  725. return;
  726. }
  727. m_readyToScroll = false;
  728. m_mouseMoveScrolled = false;
  729. QTextEdit::mousePressEvent(p_event);
  730. emit selectionChangedByMouse(textCursor().hasSelection());
  731. }
  732. void VEdit::mouseReleaseEvent(QMouseEvent *p_event)
  733. {
  734. if (m_mouseMoveScrolled || m_readyToScroll) {
  735. viewport()->setCursor(Qt::IBeamCursor);
  736. m_readyToScroll = false;
  737. m_mouseMoveScrolled = false;
  738. p_event->accept();
  739. return;
  740. }
  741. m_readyToScroll = false;
  742. m_mouseMoveScrolled = false;
  743. QTextEdit::mouseReleaseEvent(p_event);
  744. }
  745. void VEdit::mouseMoveEvent(QMouseEvent *p_event)
  746. {
  747. const int threshold = 5;
  748. if (m_readyToScroll) {
  749. int deltaX = p_event->x() - m_oriMouseX;
  750. int deltaY = p_event->y() - m_oriMouseY;
  751. if (qAbs(deltaX) >= threshold || qAbs(deltaY) >= threshold) {
  752. m_oriMouseX = p_event->x();
  753. m_oriMouseY = p_event->y();
  754. if (!m_mouseMoveScrolled) {
  755. m_mouseMoveScrolled = true;
  756. viewport()->setCursor(Qt::SizeAllCursor);
  757. }
  758. QScrollBar *verBar = verticalScrollBar();
  759. QScrollBar *horBar = horizontalScrollBar();
  760. if (verBar->isVisible()) {
  761. verBar->setValue(verBar->value() - deltaY);
  762. }
  763. if (horBar->isVisible()) {
  764. horBar->setValue(horBar->value() - deltaX);
  765. }
  766. }
  767. p_event->accept();
  768. return;
  769. }
  770. QTextEdit::mouseMoveEvent(p_event);
  771. emit selectionChangedByMouse(textCursor().hasSelection());
  772. }
  773. void VEdit::requestUpdateVimStatus()
  774. {
  775. if (m_editOps) {
  776. m_editOps->requestUpdateVimStatus();
  777. } else {
  778. emit vimStatusUpdated(NULL);
  779. }
  780. }
  781. bool VEdit::jumpTitle(bool p_forward, int p_relativeLevel, int p_repeat)
  782. {
  783. Q_UNUSED(p_forward);
  784. Q_UNUSED(p_relativeLevel);
  785. Q_UNUSED(p_repeat);
  786. return false;
  787. }
  788. QVariant VEdit::inputMethodQuery(Qt::InputMethodQuery p_query) const
  789. {
  790. if (p_query == Qt::ImEnabled) {
  791. return m_enableInputMethod;
  792. }
  793. return QTextEdit::inputMethodQuery(p_query);
  794. }
  795. void VEdit::setInputMethodEnabled(bool p_enabled)
  796. {
  797. if (m_enableInputMethod != p_enabled) {
  798. m_enableInputMethod = p_enabled;
  799. QInputMethod *im = QGuiApplication::inputMethod();
  800. im->reset();
  801. // Ask input method to query current state, which will call inputMethodQuery().
  802. im->update(Qt::ImEnabled);
  803. }
  804. }
  805. void VEdit::decorateText(TextDecoration p_decoration)
  806. {
  807. if (m_editOps) {
  808. m_editOps->decorateText(p_decoration);
  809. }
  810. }
  811. void VEdit::updateLineNumberAreaMargin()
  812. {
  813. int width = 0;
  814. if (vconfig.getEditorLineNumber()) {
  815. width = m_lineNumberArea->calculateWidth();
  816. }
  817. setViewportMargins(width, 0, 0, 0);
  818. }
  819. void VEdit::updateLineNumberArea()
  820. {
  821. if (vconfig.getEditorLineNumber()) {
  822. if (!m_lineNumberArea->isVisible()) {
  823. updateLineNumberAreaMargin();
  824. m_lineNumberArea->show();
  825. }
  826. m_lineNumberArea->update();
  827. } else if (m_lineNumberArea->isVisible()) {
  828. updateLineNumberAreaMargin();
  829. m_lineNumberArea->hide();
  830. }
  831. }
  832. void VEdit::resizeEvent(QResizeEvent *p_event)
  833. {
  834. QTextEdit::resizeEvent(p_event);
  835. if (vconfig.getEditorLineNumber()) {
  836. QRect rect = contentsRect();
  837. m_lineNumberArea->setGeometry(QRect(rect.left(),
  838. rect.top(),
  839. m_lineNumberArea->calculateWidth(),
  840. rect.height()));
  841. }
  842. }
  843. void VEdit::lineNumberAreaPaintEvent(QPaintEvent *p_event)
  844. {
  845. if (!vconfig.getEditorLineNumber()) {
  846. updateLineNumberAreaMargin();
  847. m_lineNumberArea->hide();
  848. return;
  849. }
  850. QPainter painter(m_lineNumberArea);
  851. painter.fillRect(p_event->rect(), vconfig.getEditorLineNumberBg());
  852. QTextDocument *doc = document();
  853. QAbstractTextDocumentLayout *layout = doc->documentLayout();
  854. QTextBlock block = firstVisibleBlock();
  855. int blockNumber = block.blockNumber();
  856. int offsetY = contentOffsetY();
  857. QRectF rect = layout->blockBoundingRect(block);
  858. int top = offsetY + (int)rect.y();
  859. int bottom = top + (int)rect.height();
  860. int eventTop = p_event->rect().top();
  861. int eventBtm = p_event->rect().bottom();
  862. const int digitHeight = m_lineNumberArea->getDigitHeight();
  863. const int curBlockNumber = textCursor().block().blockNumber();
  864. const bool relative = vconfig.getEditorLineNumber() == 2;
  865. const QString &fg = vconfig.getEditorLineNumberFg();
  866. painter.setPen(fg);
  867. while (block.isValid() && top <= eventBtm) {
  868. if (block.isVisible() && bottom >= eventTop) {
  869. bool currentLine = false;
  870. int number = blockNumber + 1;
  871. if (relative) {
  872. number = blockNumber - curBlockNumber;
  873. if (number == 0) {
  874. currentLine = true;
  875. number = blockNumber + 1;
  876. } else if (number < 0) {
  877. number = -number;
  878. }
  879. } else if (blockNumber == curBlockNumber) {
  880. currentLine = true;
  881. }
  882. QString numberStr = QString::number(number);
  883. if (currentLine) {
  884. QFont font = painter.font();
  885. font.setBold(true);
  886. painter.setFont(font);
  887. }
  888. painter.drawText(0,
  889. top + 2,
  890. m_lineNumberArea->width(),
  891. digitHeight,
  892. Qt::AlignRight,
  893. numberStr);
  894. if (currentLine) {
  895. QFont font = painter.font();
  896. font.setBold(false);
  897. painter.setFont(font);
  898. }
  899. }
  900. block = block.next();
  901. top = bottom;
  902. bottom = top + (int)layout->blockBoundingRect(block).height();
  903. ++blockNumber;
  904. }
  905. }
  906. int VEdit::contentOffsetY()
  907. {
  908. int offsety = 0;
  909. QScrollBar *sb = verticalScrollBar();
  910. offsety = sb->value();
  911. return -offsety;
  912. }
  913. QTextBlock VEdit::firstVisibleBlock()
  914. {
  915. QTextDocument *doc = document();
  916. QAbstractTextDocumentLayout *layout = doc->documentLayout();
  917. int offsetY = contentOffsetY();
  918. // Binary search.
  919. int idx = -1;
  920. int start = 0, end = doc->blockCount() - 1;
  921. while (start <= end) {
  922. int mid = start + (end - start) / 2;
  923. QTextBlock block = doc->findBlockByNumber(mid);
  924. if (!block.isValid()) {
  925. break;
  926. }
  927. int y = offsetY + (int)layout->blockBoundingRect(block).y();
  928. if (y == 0) {
  929. return block;
  930. } else if (y < 0) {
  931. start = mid + 1;
  932. } else {
  933. if (idx == -1 || mid < idx) {
  934. idx = mid;
  935. }
  936. end = mid - 1;
  937. }
  938. }
  939. if (idx != -1) {
  940. return doc->findBlockByNumber(idx);
  941. }
  942. // Linear search.
  943. qDebug() << "fall back to linear search for first visible block";
  944. QTextBlock block = doc->begin();
  945. while (block.isValid()) {
  946. int y = offsetY + (int)layout->blockBoundingRect(block).y();
  947. if (y >= 0) {
  948. return block;
  949. }
  950. block = block.next();
  951. }
  952. Q_ASSERT(false);
  953. return doc->begin();
  954. }
  955. int LineNumberArea::calculateWidth() const
  956. {
  957. int bc = m_document->blockCount();
  958. if (m_blockCount == bc) {
  959. return m_width;
  960. }
  961. const_cast<LineNumberArea *>(this)->m_blockCount = bc;
  962. int digits = 1;
  963. int max = qMax(1, m_blockCount);
  964. while (max >= 10) {
  965. max /= 10;
  966. ++digits;
  967. }
  968. int width = m_digitWidth * digits;
  969. const_cast<LineNumberArea *>(this)->m_width = width;
  970. return m_width;
  971. }
  972. void VEdit::makeBlockVisible(const QTextBlock &p_block)
  973. {
  974. if (!p_block.isValid() || !p_block.isVisible()) {
  975. return;
  976. }
  977. QScrollBar *vbar = verticalScrollBar();
  978. if (!vbar || !vbar->isVisible()) {
  979. // No vertical scrollbar. No need to scroll.
  980. return;
  981. }
  982. QAbstractTextDocumentLayout *layout = document()->documentLayout();
  983. int height = rect().height();
  984. QScrollBar *hbar = horizontalScrollBar();
  985. if (hbar && hbar->isVisible()) {
  986. height -= hbar->height();
  987. }
  988. bool moved = false;
  989. QRectF rect = layout->blockBoundingRect(p_block);
  990. int y = contentOffsetY() + (int)rect.y();
  991. int rectHeight = (int)rect.height();
  992. // Handle the case rectHeight >= height.
  993. if (rectHeight >= height) {
  994. if (y <= 0) {
  995. if (y + rectHeight < height) {
  996. // Need to scroll up.
  997. while (y + rectHeight < height && vbar->value() > vbar->minimum()) {
  998. moved = true;
  999. vbar->setValue(vbar->value() - vbar->singleStep());
  1000. rect = layout->blockBoundingRect(p_block);
  1001. rectHeight = (int)rect.height();
  1002. y = contentOffsetY() + (int)rect.y();
  1003. }
  1004. }
  1005. } else {
  1006. // Need to scroll down.
  1007. while (y > 0 && vbar->value() < vbar->maximum()) {
  1008. moved = true;
  1009. vbar->setValue(vbar->value() + vbar->singleStep());
  1010. rect = layout->blockBoundingRect(p_block);
  1011. rectHeight = (int)rect.height();
  1012. y = contentOffsetY() + (int)rect.y();
  1013. }
  1014. }
  1015. if (moved) {
  1016. qDebug() << "scroll to make huge block visible";
  1017. }
  1018. return;
  1019. }
  1020. while (y < 0 && vbar->value() > vbar->minimum()) {
  1021. moved = true;
  1022. vbar->setValue(vbar->value() - vbar->singleStep());
  1023. rect = layout->blockBoundingRect(p_block);
  1024. rectHeight = (int)rect.height();
  1025. y = contentOffsetY() + (int)rect.y();
  1026. }
  1027. if (moved) {
  1028. qDebug() << "scroll page down to make block visible";
  1029. return;
  1030. }
  1031. while (y + rectHeight > height && vbar->value() < vbar->maximum()) {
  1032. moved = true;
  1033. vbar->setValue(vbar->value() + vbar->singleStep());
  1034. rect = layout->blockBoundingRect(p_block);
  1035. rectHeight = (int)rect.height();
  1036. y = contentOffsetY() + (int)rect.y();
  1037. }
  1038. if (moved) {
  1039. qDebug() << "scroll page up to make block visible";
  1040. }
  1041. }