Browse Source

support highlighting trailing space

Le Tan 8 years ago
parent
commit
73530355fd

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

@@ -5,6 +5,8 @@ editor
 # QTextEdit just choose the first available font, so specify the Chinese fonts first
 # Do not use "" to quote the name
 font-family: Hiragino Sans GB, 冬青黑体, Microsoft YaHei, 微软雅黑, Microsoft YaHei UI, WenQuanYi Micro Hei, 文泉驿雅黑, Dengxian, 等线体, STXihei, 华文细黑, Liberation Sans, Droid Sans, NSimSun, 新宋体, SimSun, 宋体, Helvetica, sans-serif, Tahoma, Arial, Verdana, Geneva, Georgia, Times New Roman
+# Style for trailing space
+trailing-space: ffebee
 
 editor-selection
 foreground: eeeeee

+ 3 - 0
src/resources/vnote.ini

@@ -42,6 +42,9 @@ enable_image_caption=false
 ; Image folder name for the notes
 image_folder=_v_images
 
+; Enable trailing space highlight
+enable_trailing_space_highlight=true
+
 [session]
 tools_dock_checked=true
 

+ 18 - 0
src/vconfigmanager.cpp

@@ -143,6 +143,9 @@ void VConfigManager::initialize()
 
     m_imageFolder = getConfigFromSettings("global",
                                           "image_folder").toString();
+
+    m_enableTrailingSpaceHighlight = getConfigFromSettings("global",
+                                                           "enable_trailing_space_highlight").toBool();
 }
 
 void VConfigManager::readPredefinedColorsFromSettings()
@@ -325,6 +328,7 @@ void VConfigManager::updateMarkdownEditStyle()
 {
     static const QString defaultCurrentLineBackground = "#C5CAE9";
     static const QString defaultCurrentLineVimBackground = "#A5D6A7";
+    static const QString defaultTrailingSpaceBackground = "#FFEBEE";
 
     // Read style file .mdhl
     QString file(getEditorStyleUrl());
@@ -349,6 +353,7 @@ void VConfigManager::updateMarkdownEditStyle()
     if (editorCurrentLineIt != styles.end()) {
         auto backgroundIt = editorCurrentLineIt->find("background");
         if (backgroundIt != editorCurrentLineIt->end()) {
+            // Do not need to add "#" here, since this is a built-in attribute.
             m_editorCurrentLineBackground = *backgroundIt;
         }
 
@@ -357,6 +362,19 @@ void VConfigManager::updateMarkdownEditStyle()
             m_editorCurrentLineVimBackground = "#" + *vimBackgroundIt;
         }
     }
+
+    m_editorTrailingSpaceBackground = defaultTrailingSpaceBackground;
+    auto editorIt = styles.find("editor");
+    if (editorIt != styles.end()) {
+        auto trailingIt = editorIt->find("trailing-space");
+        if (trailingIt != editorIt->end()) {
+            m_editorTrailingSpaceBackground = "#" + *trailingIt;
+        }
+    }
+
+    qDebug() << "editor-current-line" << m_editorCurrentLineBackground;
+    qDebug() << "editor-current-line-vim" << m_editorCurrentLineVimBackground;
+    qDebug() << "editor-trailing-space" << m_editorTrailingSpaceBackground;
 }
 
 void VConfigManager::updateEditStyle()

+ 32 - 2
src/vconfigmanager.h

@@ -157,6 +157,7 @@ public:
 
     inline QString getEditorCurrentLineBackground() const;
     inline QString getEditorCurrentLineVimBackground() const;
+    inline QString getEditorTrailingSpaceBackground() const;
 
     inline bool getEnableCodeBlockHighlight() const;
     inline void setEnableCodeBlockHighlight(bool p_enabled);
@@ -174,12 +175,13 @@ public:
     inline void setEnableImageCaption(bool p_enabled);
 
     inline const QString &getImageFolder() const;
-
     // Empty string to reset the default folder.
     inline void setImageFolder(const QString &p_folder);
-
     inline bool isCustomImageFolder() const;
 
+    inline bool getEnableTrailingSpaceHighlight() const;
+    inline void setEnableTrailingSapceHighlight(bool p_enabled);
+
     // Get the folder the ini file exists.
     QString getConfigFolder() const;
 
@@ -290,9 +292,13 @@ private:
 
     // Current line background color in editor.
     QString m_editorCurrentLineBackground;
