Browse Source

editor: add line number and editor-line-number config

Le Tan 8 years ago
parent
commit
4374f2d8f1

+ 2 - 0
src/resources/styles/default.mdhl

@@ -15,6 +15,8 @@ font-family: Hiragino Sans GB, 冬青黑体, Microsoft YaHei, 微软雅黑, Micr
 # [VNote] Style for trailing space
 trailing-space: ffebee
 font-size: 12
+line-number-background: bdbdbd
+line-number-foreground: 424242
 
 editor-selection
 foreground: eeeeee

+ 4 - 0
src/resources/vnote.ini

@@ -53,6 +53,10 @@ enable_vim_mode=false
 ; Enable smart input method in Vim mode (disable IM in non-Insert modes)
 enable_smart_im_in_vim_mode=true
 
+; Display an area besides the editor area to show line number
+; 0 - None, 1 - Absolute, 2 - Relative
+editor_line_number=0
+
 [session]
 tools_dock_checked=true
 

+ 20 - 3
src/vconfigmanager.cpp

@@ -150,6 +150,9 @@ void VConfigManager::initialize()
 
     m_enableSmartImInVimMode = getConfigFromSettings("global",
                                                      "enable_smart_im_in_vim_mode").toBool();
+
+    m_editorLineNumber = getConfigFromSettings("global",
+                                               "editor_line_number").toInt();
 }
 
 void VConfigManager::readPredefinedColorsFromSettings()
@@ -336,6 +339,8 @@ void VConfigManager::updateMarkdownEditStyle()
     static const QString defaultVimVisualBg = "#90CAF9";
     static const QString defaultVimReplaceBg = "#F8BBD0";
     static const QString defaultTrailingSpaceBackground = "#FFEBEE";
+    static const QString defaultLineNumberBg = "#BDBDBD";
+    static const QString defaultLineNumberFg = "#424242";
 
     // Read style file .mdhl
     QString file(getEditorStyleUrl());
@@ -392,11 +397,23 @@ void VConfigManager::updateMarkdownEditStyle()
     }
 
     m_editorTrailingSpaceBg = defaultTrailingSpaceBackground;
+    m_editorLineNumberBg = defaultLineNumberBg;
+    m_editorLineNumberFg = defaultLineNumberFg;
     auto editorIt = styles.find("editor");
     if (editorIt != styles.end()) {
-        auto trailingIt = editorIt->find("trailing-space");
-        if (trailingIt != editorIt->end()) {
-            m_editorTrailingSpaceBg = "#" + *trailingIt;
+        auto it = editorIt->find("trailing-space");
+        if (it != editorIt->end()) {
+            m_editorTrailingSpaceBg = "#" + *it;
+        }
+
+        it = editorIt->find("line-number-background");
+        if (it != editorIt->end()) {
+            m_editorLineNumberBg = "#" + *it;
+        }
+
+        it = editorIt->find("line-number-foreground");
+        if (it != editorIt->end()) {
+            m_editorLineNumberFg = "#" + *it;
         }
     }
 }

+ 41 - 0
src/vconfigmanager.h

@@ -199,6 +199,12 @@ public:
     bool getEnableSmartImInVimMode() const;
     void setEnableSmartImInVimMode(bool p_enabled);
 
+    int getEditorLineNumber() const;
+    void setEditorLineNumber(int p_mode);
+
+    const QString &getEditorLineNumberBg() const;
+    const QString &getEditorLineNumberFg() const;
+
     // Get the folder the ini file exists.
     QString getConfigFolder() const;
 
@@ -364,6 +370,15 @@ private:
     // Enable smart input method in Vim mode.
     bool m_enableSmartImInVimMode;
 
+    // Editor line number mode.
+    int m_editorLineNumber;
+
+    // The background color of the line number area.
+    QString m_editorLineNumberBg;
+
+    // The foreground color of the line number area.
+    QString m_editorLineNumberFg;
+
     // The name of the config file in each directory, obsolete.
     // Use c_dirConfigFile instead.
     static const QString c_obsoleteDirConfigFile;
@@ -964,4 +979,30 @@ inline void VConfigManager::setEnableSmartImInVimMode(bool p_enabled)
                         m_enableSmartImInVimMode);
 }
 
