snippet: support Ctrl+E S to insert snippets

This commit is contained in:
Le Tan 2017-11-13 20:13:04 +08:00
parent 9b730c2889
commit 0a97b2480d
20 changed files with 375 additions and 31 deletions

View File

@ -218,6 +218,8 @@ Jump to the first split window on the right.
Move current tab one split window left. Move current tab one split window left.
- `Shift+L` - `Shift+L`
Move current tab one split window right. Move current tab one split window right.
- `S`
Apply a snippet in edit mode.
- `?` - `?`
Display shortcuts documentation. Display shortcuts documentation.

View File

@ -219,6 +219,8 @@ RemoveSplit=R
将当前标签页左移一个分割窗口。 将当前标签页左移一个分割窗口。
- `Shift+L` - `Shift+L`
将当前标签页右移一个分割窗口。 将当前标签页右移一个分割窗口。
- `S`
在编辑模式中应用片段。
- `?` - `?`
显示本快捷键说明。 显示本快捷键说明。

View File

@ -255,3 +255,5 @@ VerticalSplit=V
RemoveSplit=R RemoveSplit=R
; Evaluate selected text or cursor word as magic words ; Evaluate selected text or cursor word as magic words
MagicWord=M MagicWord=M
; Prompt for user to apply a snippet
ApplySnippet=S

View File

@ -268,6 +268,15 @@ QLabel[ColorTealLabel="true"] {
background-color: @Teal7; background-color: @Teal7;
} }
VSelectorItemWidget QLabel[SelectorItemShortcutLabel="true"] {
font: bold;
border: 2px solid @logo-min;
padding: 3px;
border-radius: 5px;
background-color: @logo-base;
color: @logo-max;
}
QWidget[NotebookPanel="true"] { QWidget[NotebookPanel="true"] {
padding-left: 3px; padding-left: 3px;
} }

View File

@ -97,7 +97,8 @@ SOURCES += main.cpp\
vsnippet.cpp \ vsnippet.cpp \
dialog/veditsnippetdialog.cpp \ dialog/veditsnippetdialog.cpp \
utils/vimnavigationforwidget.cpp \ utils/vimnavigationforwidget.cpp \
vtoolbox.cpp vtoolbox.cpp \
vinsertselector.cpp
HEADERS += vmainwindow.h \ HEADERS += vmainwindow.h \
vdirectorytree.h \ vdirectorytree.h \
@ -181,7 +182,8 @@ HEADERS += vmainwindow.h \
vsnippet.h \ vsnippet.h \
dialog/veditsnippetdialog.h \ dialog/veditsnippetdialog.h \
utils/vimnavigationforwidget.h \ utils/vimnavigationforwidget.h \
vtoolbox.h vtoolbox.h \
vinsertselector.h
RESOURCES += \ RESOURCES += \
vnote.qrc \ vnote.qrc \

View File

@ -516,6 +516,7 @@ QChar VUtils::keyToChar(int p_key)
if (p_key >= Qt::Key_A && p_key <= Qt::Key_Z) { if (p_key >= Qt::Key_A && p_key <= Qt::Key_Z) {
return QChar('a' + p_key - Qt::Key_A); return QChar('a' + p_key - Qt::Key_A);
} }
return QChar(); return QChar();
} }

View File

