2016-12-22 11:07:09 +01:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** Copyright (C) 2016 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.
|
|
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include "texteditorview.h"
|
|
|
|
|
|
2017-01-04 15:01:14 +01:00
|
|
|
#include "texteditorwidget.h"
|
|
|
|
|
|
2017-03-07 18:03:34 +01:00
|
|
|
#include <customnotifications.h>
|
2016-12-22 11:07:09 +01:00
|
|
|
#include <designmodecontext.h>
|
|
|
|
|
#include <designdocument.h>
|
2017-01-19 12:09:09 +01:00
|
|
|
#include <designersettings.h>
|
2016-12-22 11:07:09 +01:00
|
|
|
#include <modelnode.h>
|
|
|
|
|
#include <model.h>
|
|
|
|
|
#include <zoomaction.h>
|
|
|
|
|
#include <nodeabstractproperty.h>
|
|
|
|
|
#include <nodelistproperty.h>
|
|
|
|
|
#include <qmldesignerplugin.h>
|
2017-01-19 12:09:09 +01:00
|
|
|
|
2017-01-19 14:27:07 +01:00
|
|
|
#include <coreplugin/actionmanager/actionmanager.h>
|
|
|
|
|
#include <coreplugin/actionmanager/actioncontainer.h>
|
|
|
|
|
#include <coreplugin/actionmanager/command.h>
|
|
|
|
|
#include <coreplugin/coreconstants.h>
|
2017-01-19 12:09:09 +01:00
|
|
|
#include <coreplugin/editormanager/editormanager.h>
|
2017-01-19 14:27:07 +01:00
|
|
|
#include <coreplugin/icore.h>
|
|
|
|
|
|
2016-12-22 11:07:09 +01:00
|
|
|
#include <texteditor/texteditor.h>
|
2017-01-19 14:27:07 +01:00
|
|
|
#include <texteditor/texteditorconstants.h>
|
2017-01-19 12:09:09 +01:00
|
|
|
#include <qmljseditor/qmljseditordocument.h>
|
|
|
|
|
|
2018-02-12 16:54:32 +01:00
|
|
|
#include <qmljs/qmljsmodelmanagerinterface.h>
|
2017-01-19 14:27:07 +01:00
|
|
|
#include <qmljs/qmljsreformatter.h>
|
2017-01-19 12:09:09 +01:00
|
|
|
#include <utils/changeset.h>
|
2016-12-22 11:07:09 +01:00
|
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
#include <QPair>
|
|
|
|
|
#include <QString>
|
|
|
|
|
#include <QTimer>
|
|
|
|
|
#include <QPointer>
|
|
|
|
|
|
|
|
|
|
namespace QmlDesigner {
|
|
|
|
|
|
2017-01-19 14:27:07 +01:00
|
|
|
const char TEXTEDITOR_CONTEXT_ID[] = "QmlDesigner.TextEditorContext";
|
|
|
|
|
|
2016-12-22 11:07:09 +01:00
|
|
|
TextEditorView::TextEditorView(QObject *parent)
|
|
|
|
|
: AbstractView(parent)
|
2017-01-04 15:01:14 +01:00
|
|
|
, m_widget(new TextEditorWidget(this))
|
2017-01-19 14:27:07 +01:00
|
|
|
, m_textEditorContext(new Internal::TextEditorContext(m_widget.get()))
|
2016-12-22 11:07:09 +01:00
|
|
|
{
|
2017-01-19 14:27:07 +01:00
|
|
|
Core::ICore::addContextObject(m_textEditorContext);
|
|
|
|
|
|
|
|
|
|
Core::Context context(TEXTEDITOR_CONTEXT_ID);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* We have to register our own active auto completion shortcut, because the original short cut will
|
|
|
|
|
* use the cursor position of the original editor in the editor manager.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
QAction *completionAction = new QAction(tr("Trigger Completion"), this);
|
|
|
|
|
Core::Command *command = Core::ActionManager::registerAction(completionAction, TextEditor::Constants::COMPLETE_THIS, context);
|
2018-02-02 13:39:18 +01:00
|
|
|
command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? tr("Meta+Space") : tr("Ctrl+Space")));
|
2017-01-19 14:27:07 +01:00
|
|
|
|
|
|
|
|
connect(completionAction, &QAction::triggered, [this]() {
|
|
|
|
|
if (m_widget->textEditor())
|
|
|
|
|
m_widget->textEditor()->editorWidget()->invokeAssist(TextEditor::Completion);
|
|
|
|
|
});
|
2016-12-22 11:07:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TextEditorView::~TextEditorView()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::modelAttached(Model *model)
|
|
|
|
|
{
|
|
|
|
|
Q_ASSERT(model);
|
2017-10-16 16:31:28 +02:00
|
|
|
m_widget->clearStatusBar();
|
2016-12-22 11:07:09 +01:00
|
|
|
|
|
|
|
|
AbstractView::modelAttached(model);
|
2017-01-19 14:27:07 +01:00
|
|
|
|
|
|
|
|
TextEditor::BaseTextEditor* textEditor = qobject_cast<TextEditor::BaseTextEditor*>(
|
|
|
|
|
QmlDesignerPlugin::instance()->currentDesignDocument()->textEditor()->duplicate());
|
|
|
|
|
|
|
|
|
|
Core::Context context = textEditor->context();
|
|
|
|
|
context.prepend(TEXTEDITOR_CONTEXT_ID);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Set the context of the text editor, but we add another special context to override shortcuts.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
m_textEditorContext->setContext(context);
|
|
|
|
|
|
|
|
|
|
m_widget->setTextEditor(textEditor);
|
2016-12-22 11:07:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::modelAboutToBeDetached(Model *model)
|
|
|
|
|
{
|
|
|
|
|
AbstractView::modelAboutToBeDetached(model);
|
2017-03-16 10:52:27 +01:00
|
|
|
|
|
|
|
|
m_widget->setTextEditor(0);
|
|
|
|
|
|
2017-10-17 17:43:53 +02:00
|
|
|
// in case the user closed it explicit we do not want to do anything with the editor
|
|
|
|
|
if (TextEditor::BaseTextEditor *textEditor =
|
|
|
|
|
QmlDesignerPlugin::instance()->currentDesignDocument()->textEditor()) {
|
|
|
|
|
QmlDesignerPlugin::instance()->emitCurrentTextEditorChanged(textEditor);
|
|
|
|
|
}
|
2016-12-22 11:07:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::importsChanged(const QList<Import> &/*addedImports*/, const QList<Import> &/*removedImports*/)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::nodeAboutToBeRemoved(const ModelNode &/*removedNode*/)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::rootNodeTypeChanged(const QString &/*type*/, int /*majorVersion*/, int /*minorVersion*/)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::propertiesAboutToBeRemoved(const QList<AbstractProperty>& /*propertyList*/)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::nodeReparented(const ModelNode &/*node*/, const NodeAbstractProperty &/*newPropertyParent*/, const NodeAbstractProperty &/*oldPropertyParent*/, AbstractView::PropertyChangeFlags /*propertyChange*/)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WidgetInfo TextEditorView::widgetInfo()
|
|
|
|
|
{
|
2017-01-10 16:36:55 +01:00
|
|
|
return createWidgetInfo(m_widget.get(), 0, "TextEditor", WidgetInfo::CentralPane, 0, tr("Text Editor"), DesignerWidgetFlags::IgnoreErrors);
|
2016-12-22 11:07:09 +01:00
|
|
|
}
|
|
|
|
|
|
2018-01-17 16:06:13 +01:00
|
|
|
void TextEditorView::contextHelpId(const Core::IContext::HelpIdCallback &callback) const
|
2017-03-22 12:17:28 +01:00
|
|
|
{
|
2018-01-17 16:06:13 +01:00
|
|
|
AbstractView::contextHelpId(callback);
|
2017-03-22 12:17:28 +01:00
|
|
|
}
|
|
|
|
|
|
2018-01-17 16:06:13 +01:00
|
|
|
void TextEditorView::qmlJSEditorHelpId(const Core::IContext::HelpIdCallback &callback) const
|
2016-12-22 11:07:09 +01:00
|
|
|
{
|
2018-01-17 16:06:13 +01:00
|
|
|
if (m_widget->textEditor())
|
|
|
|
|
m_widget->textEditor()->contextHelpId(callback);
|
|
|
|
|
else
|
|
|
|
|
callback(QString());
|
2016-12-22 11:07:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::nodeIdChanged(const ModelNode& /*node*/, const QString &/*newId*/, const QString &/*oldId*/)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::selectedNodesChanged(const QList<ModelNode> &/*selectedNodeList*/,
|
|
|
|
|
const QList<ModelNode> &/*lastSelectedNodeList*/)
|
|
|
|
|
{
|
2017-01-04 14:19:52 +01:00
|
|
|
m_widget->jumpTextCursorToSelectedModelNode();
|
2016-12-22 11:07:09 +01:00
|
|
|
}
|
|
|
|
|
|
2017-03-07 18:03:34 +01:00
|
|
|
void TextEditorView::customNotification(const AbstractView * /*view*/, const QString &identifier, const QList<ModelNode> &/*nodeList*/, const QList<QVariant> &/*data*/)
|
2016-12-22 11:07:09 +01:00
|
|
|
{
|
2017-03-07 18:03:34 +01:00
|
|
|
if (identifier == StartRewriterApply)
|
2017-09-13 12:59:39 +02:00
|
|
|
m_widget->setBlockCursorSelectionSynchronisation(true);
|
2017-03-07 18:03:34 +01:00
|
|
|
else if (identifier == EndRewriterApply)
|
2017-09-13 12:59:39 +02:00
|
|
|
m_widget->setBlockCursorSelectionSynchronisation(false);
|
2016-12-22 11:07:09 +01:00
|
|
|
}
|
|
|
|
|
|
2017-01-10 17:08:14 +01:00
|
|
|
void TextEditorView::documentMessagesChanged(const QList<DocumentMessage> &errors, const QList<DocumentMessage> &)
|
2017-01-10 16:36:55 +01:00
|
|
|
{
|
|
|
|
|
if (errors.isEmpty()) {
|
|
|
|
|
m_widget->clearStatusBar();
|
|
|
|
|
} else {
|
2018-01-13 18:49:39 +01:00
|
|
|
const DocumentMessage &error = errors.constFirst();
|
2017-01-10 16:36:55 +01:00
|
|
|
m_widget->setStatusText(QString("%1 (Line: %2)").arg(error.description()).arg(error.line()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-22 11:07:09 +01:00
|
|
|
bool TextEditorView::changeToMoveTool()
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::changeToDragTool()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TextEditorView::changeToMoveTool(const QPointF &/*beginPoint*/)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::changeToSelectionTool()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::changeToResizeTool()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::changeToTransformTools()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::changeToCustomTool()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::auxiliaryDataChanged(const ModelNode &/*node*/, const PropertyName &/*name*/, const QVariant &/*data*/)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::instancesCompleted(const QVector<ModelNode> &/*completedNodeList*/)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::instanceInformationsChanged(const QMultiHash<ModelNode, InformationName> &/*informationChangeHash*/)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::instancesRenderImageChanged(const QVector<ModelNode> &/*nodeList*/)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::instancesChildrenChanged(const QVector<ModelNode> &/*nodeList*/)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::rewriterBeginTransaction()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TextEditorView::rewriterEndTransaction()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-10 16:36:55 +01:00
|
|
|
void TextEditorView::gotoCursorPosition(int line, int column)
|
|
|
|
|
{
|
|
|
|
|
if (m_widget)
|
|
|
|
|
m_widget->gotoCursorPosition(line, column);
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-19 12:09:09 +01:00
|
|
|
void TextEditorView::reformatFile()
|
|
|
|
|
{
|
|
|
|
|
int oldLine = -1;
|
|
|
|
|
|
|
|
|
|
if (m_widget)
|
|
|
|
|
oldLine = m_widget->currentLine();
|
|
|
|
|
|
|
|
|
|
QByteArray editorState = m_widget->textEditor()->saveState();
|
|
|
|
|
|
|
|
|
|
auto document =
|
|
|
|
|
qobject_cast<QmlJSEditor::QmlJSEditorDocument *>(Core::EditorManager::instance()->currentDocument());
|
|
|
|
|
|
|
|
|
|
/* Reformat document if we have a .ui.qml file */
|
|
|
|
|
if (document
|
|
|
|
|
&& document->filePath().toString().endsWith(".ui.qml")
|
2017-01-19 13:46:37 +01:00
|
|
|
&& DesignerSettings::getValue(DesignerSettingsKey::REFORMAT_UI_QML_FILES).toBool()) {
|
2017-01-19 12:09:09 +01:00
|
|
|
|
2018-02-12 16:54:32 +01:00
|
|
|
QmlJS::Document::Ptr currentDocument(document->semanticInfo().document);
|
|
|
|
|
QmlJS::Snapshot snapshot = QmlJS::ModelManagerInterface::instance()->snapshot();
|
|
|
|
|
|
|
|
|
|
if (document->isSemanticInfoOutdated()) {
|
|
|
|
|
QmlJS::Document::MutablePtr latestDocument;
|
|
|
|
|
|
|
|
|
|
const QString fileName = document->filePath().toString();
|
|
|
|
|
latestDocument = snapshot.documentFromSource(QString::fromUtf8(document->contents()),
|
|
|
|
|
fileName,
|
|
|
|
|
QmlJS::ModelManagerInterface::guessLanguageOfFile(fileName));
|
|
|
|
|
latestDocument->parseQml();
|
|
|
|
|
snapshot.insert(latestDocument);
|
|
|
|
|
|
|
|
|
|
currentDocument = latestDocument;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!currentDocument->isParsedCorrectly())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const QString &newText = QmlJS::reformat(currentDocument);
|
2017-01-19 12:09:09 +01:00
|
|
|
QTextCursor tc(document->document());
|
|
|
|
|
|
|
|
|
|
Utils::ChangeSet changeSet;
|
|
|
|
|
changeSet.replace(0, document->plainText().length(), newText);
|
|
|
|
|
changeSet.apply(&tc);
|
|
|
|
|
|
|
|
|
|
m_widget->textEditor()->restoreState(editorState);
|
|
|
|
|
|
|
|
|
|
if (m_widget)
|
|
|
|
|
m_widget->gotoCursorPosition(oldLine, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-22 11:07:09 +01:00
|
|
|
void TextEditorView::instancePropertyChanged(const QList<QPair<ModelNode, PropertyName> > &/*propertyList*/)
|
|
|
|
|
{
|
|
|
|
|
}
|
2017-01-04 15:01:14 +01:00
|
|
|
|
|
|
|
|
} // namespace QmlDesigner
|
2016-12-22 11:07:09 +01:00
|
|
|
|