+inline int VConfigManager::getEditorLineNumber() const
+{
+    return m_editorLineNumber;
+}
+
+inline void VConfigManager::setEditorLineNumber(int p_mode)
+{
+    if (m_editorLineNumber == p_mode) {
+        return;
+    }
+
+    m_editorLineNumber = p_mode;
+    setConfigToSettings("global", "editor_line_number",
+                        m_editorLineNumber);
+}
+
+inline const QString &VConfigManager::getEditorLineNumberBg() const
+{
+    return m_editorLineNumberBg;
+}
+
+inline const QString &VConfigManager::getEditorLineNumberFg() const
+{
+    return m_editorLineNumberFg;
+}
+
 #endif // VCONFIGMANAGER_H

+ 171 - 0
src/vedit.cpp

@@ -84,6 +84,16 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent)
 
     connect(this, &VEdit::selectionChanged,
             this, &VEdit::highlightSelectedWord);
+
+    m_lineNumberArea = new LineNumberArea(this);
+    connect(document(), &QTextDocument::blockCountChanged,
+            this, &VEdit::updateLineNumberAreaMargin);
+    connect(this, &QTextEdit::textChanged,
+            this, &VEdit::updateLineNumberArea);
+    connect(verticalScrollBar(), &QScrollBar::valueChanged,
+            this, &VEdit::updateLineNumberArea);
+
+    updateLineNumberAreaMargin();
 }
 
 VEdit::~VEdit()
@@ -833,3 +843,164 @@ void VEdit::decorateText(TextDecoration p_decoration)
         m_editOps->decorateText(p_decoration);
     }
 }
+
+void VEdit::updateLineNumberAreaMargin()
+{
+    int width = 0;
+    if (vconfig.getEditorLineNumber()) {
+        width = m_lineNumberArea->calculateWidth();
+    }
+
+    setViewportMargins(width, 0, 0, 0);
+}
+
+void VEdit::updateLineNumberArea()
+{
+    if (vconfig.getEditorLineNumber()) {
+        if (!m_lineNumberArea->isVisible()) {
+            updateLineNumberAreaMargin();
+            m_lineNumberArea->show();
+        }
+
+        m_lineNumberArea->update();
+    } else if (m_lineNumberArea->isVisible()) {
+        updateLineNumberAreaMargin();
+        m_lineNumberArea->hide();
+    }
+}
+
+void VEdit::resizeEvent(QResizeEvent *p_event)
+{
+    QTextEdit::resizeEvent(p_event);
+
+    if (vconfig.getEditorLineNumber()) {
+        QRect rect = contentsRect();
+        m_lineNumberArea->setGeometry(QRect(rect.left(),
+                                            rect.top(),
+                                            m_lineNumberArea->calculateWidth(),
+                                            rect.height()));
+    }
+}
+
+void VEdit::lineNumberAreaPaintEvent(QPaintEvent *p_event)
+{
+    if (!vconfig.getEditorLineNumber()) {
+        updateLineNumberAreaMargin();
+        m_lineNumberArea->hide();
+        return;
+    }
+
+    QPainter painter(m_lineNumberArea);
+    painter.fillRect(p_event->rect(), vconfig.getEditorLineNumberBg());
+
+    QTextDocument *doc = document();
+    QAbstractTextDocumentLayout *layout = doc->documentLayout();
+
+    QTextBlock block = firstVisibleBlock();
+    int blockNumber = block.blockNumber();
+    int offsetY = contentOffsetY();
+    QRectF rect = layout->blockBoundingRect(block);
+    int top = offsetY + (int)rect.y();
+    int bottom = top + (int)rect.height();
+    int eventTop = p_event->rect().top();
+    int eventBtm = p_event->rect().bottom();
+    const int digitHeight = m_lineNumberArea->getDigitHeight();
+    const int curBlockNumber = textCursor().block().blockNumber();
+    const bool relative = vconfig.getEditorLineNumber() == 2;
+    const QString &fg = vconfig.getEditorLineNumberFg();
+
+    while (block.isValid() && top <= eventBtm) {
+        if (block.isVisible() && bottom >= eventTop) {
+            QString number = QString::number(relative ? blockNumber - curBlockNumber
+                                                      : blockNumber + 1);
+            painter.setPen(fg);
+            painter.drawText(0, top + 2,
+                             m_lineNumberArea->width(),
+                             digitHeight, Qt::AlignRight, number);
+        }
+
+        block = block.next();
+        top = bottom;
+        bottom = top + (int)layout->blockBoundingRect(block).height();
+        ++blockNumber;
+    }
+}
+
+int VEdit::contentOffsetY()
+{
+    int offsety = 0;
+    QScrollBar *sb = verticalScrollBar();
+    offsety = sb->value();
+    return -offsety;
+}
+
+QTextBlock VEdit::firstVisibleBlock()
+{
+    QTextDocument *doc = document();
+    QAbstractTextDocumentLayout *layout = doc->documentLayout();
+    int offsetY = contentOffsetY();
+
+    // Binary search.
+    int idx = -1;
+    int start = 0, end = doc->blockCount() - 1;
+    while (start <= end) {
+        int mid = start + (end - start) / 2;
+        QTextBlock block = doc->findBlockByNumber(mid);
+        if (!block.isValid()) {
+            break;
+        }
+
+        int y = offsetY + (int)layout->blockBoundingRect(block).y();
+        if (y == 0) {
+            return block;
+        } else if (y < 0) {
+            start = mid + 1;
+        } else {
+            if (idx == -1 || mid < idx) {
+                idx = mid;
+            }
+
+            end = mid - 1;
+        }
+    }
+
+    if (idx != -1) {
+        return doc->findBlockByNumber(idx);
+    }
+
+    // Linear search.
+    qDebug() << "fall back to linear search for first visible block";
+    QTextBlock block = doc->begin();
+    while (block.isValid()) {
+        int y = offsetY + (int)layout->blockBoundingRect(block).y();
+        if (y >= 0) {
+            return block;
+        }
+
+        block = block.next();
+    }
+
+    Q_ASSERT(false);
+    return doc->begin();
+}
+
+int LineNumberArea::calculateWidth() const
+{
+    int bc = m_document->blockCount();
+    if (m_blockCount == bc) {
+        return m_width;
+    }
+
+    const_cast<LineNumberArea *>(this)->m_blockCount = bc;
+    int digits = 1;
+    int max = qMax(1, m_blockCount);
+    while (max >= 10) {
+        max /= 10;
+        ++digits;
+    }
+
+    int width = 2 + m_digitWidth * digits;
+    const_cast<LineNumberArea *>(this)->m_width = width;
+
+    return m_width;
+}