@ -41,7 +41,8 @@ void VButtonWithWidget::init()
m_bubbleBg = QColor("#15AE67"); m_bubbleBg = QColor("#15AE67");
QMenu *menu = new QMenu(this); QMenu *menu = new QMenu(this);
VButtonWidgetAction *act = new VButtonWidgetAction(m_popupWidget, menu); QWidgetAction *act = new QWidgetAction(menu);
act->setDefaultWidget(m_popupWidget);
menu->addAction(act); menu->addAction(act);
connect(menu, &QMenu::aboutToShow, connect(menu, &QMenu::aboutToShow,
this, [this]() { this, [this]() {

View File

@ -39,25 +39,6 @@ private:
VButtonWithWidget *m_btn; VButtonWithWidget *m_btn;
}; };
class VButtonWidgetAction : public QWidgetAction
{
Q_OBJECT
public:
VButtonWidgetAction(QWidget *p_widget, QWidget *p_parent)
: QWidgetAction(p_parent), m_widget(p_widget)
{
}
QWidget *createWidget(QWidget *p_parent)
{
m_widget->setParent(p_parent);
return m_widget;
}
private:
QWidget *m_widget;
};
// A QPushButton with popup widget. // A QPushButton with popup widget.
class VButtonWithWidget : public QPushButton class VButtonWithWidget : public QPushButton
{ {

View File

@ -885,6 +885,10 @@ void VEditArea::registerCaptainTargets()
g_config->getCaptainShortcutKeySequence("MagicWord"), g_config->getCaptainShortcutKeySequence("MagicWord"),
this, this,
evaluateMagicWordsByCaptain); evaluateMagicWordsByCaptain);
captain->registerCaptainTarget(tr("ApplySnippet"),
g_config->getCaptainShortcutKeySequence("ApplySnippet"),
this,
applySnippetByCaptain);
} }
void VEditArea::activateTabByCaptain(void *p_target, void *p_data, int p_idx) void VEditArea::activateTabByCaptain(void *p_target, void *p_data, int p_idx)
@ -989,6 +993,16 @@ void VEditArea::evaluateMagicWordsByCaptain(void *p_target, void *p_data)
} }
} }
void VEditArea::applySnippetByCaptain(void *p_target, void *p_data)
{
Q_UNUSED(p_data);
VEditArea *obj = static_cast<VEditArea *>(p_target);
VEditTab *tab = obj->getCurrentTab();
if (tab && tab->tabHasFocus()) {
tab->applySnippet();
}
}
void VEditArea::recordClosedFile(const VFileSessionInfo &p_file) void VEditArea::recordClosedFile(const VFileSessionInfo &p_file)
{ {
for (auto it = m_lastClosedFiles.begin(); it != m_lastClosedFiles.end(); ++it) { for (auto it = m_lastClosedFiles.begin(); it != m_lastClosedFiles.end(); ++it) {

View File

@ -195,6 +195,9 @@ private:
// Evaluate selected text or the word on cursor as magic words. // Evaluate selected text or the word on cursor as magic words.
static void evaluateMagicWordsByCaptain(void *p_target, void *p_data); static void evaluateMagicWordsByCaptain(void *p_target, void *p_data);
// Prompt for user to apply a snippet.
static void applySnippetByCaptain(void *p_target, void *p_data);
// End Captain mode functions. // End Captain mode functions.
int curWindowIndex; int curWindowIndex;

View File

@ -129,3 +129,7 @@ void VEditTab::applySnippet(const VSnippet *p_snippet)
{ {
Q_UNUSED(p_snippet); Q_UNUSED(p_snippet);
} }
void VEditTab::applySnippet()
{
}

View File

@ -95,6 +95,9 @@ public:
// Insert snippet @p_snippet. // Insert snippet @p_snippet.
virtual void applySnippet(const VSnippet *p_snippet); virtual void applySnippet(const VSnippet *p_snippet);
// Prompt for user to apply a snippet.
virtual void applySnippet();
public slots: public slots:
// Enter edit mode // Enter edit mode
virtual void editFile() = 0; virtual void editFile() = 0;

113
src/vinsertselector.cpp Normal file
View File

@ -0,0 +1,113 @@
#include "vinsertselector.h"
#include <QtWidgets>
#include "utils/vutils.h"
VSelectorItemWidget::VSelectorItemWidget(QWidget *p_parent)
: QWidget(p_parent)
{
}
VSelectorItemWidget::VSelectorItemWidget(const VInsertSelectorItem &p_item, QWidget *p_parent)
: QWidget(p_parent), m_name(p_item.m_name)
{
QLabel *shortcutLabel = new QLabel(p_item.m_shortcut);
shortcutLabel->setProperty("SelectorItemShortcutLabel", true);
m_btn = new QPushButton(p_item.m_name);
m_btn->setToolTip(p_item.m_toolTip);
m_btn->setProperty("SelectionBtn", true);
connect(m_btn, &QPushButton::clicked,
this, [this]() {
emit clicked(m_name);
});
QHBoxLayout *layout = new QHBoxLayout();
layout->addWidget(shortcutLabel);
layout->addWidget(m_btn, 1);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
}
VInsertSelector::VInsertSelector(int p_nrRows,
const QVector<VInsertSelectorItem> &p_items,
QWidget *p_parent)
: QWidget(p_parent),
m_items(p_items)
{
setupUI(p_nrRows < 1 ? 1 : p_nrRows);
}
void VInsertSelector::setupUI(int p_nrRows)
{
QGridLayout *layout = new QGridLayout();
int row = 0, col = 0;
for (auto const & it : m_items) {
QWidget *wid = createItemWidget(it);
layout->addWidget(wid, row, col);
if (++row == p_nrRows) {
row = 0;
++col;
}
}
setLayout(layout);
}
QWidget *VInsertSelector::createItemWidget(const VInsertSelectorItem &p_item)
{
VSelectorItemWidget *widget = new VSelectorItemWidget(p_item);
connect(widget, &VSelectorItemWidget::clicked,
this, &VInsertSelector::itemClicked);
return widget;
}
void VInsertSelector::itemClicked(const QString &p_name)
{
m_clickedItemName = p_name;
emit accepted(true);
}
void VInsertSelector::keyPressEvent(QKeyEvent *p_event)
{
QWidget::keyPressEvent(p_event);
if (p_event->key() == Qt::Key_BracketLeft
&& VUtils::isControlModifierForVim(p_event->modifiers())) {
m_clickedItemName.clear();
emit accepted(false);
return;
}
QChar ch = VUtils::keyToChar(p_event->key());
if (!ch.isNull()) {
// Activate corresponding item.
const VInsertSelectorItem *item = findItemByShortcut(ch);
if (item) {
itemClicked(item->m_name);
}
}
}
const VInsertSelectorItem *VInsertSelector::findItemByShortcut(QChar p_shortcut) const
{
for (auto const & it : m_items) {
if (it.m_shortcut == p_shortcut) {
return &it;
}
}
return NULL;
}
void VInsertSelector::showEvent(QShowEvent *p_event)
{
QWidget::showEvent(p_event);
if (!hasFocus()) {
setFocus();
}
}

87
src/vinsertselector.h Normal file
View File

@ -0,0 +1,87 @@
#ifndef VINSERTSELECTOR_H
#define VINSERTSELECTOR_H
#include <QWidget>
#include <QVector>
class QPushButton;
class QKeyEvent;
class QShowEvent;
struct VInsertSelectorItem
{
VInsertSelectorItem()
{
}
VInsertSelectorItem(const QString &p_name,
const QString &p_toolTip,
QChar p_shortcut = QChar())
: m_name(p_name), m_toolTip(p_toolTip), m_shortcut(p_shortcut)
{
}
QString m_name;
QString m_toolTip;
QChar m_shortcut;
};
class VSelectorItemWidget : public QWidget
{
Q_OBJECT
public:
explicit VSelectorItemWidget(QWidget *p_parent = nullptr);
VSelectorItemWidget(const VInsertSelectorItem &p_item, QWidget *p_parent = nullptr);
signals:
// This item widget is clicked.
void clicked(const QString &p_name);
private:
QString m_name;
QPushButton *m_btn;
};
class VInsertSelector : public QWidget
{
Q_OBJECT
public:
explicit VInsertSelector(int p_nrRows,
const QVector<VInsertSelectorItem> &p_items,
QWidget *p_parent = nullptr);
const QString &getClickedItem() const;
signals:
void accepted(bool p_accepted = true);
protected:
void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
private slots:
void itemClicked(const QString &p_name);
private:
void setupUI(int p_nrRows);
QWidget *createItemWidget(const VInsertSelectorItem &p_item);
const VInsertSelectorItem *findItemByShortcut(QChar p_shortcut) const;
QVector<VInsertSelectorItem> m_items;
QString m_clickedItemName;
};
inline const QString &VInsertSelector::getClickedItem() const
{
return m_clickedItemName;
}
#endif // VINSERTSELECTOR_H

View File

@ -64,6 +64,8 @@ public:
VEditArea *getEditArea() const; VEditArea *getEditArea() const;
VSnippetList *getSnippetList() const;
// View and edit the information of @p_file, which is an orphan file. // View and edit the information of @p_file, which is an orphan file.
void editOrphanFileInfo(VFile *p_file); void editOrphanFileInfo(VFile *p_file);
@ -411,4 +413,9 @@ inline VEditTab *VMainWindow::getCurrentTab() const
return m_curTab; return m_curTab;
} }
inline VSnippetList *VMainWindow::getSnippetList() const
{
return m_snippetList;
}
#endif // VMAINWINDOW_H #endif // VMAINWINDOW_H

View File

@ -1,7 +1,7 @@
#include <QtWidgets> #include <QtWidgets>
#include <QWebChannel> #include <QWebChannel>
#include <QFileInfo> #include <QFileInfo>
#include <QXmlStreamReader> #include <QCoreApplication>
#include "vmdtab.h" #include "vmdtab.h"
#include "vdocument.h" #include "vdocument.h"
#include "vnote.h" #include "vnote.h"
@ -19,6 +19,8 @@
#include "vmdeditor.h" #include "vmdeditor.h"
#include "vmainwindow.h" #include "vmainwindow.h"
#include "vsnippet.h" #include "vsnippet.h"
#include "vinsertselector.h"
#include "vsnippetlist.h"
extern VMainWindow *g_mainWin; extern VMainWindow *g_mainWin;
@ -728,6 +730,8 @@ void VMdTab::evaluateMagicWords()
void VMdTab::applySnippet(const VSnippet *p_snippet) void VMdTab::applySnippet(const VSnippet *p_snippet)
{ {
Q_ASSERT(p_snippet);
if (isEditMode() if (isEditMode()
&& m_file->isModifiable() && m_file->isModifiable()
&& p_snippet->getType() == VSnippet::Type::PlainText) { && p_snippet->getType() == VSnippet::Type::PlainText) {
@ -738,6 +742,79 @@ void VMdTab::applySnippet(const VSnippet *p_snippet)
m_editor->setTextCursor(cursor); m_editor->setTextCursor(cursor);
m_editor->setVimMode(VimMode::Insert); m_editor->setVimMode(VimMode::Insert);
g_mainWin->showStatusMessage(tr("Snippet applied"));
}
} else {
g_mainWin->showStatusMessage(tr("Snippet %1 is not applicable").arg(p_snippet->getName()));
}
}
void VMdTab::applySnippet()
{
if (!isEditMode() || !m_file->isModifiable()) {
g_mainWin->showStatusMessage(tr("Snippets are not applicable"));
return;
}
QPoint pos(m_editor->cursorRect().bottomRight());
QMenu menu(this);
VInsertSelector *sel = prepareSnippetSelector(&menu);
if (!sel) {
g_mainWin->showStatusMessage(tr("No available snippets defined with shortcuts"));
return;
}
QWidgetAction *act = new QWidgetAction(&menu);
act->setDefaultWidget(sel);
connect(sel, &VInsertSelector::accepted,
this, [this, &menu]() {
QKeyEvent *escEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Escape,
Qt::NoModifier);
QCoreApplication::postEvent(&menu, escEvent);
});
menu.addAction(act);
menu.exec(m_editor->mapToGlobal(pos));
QString chosenItem = sel->getClickedItem();
if (!chosenItem.isEmpty()) {
const VSnippet *snip = g_mainWin->getSnippetList()->getSnippet(chosenItem);
if (snip) {
applySnippet(snip);
} }
} }
} }
static bool selectorItemCmp(const VInsertSelectorItem &p_a, const VInsertSelectorItem &p_b)
{
if (p_a.m_shortcut < p_b.m_shortcut) {
return true;
}
return false;
}
VInsertSelector *VMdTab::prepareSnippetSelector(QWidget *p_parent)
{
auto snippets = g_mainWin->getSnippetList()->getSnippets();
QVector<VInsertSelectorItem> items;
for (auto const & snip : snippets) {
if (!snip.getShortcut().isNull()) {
items.push_back(VInsertSelectorItem(snip.getName(),
snip.getName(),
snip.getShortcut()));
}
}
if (items.isEmpty()) {
return NULL;
}
// Sort items by shortcut.
std::sort(items.begin(), items.end(), selectorItemCmp);
VInsertSelector *sel = new VInsertSelector(7, items, p_parent);
return sel;
}

