Files
qt-creator/src/plugins/qmljseditor/qmljseditorplugin.cpp

382 lines
14 KiB
C++
Raw Normal View History

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
2009-04-22 15:21:04 +02:00
#include "qmljseditingsettingspage.h"
#include "qmljseditor.h"
#include "qmljseditorconstants.h"
#include "qmljseditordocument.h"
#include "qmljseditorplugin.h"
#include "qmljseditortr.h"
#include "qmljshighlighter.h"
2010-07-08 11:32:45 +02:00
#include "qmljsoutline.h"
#include "qmljsquickfixassist.h"
#include "qmltaskmanager.h"
#include "quicktoolbar.h"
#include <qmljs/qmljsicons.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <qmljs/qmljsreformatter.h>
#include <qmljstools/qmljstoolsconstants.h>
#include <qmljstools/qmljstoolssettings.h>
#include <qmljstools/qmljscodestylepreferences.h>
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/projecttree.h>
#include <projectexplorer/taskhub.h>
#include <texteditor/command.h>
#include <texteditor/formattexteditor.h>
#include <texteditor/snippets/snippetprovider.h>
#include <texteditor/tabsettings.h>
#include <texteditor/texteditor.h>
#include <texteditor/texteditorconstants.h>
#include <utils/fsengine/fileiconprovider.h>
#include <utils/json.h>
#include <utils/macroexpander.h>
#include <utils/qtcassert.h>
2009-04-22 15:21:04 +02:00
#include <QTextDocument>
#include <QMenu>
#include <QAction>
2009-04-22 15:21:04 +02:00
using namespace QmlJSEditor::Constants;
using namespace ProjectExplorer;
using namespace Core;
using namespace Utils;
2009-04-22 15:21:04 +02:00
namespace QmlJSEditor {
namespace Internal {
class QmlJSEditorPluginPrivate : public QObject
{
public:
QmlJSEditorPluginPrivate();
void currentEditorChanged(IEditor *editor);
void runSemanticScan();
void checkCurrentEditorSemanticInfoUpToDate();
void autoFormatOnSave(IDocument *document);
Command *addToolAction(QAction *a, Context &context, Id id,
ActionContainer *c1, const QString &keySequence);
void renameUsages();
void reformatFile();
void showContextPane();
QmlJSQuickFixAssistProvider m_quickFixAssistProvider;
QmlTaskManager m_qmlTaskManager;
QAction *m_reformatFileAction = nullptr;
QPointer<QmlJSEditorDocument> m_currentDocument;
Utils::JsonSchemaManager m_jsonManager{
{ICore::userResourcePath("json/").toString(),
ICore::resourcePath("json/").toString()}};
QmlJSEditorFactory m_qmlJSEditorFactory;
QmlJSOutlineWidgetFactory m_qmlJSOutlineWidgetFactory;
QuickToolBar m_quickToolBar;
QmlJsEditingSettingsPage m_qmJSEditingSettingsPage;
};
static QmlJSEditorPlugin *m_instance = nullptr;
QmlJSEditorPlugin::QmlJSEditorPlugin()
2009-04-22 15:21:04 +02:00
{
m_instance = this;
}
QmlJSEditorPlugin::~QmlJSEditorPlugin()
2009-04-22 15:21:04 +02:00
{
delete QmlJS::Icons::instance(); // delete object held by singleton
delete d;
d = nullptr;
m_instance = nullptr;
}
bool QmlJSEditorPlugin::initialize(const QStringList &arguments, QString *errorMessage)
{
Q_UNUSED(arguments)
Q_UNUSED(errorMessage)
d = new QmlJSEditorPluginPrivate;
return true;
2009-04-22 15:21:04 +02:00
}
QmlJSEditorPluginPrivate::QmlJSEditorPluginPrivate()
2009-04-22 15:21:04 +02:00
{
TextEditor::SnippetProvider::registerGroup(Constants::QML_SNIPPETS_GROUP_ID,
Tr::tr("QML", "SnippetProvider"),
&QmlJSEditorFactory::decorateEditor);
QmlJS::ModelManagerInterface *modelManager = QmlJS::ModelManagerInterface::instance();
Automatic qmlls support (experimental) Looks for qmlls (the qml language server of Qt) and if available and set in the preferences uses it instead of the embedded code model for the supported features. Its usage is driven by two flags that can be set in the QtQuick > QML/JS Editing preferences: "use qmlls" activates the use of qmlls if available; "use latest qmlls" always uses the qmlls of the latest Qt, instead of the one of the target (with the one used to compile QtCreator as fallback). To support disabling/enabling of qmlls as soon as one changes the preferences the singleton QmllsSettingsManager can emit a signal on changes. It also keeps track of the latest qmlls binary known. QmlJS::ModelmanagerInterface::ProjectInfo is also extended to keep track of the qmlls binary. QmlJSEditorDocument uses the ProjectInfo and QmllsSettingsManager to decide if a LanguageClient::Client should be started for that document. The client uses the QmllsClient subclass to keep track of the path of the qmlls clients and use the same qmlls process or all files that use the same binary. Currently qmlls <6.4.0 are not considered because they might have too many issues. The enabling/disabling of warnings and highlight is a bit cumbersome because they are handled together in the semantic highlighter, but must be handled separately depending on the qmlls capabilities. The disabling is done at the latest moment stopping the visualization of the embedded model warnings/highlights/suggestions. The computation of the semantic info is not suppressed to support the other features (find usages, semantic highlighting if active,...). When qmlls supports more features a complete removal of the semantic info construction could be evaluated. Change-Id: I3487e1680841025cabba6b339fbfe820ef83f858 Reviewed-by: David Schulz <david.schulz@qt.io>
2022-09-28 11:16:49 +02:00
QmllsSettingsManager::instance();
// QML task updating manager
connect(modelManager, &QmlJS::ModelManagerInterface::documentChangedOnDisk,
&m_qmlTaskManager, &QmlTaskManager::updateMessages);
// recompute messages when information about libraries changes
connect(modelManager, &QmlJS::ModelManagerInterface::libraryInfoUpdated,
&m_qmlTaskManager, &QmlTaskManager::updateMessages);
// recompute messages when project data changes (files added or removed)
connect(modelManager, &QmlJS::ModelManagerInterface::projectInfoUpdated,
&m_qmlTaskManager, &QmlTaskManager::updateMessages);
connect(modelManager,
&QmlJS::ModelManagerInterface::aboutToRemoveFiles,
&m_qmlTaskManager,
&QmlTaskManager::documentsRemoved);
Context context(Constants::C_QMLJSEDITOR_ID, Constants::C_QTQUICKDESIGNEREDITOR_ID);
2009-04-22 15:21:04 +02:00
ActionContainer *contextMenu = ActionManager::createMenu(Constants::M_CONTEXT);
ActionContainer *qmlToolsMenu = ActionManager::actionContainer(Id(QmlJSTools::Constants::M_TOOLS_QMLJS));
qmlToolsMenu->addSeparator();
Command *cmd;
cmd = ActionManager::command(TextEditor::Constants::FOLLOW_SYMBOL_UNDER_CURSOR);
contextMenu->addAction(cmd);
qmlToolsMenu->addAction(cmd);
cmd = ActionManager::command(TextEditor::Constants::FIND_USAGES);
contextMenu->addAction(cmd);
qmlToolsMenu->addAction(cmd);
cmd = ActionManager::command(TextEditor::Constants::RENAME_SYMBOL);
contextMenu->addAction(cmd);
qmlToolsMenu->addAction(cmd);
QAction *semanticScan = new QAction(Tr::tr("Run Checks"), this);
cmd = ActionManager::registerAction(semanticScan, Id("QmlJSEditor.RunSemanticScan"));
cmd->setDefaultKeySequence(QKeySequence(Tr::tr("Ctrl+Shift+C")));
connect(semanticScan, &QAction::triggered, this, &QmlJSEditorPluginPrivate::runSemanticScan);
qmlToolsMenu->addAction(cmd);
m_reformatFileAction = new QAction(Tr::tr("Reformat File"), this);
cmd = ActionManager::registerAction(m_reformatFileAction,
Id("QmlJSEditor.ReformatFile"),
context);
connect(m_reformatFileAction, &QAction::triggered, this, &QmlJSEditorPluginPrivate::reformatFile);
qmlToolsMenu->addAction(cmd);
QAction *inspectElementAction = new QAction(Tr::tr("Inspect API for Element Under Cursor"), this);
cmd = ActionManager::registerAction(inspectElementAction,
Id("QmlJSEditor.InspectElementUnderCursor"),
context);
connect(inspectElementAction, &QAction::triggered, [] {
if (auto widget = qobject_cast<QmlJSEditorWidget *>(EditorManager::currentEditor()->widget()))
widget->inspectElementUnderCursor();
});
qmlToolsMenu->addAction(cmd);
QAction *showQuickToolbar = new QAction(Tr::tr("Show Qt Quick Toolbar"), this);
cmd = ActionManager::registerAction(showQuickToolbar, Constants::SHOW_QT_QUICK_HELPER, context);
cmd->setDefaultKeySequence(useMacShortcuts ? QKeySequence(Qt::META | Qt::ALT | Qt::Key_Space)
: QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Space));
connect(showQuickToolbar, &QAction::triggered, this, &QmlJSEditorPluginPrivate::showContextPane);
contextMenu->addAction(cmd);
qmlToolsMenu->addAction(cmd);
// Insert marker for "Refactoring" menu:
Command *sep = contextMenu->addSeparator();
sep->action()->setObjectName(QLatin1String(Constants::M_REFACTORING_MENU_INSERTION_POINT));
contextMenu->addSeparator();
cmd = ActionManager::command(TextEditor::Constants::AUTO_INDENT_SELECTION);
contextMenu->addAction(cmd);
cmd = ActionManager::command(TextEditor::Constants::UN_COMMENT_SELECTION);
contextMenu->addAction(cmd);
FileIconProvider::registerIconOverlayForSuffix(ProjectExplorer::Constants::FILEOVERLAY_QML, "qml");
connect(EditorManager::instance(), &EditorManager::currentEditorChanged,
this, &QmlJSEditorPluginPrivate::currentEditorChanged);
connect(EditorManager::instance(), &EditorManager::aboutToSave,
this, &QmlJSEditorPluginPrivate::autoFormatOnSave);
2009-04-22 15:21:04 +02:00
}
void QmlJSEditorPlugin::extensionsInitialized()
2009-04-22 15:21:04 +02:00
{
FileIconProvider::registerIconOverlayForMimeType(ProjectExplorer::Constants::FILEOVERLAY_UI,
"application/x-qt.ui+qml");
TaskHub::addCategory(Constants::TASK_CATEGORY_QML, Tr::tr("QML"));
TaskHub::addCategory(Constants::TASK_CATEGORY_QML_ANALYSIS, Tr::tr("QML Analysis"), false);
Automatic qmlls support (experimental) Looks for qmlls (the qml language server of Qt) and if available and set in the preferences uses it instead of the embedded code model for the supported features. Its usage is driven by two flags that can be set in the QtQuick > QML/JS Editing preferences: "use qmlls" activates the use of qmlls if available; "use latest qmlls" always uses the qmlls of the latest Qt, instead of the one of the target (with the one used to compile QtCreator as fallback). To support disabling/enabling of qmlls as soon as one changes the preferences the singleton QmllsSettingsManager can emit a signal on changes. It also keeps track of the latest qmlls binary known. QmlJS::ModelmanagerInterface::ProjectInfo is also extended to keep track of the qmlls binary. QmlJSEditorDocument uses the ProjectInfo and QmllsSettingsManager to decide if a LanguageClient::Client should be started for that document. The client uses the QmllsClient subclass to keep track of the path of the qmlls clients and use the same qmlls process or all files that use the same binary. Currently qmlls <6.4.0 are not considered because they might have too many issues. The enabling/disabling of warnings and highlight is a bit cumbersome because they are handled together in the semantic highlighter, but must be handled separately depending on the qmlls capabilities. The disabling is done at the latest moment stopping the visualization of the embedded model warnings/highlights/suggestions. The computation of the semantic info is not suppressed to support the other features (find usages, semantic highlighting if active,...). When qmlls supports more features a complete removal of the semantic info construction could be evaluated. Change-Id: I3487e1680841025cabba6b339fbfe820ef83f858 Reviewed-by: David Schulz <david.schulz@qt.io>
2022-09-28 11:16:49 +02:00
QmllsSettingsManager::instance()->setupAutoupdate();
2009-04-22 15:21:04 +02:00
}
2010-07-13 17:05:47 +02:00
ExtensionSystem::IPlugin::ShutdownFlag QmlJSEditorPlugin::aboutToShutdown()
{
return IPlugin::aboutToShutdown();
}
Utils::JsonSchemaManager *QmlJSEditorPlugin::jsonManager()
{
return &m_instance->d->m_jsonManager;
}
QuickToolBar *QmlJSEditorPlugin::quickToolBar()
{
QTC_ASSERT(m_instance && m_instance->d, return new QuickToolBar());
return &m_instance->d->m_quickToolBar;
}
void QmlJSEditorPluginPrivate::renameUsages()
{
if (auto editor = qobject_cast<QmlJSEditorWidget*>(EditorManager::currentEditor()->widget()))
editor->renameSymbolUnderCursor();
}
void QmlJSEditorPluginPrivate::reformatFile()
{
if (m_currentDocument) {
if (QmlJsEditingSettings::get().useCustomFormatCommand()) {
QString formatCommand = QmlJsEditingSettings::get().formatCommand();
if (formatCommand.isEmpty())
formatCommand = QmlJsEditingSettings::get().defaultFormatCommand();
const auto exe = FilePath::fromUserInput(globalMacroExpander()->expand(formatCommand));
const QString args = globalMacroExpander()->expand(
QmlJsEditingSettings::get().formatCommandOptions());
const CommandLine commandLine(exe, args, CommandLine::Raw);
TextEditor::Command command;
command.setExecutable(commandLine.executable().toString());
command.setProcessing(TextEditor::Command::FileProcessing);
command.addOptions(commandLine.splitArguments());
command.addOption("--inplace");
command.addOption("%file");
if (!command.isValid())
return;
const QList<Core::IEditor *> editors = Core::DocumentModel::editorsForDocument(m_currentDocument);
if (editors.isEmpty())
return;
IEditor *currentEditor = EditorManager::currentEditor();
IEditor *editor = editors.contains(currentEditor) ? currentEditor : editors.first();
if (auto widget = TextEditor::TextEditorWidget::fromEditor(editor))
TextEditor::formatEditor(widget, command);
return;
}
QmlJS::Document::Ptr document = m_currentDocument->semanticInfo().document;
QmlJS::Snapshot snapshot = QmlJS::ModelManagerInterface::instance()->snapshot();
if (m_currentDocument->isSemanticInfoOutdated()) {
QmlJS::Document::MutablePtr latestDocument;
const Utils::FilePath fileName = m_currentDocument->filePath();
latestDocument = snapshot.documentFromSource(QString::fromUtf8(m_currentDocument->contents()),
fileName,
QmlJS::ModelManagerInterface::guessLanguageOfFile(fileName));
latestDocument->parseQml();
snapshot.insert(latestDocument);
document = latestDocument;
}
if (!document->isParsedCorrectly())
return;
TextEditor::TabSettings tabSettings = m_currentDocument->tabSettings();
const QString &newText = QmlJS::reformat(document,
tabSettings.m_indentSize,
tabSettings.m_tabSize,
QmlJSTools::QmlJSToolsSettings::globalCodeStyle()->currentCodeStyleSettings().lineLength);
auto ed = qobject_cast<TextEditor::BaseTextEditor *>(EditorManager::currentEditor());
if (ed) {
TextEditor::updateEditorText(ed->editorWidget(), newText);
} else {
QTextCursor tc(m_currentDocument->document());
tc.movePosition(QTextCursor::Start);
tc.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
tc.insertText(newText);
}
}
}
void QmlJSEditorPluginPrivate::showContextPane()
{
if (auto editor = qobject_cast<QmlJSEditorWidget*>(EditorManager::currentEditor()->widget()))
editor->showContextPane();
}
Command *QmlJSEditorPluginPrivate::addToolAction(QAction *a,
Context &context, Id id,
ActionContainer *c1, const QString &keySequence)
{
Command *command = ActionManager::registerAction(a, id, context);
if (!keySequence.isEmpty())
command->setDefaultKeySequence(QKeySequence(keySequence));
c1->addAction(command);
return command;
}
QmlJSQuickFixAssistProvider *QmlJSEditorPlugin::quickFixAssistProvider()
{
return &m_instance->d->m_quickFixAssistProvider;
}
void QmlJSEditorPluginPrivate::currentEditorChanged(IEditor *editor)
{
QmlJSEditorDocument *document = nullptr;
if (editor)
document = qobject_cast<QmlJSEditorDocument *>(editor->document());
if (m_currentDocument)
m_currentDocument->disconnect(this);
m_currentDocument = document;
if (document) {
connect(document->document(), &QTextDocument::contentsChanged,
this, &QmlJSEditorPluginPrivate::checkCurrentEditorSemanticInfoUpToDate);
connect(document, &QmlJSEditorDocument::semanticInfoUpdated,
this, &QmlJSEditorPluginPrivate::checkCurrentEditorSemanticInfoUpToDate);
}
}
void QmlJSEditorPluginPrivate::runSemanticScan()
{
m_qmlTaskManager.updateSemanticMessagesNow();
TaskHub::setCategoryVisibility(Constants::TASK_CATEGORY_QML_ANALYSIS, true);
TaskHub::requestPopup();
}
void QmlJSEditorPluginPrivate::checkCurrentEditorSemanticInfoUpToDate()
{
const bool semanticInfoUpToDate = m_currentDocument && !m_currentDocument->isSemanticInfoOutdated();
m_reformatFileAction->setEnabled(semanticInfoUpToDate);
}
void QmlJSEditorPluginPrivate::autoFormatOnSave(IDocument *document)
{
if (!QmlJsEditingSettings::get().autoFormatOnSave())
return;
// Check that we are dealing with a QML/JS editor
if (document->id() != Constants::C_QMLJSEDITOR_ID
&& document->id() != Constants::C_QTQUICKDESIGNEREDITOR_ID)
return;
// Check if file is contained in the current project (if wished)
if (QmlJsEditingSettings::get().autoFormatOnlyCurrentProject()) {
const Project *pro = ProjectTree::currentProject();
if (!pro || !pro->files(Project::SourceFiles).contains(document->filePath()))
return;
}
reformatFile();
}
} // namespace Internal
} // namespace QmlJSEditor