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; 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 } // Text
} // Utils } // Utils

View File

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

View File

@@ -349,24 +349,6 @@ Utils::Text::Replacements utf16Replacements(const QTextDocument *doc,
return convertedReplacements; 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) QString selectedLines(QTextDocument *doc, const QTextBlock &startBlock, const QTextBlock &endBlock)
{ {
return Utils::Text::textAt(QTextCursor(doc), return Utils::Text::textAt(QTextCursor(doc),
@@ -554,7 +536,7 @@ Utils::Text::Replacements ClangFormatBaseIndenter::format(
clangReplacements = clangReplacements.merge(formatReplacements); clangReplacements = clangReplacements.merge(formatReplacements);
const Utils::Text::Replacements toReplace = utf16Replacements(m_doc, buffer, clangReplacements); const Utils::Text::Replacements toReplace = utf16Replacements(m_doc, buffer, clangReplacements);
applyReplacements(m_doc, toReplace); Utils::Text::applyReplacements(m_doc, toReplace);
return toReplace; return toReplace;
} }

View File

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

View File

@@ -53,6 +53,7 @@ add_qtc_plugin(TextEditor
findinopenfiles.cpp findinopenfiles.h findinopenfiles.cpp findinopenfiles.h
fontsettings.cpp fontsettings.h fontsettings.cpp fontsettings.h
fontsettingspage.cpp fontsettingspage.h fontsettingspage.ui fontsettingspage.cpp fontsettingspage.h fontsettingspage.ui
formatter.h
formattexteditor.cpp formattexteditor.h formattexteditor.cpp formattexteditor.h
highlighter.cpp highlighter.h highlighter.cpp highlighter.h
highlightersettings.cpp highlightersettings.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; CompletionAssistProvider *m_functionHintAssistProvider = nullptr;
IAssistProvider *m_quickFixProvider = nullptr; IAssistProvider *m_quickFixProvider = nullptr;
QScopedPointer<Indenter> m_indenter; QScopedPointer<Indenter> m_indenter;
QScopedPointer<Formatter> m_formatter;
bool m_fileIsReadOnly = false; bool m_fileIsReadOnly = false;
int m_autoSaveRevision = -1; 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); 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 const ExtraEncodingSettings &TextDocument::extraEncodingSettings() const
{ {
return d->m_extraEncodingSettings; return d->m_extraEncodingSettings;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -133,7 +133,8 @@ public:
QAction *m_selectAllAction = nullptr; QAction *m_selectAllAction = nullptr;
QAction *m_gotoAction = nullptr; QAction *m_gotoAction = nullptr;
QAction *m_printAction = nullptr; QAction *m_printAction = nullptr;
QAction *m_formatAction = nullptr; QAction *m_autoIndentAction = nullptr;
QAction *m_autoFormatAction = nullptr;
QAction *m_rewrapParagraphAction = nullptr; QAction *m_rewrapParagraphAction = nullptr;
QAction *m_visualizeWhitespaceAction = nullptr; QAction *m_visualizeWhitespaceAction = nullptr;
QAction *m_cleanWhitespaceAction = nullptr; QAction *m_cleanWhitespaceAction = nullptr;
@@ -323,10 +324,14 @@ void TextEditorActionHandlerPrivate::createActions()
// register "Edit -> Advanced" Menu Actions // register "Edit -> Advanced" Menu Actions
Core::ActionContainer *advancedEditMenu = Core::ActionManager::actionContainer(M_EDIT_ADVANCED); 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"), [] (TextEditorWidget *w) { w->autoIndent(); }, true, tr("Auto-&indent Selection"),
QKeySequence(tr("Ctrl+I")), QKeySequence(tr("Ctrl+I")),
G_EDIT_FORMAT, advancedEditMenu); 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, m_rewrapParagraphAction = registerAction(REWRAP_PARAGRAPH,
[] (TextEditorWidget *w) { w->rewrapParagraph(); }, true, tr("&Rewrap Paragraph"), [] (TextEditorWidget *w) { w->rewrapParagraph(); }, true, tr("&Rewrap Paragraph"),
QKeySequence(Core::useMacShortcuts ? tr("Meta+E, R") : tr("Ctrl+E, R")), 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_deleteStartOfWordCamelCaseAction;
m_modifyingActions << m_duplicateSelectionAction; m_modifyingActions << m_duplicateSelectionAction;
m_modifyingActions << m_duplicateSelectionAndCommentAction; m_modifyingActions << m_duplicateSelectionAndCommentAction;
m_modifyingActions << m_formatAction; m_modifyingActions << m_autoIndentAction;
m_modifyingActions << m_autoFormatAction;
m_modifyingActions << m_indentAction; m_modifyingActions << m_indentAction;
m_modifyingActions << m_insertLineAboveAction; m_modifyingActions << m_insertLineAboveAction;
m_modifyingActions << m_insertLineBelowAction; m_modifyingActions << m_insertLineBelowAction;
@@ -528,7 +534,8 @@ void TextEditorActionHandlerPrivate::updateActions()
bool isWritable = m_currentEditorWidget && !m_currentEditorWidget->isReadOnly(); bool isWritable = m_currentEditorWidget && !m_currentEditorWidget->isReadOnly();
foreach (QAction *a, m_modifyingActions) foreach (QAction *a, m_modifyingActions)
a->setEnabled(isWritable); 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_unCommentSelectionAction->setEnabled((m_optionalActions & TextEditorActionHandler::UnCommentSelection) && isWritable);
m_visualizeWhitespaceAction->setEnabled(m_currentEditorWidget); m_visualizeWhitespaceAction->setEnabled(m_currentEditorWidget);
m_textWrappingAction->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[] = "TextEditor.Unfold";
const char UNFOLD_ALL[] = "TextEditor.UnFoldAll"; const char UNFOLD_ALL[] = "TextEditor.UnFoldAll";
const char AUTO_INDENT_SELECTION[] = "TextEditor.AutoIndentSelection"; const char AUTO_INDENT_SELECTION[] = "TextEditor.AutoIndentSelection";
const char AUTO_FORMAT_SELECTION[] = "TextEditor.AutoFormatSelection";
const char INCREASE_FONT_SIZE[] = "TextEditor.IncreaseFontSize"; const char INCREASE_FONT_SIZE[] = "TextEditor.IncreaseFontSize";
const char DECREASE_FONT_SIZE[] = "TextEditor.DecreaseFontSize"; const char DECREASE_FONT_SIZE[] = "TextEditor.DecreaseFontSize";
const char RESET_FONT_SIZE[] = "TextEditor.ResetFontSize"; const char RESET_FONT_SIZE[] = "TextEditor.ResetFontSize";