Browse Source

support Insert Code Block tool bar button

Ctrl+M to insert a code block.
Le Tan 8 years ago
parent
commit
30dfc24a28

+ 9 - 5
src/resources/docs/shortcuts_en.md

@@ -49,13 +49,17 @@ Save current changes and exit edit mode.
 
 #### Text Editing
 - `Ctrl+B`  
-Insert bold. Press `Ctrl+B` again to exit. Current selected text will be changed to bold if exist.
+Insert bold. Press `Ctrl+B` again to exit. Current selected text will be changed to bold if exists.
 - `Ctrl+I`  
-Insert italic. Press `Ctrl+I` again to exit. Current selected text will be changed to italic if exist.
+Insert italic. Press `Ctrl+I` again to exit. Current selected text will be changed to italic if exists.
 - `Ctrl+D`  
-Insert strikethrought. Press `Ctrl+D` again to exit. Current selected text will be changed to strikethrough if exist.
+Insert strikethrought. Press `Ctrl+D` again to exit. Current selected text will be changed to strikethrough if exists.
 - `Ctrl+O`  
-Insert inline code. Press `Ctrl+O` again to exit. Current selected text will be changed to inline code if exist.
+Insert inline code. Press `Ctrl+O` again to exit. Current selected text will be changed to inline code if exists.
+- `Ctrl+M`  
+Insert fenced code block. Press `Ctrl+M` again to exit. Current selected text will be wrapped into a code block if exists.
+- `Ctrl+L`  
+Insert link.
 - `Ctrl+H`  
 Backspace. Delete a character backward.
 - `Ctrl+W`  
@@ -63,7 +67,7 @@ Delete all the characters from current cursor to the first space backward.
 - `Ctrl+U`  
 Delete all the characters from current cursor to the beginning of current line.
 - `Ctrl+<Num>`  
-Insert title at level `<Num>`. `<Num>` should be 1 to 6. Current selected text will be changed to title if exist.
+Insert title at level `<Num>`. `<Num>` should be 1 to 6. Current selected text will be changed to title if exists.
 - `Tab`/`Shift+Tab`  
 Increase or decrease the indentation. If any text is selected, the indentation will operate on all these selected lines.
 - `Shift+Enter`  

+ 4 - 0
src/resources/docs/shortcuts_zh.md

@@ -56,6 +56,10 @@
 插入删除线;再次按`Ctrl+D`退出。如果已经选择文本,则将当前选择文本改为删除线。
 - `Ctrl+O`  
 插入行内代码;再次按`Ctrl+O`退出。如果已经选择文本,则将当前选择文本改为行内代码。
+- `Ctrl+M`  
+插入代码块;再次按`Ctrl+M`退出。如果已经选择文本,则将当前选择文本嵌入到代码块中。
+- `Ctrl+L`  
+插入链接。
 - `Ctrl+H`  
 退格键,向前删除一个字符。
 - `Ctrl+W`  

+ 6 - 0
src/resources/icons/code_block.svg

@@ -0,0 +1,6 @@
+<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
+ <g>
+  <title>Layer 1</title>
+  <text style="cursor: move;" fill="#000000" stroke-width="0" x="-146.75684" y="248.49038" id="svg_4" font-size="24" font-family="serif" text-anchor="middle" xml:space="preserve" font-weight="bold" transform="matrix(16.72881317138672,0,0,16.72881317138672,2707.567729830742,-3759.186347961426) " stroke="#000000">#</text>
+ </g>
+</svg>

+ 32 - 10
src/utils/veditutils.cpp

@@ -48,7 +48,7 @@ bool VEditUtils::insertBlockWithIndent(QTextCursor &p_cursor)
 {
     V_ASSERT(!p_cursor.hasSelection());
     p_cursor.insertBlock();
-    return indentBlockAsPreviousBlock(p_cursor);
+    return indentBlockAsBlock(p_cursor, false);
 }
 
 bool VEditUtils::insertListMarkAsPreviousBlock(QTextCursor &p_cursor)
@@ -85,21 +85,16 @@ bool VEditUtils::insertListMarkAsPreviousBlock(QTextCursor &p_cursor)
 
 }
 
