diff --git a/src/hgmarkdownhighlighter.h b/src/hgmarkdownhighlighter.h index 53940b65..2ab29ce9 100644 --- a/src/hgmarkdownhighlighter.h +++ b/src/hgmarkdownhighlighter.h @@ -22,19 +22,6 @@ struct HighlightingStyle QTextCharFormat format; }; -enum HighlightBlockState -{ - Normal = 0, - - // A fenced code block. - CodeBlockStart, - CodeBlock, - CodeBlockEnd, - - // This block is inside a HTML comment region. - Comment -}; - // One continuous region for a certain markdown highlight style // within a QTextBlock. // Pay attention to the change of HighlightingStyles[] diff --git a/src/resources/hoedown.js b/src/resources/hoedown.js index 455aa2e9..e111ae63 100644 --- a/src/resources/hoedown.js +++ b/src/resources/hoedown.js @@ -41,6 +41,8 @@ var updateHtml = function(html) { } } + renderCodeBlockLineNumber(); + // If you add new logics after handling MathJax, please pay attention to // finishLoading logic. // MathJax may be not loaded for now. diff --git a/src/resources/markdown-it.js b/src/resources/markdown-it.js index d7cf43b8..6567fe28 100644 --- a/src/resources/markdown-it.js +++ b/src/resources/markdown-it.js @@ -94,6 +94,7 @@ var updateText = function(text) { insertImageCaption(); renderMermaid('lang-mermaid'); renderFlowchart('lang-flowchart'); + renderCodeBlockLineNumber(); // If you add new logics after handling MathJax, please pay attention to // finishLoading logic. diff --git a/src/resources/markdown_template.js b/src/resources/markdown_template.js index a4e910fd..59f7662c 100644 --- a/src/resources/markdown_template.js +++ b/src/resources/markdown_template.js @@ -24,6 +24,10 @@ if (typeof VEnableMathjax == 'undefined') { VEnableMathjax = false; } +if (typeof VEnableHighlightLineNumber == 'undefined') { + VEnableHighlightLineNumber = false; +} + // Add a caption (using alt text) under the image. var VImageCenterClass = 'img-center'; var VImageCaptionClass = 'img-caption'; @@ -805,3 +809,27 @@ var jumpTitle = function(forward, relativeLevel, repeat) { content.setHeader(headers[targetIdx].getAttribute("id")); setTimeout("g_muteScroll = false", 100); }; + +var renderCodeBlockLineNumber = function() { + if (!VEnableHighlightLineNumber) { + return; + } + + var codes = document.getElementsByTagName('code'); + for (var i = 0; i < codes.length; ++i) { + var code = codes[i]; + if (code.parentElement.tagName.toLowerCase() == 'pre') { + hljs.lineNumbersBlock(code); + } + } + + // Delete the last extra row. + var tables = document.getElementsByTagName('table'); + for (var i = 0; i < tables.length; ++i) { + var table = tables[i]; + if (table.classList.contains("hljs-ln")) { + var rowCount = table.rows.length; + table.deleteRow(rowCount - 1); + } + } +}; diff --git a/src/resources/marked.js b/src/resources/marked.js index f94413a8..50e5f7b7 100644 --- a/src/resources/marked.js +++ b/src/resources/marked.js @@ -49,6 +49,7 @@ var updateText = function(text) { insertImageCaption(); renderMermaid('lang-mermaid'); renderFlowchart('lang-flowchart'); + renderCodeBlockLineNumber(); // If you add new logics after handling MathJax, please pay attention to // finishLoading logic. diff --git a/src/resources/showdown.js b/src/resources/showdown.js index 089022cf..348dc3c1 100644 --- a/src/resources/showdown.js +++ b/src/resources/showdown.js @@ -76,6 +76,7 @@ var updateText = function(text) { highlightCodeBlocks(document, VEnableMermaid, VEnableFlowchart); renderMermaid('language-mermaid'); renderFlowchart('language-flowchart'); + renderCodeBlockLineNumber(); // If you add new logics after handling MathJax, please pay attention to // finishLoading logic. diff --git a/src/resources/styles/default.css b/src/resources/styles/default.css index a1f39f28..ab265c2b 100644 --- a/src/resources/styles/default.css +++ b/src/resources/styles/default.css @@ -206,3 +206,33 @@ div.img-caption { text-align: center; line-height: 1.5; } + +/* For Highlight.js Line Number */ +table.hljs-ln tr { + border: none; + background-color: transparent; +} + +table.hljs-ln tr td { + border: none; + background-color: transparent; +} + +table.hljs-ln tr td.hljs-ln-numbers { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + text-align: center; + color: #AAA; + border-right: 1px solid #CCC; + vertical-align: top; + padding-right: 5px; +} + +table.hljs-ln tr td.hljs-ln-code { + padding-left: 10px; +} diff --git a/src/resources/vnote.ini b/src/resources/vnote.ini index 1a5c98a6..7245bed4 100644 --- a/src/resources/vnote.ini +++ b/src/resources/vnote.ini @@ -57,7 +57,7 @@ enable_vim_mode=false enable_smart_im_in_vim_mode=true ; Display an area besides the editor area to show line number -; 0 - None, 1 - Absolute, 2 - Relative +; 0 - None, 1 - Absolute, 2 - Relative, 3 - CodeBlock editor_line_number=0 ; Whether minimize to system tray when closing the app @@ -90,6 +90,9 @@ enable_heading_sequence=false ; 0 - no color column color_column=0 +; Whether display line number of code block in read mode +enable_code_block_line_number=false + [session] tools_dock_checked=true diff --git a/src/utils/highlightjs/highlightjs-line-numbers.min.js b/src/utils/highlightjs/highlightjs-line-numbers.min.js new file mode 100644 index 00000000..2699f8a3 --- /dev/null +++ b/src/utils/highlightjs/highlightjs-line-numbers.min.js @@ -0,0 +1 @@ +!function(e){"use strict";function t(){var e=document.createElement("style");e.type="text/css",e.innerHTML=".{0}{border-collapse:collapse}.{0} td{padding:0}.{1}:before{content:attr({2})}".format(s,d,u),document.getElementsByTagName("head")[0].appendChild(e)}function n(){"complete"===document.readyState?r():e.addEventListener("DOMContentLoaded",r)}function r(){try{var e=document.querySelectorAll("code.hljs");for(var t in e)e.hasOwnProperty(t)&&o(e[t])}catch(n){console.error("LineNumbers error: ",n)}}function o(e){if("object"==typeof e){var t=l(e.innerHTML);if(t.length>1){for(var n="",r=0;r
{6}
'.format(c,a,d,u,i,r+1,t[r].length>0?t[r]:" ");e.innerHTML='{1}
'.format(s,n)}}}function l(e){return 0===e.length?[]:e.split(/\r\n|\r|\n/g)}var s="hljs-ln",a="hljs-ln-line",i="hljs-ln-code",c="hljs-ln-numbers",d="hljs-ln-n",u="data-line-number";String.prototype.format=String.prototype.f=function(){var e=arguments;return this.replace(/\{(\d+)\}/g,function(t,n){return e[n]?e[n]:t})},"undefined"==typeof e.hljs?console.error("highlight.js not detected!"):(e.hljs.initLineNumbersOnLoad=n,e.hljs.lineNumbersBlock=o,t())}(window); \ No newline at end of file diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp index b299e840..9a66e391 100644 --- a/src/utils/vutils.cpp +++ b/src/utils/vutils.cpp @@ -551,6 +551,11 @@ QString VUtils::generateHtmlTemplate(MarkdownConverterType p_conType, bool p_exp extraFile += "\n"; } + if (g_config->getEnableCodeBlockLineNumber()) { + extraFile += "\n" + + "\n"; + } + QString htmlTemplate; if (p_exportPdf) { htmlTemplate = VNote::s_markdownTemplatePDF; diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp index 3c0b6f5e..c094ede3 100644 --- a/src/vconfigmanager.cpp +++ b/src/vconfigmanager.cpp @@ -179,6 +179,9 @@ void VConfigManager::initialize() "enable_heading_sequence").toBool(); m_colorColumn = getConfigFromSettings("global", "color_column").toInt(); + + m_enableCodeBlockLineNumber = getConfigFromSettings("global", + "enable_code_block_line_number").toBool(); } void VConfigManager::readPredefinedColorsFromSettings() diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h index b333df10..cdf47510 100644 --- a/src/vconfigmanager.h +++ b/src/vconfigmanager.h @@ -241,6 +241,9 @@ public: const QString &getEditorColorColumnBg() const; const QString &getEditorColorColumnFg() const; + bool getEnableCodeBlockLineNumber() const; + void setEnableCodeBlockLineNumber(bool p_enabled); + // Return the configured key sequence of @p_operation. // Return empty if there is no corresponding config. QString getShortcutKeySequence(const QString &p_operation) const; @@ -484,6 +487,9 @@ private: // The column to style in code block. int m_colorColumn; + // Whether display line number of code block in read mode. + bool m_enableCodeBlockLineNumber; + // The background color of the color column. QString m_editorColorColumnBg; @@ -1266,4 +1272,21 @@ inline const QString &VConfigManager::getEditorColorColumnFg() const return m_editorColorColumnFg; } +inline bool VConfigManager::getEnableCodeBlockLineNumber() const +{ + return m_enableCodeBlockLineNumber; +} + +inline void VConfigManager::setEnableCodeBlockLineNumber(bool p_enabled) +{ + if (m_enableCodeBlockLineNumber == p_enabled) { + return; + } + + m_enableCodeBlockLineNumber = p_enabled; + setConfigToSettings("global", + "enable_code_block_line_number", + m_enableCodeBlockLineNumber); +} + #endif // VCONFIGMANAGER_H diff --git a/src/vconstants.h b/src/vconstants.h index 4f03a60e..c90ee660 100644 --- a/src/vconstants.h +++ b/src/vconstants.h @@ -61,4 +61,25 @@ enum class PreviewImageType { Block, Inline, Invalid }; enum class PreviewImageSource { Image, CodeBlock, Invalid }; +enum HighlightBlockState +{ + Normal = 0, + + // A fenced code block. + CodeBlockStart, + CodeBlock, + CodeBlockEnd, + + // This block is inside a HTML comment region. + Comment +}; + +enum class LineNumberType +{ + None = 0, + Absolute, + Relative, + CodeBlock +}; + #endif diff --git a/src/vedit.cpp b/src/vedit.cpp index 836f3602..7045254e 100644 --- a/src/vedit.cpp +++ b/src/vedit.cpp @@ -1011,7 +1011,8 @@ void VEdit::resizeEvent(QResizeEvent *p_event) void VEdit::lineNumberAreaPaintEvent(QPaintEvent *p_event) { - if (!g_config->getEditorLineNumber()) { + int lineNumberType = g_config->getEditorLineNumber(); + if (!lineNumberType) { updateLineNumberAreaMargin(); m_lineNumberArea->hide(); return; @@ -1020,8 +1021,7 @@ void VEdit::lineNumberAreaPaintEvent(QPaintEvent *p_event) QPainter painter(m_lineNumberArea); painter.fillRect(p_event->rect(), g_config->getEditorLineNumberBg()); - QTextDocument *doc = document(); - QAbstractTextDocumentLayout *layout = doc->documentLayout(); + QAbstractTextDocumentLayout *layout = document()->documentLayout(); QTextBlock block = firstVisibleBlock(); int blockNumber = block.blockNumber(); @@ -1033,16 +1033,78 @@ void VEdit::lineNumberAreaPaintEvent(QPaintEvent *p_event) int eventBtm = p_event->rect().bottom(); const int digitHeight = m_lineNumberArea->getDigitHeight(); const int curBlockNumber = textCursor().block().blockNumber(); - const bool relative = g_config->getEditorLineNumber() == 2; const QString &fg = g_config->getEditorLineNumberFg(); const int lineDistanceHeight = m_config.m_lineDistanceHeight; painter.setPen(fg); + // Display line number only in code block. + if (lineNumberType == 3) { + if (m_file->getDocType() != DocType::Markdown) { + return; + } + + int number = 0; + while (block.isValid() && top <= eventBtm) { + int blockState = block.userState(); + switch (blockState) { + case HighlightBlockState::CodeBlockStart: + Q_ASSERT(number == 0); + number = 1; + break; + + case HighlightBlockState::CodeBlockEnd: + number = 0; + break; + + case HighlightBlockState::CodeBlock: + if (number == 0) { + // Need to find current line number in code block. + QTextBlock startBlock = block.previous(); + while (startBlock.isValid()) { + if (startBlock.userState() == HighlightBlockState::CodeBlockStart) { + number = block.blockNumber() - startBlock.blockNumber(); + break; + } + + startBlock = startBlock.previous(); + } + } + + break; + + default: + break; + } + + if (blockState == HighlightBlockState::CodeBlock) { + if (block.isVisible() && bottom >= eventTop) { + QString numberStr = QString::number(number); + painter.drawText(0, + top + 2, + m_lineNumberArea->width(), + digitHeight, + Qt::AlignRight, + numberStr); + } + + ++number; + } + + block = block.next(); + top = bottom; + bottom = top + (int)layout->blockBoundingRect(block).height() + lineDistanceHeight; + } + + return; + } + + // Handle lineNumberType 1 and 2. + Q_ASSERT(lineNumberType == 1 || lineNumberType == 2); while (block.isValid() && top <= eventBtm) { if (block.isVisible() && bottom >= eventTop) { bool currentLine = false; int number = blockNumber + 1; - if (relative) { + if (lineNumberType == 2) { number = blockNumber - curBlockNumber; if (number == 0) { currentLine = true; diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index c20c3046..4933071d 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -639,6 +639,16 @@ void VMainWindow::initMarkdownMenu() markdownMenu->addAction(codeBlockAct); codeBlockAct->setChecked(g_config->getEnableCodeBlockHighlight()); + QAction *lineNumberAct = new QAction(tr("Display Line Number in Code Blocks"), this); + lineNumberAct->setToolTip(tr("Enable line number in code blocks in read mode")); + lineNumberAct->setCheckable(true); + connect(lineNumberAct, &QAction::triggered, + this, [this](bool p_checked){ + g_config->setEnableCodeBlockLineNumber(p_checked); + }); + markdownMenu->addAction(lineNumberAct); + lineNumberAct->setChecked(g_config->getEnableCodeBlockLineNumber()); + QAction *previewImageAct = new QAction(tr("Preview Images In Edit Mode"), this); previewImageAct->setToolTip(tr("Enable image preview in edit mode (re-open current tabs to make it work)")); previewImageAct->setCheckable(true); @@ -1329,6 +1339,15 @@ void VMainWindow::initEditorLineNumberMenu(QMenu *p_menu) if (lineNumberMode == 2) { act->setChecked(true); } + + act = lineNumAct->addAction(tr("CodeBlock")); + act->setToolTip(tr("Display line number in code block in edit mode (for Markdown only)")); + act->setCheckable(true); + act->setData(3); + lineNumMenu->addAction(act); + if (lineNumberMode == 3) { + act->setChecked(true); + } } void VMainWindow::updateEditorStyleMenu() diff --git a/src/vnote.cpp b/src/vnote.cpp index f2e5c98c..6806aa0b 100644 --- a/src/vnote.cpp +++ b/src/vnote.cpp @@ -45,6 +45,8 @@ const QString VNote::c_raphaelJsFile = ":/utils/flowchart.js/raphael.min.js"; const QString VNote::c_mathjaxJsFile = "https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_CHTML"; +const QString VNote::c_highlightjsLineNumberExtraFile = ":/utils/highlightjs/highlightjs-line-numbers.min.js"; + const QString VNote::c_shortcutsDocFile_en = ":/resources/docs/shortcuts_en.md"; const QString VNote::c_shortcutsDocFile_zh = ":/resources/docs/shortcuts_zh.md"; diff --git a/src/vnote.h b/src/vnote.h index 5235b0e4..5ef0f36b 100644 --- a/src/vnote.h +++ b/src/vnote.h @@ -64,6 +64,9 @@ public: // Mathjax static const QString c_mathjaxJsFile; + // Highlight.js line number plugin + static const QString c_highlightjsLineNumberExtraFile; + static const QString c_shortcutsDocFile_en; static const QString c_shortcutsDocFile_zh; diff --git a/src/vnote.qrc b/src/vnote.qrc index a392fd28..cc1266ba 100644 --- a/src/vnote.qrc +++ b/src/vnote.qrc @@ -120,5 +120,6 @@ resources/icons/editing_modified.svg resources/docs/markdown_guide_en.md resources/docs/markdown_guide_zh.md + utils/highlightjs/highlightjs-line-numbers.min.js