Browse Source

support line number in code block in both read and edit mode

Le Tan 8 years ago
parent
commit
7fd2273aad

+ 0 - 13
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[]

+ 2 - 0
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.

+ 1 - 0
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.

+ 28 - 0
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);
+        }
+    }
+};

+ 1 - 0
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.

+ 1 - 0
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.

+ 30 - 0
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;
+}

+ 4 - 1
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
 

+ 1 - 0
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<t.length;r++)n+='<tr><td class="{0}"><div class="{1} {2}" {3}="{5}"></div></td><td class="{4}"><div class="{1}">{6}</div></td></tr>'.format(c,a,d,u,i,r+1,t[r].length>0?t[r]:" ");e.innerHTML='<table class="{0}">{1}</table>'.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);

+ 5 - 0
src/utils/vutils.cpp

@@ -551,6 +551,11 @@ QString VUtils::generateHtmlTemplate(MarkdownConverterType p_conType, bool p_exp
         extraFile += "<script>var VEnableImageCaption = true;</script>\n";
     }
 
+    if (g_config->getEnableCodeBlockLineNumber()) {
+        extraFile += "<script src=\"qrc" + VNote::c_highlightjsLineNumberExtraFile + "\"></script>\n" +
+                     "<script>var VEnableHighlightLineNumber = true;</script>\n";
+    }
+
     QString htmlTemplate;
     if (p_exportPdf) {
         htmlTemplate = VNote::s_markdownTemplatePDF;

+ 3 - 0
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()

+ 23 - 0
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

+ 21 - 0
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

+ 67 - 5
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;

+ 19 - 0
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()

+ 2 - 0
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";
 

+ 3 - 0
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;
 

+ 1 - 0
src/vnote.qrc

@@ -120,5 +120,6 @@
         <file>resources/icons/editing_modified.svg</file>
         <file>resources/docs/markdown_guide_en.md</file>
         <file>resources/docs/markdown_guide_zh.md</file>
+        <file>utils/highlightjs/highlightjs-line-numbers.min.js</file>
     </qresource>
 </RCC>