File indexing completed on 2024-04-28 04:37:27

0001 /*
0002     SPDX-FileCopyrightText: 2007 Alexander Dymo <adymo@kdevelop.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "textdocument.h"
0008 
0009 #include <QAction>
0010 #include <QFile>
0011 #include <QMenu>
0012 #include <QMimeDatabase>
0013 #include <QPointer>
0014 #include <QWidget>
0015 
0016 #include <KActionCollection>
0017 #include <KConfigGroup>
0018 #include <KLocalizedString>
0019 #include <KMessageBox>
0020 #include <KMessageBox_KDevCompat>
0021 #include <KTextEditor/View>
0022 #include <KTextEditor/Document>
0023 #include <KTextEditor/ModificationInterface>
0024 #include <KTextEditor/CodeCompletionInterface>
0025 #include <KTextEditor/MarkInterface>
0026 
0027 #include <interfaces/context.h>
0028 #include <interfaces/contextmenuextension.h>
0029 #include <interfaces/ilanguagecontroller.h>
0030 #include <interfaces/icompletionsettings.h>
0031 #include <interfaces/iprojectcontroller.h>
0032 #include <interfaces/iproject.h>
0033 
0034 #include <vcs/interfaces/icontentawareversioncontrol.h>
0035 
0036 #include <language/interfaces/editorcontext.h>
0037 #include <language/backgroundparser/backgroundparser.h>
0038 
0039 #include <util/foregroundlock.h>
0040 
0041 #include "core.h"
0042 #include "mainwindow.h"
0043 #include "uicontroller.h"
0044 #include "partcontroller.h"
0045 #include "plugincontroller.h"
0046 #include "documentcontroller.h"
0047 #include "ktexteditorpluginintegration.h"
0048 #include "debug.h"
0049 
0050 #include <path.h>
0051 #include <shellutils.h>
0052 
0053 namespace KDevelop {
0054 
0055 const int MAX_DOC_SETTINGS = 20;
0056 
0057 // This sets cursor position and selection on the view to the given
0058 // range. Selection is set only for non-empty ranges
0059 // Factored into a function since its needed in 3 places already
0060 static void selectAndReveal( KTextEditor::View* view, const KTextEditor::Range& range ) {
0061     Q_ASSERT(view);
0062     if (range.isValid()) {
0063         view->setCursorPosition(range.start());
0064         view->setSelection(range);
0065     }
0066 }
0067 
0068 class TextDocumentPrivate
0069 {
0070 public:
0071     explicit TextDocumentPrivate(TextDocument *textDocument)
0072         : q(textDocument)
0073     {
0074     }
0075 
0076     ~TextDocumentPrivate()
0077     {
0078         // Handle the case we are being deleted while the context menu is not yet hidden.
0079         // We want to remove all actions we added to it, especially those not owned by the document
0080         // but by the plugins (i.e. created on-the-fly during ContextMenuExtension::populateMenu
0081         // with ownership set to our addedContextMenu)
0082         cleanContextMenu();
0083 
0084         saveSessionConfig();
0085         delete document;
0086     }
0087 
0088     void setStatus(KTextEditor::Document* document, bool dirty)
0089     {
0090         QIcon statusIcon;
0091 
0092         if (document->isModified())
0093             if (dirty) {
0094                 state = IDocument::DirtyAndModified;
0095                 statusIcon = QIcon::fromTheme(QStringLiteral("edit-delete"));
0096             } else {
0097                 state = IDocument::Modified;
0098                 statusIcon = QIcon::fromTheme(QStringLiteral("document-save"));
0099             }
0100         else
0101             if (dirty) {
0102                 state = IDocument::Dirty;
0103                 statusIcon = QIcon::fromTheme(QStringLiteral("document-revert"));
0104             } else {
0105                 state = IDocument::Clean;
0106             }
0107 
0108         q->notifyStateChanged();
0109         q->setStatusIcon(statusIcon);
0110     }
0111 
0112     inline KConfigGroup katePartSettingsGroup() const
0113     {
0114         return KSharedConfig::openConfig()->group("KatePart Settings");
0115     }
0116 
0117     inline QString docConfigGroupName() const
0118     {
0119         return document->url().toDisplayString(QUrl::PreferLocalFile);
0120     }
0121 
0122     inline KConfigGroup docConfigGroup() const
0123     {
0124         return katePartSettingsGroup().group(docConfigGroupName());
0125     }
0126 
0127     void saveSessionConfig()
0128     {
0129         if(document && document->url().isValid()) {
0130             // make sure only MAX_DOC_SETTINGS entries are stored
0131             KConfigGroup katePartSettings = katePartSettingsGroup();
0132             // ordered list of documents
0133             QStringList documents = katePartSettings.readEntry("documents", QStringList());
0134             // ensure this document is "new", i.e. at the end of the list
0135             documents.removeOne(docConfigGroupName());
0136             documents.append(docConfigGroupName());
0137             // remove "old" documents + their group
0138             while(documents.size() >= MAX_DOC_SETTINGS) {
0139                 katePartSettings.group(documents.takeFirst()).deleteGroup();
0140             }
0141             // update order
0142             katePartSettings.writeEntry("documents", documents);
0143 
0144             // actually save session config
0145             KConfigGroup group = docConfigGroup();
0146             document->writeSessionConfig(group);
0147         }
0148     }
0149 
0150     void loadSessionConfig()
0151     {
0152         if (!document || !katePartSettingsGroup().hasGroup(docConfigGroupName())) {
0153             return;
0154         }
0155 
0156         document->readSessionConfig(docConfigGroup(), {QStringLiteral("SkipUrl")});
0157     }
0158 
0159     // Determines whether the current contents of this document in the editor
0160     // could be retrieved from the VCS if they were dismissed.
0161     void queryCanRecreateFromVcs(KTextEditor::Document* document) const {
0162         // Find projects by checking which one contains the file's parent directory,
0163         // to avoid issues with the cmake manager temporarily removing files from a project
0164         // during reloading.
0165         KDevelop::Path path(document->url());
0166         const auto projects = Core::self()->projectController()->projects();
0167         auto projectIt = std::find_if(projects.begin(), projects.end(), [&](KDevelop::IProject* project) {
0168             return project->path().isParentOf(path);
0169         });
0170         if (projectIt == projects.end()) {
0171             return;
0172         }
0173         IProject* project = *projectIt;
0174 
0175         IContentAwareVersionControl* iface;
0176         iface = qobject_cast< KDevelop::IContentAwareVersionControl* >(project->versionControlPlugin());
0177         if (!iface) {
0178             return;
0179         }
0180         if ( !qobject_cast<KTextEditor::ModificationInterface*>( document ) ) {
0181             return;
0182         }
0183 
0184         CheckInRepositoryJob* req = iface->isInRepository( document );
0185         if ( !req ) {
0186             return;
0187         }
0188         QObject::connect(req, &CheckInRepositoryJob::finished,
0189                             q, &TextDocument::repositoryCheckFinished);
0190         // Abort the request when the user edits the document
0191         QObject::connect(q->textDocument(), &KTextEditor::Document::textChanged,
0192                             req, &CheckInRepositoryJob::abort);
0193     }
0194 
0195     void modifiedOnDisk(KTextEditor::Document *document, bool /*isModified*/,
0196         KTextEditor::ModificationInterface::ModifiedOnDiskReason reason)
0197     {
0198         bool dirty = false;
0199         switch (reason)
0200         {
0201             case KTextEditor::ModificationInterface::OnDiskUnmodified:
0202                 break;
0203             case KTextEditor::ModificationInterface::OnDiskModified:
0204             case KTextEditor::ModificationInterface::OnDiskCreated:
0205             case KTextEditor::ModificationInterface::OnDiskDeleted:
0206                 dirty = true;
0207                 break;
0208         }
0209 
0210         // In some cases, the VCS (e.g. git) can know whether the old contents are "valuable", i.e.
0211         // not retrieveable from the VCS. If that is not the case, then the document can safely be
0212         // reloaded without displaying a dialog asking the user.
0213         if ( dirty ) {
0214             queryCanRecreateFromVcs(document);
0215         }
0216         setStatus(document, dirty);
0217     }
0218 
0219     void cleanContextMenu()
0220     {
0221         if (!addedContextMenu) {
0222             return;
0223         }
0224 
0225         if (currentContextMenu) {
0226             const auto actions = addedContextMenu->actions();
0227             for (QAction* action : actions) {
0228                 currentContextMenu->removeAction(action);
0229             }
0230             currentContextMenu.clear();
0231         }
0232 
0233         // The addedContextMenu owns those actions created on-the-fly for the context menu
0234         // (other than those actions only shared for the context menu, but also used elsewhere)
0235         // and thuse deletes then on its own destruction.
0236         // Some actions potentially could be connected to triggered-signal handlers
0237         // using Qt::QueuedConnection (at least SwitchToBuddyPlugin does so currently).
0238         // Deleting them here also would also delete the connection before the handler is triggered.
0239         // So we delay the menu's and thus their destruction to the next eventloop by default.
0240         addedContextMenu->deleteLater();
0241         addedContextMenu = nullptr;
0242     }
0243 
0244     TextDocument * const q;
0245 
0246     QPointer<KTextEditor::Document> document;
0247     IDocument::DocumentState state = IDocument::Clean;
0248     QString encoding;
0249     bool loaded = false;
0250     // we want to remove the added stuff when the menu hides
0251     QMenu* addedContextMenu = nullptr;
0252     QPointer<QMenu> currentContextMenu;
0253 };
0254 
0255 class TextViewPrivate
0256 {
0257 public:
0258     explicit TextViewPrivate(TextView* q) : q(q) {}
0259 
0260     TextView* const q;
0261     QPointer<KTextEditor::View> view;
0262     KTextEditor::Range initialRange;
0263 };
0264 
0265 TextDocument::TextDocument(const QUrl &url, ICore* core, const QString& encoding)
0266     : PartDocument(url, core)
0267     , d_ptr(new TextDocumentPrivate(this))
0268 {
0269     Q_D(TextDocument);
0270 
0271     d->encoding = encoding;
0272 }
0273 
0274 TextDocument::~TextDocument() = default;
0275 
0276 KTextEditor::Document *TextDocument::textDocument() const
0277 {
0278     Q_D(const TextDocument);
0279 
0280     return d->document;
0281 }
0282 
0283 QWidget *TextDocument::createViewWidget(QWidget *parent)
0284 {
0285     Q_D(TextDocument);
0286 
0287     KTextEditor::View* view = nullptr;
0288 
0289     if (!d->document)
0290     {
0291         d->document = Core::self()->partControllerInternal()->createTextPart();
0292 
0293         // Connect to the first text changed signal, it occurs before the completed() signal
0294         connect(d->document.data(), &KTextEditor::Document::textChanged, this, &TextDocument::slotDocumentLoaded);
0295         // Also connect to the completed signal, sometimes the first text changed signal is missed because the part loads too quickly (? TODO - confirm this is necessary)
0296         connect(d->document.data(), QOverload<>::of(&KTextEditor::Document::completed),
0297                 this, &TextDocument::slotDocumentLoaded);
0298 
0299         // force a reparse when a document gets reloaded
0300         connect(d->document.data(), &KTextEditor::Document::reloaded,
0301                 this, [] (KTextEditor::Document* document) {
0302             ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(document->url()),
0303                     TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate,
0304                     BackgroundParser::BestPriority, nullptr);
0305         });
0306 
0307         // Set encoding passed via constructor
0308         // Needs to be done before openUrl, else katepart won't use the encoding
0309         // @see KTextEditor::Document::setEncoding
0310         if (!d->encoding.isEmpty())
0311             d->document->setEncoding(d->encoding);
0312 
0313         if (!url().isEmpty() && !DocumentController::isEmptyDocumentUrl(url()))
0314             d->document->openUrl( url() );
0315 
0316         d->setStatus(d->document, false);
0317 
0318         /* It appears, that by default a part will be deleted the
0319            first view containing it is deleted.  Since we do want
0320            to have several views, disable that behaviour.  */
0321         d->document->setAutoDeletePart(false);
0322 
0323         Core::self()->partController()->addPart(d->document, false);
0324 
0325         d->loadSessionConfig();
0326 
0327         connect(d->document.data(), &KTextEditor::Document::modifiedChanged,
0328                  this, &TextDocument::newDocumentStatus);
0329         connect(d->document.data(), &KTextEditor::Document::textChanged,
0330                  this, &TextDocument::textChanged);
0331         connect(d->document.data(), &KTextEditor::Document::documentUrlChanged,
0332                  this, &TextDocument::documentUrlChanged);
0333         connect(d->document.data(), &KTextEditor::Document::documentSavedOrUploaded,
0334                  this, &TextDocument::documentSaved );
0335 
0336         if (qobject_cast<KTextEditor::MarkInterface*>(d->document)) {
0337             // can't use new signal/slot syntax here, MarkInterface is not a QObject
0338             connect(d->document.data(), SIGNAL(marksChanged(KTextEditor::Document*)),
0339                     this, SLOT(saveSessionConfig()));
0340         }
0341 
0342         if (auto iface = qobject_cast<KTextEditor::ModificationInterface*>(d->document)) {
0343             iface->setModifiedOnDiskWarning(true);
0344             // can't use new signal/slot syntax here, ModificationInterface is not a QObject
0345             connect(d->document.data(), SIGNAL(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)),
0346                 this, SLOT(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)));
0347         }
0348 
0349         notifyTextDocumentCreated();
0350     }
0351 
0352     view = d->document->createView(parent, Core::self()->uiControllerInternal()->defaultMainWindow()->kateWrapper()->interface());
0353 
0354     // get rid of some actions regarding the config dialog, we merge that one into the kdevelop menu already
0355     delete view->actionCollection()->action(QStringLiteral("set_confdlg"));
0356     delete view->actionCollection()->action(QStringLiteral("editor_options"));
0357 
0358     view->setStatusBarEnabled(Core::self()->partControllerInternal()->showTextEditorStatusBar());
0359 
0360     connect(view, &KTextEditor::View::contextMenuAboutToShow, this, &TextDocument::populateContextMenu);
0361 
0362     if (auto* cc = qobject_cast<KTextEditor::CodeCompletionInterface*>(view))
0363         cc->setAutomaticInvocationEnabled(core()->languageController()->completionSettings()->automaticCompletionEnabled());
0364 
0365     return view;
0366 }
0367 
0368 KParts::Part *TextDocument::partForView(QWidget *view) const
0369 {
0370     Q_D(const TextDocument);
0371 
0372     if (d->document && d->document->views().contains(qobject_cast<KTextEditor::View*>(view)))
0373         return d->document;
0374     return nullptr;
0375 }
0376 
0377 
0378 
0379 // KDevelop::IDocument implementation
0380 
0381 void TextDocument::reload()
0382 {
0383     Q_D(TextDocument);
0384 
0385     if (!d->document)
0386         return;
0387 
0388     KTextEditor::ModificationInterface* modif=nullptr;
0389     if(d->state ==Dirty) {
0390         modif = qobject_cast<KTextEditor::ModificationInterface*>(d->document);
0391         modif->setModifiedOnDiskWarning(false);
0392     }
0393     d->document->documentReload();
0394     if(modif)
0395         modif->setModifiedOnDiskWarning(true);
0396 }
0397 
0398 bool TextDocument::save(DocumentSaveMode mode)
0399 {
0400     Q_D(TextDocument);
0401 
0402     if (!d->document)
0403         return true;
0404 
0405     if (mode & Discard)
0406         return true;
0407 
0408     switch (d->state)
0409     {
0410         case IDocument::Clean:
0411             return true;
0412 
0413         case IDocument::Modified:
0414             break;
0415 
0416         case IDocument::Dirty:
0417         case IDocument::DirtyAndModified:
0418             if (!(mode & Silent))
0419             {
0420                 int code = KMessageBox::warningTwoActionsCancel(
0421                     Core::self()->uiController()->activeMainWindow(),
0422                     i18n("The file \"%1\" is modified on disk.\n\nAre "
0423                          "you sure you want to overwrite it? (External "
0424                          "changes will be lost.)",
0425                          d->document->url().toLocalFile()),
0426                     i18nc("@title:window", "Document Externally Modified"),
0427                     KGuiItem(i18nc("@action:button", "Overwrite External Changes"), QStringLiteral("document-save")),
0428                     KStandardGuiItem::discard());
0429                 if (code != KMessageBox::PrimaryAction)
0430                     return false;
0431             }
0432             break;
0433     }
0434 
0435     if (!KDevelop::ensureWritable(QList<QUrl>() << url())) {
0436         return false;
0437     }
0438 
0439     return d->document->documentSave();
0440 }
0441 
0442 IDocument::DocumentState TextDocument::state() const
0443 {
0444     Q_D(const TextDocument);
0445 
0446     return d->state;
0447 }
0448 
0449 KTextEditor::Cursor KDevelop::TextDocument::cursorPosition() const
0450 {
0451     Q_D(const TextDocument);
0452 
0453     if (!d->document) {
0454         return KTextEditor::Cursor::invalid();
0455     }
0456 
0457     KTextEditor::View *view = activeTextView();
0458 
0459     if (view)
0460         return view->cursorPosition();
0461 
0462     return KTextEditor::Cursor::invalid();
0463 }
0464 
0465 void TextDocument::setCursorPosition(const KTextEditor::Cursor &cursor)
0466 {
0467     Q_D(TextDocument);
0468 
0469     if (!cursor.isValid() || !d->document)
0470         return;
0471 
0472     KTextEditor::View *view = activeTextView();
0473 
0474     // Rodda: Cursor must be accurate here, to the definition of accurate for KTextEditor::Cursor.
0475     // ie, starting from 0,0
0476 
0477     if (view)
0478         selectAndReveal(view, {cursor, cursor});
0479 }
0480 
0481 KTextEditor::Range TextDocument::textSelection() const
0482 {
0483     Q_D(const TextDocument);
0484 
0485     if (!d->document) {
0486         return KTextEditor::Range::invalid();
0487     }
0488 
0489     KTextEditor::View *view = activeTextView();
0490 
0491     if (view && view->selection()) {
0492         return view->selectionRange();
0493     }
0494 
0495     return PartDocument::textSelection();
0496 }
0497 
0498 QString TextDocument::text(const KTextEditor::Range &range) const
0499 {
0500     VERIFY_FOREGROUND_LOCKED
0501     Q_D(const TextDocument);
0502 
0503     if (!d->document) {
0504         return QString();
0505     }
0506 
0507     return d->document->text( range );
0508 }
0509 
0510 QString TextDocument::textLine() const
0511 {
0512     VERIFY_FOREGROUND_LOCKED
0513     Q_D(const TextDocument);
0514 
0515     if (!d->document) {
0516         return QString();
0517     }
0518 
0519     KTextEditor::View *view = activeTextView();
0520 
0521     if (view) {
0522         return d->document->line( view->cursorPosition().line() );
0523     }
0524 
0525     return PartDocument::textLine();
0526 }
0527 
0528 QString TextDocument::textWord() const
0529 {
0530     VERIFY_FOREGROUND_LOCKED
0531     Q_D(const TextDocument);
0532 
0533     if (!d->document) {
0534         return QString();
0535     }
0536 
0537     KTextEditor::View *view = activeTextView();
0538 
0539     if (view) {
0540         KTextEditor::Cursor start = view->cursorPosition();
0541         qCDebug(SHELL) << "got start position from view:" << start.line() << start.column();
0542         QString linestr = textLine();
0543         int startPos = qMax( qMin( start.column(), linestr.length() - 1 ), 0 );
0544         int endPos = startPos;
0545         startPos --;
0546         while (startPos >= 0 &&
0547                (linestr[startPos].isLetterOrNumber() || linestr[startPos] == QLatin1Char('_') || linestr[startPos] == QLatin1Char('~'))) {
0548             --startPos;
0549         }
0550 
0551         while (endPos < linestr.length() &&
0552                (linestr[endPos].isLetterOrNumber() || linestr[endPos] == QLatin1Char('_') || linestr[endPos] == QLatin1Char('~'))) {
0553             ++endPos;
0554         }
0555         if( startPos != endPos )
0556         {
0557             qCDebug(SHELL) << "found word" << startPos << endPos << linestr.mid( startPos+1, endPos - startPos - 1 );
0558             return linestr.mid( startPos + 1, endPos - startPos - 1 );
0559         }
0560     }
0561 
0562     return PartDocument::textWord();
0563 }
0564 
0565 void TextDocument::setTextSelection(const KTextEditor::Range &range)
0566 {
0567     Q_D(TextDocument);
0568 
0569     if (!range.isValid() || !d->document)
0570         return;
0571 
0572     KTextEditor::View *view = activeTextView();
0573 
0574     if (view) {
0575         selectAndReveal(view, range);
0576     }
0577 }
0578 
0579 Sublime::View* TextDocument::newView(Sublime::Document* doc)
0580 {
0581     Q_UNUSED(doc);
0582     return new TextView(this);
0583 }
0584 
0585 }
0586 
0587 KDevelop::TextView::TextView(TextDocument * doc)
0588     : View(doc, View::TakeOwnership)
0589     , d_ptr(new TextViewPrivate(this))
0590 {
0591 }
0592 
0593 KDevelop::TextView::~TextView() = default;
0594 
0595 QWidget * KDevelop::TextView::createWidget(QWidget * parent)
0596 {
0597     Q_D(TextView);
0598 
0599     auto textDocument = qobject_cast<TextDocument*>(document());
0600     Q_ASSERT(textDocument);
0601     QWidget* widget = textDocument->createViewWidget(parent);
0602     d->view = qobject_cast<KTextEditor::View*>(widget);
0603     Q_ASSERT(d->view);
0604     connect(d->view.data(), &KTextEditor::View::cursorPositionChanged, this, &KDevelop::TextView::sendStatusChanged);
0605     return widget;
0606 }
0607 
0608 void KDevelop::TextView::setInitialRange(const KTextEditor::Range& range)
0609 {
0610     Q_D(TextView);
0611 
0612     if (d->view) {
0613         selectAndReveal(d->view, range);
0614     } else {
0615         d->initialRange = range;
0616     }
0617 }
0618 
0619 KTextEditor::Range KDevelop::TextView::initialRange() const
0620 {
0621     Q_D(const TextView);
0622 
0623     return d->initialRange;
0624 }
0625 
0626 void KDevelop::TextView::readSessionConfig(KConfigGroup& config)
0627 {
0628     Q_D(TextView);
0629 
0630     if (!d->view) {
0631         return;
0632     }
0633     d->view->readSessionConfig(config);
0634 }
0635 
0636 void KDevelop::TextView::writeSessionConfig(KConfigGroup& config)
0637 {
0638     Q_D(TextView);
0639 
0640     if (!d->view) {
0641         return;
0642     }
0643     d->view->writeSessionConfig(config);
0644 }
0645 
0646 QString KDevelop::TextDocument::documentType() const
0647 {
0648     return QStringLiteral("Text");
0649 }
0650 
0651 QIcon KDevelop::TextDocument::defaultIcon() const
0652 {
0653     Q_D(const TextDocument);
0654 
0655     if (d->document) {
0656         QMimeType mime = QMimeDatabase().mimeTypeForName(d->document->mimeType());
0657         QIcon icon = QIcon::fromTheme(mime.iconName());
0658         if (!icon.isNull()) {
0659             return icon;
0660         }
0661     }
0662     return PartDocument::defaultIcon();
0663 }
0664 
0665 KTextEditor::View *KDevelop::TextView::textView() const
0666 {
0667     Q_D(const TextView);
0668 
0669     return d->view;
0670 }
0671 
0672 QString KDevelop::TextView::viewStatus() const
0673 {
0674     Q_D(const TextView);
0675 
0676     // only show status when KTextEditor's own status bar isn't already enabled
0677     const bool showStatus = !Core::self()->partControllerInternal()->showTextEditorStatusBar();
0678     if (!showStatus) {
0679         return QString();
0680     }
0681 
0682     const KTextEditor::Cursor pos = d->view ? d->view->cursorPosition() : KTextEditor::Cursor::invalid();
0683     return i18n(" Line: %1 Col: %2 ", pos.line() + 1, pos.column() + 1);
0684 }
0685 
0686 void KDevelop::TextView::sendStatusChanged()
0687 {
0688     emit statusChanged(this);
0689 }
0690 
0691 KTextEditor::View* KDevelop::TextDocument::activeTextView() const
0692 {
0693     KTextEditor::View* fallback = nullptr;
0694     for (auto view : views()) {
0695         auto textView = qobject_cast<TextView*>(view)->textView();
0696         if (!textView) {
0697             continue;
0698         }
0699         if (textView->hasFocus()) {
0700             return textView;
0701         } else if (textView->isVisible()) {
0702             fallback = textView;
0703         } else if (!fallback) {
0704             fallback = textView;
0705         }
0706     }
0707     return fallback;
0708 }
0709 
0710 void KDevelop::TextDocument::newDocumentStatus(KTextEditor::Document *document)
0711 {
0712     Q_D(TextDocument);
0713 
0714     bool dirty = (d->state == IDocument::Dirty || d->state == IDocument::DirtyAndModified);
0715 
0716     d->setStatus(document, dirty);
0717 }
0718 
0719 void KDevelop::TextDocument::textChanged(KTextEditor::Document *document)
0720 {
0721     Q_UNUSED(document);
0722     notifyContentChanged();
0723 }
0724 
0725 void KDevelop::TextDocument::unpopulateContextMenu()
0726 {
0727     Q_D(TextDocument);
0728 
0729     auto* menu = qobject_cast<QMenu*>(sender());
0730 
0731     disconnect(menu, &QMenu::aboutToHide, this, &TextDocument::unpopulateContextMenu);
0732 
0733     d->cleanContextMenu();
0734 }
0735 
0736 void KDevelop::TextDocument::populateContextMenu( KTextEditor::View* v, QMenu* menu )
0737 {
0738     Q_D(TextDocument);
0739 
0740     if (d->addedContextMenu) {
0741         qCWarning(SHELL) << "populateContextMenu() called while we still handled another menu.";
0742         d->cleanContextMenu();
0743     }
0744 
0745     d->currentContextMenu = menu;
0746     connect(menu, &QMenu::aboutToHide, this, &TextDocument::unpopulateContextMenu);
0747 
0748     d->addedContextMenu = new QMenu();
0749 
0750     EditorContext c(v, v->cursorPosition());
0751     auto extensions = Core::self()->pluginController()->queryPluginsForContextMenuExtensions(&c, d->addedContextMenu);
0752 
0753     ContextMenuExtension::populateMenu(d->addedContextMenu, extensions);
0754 
0755     const auto actions = d->addedContextMenu->actions();
0756     for (QAction* action : actions) {
0757         menu->addAction(action);
0758     }
0759 }
0760 
0761 void KDevelop::TextDocument::repositoryCheckFinished(bool canRecreate) {
0762     Q_D(TextDocument);
0763 
0764     if ( d->state != IDocument::Dirty && d->state != IDocument::DirtyAndModified ) {
0765         // document is not dirty for whatever reason, nothing to do.
0766         return;
0767     }
0768     if ( ! canRecreate ) {
0769         return;
0770     }
0771     KTextEditor::ModificationInterface* modIface = qobject_cast<KTextEditor::ModificationInterface*>( d->document );
0772     Q_ASSERT(modIface);
0773     // Ok, all safe, we can clean up the document. Close it if the file is gone,
0774     // and reload if it's still there.
0775     d->setStatus(d->document, false);
0776     modIface->setModifiedOnDisk(KTextEditor::ModificationInterface::OnDiskUnmodified);
0777     if ( QFile::exists(d->document->url().path()) ) {
0778         reload();
0779     } else {
0780         close(KDevelop::IDocument::Discard);
0781     }
0782 }
0783 
0784 void KDevelop::TextDocument::slotDocumentLoaded()
0785 {
0786     Q_D(TextDocument);
0787 
0788     if (d->loaded)
0789         return;
0790     // Tell the editor integrator first
0791     d->loaded = true;
0792     notifyLoaded();
0793 }
0794 
0795 void KDevelop::TextDocument::documentSaved(KTextEditor::Document* document, bool saveAs)
0796 {
0797     Q_UNUSED(document);
0798     Q_UNUSED(saveAs);
0799     notifySaved();
0800     notifyStateChanged();
0801 }
0802 
0803 void KDevelop::TextDocument::documentUrlChanged(KTextEditor::Document* document)
0804 {
0805     Q_D(TextDocument);
0806 
0807     Q_UNUSED(document);
0808     if (url() != d->document->url())
0809         setUrl(d->document->url());
0810 }
0811 
0812 #include "moc_textdocument.cpp"