vnote/src/widgets/textviewwindowhelper.h
2021-08-28 11:03:47 +08:00

330 lines
13 KiB
C++

#ifndef TEXTVIEWWINDOWHELPER_H
#define TEXTVIEWWINDOWHELPER_H
#include <QFileInfo>
#include <QTextCursor>
#include <QRegularExpression>
#include <QTextBlock>
#include <QSharedPointer>
#include <vtextedit/texteditorconfig.h>
#include <core/texteditorconfig.h>
#include <core/configmgr.h>
#include <utils/widgetutils.h>
#include <snippet/snippetmgr.h>
#include <search/searchtoken.h>
#include "quickselector.h"
namespace vte
{
class ViConfig;
}
namespace vnotex
{
class TextEditorConfig;
// Abstract some common logics for TextViewWindow and MarkdownViewWindow.
class TextViewWindowHelper
{
public:
TextViewWindowHelper() = delete;
template <typename _ViewWindow>
static void connectEditor(_ViewWindow *p_win)
{
auto editor = p_win->m_editor;
p_win->connect(editor, &vte::VTextEditor::focusIn,
p_win, [p_win]() {
emit p_win->focused(p_win);
});
p_win->connect(editor->getTextEdit(), &vte::VTextEdit::contentsChanged,
p_win, [p_win, editor]() {
if (p_win->m_propogateEditorToBuffer) {
p_win->getBuffer()->setModified(editor->isModified());
p_win->getBuffer()->invalidateContent(
p_win, [p_win](int p_revision) {
p_win->setBufferRevisionAfterInvalidation(p_revision);
});
}
});
}
template <typename _ViewWindow>
static void handleBufferChanged(_ViewWindow *p_win)
{
p_win->m_propogateEditorToBuffer = false;
p_win->syncEditorFromBuffer();
p_win->m_propogateEditorToBuffer = true;
emit p_win->statusChanged();
emit p_win->modeChanged();
}
static QSharedPointer<vte::TextEditorConfig> createTextEditorConfig(const TextEditorConfig &p_config,
const QSharedPointer<vte::ViConfig> &p_viConfig,
const QString &p_themeFile,
const QString &p_syntaxTheme,
LineEndingPolicy p_lineEndingPolicy)
{
auto editorConfig = QSharedPointer<vte::TextEditorConfig>::create();
editorConfig->m_viConfig = p_viConfig;
if (!p_themeFile.isEmpty()) {
editorConfig->m_theme = vte::Theme::createThemeFromFile(p_themeFile);
}
editorConfig->m_syntaxTheme = p_syntaxTheme;
switch (p_config.getLineNumberType()) {
case TextEditorConfig::LineNumberType::Absolute:
editorConfig->m_lineNumberType = vte::VTextEditor::LineNumberType::Absolute;
break;
case TextEditorConfig::LineNumberType::Relative:
editorConfig->m_lineNumberType = vte::VTextEditor::LineNumberType::Relative;
break;
case TextEditorConfig::LineNumberType::None:
editorConfig->m_lineNumberType = vte::VTextEditor::LineNumberType::None;
break;
}
editorConfig->m_textFoldingEnabled = p_config.getTextFoldingEnabled();
switch (p_config.getInputMode()) {
case TextEditorConfig::InputMode::ViMode:
editorConfig->m_inputMode = vte::InputMode::ViMode;
break;
default:
editorConfig->m_inputMode = vte::InputMode::NormalMode;
break;
}
editorConfig->m_scaleFactor = WidgetUtils::calculateScaleFactor();
switch (p_config.getCenterCursor()) {
case TextEditorConfig::CenterCursor::NeverCenter:
editorConfig->m_centerCursor = vte::CenterCursor::NeverCenter;
break;
case TextEditorConfig::CenterCursor::AlwaysCenter:
editorConfig->m_centerCursor = vte::CenterCursor::AlwaysCenter;
break;
case TextEditorConfig::CenterCursor::CenterOnBottom:
editorConfig->m_centerCursor = vte::CenterCursor::CenterOnBottom;
break;
}
switch (p_config.getWrapMode()) {
case TextEditorConfig::WrapMode::NoWrap:
editorConfig->m_wrapMode = vte::WrapMode::NoWrap;
break;
case TextEditorConfig::WrapMode::WordWrap:
editorConfig->m_wrapMode = vte::WrapMode::WordWrap;
break;
case TextEditorConfig::WrapMode::WrapAnywhere:
editorConfig->m_wrapMode = vte::WrapMode::WrapAnywhere;
break;
case TextEditorConfig::WrapMode::WordWrapOrAnywhere:
editorConfig->m_wrapMode = vte::WrapMode::WordWrapOrAnywhere;
break;
}
editorConfig->m_expandTab = p_config.getExpandTabEnabled();
editorConfig->m_tabStopWidth = p_config.getTabStopWidth();
switch (p_lineEndingPolicy) {
case LineEndingPolicy::Platform:
editorConfig->m_lineEndingPolicy = vte::LineEndingPolicy::Platform;
break;
case LineEndingPolicy::File:
editorConfig->m_lineEndingPolicy = vte::LineEndingPolicy::File;
break;
case LineEndingPolicy::LF:
editorConfig->m_lineEndingPolicy = vte::LineEndingPolicy::LF;
break;
case LineEndingPolicy::CRLF:
editorConfig->m_lineEndingPolicy = vte::LineEndingPolicy::CRLF;
break;
case LineEndingPolicy::CR:
editorConfig->m_lineEndingPolicy = vte::LineEndingPolicy::CR;
break;
}
return editorConfig;
}
static vte::FindFlags toEditorFindFlags(FindOptions p_options)
{
vte::FindFlags flags;
if (p_options & FindOption::FindBackward) {
flags |= vte::FindFlag::FindBackward;
}
if (p_options & FindOption::CaseSensitive) {
flags |= vte::FindFlag::CaseSensitive;
}
if (p_options & FindOption::WholeWordOnly) {
flags |= vte::FindFlag::WholeWordOnly;
}
if (p_options & FindOption::RegularExpression) {
flags |= vte::FindFlag::RegularExpression;
}
return flags;
}
template <typename _ViewWindow>
static void handleFindTextChanged(_ViewWindow *p_win, const QString &p_text, FindOptions p_options)
{
if (p_options & FindOption::IncrementalSearch) {
p_win->m_editor->peekText(p_text, toEditorFindFlags(p_options));
}
}
template <typename _ViewWindow>
static void handleFindNext(_ViewWindow *p_win, const QStringList &p_texts, FindOptions p_options)
{
const auto result = p_win->m_editor->findText(p_texts, toEditorFindFlags(p_options));
p_win->showFindResult(p_texts, result.m_totalMatches, result.m_currentMatchIndex);
}
template <typename _ViewWindow>
static void handleReplace(_ViewWindow *p_win, const QString &p_text, FindOptions p_options, const QString &p_replaceText)
{
const auto result = p_win->m_editor->replaceText(p_text, toEditorFindFlags(p_options), p_replaceText);
p_win->showReplaceResult(p_text, result.m_totalMatches);
}
template <typename _ViewWindow>
static void handleReplaceAll(_ViewWindow *p_win, const QString &p_text, FindOptions p_options, const QString &p_replaceText)
{
const auto result = p_win->m_editor->replaceAll(p_text, toEditorFindFlags(p_options), p_replaceText);
p_win->showReplaceResult(p_text, result.m_totalMatches);
}
template <typename _ViewWindow>
static void handleFindAndReplaceWidgetClosed(_ViewWindow *p_win)
{
p_win->m_editor->clearIncrementalSearchHighlight();
p_win->m_editor->clearSearchHighlight();
}
template <typename _ViewWindow>
static void applySnippet(_ViewWindow *p_win, const QString &p_name)
{
if (p_win->m_editor->isReadOnly() || p_name.isEmpty()) {
qWarning() << "failed to apply snippet" << p_name << "to a read-only buffer";
return;
}
SnippetMgr::getInst().applySnippet(p_name,
p_win->m_editor->getTextEdit(),
SnippetMgr::generateOverrides(p_win->getBuffer()));
p_win->m_editor->enterInsertModeIfApplicable();
p_win->showMessage(vnotex::ViewWindow::tr("Snippet applied: %1").arg(p_name));
}
template <typename _ViewWindow>
static void applySnippet(_ViewWindow *p_win)
{
if (p_win->m_editor->isReadOnly()) {
qWarning() << "failed to apply snippet to a read-only buffer";
return;
}
QString snippetName;
auto textEdit = p_win->m_editor->getTextEdit();
if (!textEdit->hasSelection()) {
// Fetch the snippet symbol containing current cursor.
auto cursor = textEdit->textCursor();
const auto block = cursor.block();
const auto text = block.text();
const int pib = cursor.positionInBlock();
QRegularExpression regExp(SnippetMgr::c_snippetSymbolRegExp);
QRegularExpressionMatch match;
int idx = text.lastIndexOf(regExp, pib, &match);
if (idx >= 0 && (idx + match.capturedLength(0) >= pib)) {
// Found one symbol under current cursor.
snippetName = match.captured(1);
if (!SnippetMgr::getInst().find(snippetName)) {
p_win->showMessage(vnotex::ViewWindow::tr("Snippet (%1) not found").arg(snippetName));
return;
}
// Remove the symbol and apply snippet later.
cursor.setPosition(block.position() + idx);
cursor.setPosition(block.position() + idx + match.capturedLength(0), QTextCursor::KeepAnchor);
cursor.removeSelectedText();
textEdit->setTextCursor(cursor);
}
}
if (snippetName.isEmpty()) {
// Prompt for snippet.
snippetName = promptForSnippet(p_win);
}
if (!snippetName.isEmpty()) {
applySnippet(p_win, snippetName);
}
}
template <typename _ViewWindow>
static QString promptForSnippet(_ViewWindow *p_win)
{
const auto snippets = SnippetMgr::getInst().getSnippets();
if (snippets.isEmpty()) {
p_win->showMessage(vnotex::ViewWindow::tr("Snippet not available"));
return QString();
}
QVector<QuickSelectorItem> items;
for (const auto &snip : snippets) {
items.push_back(QuickSelectorItem(snip->getName(),
snip->getName(),
snip->getDescription(),
snip->getShortcutString()));
}
// Ownership will be transferred to showFloatingWidget().
auto selector = new QuickSelector(vnotex::ViewWindow::tr("Select Snippet"),
items,
true,
p_win);
auto ret = p_win->showFloatingWidget(selector);
return ret.toString();
}
template <typename _ViewWindow>
static QPoint getFloatingWidgetPosition(_ViewWindow *p_win)
{
auto textEdit = p_win->m_editor->getTextEdit();
auto localPos = textEdit->cursorRect().bottomRight();
if (!textEdit->rect().contains(localPos)) {
localPos = QPoint(5, 5);
}
return textEdit->mapToGlobal(localPos);
}
template <typename _ViewWindow>
static void findTextBySearchToken(_ViewWindow *p_win, const QSharedPointer<SearchToken> &p_token, int p_currentMatchLine)
{
const auto patterns = p_token->toPatterns();
p_win->updateLastFindInfo(patterns.first, patterns.second);
const auto result = p_win->m_editor->findText(patterns.first, toEditorFindFlags(patterns.second), 0, -1, p_currentMatchLine);
p_win->showFindResult(patterns.first, result.m_totalMatches, result.m_currentMatchIndex);
}
};
}
#endif