Editor: add formatter support

Change-Id: I65590273b2541e08a39970cd9bb4739a5634b2f7
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2019-11-18 10:38:35 +01:00
parent cadb00cdf3
commit 4070d6a289
14 changed files with 154 additions and 35 deletions

View File

@@ -215,5 +215,25 @@ bool utf8AdvanceCodePoint(const char *&current)
return true;
}
void applyReplacements(QTextDocument *doc, const Replacements &replacements)
{
if (replacements.empty())
return;
int fullOffsetShift = 0;
QTextCursor editCursor(doc);
editCursor.beginEditBlock();
for (const Utils::Text::Replacement &replacement : replacements) {
editCursor.setPosition(replacement.offset + fullOffsetShift);
editCursor.movePosition(QTextCursor::NextCharacter,
QTextCursor::KeepAnchor,
replacement.length);
editCursor.removeSelectedText();
editCursor.insertText(replacement.text);
fullOffsetShift += replacement.text.length() - replacement.length;
}
editCursor.endEditBlock();
}
} // Text
} // Utils

View File

@@ -53,6 +53,8 @@ struct Replacement
};
using Replacements = std::vector<Replacement>;
QTCREATOR_UTILS_EXPORT void applyReplacements(QTextDocument *doc, const Replacements &replacements);
// line is 1-based, column is 1-based
QTCREATOR_UTILS_EXPORT bool convertPosition(const QTextDocument *document,
int pos,

View File

@@ -349,24 +349,6 @@ Utils::Text::Replacements utf16Replacements(const QTextDocument *doc,
return convertedReplacements;
}
void applyReplacements(QTextDocument *doc, const Utils::Text::Replacements &replacements)
{
if (replacements.empty())
return;
int fullOffsetShift = 0;
QTextCursor editCursor(doc);
for (const Utils::Text::Replacement &replacement : replacements) {
editCursor.setPosition(replacement.offset + fullOffsetShift);
editCursor.movePosition(QTextCursor::NextCharacter,
QTextCursor::KeepAnchor,
replacement.length);
editCursor.removeSelectedText();
editCursor.insertText(replacement.text);
fullOffsetShift += replacement.text.length() - replacement.length;
}
}
QString selectedLines(QTextDocument *doc, const QTextBlock &startBlock, const QTextBlock &endBlock)
{
return Utils::Text::textAt(QTextCursor(doc),
@@ -554,7 +536,7 @@ Utils::Text::Replacements ClangFormatBaseIndenter::format(
clangReplacements = clangReplacements.merge(formatReplacements);
const Utils::Text::Replacements toReplace = utf16Replacements(m_doc, buffer, clangReplacements);
applyReplacements(m_doc, toReplace);
Utils::Text::applyReplacements(m_doc, toReplace);
return toReplace;
}

View File

@@ -287,7 +287,7 @@ void ClangFormatConfigWidget::updatePreview()
QTextCursor cursor(m_preview->document());
cursor.setPosition(0);
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
m_preview->textDocument()->autoFormatOrIndent(cursor);
m_preview->textDocument()->autoIndent(cursor);
}
static inline void ltrim(std::string &s)

View File

@@ -53,6 +53,7 @@ add_qtc_plugin(TextEditor
findinopenfiles.cpp findinopenfiles.h
fontsettings.cpp fontsettings.h
fontsettingspage.cpp fontsettingspage.h fontsettingspage.ui
formatter.h
formattexteditor.cpp formattexteditor.h
highlighter.cpp highlighter.h
highlightersettings.cpp highlightersettings.h

View File

@@ -0,0 +1,72 @@
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "texteditor_global.h"
#include <utils/textutils.h>
#include <QFutureWatcher>
#include <QString>
QT_BEGIN_NAMESPACE
class QChar;
class QTextCursor;
QT_END_NAMESPACE
namespace TextEditor {
class TabSettings;
class Formatter
{
public:
Formatter() = default;
virtual ~Formatter() = default;
virtual QFutureWatcher<Utils::Text::Replacements> *format(
const QTextCursor & /*cursor*/, const TextEditor::TabSettings & /*tabSettings*/)
{
return nullptr;
}
virtual bool isElectricCharacter(const QChar & /*ch*/) const { return false; }
virtual bool supportsAutoFormat() const { return false; }
virtual QFutureWatcher<Utils::Text::Replacements> *autoFormat(
const QTextCursor & /*cursor*/, const TextEditor::TabSettings & /*tabSettings*/)
{
return nullptr;
}
virtual bool supportsFormatOnSave() const { return false; }
virtual QFutureWatcher<Utils::Text::Replacements> *formatOnSave(
const QTextCursor & /*cursor*/, const TextEditor::TabSettings & /*tabSettings*/)
{
return nullptr;
}
};
} // namespace TextEditor

View File

@@ -103,6 +103,7 @@ public:
CompletionAssistProvider *m_functionHintAssistProvider = nullptr;
IAssistProvider *m_quickFixProvider = nullptr;
QScopedPointer<Indenter> m_indenter;
QScopedPointer<Formatter> m_formatter;
bool m_fileIsReadOnly = false;
int m_autoSaveRevision = -1;
@@ -508,6 +509,25 @@ QTextCursor TextDocument::unindent(const QTextCursor &cursor, bool blockSelectio
return d->indentOrUnindent(cursor, false, tabSettings(), blockSelection, column, offset);
}
void TextDocument::setFormatter(Formatter *formatter)
{
d->m_formatter.reset(formatter);
}
void TextDocument::autoFormat(const QTextCursor &cursor)
{
using namespace Utils::Text;
if (!d->m_formatter)
return;
if (QFutureWatcher<Replacements> *watcher = d->m_formatter->format(cursor, tabSettings())) {
connect(watcher, &QFutureWatcher<Replacements>::finished, this, [this, watcher]() {
if (!watcher->isCanceled())
Utils::Text::applyReplacements(document(), watcher->result());
delete watcher;
});
}
}
const ExtraEncodingSettings &TextDocument::extraEncodingSettings() const
{
return d->m_extraEncodingSettings;

View File

@@ -26,6 +26,7 @@
#pragma once
#include "texteditor_global.h"
#include "formatter.h"
#include "indenter.h"
#include <coreplugin/id.h>
@@ -93,11 +94,13 @@ public:
int currentCursorPosition = -1);
void autoReindent(const QTextCursor &cursor, int currentCursorPosition = -1);
void autoFormatOrIndent(const QTextCursor &cursor);
QTextCursor indent(const QTextCursor &cursor, bool blockSelection = false, int column = 0,
int *offset = nullptr);
QTextCursor indent(const QTextCursor &cursor, bool blockSelection, int column, int *offset);
QTextCursor unindent(const QTextCursor &cursor, bool blockSelection = false, int column = 0,
int *offset = nullptr);
void setFormatter(Formatter *indenter); // transfers ownership
void autoFormat(const QTextCursor &cursor);
TextMarks marks() const;
bool addMark(TextMark *mark);
TextMarks marksAt(int line) const;

View File

@@ -70,21 +70,22 @@
#include <coreplugin/find/basetextfind.h>
#include <coreplugin/find/highlightscrollbarcontroller.h>
#include <utils/algorithm.h>
#include <utils/textutils.h>
#include <utils/camelcasecursor.h>
#include <utils/fixedsizeclicklabel.h>
#include <utils/fileutils.h>
#include <utils/dropsupport.h>
#include <utils/executeondestruction.h>
#include <utils/fadingindicator.h>
#include <utils/filesearch.h>
#include <utils/fileutils.h>
#include <utils/fixedsizeclicklabel.h>
#include <utils/hostosinfo.h>
#include <utils/mimetypes/mimedatabase.h>
#include <utils/qtcassert.h>
#include <utils/styledbar.h>
#include <utils/stylehelper.h>
#include <utils/textutils.h>
#include <utils/theme/theme.h>
#include <utils/tooltip/tooltip.h>
#include <utils/uncommentselection.h>
#include <utils/theme/theme.h>
#include <QAbstractTextDocumentLayout>
#include <QApplication>
@@ -1161,6 +1162,7 @@ void TextEditorWidgetPrivate::print(QPrinter *printer)
return;
doc = doc->clone(doc);
Utils::ExecuteOnDestruction docDeleter([doc]() { delete doc; });
QTextOption opt = doc->defaultTextOption();
opt.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
@@ -1256,7 +1258,7 @@ void TextEditorWidgetPrivate::print(QPrinter *printer)
for (int j = 0; j < pageCopies; ++j) {
if (printer->printerState() == QPrinter::Aborted
|| printer->printerState() == QPrinter::Error)
goto UserCanceled;
return;
printPage(page, &p, doc, body, titleBox, title);
if (j < pageCopies - 1)
printer->newPage();
@@ -1276,9 +1278,6 @@ void TextEditorWidgetPrivate::print(QPrinter *printer)
if ( i < docCopies - 1)
printer->newPage();
}
UserCanceled:
delete doc;
}
@@ -7194,6 +7193,14 @@ void TextEditorWidget::unCommentSelection()
Utils::unCommentSelection(this, d->m_commentDefinition);
}
void TextEditorWidget::autoFormat()
{
QTextCursor cursor = textCursor();
cursor.beginEditBlock();
d->m_document->autoFormat(cursor);
cursor.endEditBlock();
}
void TextEditorWidget::encourageApply()
{
if (!d->m_snippetOverlay->isVisible() || d->m_snippetOverlay->isEmpty())

View File

@@ -349,6 +349,8 @@ public:
virtual void rewrapParagraph();
virtual void unCommentSelection();
virtual void autoFormat();
virtual void encourageApply();
virtual void setDisplaySettings(const TextEditor::DisplaySettings &);

View File

@@ -195,7 +195,8 @@ HEADERS += texteditorplugin.h \
textstyles.h \
formattexteditor.h \
command.h \
indenter.h
indenter.h \
formatter.h
FORMS += \
displaysettingspage.ui \

View File

@@ -84,6 +84,7 @@ Project {
"fontsettingspage.cpp",
"fontsettingspage.h",
"fontsettingspage.ui",
"formatter.h",
"formattexteditor.cpp",
"formattexteditor.h",
"highlighter.cpp",

View File

@@ -133,7 +133,8 @@ public:
QAction *m_selectAllAction = nullptr;
QAction *m_gotoAction = nullptr;
QAction *m_printAction = nullptr;
QAction *m_formatAction = nullptr;
QAction *m_autoIndentAction = nullptr;
QAction *m_autoFormatAction = nullptr;
QAction *m_rewrapParagraphAction = nullptr;
QAction *m_visualizeWhitespaceAction = nullptr;
QAction *m_cleanWhitespaceAction = nullptr;
@@ -323,10 +324,14 @@ void TextEditorActionHandlerPrivate::createActions()
// register "Edit -> Advanced" Menu Actions
Core::ActionContainer *advancedEditMenu = Core::ActionManager::actionContainer(M_EDIT_ADVANCED);
m_formatAction = registerAction(AUTO_INDENT_SELECTION,
m_autoIndentAction = registerAction(AUTO_INDENT_SELECTION,
[] (TextEditorWidget *w) { w->autoIndent(); }, true, tr("Auto-&indent Selection"),
QKeySequence(tr("Ctrl+I")),
G_EDIT_FORMAT, advancedEditMenu);
m_autoFormatAction = registerAction(AUTO_FORMAT_SELECTION,
[] (TextEditorWidget *w) { w->autoFormat(); }, true, tr("Auto-&format Selection"),
QKeySequence(tr("Ctrl+;")),
G_EDIT_FORMAT, advancedEditMenu);
m_rewrapParagraphAction = registerAction(REWRAP_PARAGRAPH,
[] (TextEditorWidget *w) { w->rewrapParagraph(); }, true, tr("&Rewrap Paragraph"),
QKeySequence(Core::useMacShortcuts ? tr("Meta+E, R") : tr("Ctrl+E, R")),
@@ -499,7 +504,8 @@ void TextEditorActionHandlerPrivate::createActions()
m_modifyingActions << m_deleteStartOfWordCamelCaseAction;
m_modifyingActions << m_duplicateSelectionAction;
m_modifyingActions << m_duplicateSelectionAndCommentAction;
m_modifyingActions << m_formatAction;
m_modifyingActions << m_autoIndentAction;
m_modifyingActions << m_autoFormatAction;
m_modifyingActions << m_indentAction;
m_modifyingActions << m_insertLineAboveAction;
m_modifyingActions << m_insertLineBelowAction;
@@ -528,7 +534,8 @@ void TextEditorActionHandlerPrivate::updateActions()
bool isWritable = m_currentEditorWidget && !m_currentEditorWidget->isReadOnly();
foreach (QAction *a, m_modifyingActions)
a->setEnabled(isWritable);
m_formatAction->setEnabled((m_optionalActions & TextEditorActionHandler::Format) && isWritable);
m_autoIndentAction->setEnabled((m_optionalActions & TextEditorActionHandler::Format) && isWritable);
m_autoFormatAction->setEnabled((m_optionalActions & TextEditorActionHandler::Format) && isWritable);
m_unCommentSelectionAction->setEnabled((m_optionalActions & TextEditorActionHandler::UnCommentSelection) && isWritable);
m_visualizeWhitespaceAction->setEnabled(m_currentEditorWidget);
m_textWrappingAction->setEnabled(m_currentEditorWidget);

View File

@@ -130,6 +130,7 @@ const char FOLD[] = "TextEditor.Fold";
const char UNFOLD[] = "TextEditor.Unfold";
const char UNFOLD_ALL[] = "TextEditor.UnFoldAll";
const char AUTO_INDENT_SELECTION[] = "TextEditor.AutoIndentSelection";
const char AUTO_FORMAT_SELECTION[] = "TextEditor.AutoFormatSelection";
const char INCREASE_FONT_SIZE[] = "TextEditor.IncreaseFontSize";
const char DECREASE_FONT_SIZE[] = "TextEditor.DecreaseFontSize";
const char RESET_FONT_SIZE[] = "TextEditor.ResetFontSize";