+
     // Current line background color in editor in Vim mode.
     QString m_editorCurrentLineVimBackground;
 
+    // Trailing space background color in editor.
+    QString m_editorTrailingSpaceBackground;
+
     // Enable colde block syntax highlight.
     bool m_enableCodeBlockHighlight;
 
@@ -312,6 +318,9 @@ private:
     // Each notebook can specify its custom folder.
     QString m_imageFolder;
 
+    // Enable trailing-space highlight.
+    bool m_enableTrailingSpaceHighlight;
+
     // The name of the config file in each directory, obsolete.
     // Use c_dirConfigFile instead.
     static const QString c_obsoleteDirConfigFile;
@@ -722,6 +731,11 @@ inline QString VConfigManager::getEditorCurrentLineVimBackground() const
     return m_editorCurrentLineVimBackground;
 }
 
+inline QString VConfigManager::getEditorTrailingSpaceBackground() const
+{
+    return m_editorTrailingSpaceBackground;
+}
+
 inline bool VConfigManager::getEnableCodeBlockHighlight() const
 {
     return m_enableCodeBlockHighlight;
@@ -826,4 +840,20 @@ inline bool VConfigManager::isCustomImageFolder() const
     return m_imageFolder != getDefaultConfig("global", "image_folder").toString();
 }
 
+inline bool VConfigManager::getEnableTrailingSpaceHighlight() const
+{
+    return m_enableTrailingSpaceHighlight;
+}
+
+inline void VConfigManager::setEnableTrailingSapceHighlight(bool p_enabled)
+{
+    if (m_enableTrailingSpaceHighlight == p_enabled) {
+        return;
+    }
+
+    m_enableTrailingSpaceHighlight = p_enabled;
+    setConfigToSettings("global", "enable_trailing_space_highlight",
+                        m_enableTrailingSpaceHighlight);
+}
+
 #endif // VCONFIGMANAGER_H

+ 124 - 42
src/vedit.cpp

@@ -17,12 +17,13 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent)
     : QTextEdit(p_parent), m_file(p_file), m_editOps(NULL)
 {
     const int labelTimerInterval = 500;
-    const int selectedWordTimerInterval = 500;
+    const int extraSelectionHighlightTimer = 500;
     const int labelSize = 64;
 
     m_cursorLineColor = QColor(g_vnote->getColorFromPalette("Indigo1"));
     m_selectedWordColor = QColor("Yellow");
     m_searchedWordColor = QColor(g_vnote->getColorFromPalette("Green4"));
+    m_trailingSpaceColor = QColor(vconfig.getEditorTrailingSpaceBackground());
 
     QPixmap wrapPixmap(":/resources/icons/search_wrap.svg");
     m_wrapLabel = new QLabel(this);
@@ -34,11 +35,11 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent)
     connect(m_labelTimer, &QTimer::timeout,
             this, &VEdit::labelTimerTimeout);
 
-    m_selectedWordTimer = new QTimer(this);
-    m_selectedWordTimer->setSingleShot(true);
-    m_selectedWordTimer->setInterval(selectedWordTimerInterval);
-    connect(m_selectedWordTimer, &QTimer::timeout,
-            this, &VEdit::highlightSelectedWord);
+    m_highlightTimer = new QTimer(this);
+    m_highlightTimer->setSingleShot(true);
+    m_highlightTimer->setInterval(extraSelectionHighlightTimer);
+    connect(m_highlightTimer, &QTimer::timeout,
+            this, &VEdit::doHighlightExtraSelections);
 
     connect(document(), &QTextDocument::modificationChanged,
             (VFile *)m_file, &VFile::setModified);
@@ -47,9 +48,10 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent)
     updateFontAndPalette();
 
     connect(this, &VEdit::cursorPositionChanged,
-            this, &VEdit::highlightCurrentLine);
+            this, &VEdit::handleCursorPositionChanged);
+
     connect(this, &VEdit::selectionChanged,
-            this, &VEdit::triggerHighlightSelectedWord);
+            this, &VEdit::highlightSelectedWord);
 }
 
 VEdit::~VEdit()
@@ -374,7 +376,17 @@ void VEdit::updateFontAndPalette()
     setPalette(vconfig.getBaseEditPalette());
 }
 
