vedit.cpp 37 KB

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