View File

@ -12,6 +12,7 @@ class VWebView;
class QStackedLayout; class QStackedLayout;
class VDocument; class VDocument;
class VMdEditor; class VMdEditor;
class VInsertSelector;
class VMdTab : public VEditTab class VMdTab : public VEditTab
{ {
@ -77,6 +78,8 @@ public:
void applySnippet(const VSnippet *p_snippet) Q_DECL_OVERRIDE; void applySnippet(const VSnippet *p_snippet) Q_DECL_OVERRIDE;
void applySnippet() Q_DECL_OVERRIDE;
public slots: public slots:
// Enter edit mode. // Enter edit mode.
void editFile() Q_DECL_OVERRIDE; void editFile() Q_DECL_OVERRIDE;
@ -156,6 +159,9 @@ private:
// Return true if succeed. // Return true if succeed.
bool restoreFromTabInfo(const VEditTabInfo &p_info) Q_DECL_OVERRIDE; bool restoreFromTabInfo(const VEditTabInfo &p_info) Q_DECL_OVERRIDE;
// Prepare insert selector with snippets.
VInsertSelector *prepareSnippetSelector(QWidget *p_parent = nullptr);
VMdEditor *m_editor; VMdEditor *m_editor;
VWebView *m_webViewer; VWebView *m_webViewer;
VDocument *m_document; VDocument *m_document;

View File

@ -162,11 +162,16 @@ bool VSnippet::apply(QTextCursor &p_cursor) const
// Evaluate the content. // Evaluate the content.
QString content = g_mwMgr->evaluate(m_content); QString content = g_mwMgr->evaluate(m_content);
if (content.isEmpty()) {
p_cursor.endEditBlock();
return true;
}
// Find the cursor mark and break the content. // Find the cursor mark and break the content.
QString secondPart; QString secondPart;
if (!m_cursorMark.isEmpty()) { if (!m_cursorMark.isEmpty()) {
QStringList parts = content.split(m_cursorMark, QString::SkipEmptyParts); QStringList parts = content.split(m_cursorMark);
Q_ASSERT(parts.size() < 3); Q_ASSERT(parts.size() < 3 && parts.size() > 0);
content = parts[0]; content = parts[0];
if (parts.size() == 2) { if (parts.size() == 2) {
@ -175,13 +180,14 @@ bool VSnippet::apply(QTextCursor &p_cursor) const
} }
// Replace the selection mark. // Replace the selection mark.
if (!m_selectionMark.isEmpty()) { // Content may be empty.
if (!m_selectionMark.isEmpty() && !content.isEmpty()) {
content.replace(m_selectionMark, selection); content.replace(m_selectionMark, selection);
} }
int pos = p_cursor.position() + content.size(); int pos = p_cursor.position() + content.size();
if (!secondPart.isEmpty()) { if (!m_selectionMark.isEmpty() && !secondPart.isEmpty()) {
secondPart.replace(m_selectionMark, selection); secondPart.replace(m_selectionMark, selection);
content += secondPart; content += secondPart;
} }

View File

@ -144,7 +144,8 @@ void VSnippetList::newSnippet()
dialog.getTypeInput(), dialog.getTypeInput(),
dialog.getContentInput(), dialog.getContentInput(),
dialog.getCursorMarkInput(), dialog.getCursorMarkInput(),
dialog.getSelectionMarkInput()); dialog.getSelectionMarkInput(),
dialog.getShortcutInput());
QString errMsg; QString errMsg;
if (!addSnippet(snippet, &errMsg)) { if (!addSnippet(snippet, &errMsg)) {
@ -215,8 +216,9 @@ void VSnippetList::deleteSelectedItems()
} }
for (auto const & item : selectedItems) { for (auto const & item : selectedItems) {
items.push_back(ConfirmItemInfo(item->text(), QString name = item->data(Qt::UserRole).toString();
item->text(), items.push_back(ConfirmItemInfo(name,
name,
"", "",
NULL)); NULL));
} }
@ -377,7 +379,10 @@ void VSnippetList::updateContent()
for (int i = 0; i < m_snippets.size(); ++i) { for (int i = 0; i < m_snippets.size(); ++i) {
const VSnippet &snip = m_snippets[i]; const VSnippet &snip = m_snippets[i];
QListWidgetItem *item = new QListWidgetItem(snip.getName()); QString text = QString("%1%2").arg(snip.getName())
.arg(snip.getShortcut().isNull()
? "" : QString(" [%1]").arg(snip.getShortcut()));
QListWidgetItem *item = new QListWidgetItem(text);
item->setToolTip(snip.getName()); item->setToolTip(snip.getName());
item->setData(Qt::UserRole, snip.getName()); item->setData(Qt::UserRole, snip.getName());

View File

@ -23,6 +23,10 @@ class VSnippetList : public QWidget, public VNavigationMode
public: public:
explicit VSnippetList(QWidget *p_parent = nullptr); explicit VSnippetList(QWidget *p_parent = nullptr);
const QVector<VSnippet> &getSnippets() const;
const VSnippet *getSnippet(const QString &p_name) const;
// Implementations for VNavigationMode. // Implementations for VNavigationMode.
void showNavigation() Q_DECL_OVERRIDE; void showNavigation() Q_DECL_OVERRIDE;
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE; bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
@ -98,4 +102,19 @@ private:
static const QString c_infoShortcutSequence; static const QString c_infoShortcutSequence;
}; };
inline const QVector<VSnippet> &VSnippetList::getSnippets() const
{
return m_snippets;
}
inline const VSnippet *VSnippetList::getSnippet(const QString &p_name) const
{
for (auto const & snip : m_snippets) {
if (snip.getName() == p_name) {
return &snip;
}
}
return NULL;
}
#endif // VSNIPPETLIST_H #endif // VSNIPPETLIST_H