-void VEdit::highlightExtraSelections()
+void VEdit::highlightExtraSelections(bool p_now)
+{
+    m_highlightTimer->stop();
+    if (p_now) {
+        doHighlightExtraSelections();
+    } else {
+        m_highlightTimer->start();
+    }
+}
+
+void VEdit::doHighlightExtraSelections()
 {
     int nrExtra = m_extraSelections.size();
     Q_ASSERT(nrExtra == (int)SelectionId::MaxSelection);
@@ -382,6 +394,7 @@ void VEdit::highlightExtraSelections()
     for (int i = 0; i < nrExtra; ++i) {
         extraSelects.append(m_extraSelections[i]);
     }
+
     setExtraSelections(extraSelects);
 }
 
@@ -390,23 +403,24 @@ void VEdit::highlightCurrentLine()
     QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::CurrentLine];
     if (vconfig.getHighlightCursorLine() && !isReadOnly()) {
         // Need to highlight current line.
-        selects.clear();
-
         QTextEdit::ExtraSelection select;
         select.format.setBackground(m_cursorLineColor);
         select.format.setProperty(QTextFormat::FullWidthSelection, true);
         select.cursor = textCursor();
         select.cursor.clearSelection();
+
+        selects.clear();
         selects.append(select);
     } else {
         // Need to clear current line highlight.
         if (selects.isEmpty()) {
             return;
         }
+
         selects.clear();
     }
 
-    highlightExtraSelections();
+    highlightExtraSelections(true);
 }
 
 void VEdit::setReadOnly(bool p_ro)
@@ -417,20 +431,67 @@ void VEdit::setReadOnly(bool p_ro)
 
 void VEdit::highlightSelectedWord()
 {
-    QString text;
-    if (vconfig.getHighlightSelectedWord()) {
-        text = textCursor().selectedText().trimmed();
-        if (wordInSearchedSelection(text)) {
-            qDebug() << "select searched word, just skip";
-            text = "";
+    QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SelectedWord];
+    if (!vconfig.getHighlightSelectedWord()) {
+        if (!selects.isEmpty()) {
+            selects.clear();
+            highlightExtraSelections(true);
         }
+
+        return;
     }
+
+    QString text = textCursor().selectedText().trimmed();
+    if (text.isEmpty() || wordInSearchedSelection(text)) {
+        selects.clear();
+        highlightExtraSelections(true);
+        return;
+    }
+
     QTextCharFormat format;
     format.setBackground(m_selectedWordColor);
     highlightTextAll(text, FindOption::CaseSensitive, SelectionId::SelectedWord,
                      format);
 }
 
+// Do not highlight trailing spaces with current cursor right behind.
+static void trailingSpaceFilter(VEdit *p_editor, QList<QTextEdit::ExtraSelection> &p_result)
+{
+    QTextCursor cursor = p_editor->textCursor();
+    if (!cursor.atBlockEnd()) {
+        return;
+    }
+
+    int cursorPos = cursor.position();
+    for (auto it = p_result.begin(); it != p_result.end(); ++it) {
+        if (it->cursor.selectionEnd() == cursorPos) {
+            p_result.erase(it);
+
+            // There will be only one.
+            return;
+        }
+    }
+}
+
+void VEdit::highlightTrailingSpace()
+{
+    if (!vconfig.getEnableTrailingSpaceHighlight()) {
+        QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::TrailingSapce];
+        if (!selects.isEmpty()) {
+            selects.clear();
+            highlightExtraSelections(true);
+        }
+        return;
+    }
+
+    QTextCharFormat format;
+    format.setBackground(m_trailingSpaceColor);
+    QString text("\\s+$");
+    highlightTextAll(text, FindOption::RegularExpression,
+                     SelectionId::TrailingSapce, format,
+                     trailingSpaceFilter);
+}
+
 bool VEdit::wordInSearchedSelection(const QString &p_text)
 {
     QString text = p_text.trimmed();
@@ -444,24 +505,9 @@ bool VEdit::wordInSearchedSelection(const QString &p_text)
     return false;
 }
 