-bool VEditUtils::indentBlockAsPreviousBlock(QTextCursor &p_cursor)
+bool VEditUtils::indentBlockAsBlock(QTextCursor &p_cursor, bool p_next)
 {
     bool changed = false;
     QTextBlock block = p_cursor.block();
-    if (block.blockNumber() == 0) {
-        // The first block.
+    QTextBlock refBlock = p_next ? block.next() : block.previous();
+    if (!refBlock.isValid()) {
         return false;
     }
 
-    QTextBlock preBlock = block.previous();
-    QString text = preBlock.text();
-    QRegExp regExp("(^\\s*)");
-    regExp.indexIn(text);
-    V_ASSERT(regExp.captureCount() == 1);
-    QString leadingSpaces = regExp.capturedTexts()[1];
+    QString leadingSpaces = fetchIndentSpaces(refBlock);
 
     moveCursorFirstNonSpaceCharacter(p_cursor, QTextCursor::MoveAnchor);
     if (!p_cursor.atBlockStart()) {
@@ -789,3 +784,30 @@ void VEditUtils::findCurrentWORD(const QTextCursor &p_cursor,
     p_end += block.position();
 }
 
+QString VEditUtils::fetchIndentSpaces(const QTextBlock &p_block)
+{
+    QString text = p_block.text();
+    QRegExp regExp("(^\\s*)");
+    regExp.indexIn(text);
+    Q_ASSERT(regExp.captureCount() == 1);
+    return regExp.capturedTexts()[1];
+}
+
+void VEditUtils::insertBlock(QTextCursor &p_cursor,
+                             bool p_above)
+{
+    p_cursor.movePosition(p_above ? QTextCursor::StartOfBlock
+                                  : QTextCursor::EndOfBlock,
+                          QTextCursor::MoveAnchor,
+                          1);
+
+    p_cursor.insertBlock();
+
+    if (p_above) {
+        p_cursor.movePosition(QTextCursor::PreviousBlock,
+                              QTextCursor::MoveAnchor,
+                              1);
+    }
+
+    p_cursor.movePosition(QTextCursor::EndOfBlock);
+}

+ 11 - 2
src/utils/veditutils.h

@@ -24,10 +24,11 @@ public:
     // Need to call setTextCursor() to make it take effect.
     static void moveCursorFirstNonSpaceCharacter(QTextCursor &p_cursor,
                                                  QTextCursor::MoveMode p_mode);
-    // Indent current block as previous block.
+    // Indent current block as next/previous block.
     // Return true if some changes have been made.
     // @p_cursor will be placed at the position after inserting leading spaces.
-    static bool indentBlockAsPreviousBlock(QTextCursor &p_cursor);
+    // @p_next: indent as next block or previous block.
+    static bool indentBlockAsBlock(QTextCursor &p_cursor, bool p_next);
 
     // Returns true if two blocks has the same indent.
     static bool hasSameIndent(const QTextBlock &p_blocka, const QTextBlock &p_blockb);
@@ -157,6 +158,14 @@ public:
                                 int &p_start,
                                 int &p_end);
 
+    // Return the leading spaces of @p_block.
+    static QString fetchIndentSpaces(const QTextBlock &p_block);
+
+    // Insert a block above/below current block. Move the cursor to the start of
+    // the new block after insertion.
+    static void insertBlock(QTextCursor &p_cursor,
+                            bool p_above);
+
 private:
     VEditUtils() {}
 };

+ 2 - 2
src/utils/vvim.cpp

@@ -296,7 +296,7 @@ static void insertChangeBlockAfterDeletion(QTextCursor &p_cursor, int p_deletion
     }
 
     if (g_config->getAutoIndent()) {
-        VEditUtils::indentBlockAsPreviousBlock(p_cursor);
+        VEditUtils::indentBlockAsBlock(p_cursor, false);
     }
 }
 