+ 62 - 0
src/vedit.h

@@ -7,6 +7,7 @@
 #include <QVector>
 #include <QList>
 #include <QColor>
+#include <QRect>
 #include <QFontMetrics>
 #include "vconstants.h"
 #include "vtoc.h"
@@ -16,6 +17,10 @@ class VEditOperations;
 class QLabel;
 class QTimer;
 class VVim;
+class QPaintEvent;
+class QResizeEvent;
+class QSize;
+class QWidget;
 
 enum class SelectionId {
     CurrentLine = 0,
@@ -55,6 +60,8 @@ public:
     bool m_highlightWholeBlock;
 };
 
+class LineNumberArea;
+
 class VEdit : public QTextEdit
 {
     Q_OBJECT
@@ -95,6 +102,9 @@ public:
     // Insert decoration markers or decorate selected text.
     void decorateText(TextDecoration p_decoration);
 
+    // LineNumberArea will call this to request paint itself.
+    void lineNumberAreaPaintEvent(QPaintEvent *p_event);
+
 signals:
     // Request VEditTab to save and exit edit mode.
     void saveAndRead();
@@ -137,6 +147,11 @@ private slots:
     void highlightTrailingSpace();
     void handleCursorPositionChanged();
 
+    // Update viewport margin to hold the line number area.
+    void updateLineNumberAreaMargin();
+
+    void updateLineNumberArea();
+
 protected:
     QPointer<VFile> m_file;
     VEditOperations *m_editOps;
@@ -151,6 +166,8 @@ protected:
     virtual void mouseReleaseEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE;
     virtual void mouseMoveEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE;
 
+    virtual void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE;
+
     // Update m_config according to VConfigManager.
     void updateConfig();
 
@@ -177,6 +194,8 @@ private:
     // Whether enable input method.
     bool m_enableInputMethod;
 
+    LineNumberArea *m_lineNumberArea;
+
     void showWrapLabel();
 
     // Trigger the timer to request highlight.
@@ -196,8 +215,51 @@ private:
 
     void highlightSearchedWord(const QString &p_text, uint p_options);
     bool wordInSearchedSelection(const QString &p_text);
+
+    // Return the first visible block.
+    QTextBlock firstVisibleBlock();
+
+    // Return the y offset of the content.
+    int contentOffsetY();
 };
 