-void VEdit::triggerHighlightSelectedWord()
-{
-    QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SelectedWord];
-    if (!vconfig.getHighlightSelectedWord() && selects.isEmpty()) {
-        return;
-    }
-    m_selectedWordTimer->stop();
-    QString text = textCursor().selectedText().trimmed();
-    if (text.isEmpty()) {
-        // Clear previous selection right now.
-        highlightSelectedWord();
-    } else {
-        m_selectedWordTimer->start();
-    }
-}
-
 void VEdit::highlightTextAll(const QString &p_text, uint p_options,
-                             SelectionId p_id, QTextCharFormat p_format)
+                             SelectionId p_id, QTextCharFormat p_format,
+                             void (*p_filter)(VEdit *, QList<QTextEdit::ExtraSelection> &))
 {
     QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)p_id];
     if (!p_text.isEmpty()) {
@@ -474,32 +520,42 @@ void VEdit::highlightTextAll(const QString &p_text, uint p_options,
             select.cursor = occurs[i];
             selects.append(select);
         }
-        qDebug() << "highlight" << selects.size() << "occurences of" << p_text;
     } else {
         if (selects.isEmpty()) {
             return;
         }
         selects.clear();
     }
+
+    if (p_filter) {
+        p_filter(this, selects);
+    }
+
     highlightExtraSelections();
 }
 
 void VEdit::highlightSearchedWord(const QString &p_text, uint p_options)
 {
+    QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SearchedKeyword];
+    if (!vconfig.getHighlightSearchedWord() || p_text.isEmpty()) {
+        if (!selects.isEmpty()) {
+            selects.clear();
+            highlightExtraSelections(true);
+        }
+
+        return;
+    }
+
     QTextCharFormat format;
     format.setBackground(m_searchedWordColor);
-    QString text;
-    if (vconfig.getHighlightSearchedWord()) {
-        text = p_text;
-    }
-    highlightTextAll(text, p_options, SelectionId::SearchedKeyword, format);
+    highlightTextAll(p_text, p_options, SelectionId::SearchedKeyword, format);
 }
 
 void VEdit::clearSearchedWordHighlight()
 {
     QList<QTextEdit::ExtraSelection> &selects = m_extraSelections[(int)SelectionId::SearchedKeyword];
     selects.clear();
-    highlightExtraSelections();
+    highlightExtraSelections(true);
 }
 
 void VEdit::contextMenuEvent(QContextMenuEvent *p_event)
@@ -569,3 +625,29 @@ VFile *VEdit::getFile() const
     return m_file;
 }
 
+void VEdit::handleCursorPositionChanged()
+{
+    static QTextCursor lastCursor;
+
+    QTextCursor cursor = textCursor();
+    if (lastCursor.isNull() || cursor.blockNumber() != lastCursor.blockNumber()) {
+        highlightCurrentLine();
+        highlightTrailingSpace();
+    } else {
+        // Judge whether we have trailing space at current line.
+        QString text = cursor.block().text();
+        if (text.rbegin()->isSpace()) {
+            highlightTrailingSpace();
+        }
+
+        // Handle word-wrap in one block.
+        // Highlight current line if in different visual line.
+        if ((lastCursor.positionInBlock() - lastCursor.columnNumber()) !=
+            (cursor.positionInBlock() - cursor.columnNumber())) {
+            highlightCurrentLine();
+        }
+    }
+
+    lastCursor = cursor;
+}
+

+ 23 - 5
src/vedit.h

@@ -19,6 +19,7 @@ enum class SelectionId {
     CurrentLine = 0,
     SelectedWord,
     SearchedKeyword,
+    TrailingSapce,
     MaxSelection
 };
 
@@ -57,11 +58,12 @@ signals:
 
 private slots:
     void labelTimerTimeout();
-    void triggerHighlightSelectedWord();
     void highlightSelectedWord();
     void handleSaveExitAct();
     void handleDiscardExitAct();
     void handleEditAct();
+    void highlightTrailingSpace();
+    void handleCursorPositionChanged();
 
 protected slots:
     virtual void highlightCurrentLine();
@@ -77,19 +79,35 @@ protected:
 private:
     QLabel *m_wrapLabel;
     QTimer *m_labelTimer;
-    // highlightExtraSelections() will highlight these selections.
+
+    // doHighlightExtraSelections() will highlight these selections.
     // Selections are indexed by SelectionId.
     QVector<QList<QTextEdit::ExtraSelection> > m_extraSelections;
-    QTimer *m_selectedWordTimer;
+
     QColor m_selectedWordColor;
     QColor m_searchedWordColor;
+    QColor m_trailingSpaceColor;
+
+    // Timer for extra selections highlight.
+    QTimer *m_highlightTimer;
 
     void showWrapLabel();
