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.
- `Shift+L`
Move current tab one split window right.
- `S`
Apply a snippet in edit mode.
- `?`
Display shortcuts documentation.

View File

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

View File

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

View File

@ -268,6 +268,15 @@ QLabel[ColorTealLabel="true"] {
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"] {
padding-left: 3px;
}

View File

@ -97,7 +97,8 @@ SOURCES += main.cpp\
vsnippet.cpp \
dialog/veditsnippetdialog.cpp \
utils/vimnavigationforwidget.cpp \
vtoolbox.cpp
vtoolbox.cpp \
vinsertselector.cpp
HEADERS += vmainwindow.h \
vdirectorytree.h \
@ -181,7 +182,8 @@ HEADERS += vmainwindow.h \
vsnippet.h \
dialog/veditsnippetdialog.h \
utils/vimnavigationforwidget.h \
vtoolbox.h
vtoolbox.h \
vinsertselector.h
RESOURCES += \
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) {
return QChar('a' + p_key - Qt::Key_A);
}
return QChar();
}

View File

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

View File

@ -39,25 +39,6 @@ private:
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.
class VButtonWithWidget : public QPushButton
{

View File

@ -885,6 +885,10 @@ void VEditArea::registerCaptainTargets()
g_config->getCaptainShortcutKeySequence("MagicWord"),
this,
evaluateMagicWordsByCaptain);
captain->registerCaptainTarget(tr("ApplySnippet"),
g_config->getCaptainShortcutKeySequence("ApplySnippet"),
this,
applySnippetByCaptain);
}
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)
{
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.
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.
int curWindowIndex;

View File

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

View File

@ -95,6 +95,9 @@ public:
// Insert snippet @p_snippet.
virtual void applySnippet(const VSnippet *p_snippet);
// Prompt for user to apply a snippet.
virtual void applySnippet();
public slots:
// Enter edit mode
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;
VSnippetList *getSnippetList() const;
// View and edit the information of @p_file, which is an orphan file.
void editOrphanFileInfo(VFile *p_file);
@ -411,4 +413,9 @@ inline VEditTab *VMainWindow::getCurrentTab() const
return m_curTab;
}
inline VSnippetList *VMainWindow::getSnippetList() const
{
return m_snippetList;
}
#endif // VMAINWINDOW_H

View File

@ -1,7 +1,7 @@
#include <QtWidgets>
#include <QWebChannel>
#include <QFileInfo>
#include <QXmlStreamReader>
#include <QCoreApplication>
#include "vmdtab.h"
#include "vdocument.h"
#include "vnote.h"
@ -19,6 +19,8 @@
#include "vmdeditor.h"
#include "vmainwindow.h"
#include "vsnippet.h"
#include "vinsertselector.h"
#include "vsnippetlist.h"
extern VMainWindow *g_mainWin;
@ -728,6 +730,8 @@ void VMdTab::evaluateMagicWords()
void VMdTab::applySnippet(const VSnippet *p_snippet)
{
Q_ASSERT(p_snippet);
if (isEditMode()
&& m_file->isModifiable()
&& p_snippet->getType() == VSnippet::Type::PlainText) {
@ -738,6 +742,79 @@ void VMdTab::applySnippet(const VSnippet *p_snippet)
m_editor->setTextCursor(cursor);
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 VDocument;
class VMdEditor;
class VInsertSelector;
class VMdTab : public VEditTab
{
@ -77,6 +78,8 @@ public:
void applySnippet(const VSnippet *p_snippet) Q_DECL_OVERRIDE;
void applySnippet() Q_DECL_OVERRIDE;
public slots:
// Enter edit mode.
void editFile() Q_DECL_OVERRIDE;
@ -156,6 +159,9 @@ private:
// Return true if succeed.
bool restoreFromTabInfo(const VEditTabInfo &p_info) Q_DECL_OVERRIDE;
// Prepare insert selector with snippets.
VInsertSelector *prepareSnippetSelector(QWidget *p_parent = nullptr);
VMdEditor *m_editor;
VWebView *m_webViewer;
VDocument *m_document;

View File

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

View File

@ -144,7 +144,8 @@ void VSnippetList::newSnippet()
dialog.getTypeInput(),
dialog.getContentInput(),
dialog.getCursorMarkInput(),
dialog.getSelectionMarkInput());
dialog.getSelectionMarkInput(),
dialog.getShortcutInput());
QString errMsg;
if (!addSnippet(snippet, &errMsg)) {
@ -215,8 +216,9 @@ void VSnippetList::deleteSelectedItems()
}
for (auto const & item : selectedItems) {
items.push_back(ConfirmItemInfo(item->text(),
item->text(),
QString name = item->data(Qt::UserRole).toString();
items.push_back(ConfirmItemInfo(name,
name,
"",
NULL));
}
@ -377,7 +379,10 @@ void VSnippetList::updateContent()
for (int i = 0; i < m_snippets.size(); ++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->setData(Qt::UserRole, snip.getName());

View File

@ -23,6 +23,10 @@ class VSnippetList : public QWidget, public VNavigationMode
public:
explicit VSnippetList(QWidget *p_parent = nullptr);
const QVector<VSnippet> &getSnippets() const;
const VSnippet *getSnippet(const QString &p_name) const;
// Implementations for VNavigationMode.
void showNavigation() Q_DECL_OVERRIDE;
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
@ -98,4 +102,19 @@ private:
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