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"