-    void highlightExtraSelections();
+
+    // Trigger the timer to request highlight.
+    // If @p_now is true, stop the timer and highlight immediately.
+    void highlightExtraSelections(bool p_now = false);
+
+    // Do the real work to highlight extra selections.
+    void doHighlightExtraSelections();
+
     // Find all the occurences of @p_text.
     QList<QTextCursor> findTextAll(const QString &p_text, uint p_options);
+
+    // @p_fileter: a function to filter out highlight results.
     void highlightTextAll(const QString &p_text, uint p_options,
-                          SelectionId p_id, QTextCharFormat p_format);
+                          SelectionId p_id, QTextCharFormat p_format,
+                          void (*p_filter)(VEdit *, QList<QTextEdit::ExtraSelection> &) = NULL);
+
     void highlightSearchedWord(const QString &p_text, uint p_options);
     bool wordInSearchedSelection(const QString &p_text);
 };

+ 15 - 0
src/vmainwindow.cpp

@@ -600,6 +600,13 @@ void VMainWindow::initEditMenu()
     connect(selectedWordAct, &QAction::triggered,
             this, &VMainWindow::changeHighlightSelectedWord);
 
+    // Highlight trailing space.
+    QAction *trailingSapceAct = new QAction(tr("Highlight Trailing Sapces"), this);
+    trailingSapceAct->setToolTip(tr("Highlight all the spaces at the end of a line"));
+    trailingSapceAct->setCheckable(true);
+    connect(trailingSapceAct, &QAction::triggered,
+            this, &VMainWindow::changeHighlightTrailingSapce);
+
     editMenu->addAction(m_insertImageAct);
     editMenu->addSeparator();
     m_insertImageAct->setEnabled(false);
@@ -672,6 +679,9 @@ void VMainWindow::initEditMenu()
 
     editMenu->addAction(selectedWordAct);
     selectedWordAct->setChecked(vconfig.getHighlightSelectedWord());
+
+    editMenu->addAction(trailingSapceAct);
+    trailingSapceAct->setChecked(vconfig.getEnableTrailingSpaceHighlight());
 }
 
 void VMainWindow::initDockWindows()
@@ -784,6 +794,11 @@ void VMainWindow::changeHighlightSearchedWord(bool p_checked)
     vconfig.setHighlightSearchedWord(p_checked);
 }
 
+void VMainWindow::changeHighlightTrailingSapce(bool p_checked)
+{
+    vconfig.setEnableTrailingSapceHighlight(p_checked);
+}
+
 void VMainWindow::setTabStopWidth(QAction *action)
 {
     if (!action) {

+ 1 - 0
src/vmainwindow.h

@@ -59,6 +59,7 @@ private slots:
     void changeHighlightCursorLine(bool p_checked);
     void changeHighlightSelectedWord(bool p_checked);
     void changeHighlightSearchedWord(bool p_checked);
+    void changeHighlightTrailingSapce(bool p_checked);
     void handleCurTabStatusChanged(const VFile *p_file, const VEditTab *p_editTab, bool p_editMode);
     void onePanelView();
     void twoPanelView();

+ 14 - 0
src/vstyleparser.cpp

@@ -186,8 +186,10 @@ void VStyleParser::fetchMarkdownEditorStyles(QPalette &palette, QFont &font,
                                              QMap<QString, QMap<QString, QString>> &styles) const
 {
     QString ruleKey;
+
     // editor
     pmh_style_attribute *editorStyles = markdownStyles->editor_styles;
+    ruleKey = "editor";
     while (editorStyles) {
         switch (editorStyles->type) {
         case pmh_attr_type_foreground_color:
@@ -210,6 +212,16 @@ void VStyleParser::fetchMarkdownEditorStyles(QPalette &palette, QFont &font,
             break;
         }
 
+        // Get custom styles:
+        //     trailing-space.
+        case pmh_attr_type_other:
+        {
+            QString attrName(editorStyles->name);
+            QString value(editorStyles->value->string);
+            styles[ruleKey][attrName] = value;
+            break;
+        }
+
         default:
                 qWarning() << "unimplemented editor attr type:" << editorStyles->type;
         }
@@ -229,6 +241,8 @@ void VStyleParser::fetchMarkdownEditorStyles(QPalette &palette, QFont &font,
             break;
         }
 
+        // Get custom styles:
+        //     vim-background.
         case pmh_attr_type_other:
         {
             QString attrName(curLineStyles->name);