/**************************************************************************** ** ** 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 "cppeditordocument.h" #include "cppeditorconstants.h" #include "cppeditorplugin.h" #include "cpphighlighter.h" #include "cppquickfixassistant.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { CppTools::CppModelManager *mm() { return CppTools::CppModelManager::instance(); } } // anonymous namespace using namespace TextEditor; namespace CppEditor { namespace Internal { enum { processDocumentIntervalInMs = 150 }; class CppEditorDocumentHandleImpl : public CppTools::CppEditorDocumentHandle { public: CppEditorDocumentHandleImpl(CppEditorDocument *cppEditorDocument) : m_cppEditorDocument(cppEditorDocument) , m_registrationFilePath(cppEditorDocument->filePath().toString()) { mm()->registerCppEditorDocument(this); } ~CppEditorDocumentHandleImpl() override { mm()->unregisterCppEditorDocument(m_registrationFilePath); } QString filePath() const override { return m_cppEditorDocument->filePath().toString(); } QByteArray contents() const override { return m_cppEditorDocument->contentsText(); } unsigned revision() const override { return m_cppEditorDocument->contentsRevision(); } CppTools::BaseEditorDocumentProcessor *processor() const override { return m_cppEditorDocument->processor(); } void resetProcessor() override { m_cppEditorDocument->resetProcessor(); } private: CppEditor::Internal::CppEditorDocument * const m_cppEditorDocument; // The file path of the editor document can change (e.g. by "Save As..."), so make sure // that un-registration happens with the path the document was registered. const QString m_registrationFilePath; }; CppEditorDocument::CppEditorDocument() : m_minimizableInfoBars(*infoBar()) { setId(CppEditor::Constants::CPPEDITOR_ID); setSyntaxHighlighter(new CppHighlighter); ICodeStylePreferencesFactory *factory = TextEditorSettings::codeStyleFactory(CppTools::Constants::CPP_SETTINGS_ID); setIndenter(factory->createIndenter(document())); connect(this, &TextEditor::TextDocument::tabSettingsChanged, this, &CppEditorDocument::invalidateFormatterCache); connect(this, &Core::IDocument::mimeTypeChanged, this, &CppEditorDocument::onMimeTypeChanged); connect(this, &Core::IDocument::aboutToReload, this, &CppEditorDocument::onAboutToReload); connect(this, &Core::IDocument::reloadFinished, this, &CppEditorDocument::onReloadFinished); connect(this, &IDocument::filePathChanged, this, &CppEditorDocument::onFilePathChanged); connect(&m_parseContextModel, &ParseContextModel::preferredParseContextChanged, this, &CppEditorDocument::reparseWithPreferredParseContext); // See also onFilePathChanged() for more initialization } bool CppEditorDocument::isObjCEnabled() const { return m_isObjCEnabled; } CppTools::CppCompletionAssistProvider *CppEditorDocument::completionAssistProvider() const { return m_completionAssistProvider; } CppTools::CppCompletionAssistProvider *CppEditorDocument::functionHintAssistProvider() const { return m_functionHintAssistProvider; } TextEditor::IAssistProvider *CppEditorDocument::quickFixAssistProvider() const { return CppEditorPlugin::instance()->quickFixProvider(); } void CppEditorDocument::recalculateSemanticInfoDetached() { CppTools::BaseEditorDocumentProcessor *p = processor(); QTC_ASSERT(p, return); p->recalculateSemanticInfoDetached(true); } CppTools::SemanticInfo CppEditorDocument::recalculateSemanticInfo() { CppTools::BaseEditorDocumentProcessor *p = processor(); QTC_ASSERT(p, CppTools::SemanticInfo()); return p->recalculateSemanticInfo(); } QByteArray CppEditorDocument::contentsText() const { QMutexLocker locker(&m_cachedContentsLock); const int currentRevision = document()->revision(); if (m_cachedContentsRevision != currentRevision && !m_fileIsBeingReloaded) { m_cachedContentsRevision = currentRevision; m_cachedContents = plainText().toUtf8(); } return m_cachedContents; } void CppEditorDocument::applyFontSettings() { if (TextEditor::SyntaxHighlighter *highlighter = syntaxHighlighter()) highlighter->clearAllExtraFormats(); // Clear all additional formats since they may have changed TextDocument::applyFontSettings(); // rehighlights and updates additional formats if (m_processor) m_processor->semanticRehighlight(); } void CppEditorDocument::invalidateFormatterCache() { CppTools::QtStyleCodeFormatter formatter; formatter.invalidateCache(document()); } void CppEditorDocument::onMimeTypeChanged() { const QString &mt = mimeType(); m_isObjCEnabled = (mt == QLatin1String(CppTools::Constants::OBJECTIVE_C_SOURCE_MIMETYPE) || mt == QLatin1String(CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE)); m_completionAssistProvider = mm()->completionAssistProvider(); m_functionHintAssistProvider = mm()->functionHintAssistProvider(); initializeTimer(); } void CppEditorDocument::onAboutToReload() { QTC_CHECK(!m_fileIsBeingReloaded); m_fileIsBeingReloaded = true; processor()->invalidateDiagnostics(); } void CppEditorDocument::onReloadFinished() { QTC_CHECK(m_fileIsBeingReloaded); m_fileIsBeingReloaded = false; m_processorRevision = document()->revision(); processDocument(); } void CppEditorDocument::reparseWithPreferredParseContext(const QString &parseContextId) { using namespace CppTools; // Update parser setPreferredParseContext(parseContextId); // Remember the setting const QString key = Constants::PREFERRED_PARSE_CONTEXT + filePath().toString(); ProjectExplorer::SessionManager::setValue(key, parseContextId); // Reprocess scheduleProcessDocument(); } void CppEditorDocument::onFilePathChanged(const Utils::FilePath &oldPath, const Utils::FilePath &newPath) { Q_UNUSED(oldPath) if (!newPath.isEmpty()) { indenter()->setFileName(newPath); setMimeType(Utils::mimeTypeForFile(newPath.toFileInfo()).name()); connect(this, &Core::IDocument::contentsChanged, this, &CppEditorDocument::scheduleProcessDocument, Qt::UniqueConnection); // Un-Register/Register in ModelManager m_editorDocumentHandle.reset(); m_editorDocumentHandle.reset(new CppEditorDocumentHandleImpl(this)); resetProcessor(); applyPreferredParseContextFromSettings(); applyExtraPreprocessorDirectivesFromSettings(); m_processorRevision = document()->revision(); processDocument(); } } void CppEditorDocument::scheduleProcessDocument() { if (m_fileIsBeingReloaded) return; m_processorRevision = document()->revision(); m_processorTimer.start(); processor()->editorDocumentTimerRestarted(); } void CppEditorDocument::processDocument() { processor()->invalidateDiagnostics(); if (processor()->isParserRunning() || m_processorRevision != contentsRevision()) { m_processorTimer.start(); processor()->editorDocumentTimerRestarted(); return; } m_processorTimer.stop(); if (m_fileIsBeingReloaded || filePath().isEmpty()) return; processor()->run(); } void CppEditorDocument::resetProcessor() { releaseResources(); processor(); // creates a new processor } void CppEditorDocument::applyPreferredParseContextFromSettings() { if (filePath().isEmpty()) return; const QString key = Constants::PREFERRED_PARSE_CONTEXT + filePath().toString(); const QString parseContextId = ProjectExplorer::SessionManager::value(key).toString(); setPreferredParseContext(parseContextId); } void CppEditorDocument::applyExtraPreprocessorDirectivesFromSettings() { if (filePath().isEmpty()) return; const QString key = Constants::EXTRA_PREPROCESSOR_DIRECTIVES + filePath().toString(); const QByteArray directives = ProjectExplorer::SessionManager::value(key).toString().toUtf8(); setExtraPreprocessorDirectives(directives); } void CppEditorDocument::setExtraPreprocessorDirectives(const QByteArray &directives) { const auto parser = processor()->parser(); QTC_ASSERT(parser, return); CppTools::BaseEditorDocumentParser::Configuration config = parser->configuration(); if (config.editorDefines != directives) { config.editorDefines = directives; processor()->setParserConfig(config); emit preprocessorSettingsChanged(!directives.trimmed().isEmpty()); } } void CppEditorDocument::setPreferredParseContext(const QString &parseContextId) { const CppTools::BaseEditorDocumentParser::Ptr parser = processor()->parser(); QTC_ASSERT(parser, return); CppTools::BaseEditorDocumentParser::Configuration config = parser->configuration(); if (config.preferredProjectPartId != parseContextId) { config.preferredProjectPartId = parseContextId; processor()->setParserConfig(config); } } unsigned CppEditorDocument::contentsRevision() const { return document()->revision(); } void CppEditorDocument::releaseResources() { if (m_processor) disconnect(m_processor.data(), nullptr, this, nullptr); m_processor.reset(); } void CppEditorDocument::showHideInfoBarAboutMultipleParseContexts(bool show) { const Utils::Id id = Constants::MULTIPLE_PARSE_CONTEXTS_AVAILABLE; if (show) { Utils::InfoBarEntry info(id, tr("Note: Multiple parse contexts are available for this file. " "Choose the preferred one from the editor toolbar."), Utils::InfoBarEntry::GlobalSuppression::Enabled); info.removeCancelButton(); if (infoBar()->canInfoBeAdded(id)) infoBar()->addInfo(info); } else { infoBar()->removeInfo(id); } } void CppEditorDocument::initializeTimer() { m_processorTimer.setSingleShot(true); m_processorTimer.setInterval(processDocumentIntervalInMs); connect(&m_processorTimer, &QTimer::timeout, this, &CppEditorDocument::processDocument, Qt::UniqueConnection); } ParseContextModel &CppEditorDocument::parseContextModel() { return m_parseContextModel; } QFuture CppEditorDocument::cursorInfo(const CppTools::CursorInfoParams ¶ms) { return processor()->cursorInfo(params); } const MinimizableInfoBars &CppEditorDocument::minimizableInfoBars() const { return m_minimizableInfoBars; } CppTools::BaseEditorDocumentProcessor *CppEditorDocument::processor() { if (!m_processor) { m_processor.reset(mm()->createEditorDocumentProcessor(this)); connect(m_processor.data(), &CppTools::BaseEditorDocumentProcessor::projectPartInfoUpdated, [this] (const CppTools::ProjectPartInfo &info) { using namespace CppTools; const bool hasProjectPart = !(info.hints & ProjectPartInfo::IsFallbackMatch); m_minimizableInfoBars.processHasProjectPart(hasProjectPart); m_parseContextModel.update(info); const bool isAmbiguous = info.hints & ProjectPartInfo::IsAmbiguousMatch; const bool isProjectFile = info.hints & ProjectPartInfo::IsFromProjectMatch; showHideInfoBarAboutMultipleParseContexts(isAmbiguous && isProjectFile); }); connect(m_processor.data(), &CppTools::BaseEditorDocumentProcessor::codeWarningsUpdated, [this] (unsigned revision, const QList selections, const std::function &creator, const TextEditor::RefactorMarkers &refactorMarkers) { emit codeWarningsUpdated(revision, selections, refactorMarkers); m_minimizableInfoBars.processHeaderDiagnostics(creator); }); connect(m_processor.data(), &CppTools::BaseEditorDocumentProcessor::ifdefedOutBlocksUpdated, this, &CppEditorDocument::ifdefedOutBlocksUpdated); connect(m_processor.data(), &CppTools::BaseEditorDocumentProcessor::cppDocumentUpdated, [this](const CPlusPlus::Document::Ptr document) { // Update syntax highlighter auto *highlighter = qobject_cast(syntaxHighlighter()); highlighter->setLanguageFeatures(document->languageFeatures()); // Forward signal emit cppDocumentUpdated(document); }); connect(m_processor.data(), &CppTools::BaseEditorDocumentProcessor::semanticInfoUpdated, this, &CppEditorDocument::semanticInfoUpdated); } return m_processor.data(); } TextEditor::TabSettings CppEditorDocument::tabSettings() const { return indenter()->tabSettings().value_or(TextEditor::TextDocument::tabSettings()); } bool CppEditorDocument::save(QString *errorString, const QString &fileName, bool autoSave) { Utils::ExecuteOnDestruction resetSettingsOnScopeExit; if (indenter()->formatOnSave() && !autoSave) { auto *layout = qobject_cast(document()->documentLayout()); const int documentRevision = layout->lastSaveRevision; TextEditor::RangesInLines editedRanges; TextEditor::RangeInLines lastRange{-1, -1}; for (int i = 0; i < document()->blockCount(); ++i) { const QTextBlock block = document()->findBlockByNumber(i); if (block.revision() == documentRevision) { if (lastRange.startLine != -1) editedRanges.push_back(lastRange); lastRange.startLine = lastRange.endLine = -1; continue; } // block.revision() != documentRevision if (lastRange.startLine == -1) lastRange.startLine = block.blockNumber() + 1; lastRange.endLine = block.blockNumber() + 1; } if (lastRange.startLine != -1) editedRanges.push_back(lastRange); if (!editedRanges.empty()) { QTextCursor cursor(document()); cursor.beginEditBlock(); indenter()->format(editedRanges); cursor.endEditBlock(); } TextEditor::StorageSettings settings = storageSettings(); resetSettingsOnScopeExit.reset( [this, defaultSettings = settings]() { setStorageSettings(defaultSettings); }); settings.m_cleanWhitespace = false; setStorageSettings(settings); } return TextEditor::TextDocument::save(errorString, fileName, autoSave); } } // namespace Internal } // namespace CppEditor