+class LineNumberArea : public QWidget
+{
+public:
+    LineNumberArea(VEdit *p_editor)
+        : QWidget(p_editor), m_editor(p_editor),
+          m_document(p_editor->document()),
+          m_width(0), m_blockCount(-1)
+    {
+        m_digitWidth = m_editor->fontMetrics().width(QLatin1Char('9'));
+        m_digitHeight = m_editor->fontMetrics().height();
+    }
+
+    QSize sizeHint() const Q_DECL_OVERRIDE
+    {
+        return QSize(calculateWidth(), 0);
+    }
+
+    int calculateWidth() const;
+
+    int getDigitHeight() const
+    {
+        return m_digitHeight;
+    }
 
+protected:
+    void paintEvent(QPaintEvent *p_event) Q_DECL_OVERRIDE
+    {
+        m_editor->lineNumberAreaPaintEvent(p_event);
+    }
+
+private:
+    VEdit *m_editor;
+    const QTextDocument *m_document;
+    int m_width;
+    int m_blockCount;
+    int m_digitWidth;
+    int m_digitHeight;
+};
 
 #endif // VEDIT_H

+ 47 - 0
src/vmainwindow.cpp

@@ -848,6 +848,8 @@ void VMainWindow::initEditMenu()
 
     initEditorBackgroundMenu(editMenu);
 
+    initEditorLineNumberMenu(editMenu);
+
     editMenu->addAction(cursorLineAct);
     cursorLineAct->setChecked(vconfig.getHighlightCursorLine());
 
@@ -1130,6 +1132,51 @@ void VMainWindow::initEditorBackgroundMenu(QMenu *menu)
     }
 }
 
+void VMainWindow::initEditorLineNumberMenu(QMenu *p_menu)
+{
+    QMenu *lineNumMenu = p_menu->addMenu(tr("Line Number"));
+    lineNumMenu->setToolTipsVisible(true);
+
+    QActionGroup *lineNumAct = new QActionGroup(lineNumMenu);
+    connect(lineNumAct, &QActionGroup::triggered,
+            this, [this](QAction *p_action){
+                if (!p_action) {
+                    return;
+                }
+
+                vconfig.setEditorLineNumber(p_action->data().toInt());
+            });
+
+    int lineNumberMode = vconfig.getEditorLineNumber();
+
+    QAction *act = lineNumAct->addAction(tr("None"));
+    act->setToolTip(tr("Do not display line number in edit mode"));
+    act->setCheckable(true);
+    act->setData(0);
+    lineNumMenu->addAction(act);
+    if (lineNumberMode == 0) {
+        act->setChecked(true);
+    }
+
+    act = lineNumAct->addAction(tr("Absolute"));
+    act->setToolTip(tr("Display absolute line number in edit mode"));
+    act->setCheckable(true);
+    act->setData(1);
+    lineNumMenu->addAction(act);
+    if (lineNumberMode == 1) {
+        act->setChecked(true);
+    }
+
+    act = lineNumAct->addAction(tr("Relative"));
+    act->setToolTip(tr("Display line number relative to current cursor line in edit mode"));
+    act->setCheckable(true);
+    act->setData(2);
+    lineNumMenu->addAction(act);
+    if (lineNumberMode == 2) {
+        act->setChecked(true);
+    }
+}
+
 void VMainWindow::updateEditorStyleMenu()
 {
     QMenu *menu = dynamic_cast<QMenu *>(sender());

+ 4 - 0
src/vmainwindow.h

@@ -124,6 +124,10 @@ private:
     void initRenderBackgroundMenu(QMenu *menu);
     void initRenderStyleMenu(QMenu *p_menu);
     void initEditorBackgroundMenu(QMenu *menu);
+
+    // Init the Line Number submenu in Edit menu.
+    void initEditorLineNumberMenu(QMenu *p_menu);
+
     void initEditorStyleMenu(QMenu *p_emnu);
     void changeSplitterView(int nrPanel);
     void updateWindowTitle(const QString &str);

+ 1 - 1
src/vstyleparser.cpp

@@ -234,7 +234,7 @@ void VStyleParser::fetchMarkdownEditorStyles(QPalette &palette, QFont &font,
         }
 
         // Get custom styles:
-        //     trailing-space.
+        //     trailing-space, line-number-background, line-number-foreground
         case pmh_attr_type_other:
         {
             QString attrName(editorStyles->name);