support Insert Code Block tool bar button

Ctrl+M to insert a code block.
This commit is contained in:
Le Tan 2017-10-19 19:43:10 +08:00
parent e66b70b6ff
commit 30dfc24a28
12 changed files with 189 additions and 25 deletions

View File

@ -49,13 +49,17 @@ Save current changes and exit edit mode.
#### Text Editing #### Text Editing
- `Ctrl+B` - `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` - `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` - `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` - `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` - `Ctrl+H`
Backspace. Delete a character backward. Backspace. Delete a character backward.
- `Ctrl+W` - `Ctrl+W`
@ -63,7 +67,7 @@ Delete all the characters from current cursor to the first space backward.
- `Ctrl+U` - `Ctrl+U`
Delete all the characters from current cursor to the beginning of current line. Delete all the characters from current cursor to the beginning of current line.
- `Ctrl+<Num>` - `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` - `Tab`/`Shift+Tab`
Increase or decrease the indentation. If any text is selected, the indentation will operate on all these selected lines. Increase or decrease the indentation. If any text is selected, the indentation will operate on all these selected lines.
- `Shift+Enter` - `Shift+Enter`

View File

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

View File

@ -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>

After

Width:  |  Height:  |  Size: 470 B

View File

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

View File

@ -24,10 +24,11 @@ public:
// Need to call setTextCursor() to make it take effect. // Need to call setTextCursor() to make it take effect.
static void moveCursorFirstNonSpaceCharacter(QTextCursor &p_cursor, static void moveCursorFirstNonSpaceCharacter(QTextCursor &p_cursor,
QTextCursor::MoveMode p_mode); 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. // Return true if some changes have been made.
// @p_cursor will be placed at the position after inserting leading spaces. // @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. // Returns true if two blocks has the same indent.
static bool hasSameIndent(const QTextBlock &p_blocka, const QTextBlock &p_blockb); static bool hasSameIndent(const QTextBlock &p_blocka, const QTextBlock &p_blockb);
@ -157,6 +158,14 @@ public:
int &p_start, int &p_start,
int &p_end); 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: private:
VEditUtils() {} VEditUtils() {}
}; };

View File

@ -296,7 +296,7 @@ static void insertChangeBlockAfterDeletion(QTextCursor &p_cursor, int p_deletion
} }
if (g_config->getAutoIndent()) { 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; bool textInserted = false;
if (g_config->getAutoIndent()) { if (g_config->getAutoIndent()) {
textInserted = VEditUtils::indentBlockAsPreviousBlock(cursor); textInserted = VEditUtils::indentBlockAsBlock(cursor, false);
if (g_config->getAutoList()) { if (g_config->getAutoList()) {
textInserted = VEditUtils::insertListMarkAsPreviousBlock(cursor) textInserted = VEditUtils::insertListMarkAsPreviousBlock(cursor)
|| textInserted; || textInserted;

View File

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

View File

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

View File

@ -463,6 +463,21 @@ void VMainWindow::initEditToolBar(QSize p_iconSize)
m_editToolBar->addAction(inlineCodeAct); 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. // Insert link.
QAction *insetLinkAct = new QAction(QIcon(":/resources/icons/link.svg"), QAction *insetLinkAct = new QAction(QIcon(":/resources/icons/link.svg"),
tr("Insert Link (Ctrl+L)"), this); tr("Insert Link (Ctrl+L)"), this);

View File

@ -289,6 +289,17 @@ bool VMdEditOperations::handleKeyPressEvent(QKeyEvent *p_event)
break; break;
} }
case Qt::Key_M:
{
if (modifiers == Qt::ControlModifier) {
decorateCodeBlock();
p_event->accept();
ret = true;
}
break;
}
case Qt::Key_O: case Qt::Key_O:
{ {
if (modifiers == Qt::ControlModifier) { if (modifiers == Qt::ControlModifier) {
@ -684,6 +695,10 @@ void VMdEditOperations::decorateText(TextDecoration p_decoration)
decorateInlineCode(); decorateInlineCode();
break; break;
case TextDecoration::CodeBlock:
decorateCodeBlock();
break;
default: default:
validDecoration = false; validDecoration = false;
qDebug() << "decoration" << (int)p_decoration << "is not implemented yet"; qDebug() << "decoration" << (int)p_decoration << "is not implemented yet";
@ -807,6 +822,86 @@ void VMdEditOperations::decorateInlineCode()
m_editor->setTextCursor(cursor); 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() void VMdEditOperations::decorateStrikethrough()
{ {
QTextCursor cursor = m_editor->textCursor(); QTextCursor cursor = m_editor->textCursor();

View File

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

View File

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