@@ -911,7 +911,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
 
                 bool textInserted = false;
                 if (g_config->getAutoIndent()) {
-                    textInserted = VEditUtils::indentBlockAsPreviousBlock(cursor);
+                    textInserted = VEditUtils::indentBlockAsBlock(cursor, false);
                     if (g_config->getAutoList()) {
                         textInserted = VEditUtils::insertListMarkAsPreviousBlock(cursor)
                                        || textInserted;

+ 1 - 0
src/vcaptain.h

@@ -9,6 +9,7 @@
 
 class QKeyEvent;
 class VNavigationMode;
+class QShortcut;
 
 // void func(void *p_target, void *p_data);
 typedef std::function<void(void *, void *)> CaptainFunc;

+ 10 - 6
src/vconstants.h

@@ -52,12 +52,16 @@ namespace DirConfig
 
 static const QString c_emptyHeaderName = "[EMPTY]";
 
-enum class TextDecoration { None,
-                            Bold,
-                            Italic,
-                            Underline,
-                            Strikethrough,
-                            InlineCode };
+enum class TextDecoration
+{
+    None,
+    Bold,
+    Italic,
+    Underline,
+    Strikethrough,
+    InlineCode,
+    CodeBlock
+};
 
 enum FindOption
 {

+ 15 - 0
src/vmainwindow.cpp

@@ -463,6 +463,21 @@ void VMainWindow::initEditToolBar(QSize p_iconSize)
 
     m_editToolBar->addAction(inlineCodeAct);
 
+    QAction *codeBlockAct = new QAction(QIcon(":/resources/icons/code_block.svg"),
+                                        tr("Code Block (Ctrl+M)"),
+                                        this);
+    codeBlockAct->setStatusTip(tr("Insert fenced code block text or wrap selected text into a fenced code block"));
+    connect(codeBlockAct, &QAction::triggered,
+            this, [this](){
+                if (m_curTab) {
+                    m_curTab->decorateText(TextDecoration::CodeBlock);
+                }
+            });
+
+    m_editToolBar->addAction(codeBlockAct);
+
+    m_editToolBar->addSeparator();
+
     // Insert link.
     QAction *insetLinkAct = new QAction(QIcon(":/resources/icons/link.svg"),
                                         tr("Insert Link (Ctrl+L)"), this);

+ 95 - 0
src/vmdeditoperations.cpp

@@ -289,6 +289,17 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
         break;
     }
 
+    case Qt::Key_M:
+    {
+        if (modifiers == Qt::ControlModifier) {
+            decorateCodeBlock();
+            p_event->accept();
+            ret = true;
+        }
+
+        break;
+    }
+
     case Qt::Key_O:
     {
         if (modifiers == Qt::ControlModifier) {
@@ -684,6 +695,10 @@ void VMdEditOperations::decorateText(TextDecoration p_decoration)
         decorateInlineCode();
         break;
 
+    case TextDecoration::CodeBlock:
+        decorateCodeBlock();
+        break;
+
     default:
         validDecoration = false;
         qDebug() << "decoration" << (int)p_decoration << "is not implemented yet";
@@ -807,6 +822,86 @@ void VMdEditOperations::decorateInlineCode()
     m_editor->setTextCursor(cursor);
 }
 
+void VMdEditOperations::decorateCodeBlock()
+{
+    const QString marker("```");
+
+    QTextCursor cursor = m_editor->textCursor();
+    cursor.beginEditBlock();
+    if (cursor.hasSelection()) {
+        // Insert ``` around the selected text.
+        int start = cursor.selectionStart();
+        int end = cursor.selectionEnd();
+
+        QString indentation = VEditUtils::fetchIndentSpaces(cursor.block());
+
+        // Insert the end marker first.
+        cursor.setPosition(end, QTextCursor::MoveAnchor);
+        VEditUtils::insertBlock(cursor, false);
+        VEditUtils::indentBlock(cursor, indentation);
+        cursor.insertText(marker);
+
+        // Insert the start marker.
+        cursor.setPosition(start, QTextCursor::MoveAnchor);
+        VEditUtils::insertBlock(cursor, true);
+        VEditUtils::indentBlock(cursor, indentation);
+        cursor.insertText(marker);
+    } else {
+        // Insert ``` ``` and place cursor after the first marker.
+        // Or if current block or next block is ```, we will skip it.
+        QTextBlock block = cursor.block();
+        int state = block.userState();
+        if (state == HighlightBlockState::CodeBlock
+            || state == HighlightBlockState::CodeBlockStart
+            || state == HighlightBlockState::CodeBlockEnd) {
+            // Find the block end.
+            while (block.isValid()) {
+                if (block.userState() == HighlightBlockState::CodeBlockEnd) {
+                    break;
+                }
+
+                block = block.next();
+            }
+
+            if (block.isValid()) {
+                // It is CodeBlockEnd.
+                cursor.setPosition(block.position());
+                if (block.next().isValid()) {
+                    cursor.movePosition(QTextCursor::NextBlock);
+                    cursor.movePosition(QTextCursor::StartOfBlock);
+                } else {
+                    cursor.movePosition(QTextCursor::EndOfBlock);
+                }
+            } else {
+                // Reach the end of the document.
+                cursor.movePosition(QTextCursor::End);
+            }
+        } else {
+            bool insertInline = false;
+            if (!cursor.atBlockEnd()) {
+                cursor.insertBlock();
+                cursor.movePosition(QTextCursor::PreviousBlock);
+            } else if (cursor.atBlockStart()) {
+                insertInline = true;
+            }
+
+            if (!insertInline) {
+                VEditUtils::insertBlock(cursor, false);
+                VEditUtils::indentBlockAsBlock(cursor, false);
+            }
+
+            cursor.insertText(marker);
+
+            VEditUtils::insertBlock(cursor, true);
+            VEditUtils::indentBlockAsBlock(cursor, true);
+            cursor.insertText(marker);
+        }
+    }
+
+    cursor.endEditBlock();
+    m_editor->setTextCursor(cursor);
+}
+
 void VMdEditOperations::decorateStrikethrough()
 {
     QTextCursor cursor = m_editor->textCursor();

+ 3 - 0
src/vmdeditoperations.h

@@ -72,6 +72,9 @@ private:
     // Insert inline-code marker or set selected text inline-coded.
     void decorateInlineCode();
 
+    // Insert inline-code marker or set selected text inline-coded.
+    void decorateCodeBlock();
+
     // Insert strikethrough marker or set selected text strikethrough.
     void decorateStrikethrough();
 

+ 1 - 0
src/vnote.qrc

@@ -133,5 +133,6 @@
         <file>resources/icons/compact_mode.svg</file>
         <file>resources/icons/heading_sequence.svg</file>
         <file>resources/icons/link.svg</file>
+        <file>resources/icons/code_block.svg</file>
     </qresource>
 </RCC>