diff --git a/interfaces/idocument.cpp b/interfaces/idocument.cpp index ee05796b8e..a09703def0 100644 --- a/interfaces/idocument.cpp +++ b/interfaces/idocument.cpp @@ -1,140 +1,146 @@ /*************************************************************************** * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "idocument.h" #include "icore.h" #include "idocumentcontroller.h" namespace KDevelop { class IDocumentPrivate { public: inline IDocumentPrivate(KDevelop::ICore *core) : m_core(core), scriptWrapper(0) {} KDevelop::ICore* m_core; QObject *scriptWrapper; QString m_prettyName; /* Internal access to the wrapper script object */ static inline QObject *&getWrapper(IDocument *doc) { return doc->d->scriptWrapper; } }; /* This allows the scripting backend to register the scripting wrapper. Not beautiful, but makes sure it doesn't expand to much code. */ QObject *&getWrapper(IDocument *doc) { return IDocumentPrivate::getWrapper(doc); } IDocument::IDocument( KDevelop::ICore* core ) : d(new IDocumentPrivate(core)) { } IDocument::~IDocument() { delete d->scriptWrapper; delete d; } KDevelop::ICore* IDocument::core() { return d->m_core; } void IDocument::notifySaved() { emit core()->documentController()->documentSaved(this); } void IDocument::notifyStateChanged() { emit core()->documentController()->documentStateChanged(this); } void IDocument::notifyActivated() { emit core()->documentController()->documentActivated(this); } void IDocument::notifyContentChanged() { emit core()->documentController()->documentContentChanged(this); } bool IDocument::isTextDocument() const { return false; } void IDocument::notifyTextDocumentCreated() { emit core()->documentController()->textDocumentCreated(this); } KTextEditor::Range IDocument::textSelection() const { return KTextEditor::Range::invalid(); } QString IDocument::textLine() const { return QString(); } QString IDocument::textWord() const { return QString(); } QString IDocument::prettyName() const { return d->m_prettyName; } void IDocument::setPrettyName(QString name) { d->m_prettyName = name; } void IDocument::notifyUrlChanged() { emit core()->documentController()->documentUrlChanged(this); } void IDocument::notifyLoaded() { emit core()->documentController()->documentLoadedPrepare(this); emit core()->documentController()->documentLoaded(this); } KTextEditor::View* IDocument::activeTextView() const { return 0; } +QString KDevelop::IDocument::text(const KTextEditor::Range& range) const +{ + Q_UNUSED(range); + return {}; +} + } diff --git a/interfaces/idocument.h b/interfaces/idocument.h index d939734754..732a7dc22f 100644 --- a/interfaces/idocument.h +++ b/interfaces/idocument.h @@ -1,214 +1,219 @@ /*************************************************************************** * Copyright 2006 Hamish Rodda * * Copyright 2007 Alexander Dymo * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_IDOCUMENT_H #define KDEVPLATFORM_IDOCUMENT_H #include #include "interfacesexport.h" namespace KParts { class Part; class MainWindow; } namespace KTextEditor { class Cursor; class Document; class Range; class View; } namespace Sublime{ class View; } class QMimeType; class QWidget; namespace KDevelop { class ICore; /** * A single document being edited by the IDE. * * The base class for tracking a document. It contains the URL, * the part, and any associated metadata for the document. * * The advantages are: * - an easier key for use in maps and the like * - a value which does not change when the filename changes * - clearer distinction in the code between an open document and a url * (which may or may not be open) */ class KDEVPLATFORMINTERFACES_EXPORT IDocument { public: virtual ~IDocument(); /**Document state.*/ enum DocumentState { Clean, /**< Document is not touched.*/ Modified, /**< Document is modified inside the IDE.*/ Dirty, /**< Document is modified by an external process.*/ DirtyAndModified /**< Document is modified inside the IDE and at the same time by an external process.*/ }; enum DocumentSaveMode { Default = 0x0 /**< standard save mode, gives a warning message if the file was modified outside the editor */, Silent = 0x1 /**< silent save mode, doesn't warn the user if the file was modified outside the editor */, Discard = 0x2 /**< discard mode, don't save any unchanged data */ }; /** * Returns the URL of this document. */ virtual QUrl url() const = 0; /** * Returns the mimetype of the document. */ virtual QMimeType mimeType() const = 0; /** * Returns the part for given @p view if this document is a KPart document or 0 otherwise. */ virtual KParts::Part* partForView(QWidget *view) const = 0; /** * Returns whether this document is a text document. */ virtual bool isTextDocument() const; /** * Set a 'pretty' name for this document. That name will be used when displaying the document in the UI, * instead of the filename and/or path. * @param name The pretty name to use. Give an empty name to reset. * */ virtual void setPrettyName(QString name); /** * returns the pretty name of this document that was set through setPrettyName(...). * If no pretty name was set, an empty string is returned. * */ virtual QString prettyName() const; /** * Returns the text editor, if this is a text document or 0 otherwise. */ virtual KTextEditor::Document* textDocument() const = 0; /** * Saves the document. * @return true if the document was saved, false otherwise */ virtual bool save(DocumentSaveMode mode = Default) = 0; /** * Reloads the document. */ virtual void reload() = 0; /** * Requests that the document be closed. * * \returns whether the document was successfully closed. */ virtual bool close(DocumentSaveMode mode = Default) = 0; /** * Enquires whether this document is currently active in the currently active mainwindow. */ virtual bool isActive() const = 0; /** * Checks the state of this document. * @return The document state. */ virtual DocumentState state() const = 0; /** * Access the current text cursor position, if possible. * * \returns the current text cursor position, or an invalid cursor otherwise. */ virtual KTextEditor::Cursor cursorPosition() const = 0; /** * Set the current text cursor position, if possible. * * \param cursor new cursor position. */ virtual void setCursorPosition(const KTextEditor::Cursor &cursor) = 0; /** * Retrieve the current text selection, if one exists. * * \returns the current text selection */ virtual KTextEditor::Range textSelection() const; /** * Set the current text selection, if possible. * * \param range new cursor position. */ virtual void setTextSelection(const KTextEditor::Range &range) = 0; + /** + * @returns the text in a given range + */ + virtual QString text(const KTextEditor::Range &range) const; + /** * Retrieve the current text line, if one exists. * * @returns the current text line */ virtual QString textLine() const; /** * Retrieve the current text word, if one exists. * * @returns the current text word */ virtual QString textWord() const; /** * Performs document activation actions if any. * This needs to call notifyActivated() */ virtual void activate(Sublime::View *activeView, KParts::MainWindow *mainWindow) = 0; /** * @returns the active text view in case it's a text document and it has one. */ virtual KTextEditor::View* activeTextView() const; protected: ICore* core(); IDocument( ICore* ); void notifySaved(); void notifyStateChanged(); void notifyActivated(); void notifyContentChanged(); void notifyTextDocumentCreated(); void notifyUrlChanged(); void notifyLoaded(); private: friend class IDocumentPrivate; class IDocumentPrivate* const d; }; } #endif diff --git a/shell/textdocument.cpp b/shell/textdocument.cpp index f976680b58..8a10bd0fa9 100644 --- a/shell/textdocument.cpp +++ b/shell/textdocument.cpp @@ -1,779 +1,788 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "textdocument.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "mainwindow.h" #include "uicontroller.h" #include "partcontroller.h" #include "plugincontroller.h" #include "documentcontroller.h" #include "debug.h" #include namespace KDevelop { const int MAX_DOC_SETTINGS = 20; // This sets cursor position and selection on the view to the given // range. Selection is set only for non-empty ranges // Factored into a function since its needed in 3 places already static void selectAndReveal( KTextEditor::View* view, const KTextEditor::Range& range ) { Q_ASSERT(view); if (range.isValid()) { view->setCursorPosition(range.start()); if (!range.isEmpty()) { view->setSelection(range); } } } struct TextDocumentPrivate { TextDocumentPrivate(TextDocument *textDocument) : document(nullptr), state(IDocument::Clean), encoding(), q(textDocument) , m_loaded(false), m_addedContextMenu(0) { } ~TextDocumentPrivate() { delete m_addedContextMenu; m_addedContextMenu = 0; saveSessionConfig(); delete document; } QPointer document; IDocument::DocumentState state; QString encoding; void setStatus(KTextEditor::Document* document, bool dirty) { QIcon statusIcon; if (document->isModified()) if (dirty) { state = IDocument::DirtyAndModified; statusIcon = QIcon::fromTheme(QStringLiteral("edit-delete")); } else { state = IDocument::Modified; statusIcon = QIcon::fromTheme(QStringLiteral("document-save")); } else if (dirty) { state = IDocument::Dirty; statusIcon = QIcon::fromTheme(QStringLiteral("document-revert")); } else { state = IDocument::Clean; } q->notifyStateChanged(); Core::self()->uiControllerInternal()->setStatusIcon(q, statusIcon); } inline KConfigGroup katePartSettingsGroup() const { return KSharedConfig::openConfig()->group("KatePart Settings"); } inline QString docConfigGroupName() const { return document->url().toDisplayString(QUrl::PreferLocalFile); } inline KConfigGroup docConfigGroup() const { return katePartSettingsGroup().group(docConfigGroupName()); } void saveSessionConfig() { if(document && document->url().isValid()) { // make sure only MAX_DOC_SETTINGS entries are stored KConfigGroup katePartSettings = katePartSettingsGroup(); // ordered list of documents QStringList documents = katePartSettings.readEntry("documents", QStringList()); // ensure this document is "new", i.e. at the end of the list documents.removeOne(docConfigGroupName()); documents.append(docConfigGroupName()); // remove "old" documents + their group while(documents.size() >= MAX_DOC_SETTINGS) { katePartSettings.group(documents.takeFirst()).deleteGroup(); } // update order katePartSettings.writeEntry("documents", documents); // actually save session config KConfigGroup group = docConfigGroup(); document->writeSessionConfig(group); } } void loadSessionConfig() { if (!document || !katePartSettingsGroup().hasGroup(docConfigGroupName())) { return; } document->readSessionConfig(docConfigGroup(), {QStringLiteral("SkipUrl")}); } // Determines whether the current contents of this document in the editor // could be retrieved from the VCS if they were dismissed. void queryCanRecreateFromVcs(KTextEditor::Document* document) const { IProject* project = 0; // Find projects by checking which one contains the file's parent directory, // to avoid issues with the cmake manager temporarily removing files from a project // during reloading. KDevelop::Path path(document->url()); foreach ( KDevelop::IProject* current, Core::self()->projectController()->projects() ) { if ( current->path().isParentOf(path) ) { project = current; break; } } if (!project) { return; } IContentAwareVersionControl* iface; iface = qobject_cast< KDevelop::IContentAwareVersionControl* >(project->versionControlPlugin()); if (!iface) { return; } if ( !qobject_cast( document ) ) { return; } CheckInRepositoryJob* req = iface->isInRepository( document ); if ( !req ) { return; } QObject::connect(req, &CheckInRepositoryJob::finished, q, &TextDocument::repositoryCheckFinished); // Abort the request when the user edits the document QObject::connect(q->textDocument(), &KTextEditor::Document::textChanged, req, &CheckInRepositoryJob::abort); } void modifiedOnDisk(KTextEditor::Document *document, bool /*isModified*/, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) { bool dirty = false; switch (reason) { case KTextEditor::ModificationInterface::OnDiskUnmodified: break; case KTextEditor::ModificationInterface::OnDiskModified: case KTextEditor::ModificationInterface::OnDiskCreated: case KTextEditor::ModificationInterface::OnDiskDeleted: dirty = true; break; } // In some cases, the VCS (e.g. git) can know whether the old contents are "valuable", i.e. // not retrieveable from the VCS. If that is not the case, then the document can safely be // reloaded without displaying a dialog asking the user. if ( dirty ) { queryCanRecreateFromVcs(document); } setStatus(document, dirty); } TextDocument * const q; bool m_loaded; // we want to remove the added stuff when the menu hides QMenu* m_addedContextMenu; }; struct TextViewPrivate { TextViewPrivate(TextView* q) : q(q) {} TextView* const q; QPointer view; KTextEditor::Range initialRange; }; TextDocument::TextDocument(const QUrl &url, ICore* core, const QString& encoding) :PartDocument(url, core), d(new TextDocumentPrivate(this)) { d->encoding = encoding; } TextDocument::~TextDocument() { delete d; } bool TextDocument::isTextDocument() const { if( !d->document ) { /// @todo Somehow it can happen that d->document is zero, which makes /// code relying on "isTextDocument() == (bool)textDocument()" crash qWarning() << "Broken text-document: " << url(); return false; } return true; } KTextEditor::Document *TextDocument::textDocument() const { return d->document; } QWidget *TextDocument::createViewWidget(QWidget *parent) { KTextEditor::View* view = 0L; if (!d->document) { d->document = Core::self()->partControllerInternal()->createTextPart(); // Connect to the first text changed signal, it occurs before the completed() signal connect(d->document.data(), &KTextEditor::Document::textChanged, this, &TextDocument::slotDocumentLoaded); // 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) connect(d->document.data(), static_cast(&KTextEditor::Document::completed), this, &TextDocument::slotDocumentLoaded); // force a reparse when a document gets reloaded connect(d->document.data(), &KTextEditor::Document::reloaded, this, [] (KTextEditor::Document* document) { ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(document->url()), (TopDUContext::Features) ( TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate ), BackgroundParser::BestPriority, 0); }); // Set encoding passed via constructor // Needs to be done before openUrl, else katepart won't use the encoding // @see KTextEditor::Document::setEncoding if (!d->encoding.isEmpty()) d->document->setEncoding(d->encoding); if (!url().isEmpty() && !DocumentController::isEmptyDocumentUrl(url())) d->document->openUrl( url() ); d->setStatus(d->document, false); /* It appears, that by default a part will be deleted the first view containing it is deleted. Since we do want to have several views, disable that behaviour. */ d->document->setAutoDeletePart(false); Core::self()->partController()->addPart(d->document, false); d->loadSessionConfig(); connect(d->document.data(), &KTextEditor::Document::modifiedChanged, this, &TextDocument::newDocumentStatus); connect(d->document.data(), &KTextEditor::Document::textChanged, this, &TextDocument::textChanged); connect(d->document.data(), &KTextEditor::Document::documentUrlChanged, this, &TextDocument::documentUrlChanged); connect(d->document.data(), &KTextEditor::Document::documentSavedOrUploaded, this, &TextDocument::documentSaved ); if (qobject_cast(d->document)) { // can't use new signal/slot syntax here, MarkInterface is not a QObject connect(d->document.data(), SIGNAL(marksChanged(KTextEditor::Document*)), this, SLOT(saveSessionConfig())); } if (auto iface = qobject_cast(d->document)) { iface->setModifiedOnDiskWarning(true); // can't use new signal/slot syntax here, ModificationInterface is not a QObject connect(d->document.data(), SIGNAL(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason))); } notifyTextDocumentCreated(); } view = d->document->createView(parent); // get rid of some actions regarding the config dialog, we merge that one into the kdevelop menu already delete view->actionCollection()->action(QStringLiteral("set_confdlg")); delete view->actionCollection()->action(QStringLiteral("editor_options")); view->setStatusBarEnabled(Core::self()->partControllerInternal()->showTextEditorStatusBar()); connect(view, &KTextEditor::View::contextMenuAboutToShow, this, &TextDocument::populateContextMenu); if (KTextEditor::CodeCompletionInterface* cc = dynamic_cast(view)) cc->setAutomaticInvocationEnabled(core()->languageController()->completionSettings()->automaticCompletionEnabled()); if (KTextEditor::ConfigInterface *config = qobject_cast(view)) { config->setConfigValue(QStringLiteral("allow-mark-menu"), false); config->setConfigValue(QStringLiteral("default-mark-type"), KTextEditor::MarkInterface::BreakpointActive); } return view; } KParts::Part *TextDocument::partForView(QWidget *view) const { if (d->document && d->document->views().contains((KTextEditor::View*)view)) return d->document; return 0; } // KDevelop::IDocument implementation void TextDocument::reload() { if (!d->document) return; KTextEditor::ModificationInterface* modif=0; if(d->state==Dirty) { modif = qobject_cast(d->document); modif->setModifiedOnDiskWarning(false); } d->document->documentReload(); if(modif) modif->setModifiedOnDiskWarning(true); } bool TextDocument::save(DocumentSaveMode mode) { if (!d->document) return true; if (mode & Discard) return true; switch (d->state) { case IDocument::Clean: return true; case IDocument::Modified: break; case IDocument::Dirty: case IDocument::DirtyAndModified: if (!(mode & Silent)) { int code = KMessageBox::warningYesNoCancel( Core::self()->uiController()->activeMainWindow(), i18n("The file \"%1\" is modified on disk.\n\nAre " "you sure you want to overwrite it? (External " "changes will be lost.)", d->document->url().toLocalFile()), i18nc("@title:window", "Document Externally Modified")); if (code != KMessageBox::Yes) return false; } break; } QUrl urlBeforeSave = d->document->url(); if (d->document->documentSave()) { if (d->document->url() != urlBeforeSave) notifyUrlChanged(); return true; } return false; } IDocument::DocumentState TextDocument::state() const { return d->state; } KTextEditor::Cursor KDevelop::TextDocument::cursorPosition() const { if (!d->document) { return KTextEditor::Cursor::invalid(); } KTextEditor::View *view = activeTextView(); if (view) return view->cursorPosition(); return KTextEditor::Cursor::invalid(); } void TextDocument::setCursorPosition(const KTextEditor::Cursor &cursor) { if (!cursor.isValid() || !d->document) return; KTextEditor::View *view = activeTextView(); // Rodda: Cursor must be accurate here, to the definition of accurate for KTextEditor::Cursor. // ie, starting from 0,0 if (view) view->setCursorPosition(cursor); } KTextEditor::Range TextDocument::textSelection() const { if (!d->document) { return KTextEditor::Range::invalid(); } KTextEditor::View *view = activeTextView(); if (view && view->selection()) { return view->selectionRange(); } return PartDocument::textSelection(); } +QString TextDocument::text(const KTextEditor::Range &range) const +{ + if (!d->document) { + return QString(); + } + + return d->document->text( range ); +} + QString TextDocument::textLine() const { if (!d->document) { return QString(); } KTextEditor::View *view = activeTextView(); if (view) { return d->document->line( view->cursorPosition().line() ); } return PartDocument::textLine(); } QString TextDocument::textWord() const { if (!d->document) { return QString(); } KTextEditor::View *view = activeTextView(); if (view) { KTextEditor::Cursor start = view->cursorPosition(); qCDebug(SHELL) << "got start position from view:" << start.line() << start.column(); QString linestr = textLine(); int startPos = qMax( qMin( start.column(), linestr.length() - 1 ), 0 ); int endPos = startPos; startPos --; while( startPos >= 0 && ( linestr[startPos].isLetterOrNumber() || linestr[startPos] == '_' || linestr[startPos] == '~' ) ) { --startPos; } while( endPos < linestr.length() && ( linestr[endPos].isLetterOrNumber() || linestr[endPos] == '_' || linestr[endPos] == '~' ) ) { ++endPos; } if( startPos != endPos ) { qCDebug(SHELL) << "found word" << startPos << endPos << linestr.mid( startPos+1, endPos - startPos - 1 ); return linestr.mid( startPos + 1, endPos - startPos - 1 ); } } return PartDocument::textWord(); } void TextDocument::setTextSelection(const KTextEditor::Range &range) { if (!range.isValid() || !d->document) return; KTextEditor::View *view = activeTextView(); if (view) { selectAndReveal(view, range); } } bool TextDocument::close(DocumentSaveMode mode) { if (!PartDocument::close(mode)) return false; if ( d->document ) { d->saveSessionConfig(); delete d->document; //We have to delete the document right now, to prevent random crashes in the event handler } return true; } Sublime::View* TextDocument::newView(Sublime::Document* doc) { Q_UNUSED(doc); return new TextView(this); } } KDevelop::TextView::TextView(TextDocument * doc) : View(doc, View::TakeOwnership), d(new TextViewPrivate(this)) { } KDevelop::TextView::~TextView() { delete d; } QWidget * KDevelop::TextView::createWidget(QWidget * parent) { auto textDocument = qobject_cast(document()); Q_ASSERT(textDocument); QWidget* widget = textDocument->createViewWidget(parent); d->view = qobject_cast(widget); Q_ASSERT(d->view); connect(d->view.data(), &KTextEditor::View::cursorPositionChanged, this, &KDevelop::TextView::sendStatusChanged); return widget; } QString KDevelop::TextView::viewState() const { if (d->view) { if (d->view->selection()) { KTextEditor::Range selection = d->view->selectionRange(); return QStringLiteral("Selection=%1,%2,%3,%4").arg(selection.start().line()) .arg(selection.start().column()) .arg(selection.end().line()) .arg(selection.end().column()); } else { KTextEditor::Cursor cursor = d->view->cursorPosition(); return QStringLiteral("Cursor=%1,%2").arg(cursor.line()).arg(cursor.column()); } } else { KTextEditor::Range selection = d->initialRange; return QStringLiteral("Selection=%1,%2,%3,%4").arg(selection.start().line()) .arg(selection.start().column()) .arg(selection.end().line()) .arg(selection.end().column()); } } void KDevelop::TextView::setInitialRange(const KTextEditor::Range& range) { if (d->view) { selectAndReveal(d->view, range); } else { d->initialRange = range; } } KTextEditor::Range KDevelop::TextView::initialRange() const { return d->initialRange; } void KDevelop::TextView::setState(const QString & state) { static QRegExp reCursor("Cursor=([\\d]+),([\\d]+)"); static QRegExp reSelection("Selection=([\\d]+),([\\d]+),([\\d]+),([\\d]+)"); if (reCursor.exactMatch(state)) { setInitialRange(KTextEditor::Range(KTextEditor::Cursor(reCursor.cap(1).toInt(), reCursor.cap(2).toInt()), 0)); } else if (reSelection.exactMatch(state)) { KTextEditor::Range range(reSelection.cap(1).toInt(), reSelection.cap(2).toInt(), reSelection.cap(3).toInt(), reSelection.cap(4).toInt()); setInitialRange(range); } } QString KDevelop::TextDocument::documentType() const { return QStringLiteral("Text"); } QIcon KDevelop::TextDocument::defaultIcon() const { if (d->document) { QMimeType mime = QMimeDatabase().mimeTypeForName(d->document->mimeType()); QIcon icon = QIcon::fromTheme(mime.iconName()); if (!icon.isNull()) { return icon; } } return PartDocument::defaultIcon(); } KTextEditor::View *KDevelop::TextView::textView() const { return d->view; } QString KDevelop::TextView::viewStatus() const { // only show status when KTextEditor's own status bar isn't already enabled const bool showStatus = !Core::self()->partControllerInternal()->showTextEditorStatusBar(); if (!showStatus) { return QString(); } const KTextEditor::Cursor pos = d->view ? d->view->cursorPosition() : KTextEditor::Cursor::invalid(); return i18n(" Line: %1 Col: %2 ", pos.line() + 1, pos.column() + 1); } void KDevelop::TextView::sendStatusChanged() { emit statusChanged(this); } KTextEditor::View* KDevelop::TextDocument::activeTextView() const { KTextEditor::View* fallback = nullptr; for (auto view : views()) { auto textView = qobject_cast(view)->textView(); if (!textView) { continue; } if (textView->hasFocus()) { return textView; } else if (textView->isVisible()) { fallback = textView; } else if (!fallback) { fallback = textView; } } return fallback; } void KDevelop::TextDocument::newDocumentStatus(KTextEditor::Document *document) { bool dirty = (d->state == IDocument::Dirty || d->state == IDocument::DirtyAndModified); d->setStatus(document, dirty); } void KDevelop::TextDocument::textChanged(KTextEditor::Document *document) { Q_UNUSED(document); notifyContentChanged(); } void KDevelop::TextDocument::populateContextMenu( KTextEditor::View* v, QMenu* menu ) { if (d->m_addedContextMenu) { foreach ( QAction* action, d->m_addedContextMenu->actions() ) { menu->removeAction(action); } delete d->m_addedContextMenu; } d->m_addedContextMenu = new QMenu(); EditorContext c(v, v->cursorPosition()); auto extensions = Core::self()->pluginController()->queryPluginsForContextMenuExtensions(&c); ContextMenuExtension::populateMenu(d->m_addedContextMenu, extensions); { QUrl url = v->document()->url(); QList< ProjectBaseItem* > items = Core::self()->projectController()->projectModel()->itemsForPath( IndexedString(url) ); if (!items.isEmpty()) { populateParentItemsMenu( items.front(), d->m_addedContextMenu ); } } foreach ( QAction* action, d->m_addedContextMenu->actions() ) { menu->addAction(action); } } void KDevelop::TextDocument::repositoryCheckFinished(bool canRecreate) { if ( d->state != IDocument::Dirty && d->state != IDocument::DirtyAndModified ) { // document is not dirty for whatever reason, nothing to do. return; } if ( ! canRecreate ) { return; } KTextEditor::ModificationInterface* modIface = qobject_cast( d->document ); Q_ASSERT(modIface); // Ok, all safe, we can clean up the document. Close it if the file is gone, // and reload if it's still there. d->setStatus(d->document, false); modIface->setModifiedOnDisk(KTextEditor::ModificationInterface::OnDiskUnmodified); if ( QFile::exists(d->document->url().path()) ) { reload(); } else { close(KDevelop::IDocument::Discard); } } void KDevelop::TextDocument::slotDocumentLoaded() { if (d->m_loaded) return; // Tell the editor integrator first d->m_loaded = true; notifyLoaded(); } void KDevelop::TextDocument::documentSaved(KTextEditor::Document* document, bool saveAs) { Q_UNUSED(document); Q_UNUSED(saveAs); notifySaved(); notifyStateChanged(); } void KDevelop::TextDocument::documentUrlChanged(KTextEditor::Document* document) { Q_UNUSED(document); if (url() != d->document->url()) setUrl(d->document->url()); } #include "moc_textdocument.cpp" diff --git a/shell/textdocument.h b/shell/textdocument.h index 899be6fa72..499b2dd12e 100644 --- a/shell/textdocument.h +++ b/shell/textdocument.h @@ -1,126 +1,128 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_TEXTDOCUMENT_H #define KDEVPLATFORM_TEXTDOCUMENT_H #include #include #include #include "partdocument.h" #include "shellexport.h" class QMenu; namespace KTextEditor { class View; } namespace KDevelop { /** Text document which represents KTextEditor documents. Usually Kate documents are represented by this class but TextDocument is not limited to Kate. Each conforming text editor will work. */ class KDEVPLATFORMSHELL_EXPORT TextDocument: public PartDocument { Q_OBJECT public: TextDocument(const QUrl &url, ICore*, const QString& encoding ); ~TextDocument() override; QWidget *createViewWidget(QWidget *parent = nullptr) override; KParts::Part *partForView(QWidget *view) const override; bool close(DocumentSaveMode mode = Default) override; bool save(DocumentSaveMode mode = Default) override; DocumentState state() const override; KTextEditor::Cursor cursorPosition() const override; void setCursorPosition(const KTextEditor::Cursor &cursor) override; KTextEditor::Range textSelection() const override; void setTextSelection(const KTextEditor::Range &range) override; + QString text(const KTextEditor::Range &range) const override; + QString textLine() const override; QString textWord() const override; bool isTextDocument() const override; KTextEditor::Document* textDocument() const override; QString documentType() const override; QIcon defaultIcon() const override; KTextEditor::View* activeTextView() const override; public Q_SLOTS: void reload() override; protected: Sublime::View *newView(Sublime::Document *doc) override; private: Q_PRIVATE_SLOT(d, void saveSessionConfig()); Q_PRIVATE_SLOT(d, void modifiedOnDisk(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason)); void newDocumentStatus(KTextEditor::Document*); void populateContextMenu(KTextEditor::View*, QMenu*); void textChanged(KTextEditor::Document*); void documentUrlChanged(KTextEditor::Document*); void slotDocumentLoaded(); void documentSaved(KTextEditor::Document*,bool); void repositoryCheckFinished(bool); struct TextDocumentPrivate * const d; friend struct TextDocumentPrivate; }; class KDEVPLATFORMSHELL_EXPORT TextView : public Sublime::View { Q_OBJECT public: explicit TextView(TextDocument* doc); ~TextView() override; QWidget *createWidget(QWidget *parent = nullptr) override; KTextEditor::View *textView() const; QString viewStatus() const override; QString viewState() const override; void setState(const QString& state) override; void setInitialRange(const KTextEditor::Range& range); KTextEditor::Range initialRange() const; private: void sendStatusChanged(); struct TextViewPrivate* const d; }; } #endif