diff --git a/shell/documentcontroller.cpp b/shell/documentcontroller.cpp index d736b282c6..d9fb51e6ca 100644 --- a/shell/documentcontroller.cpp +++ b/shell/documentcontroller.cpp @@ -1,1213 +1,1213 @@ /* This file is part of the KDE project Copyright 2002 Matthias Hoelzer-Kluepfel Copyright 2002 Bernd Gehrmann Copyright 2003 Roberto Raggi Copyright 2003-2008 Hamish Rodda Copyright 2003 Harald Fernengel Copyright 2003 Jens Dagerbo Copyright 2005 Adam Treat Copyright 2004-2007 Alexander Dymo Copyright 2007 Andreas Pakulat This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "documentcontroller.h" #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 "textdocument.h" #include "uicontroller.h" #include "partcontroller.h" #include "savedialog.h" #include #include #include "workingsetcontroller.h" #include #ifdef HAVE_KOMPARE #include "patchdocument.h" #endif #define EMPTY_DOCUMENT_URL i18n("Untitled") namespace KDevelop { struct DocumentControllerPrivate { DocumentControllerPrivate(DocumentController* c) : controller(c) , fileOpenRecent(0) , globalTextEditorInstance(0) { } ~DocumentControllerPrivate() { //delete temporary files so they are removed from disk foreach (KTemporaryFile *temp, tempFiles) delete temp; } QString presetEncoding; // used to map urls to open docs QHash< KUrl, IDocument* > documents; QHash< QString, IDocumentFactory* > factories; QList tempFiles; struct HistoryEntry { HistoryEntry() {} HistoryEntry( const KUrl & u, const KTextEditor::Cursor& cursor ); KUrl url; KTextEditor::Cursor cursor; int id; }; void removeDocument(Sublime::Document *doc) { QList urlsForDoc = documents.keys(dynamic_cast(doc)); foreach (const KUrl &url, urlsForDoc) { kDebug() << "destroying document" << doc; documents.remove(url); } } void chooseDocument() { KUrl dir; if( controller->activeDocument() ) { dir = KUrl( controller->activeDocument()->url() ); dir.setFileName(QString()); }else { dir = KGlobal::config()->group("Open File").readEntry( "Last Open File Directory", Core::self()->projectController()->projectsBaseDirectory() ); } KEncodingFileDialog::Result res = KEncodingFileDialog::getOpenUrlsAndEncoding( controller->encoding(), dir.url(), i18n( "*|Text File\n" ), Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "Open File" ) ); if( !res.URLs.isEmpty() ) { QString encoding = res.encoding; foreach( const KUrl& u, res.URLs ) { openDocumentInternal(u, QString(), KTextEditor::Range::invalid(), encoding ); } } } void changeDocumentUrl(KDevelop::IDocument* document) { QMutableHashIterator it = documents; while (it.hasNext()) { if (it.next().value() == document) { if (documents.contains(document->url())) { // Weird situation (saving as a file that is aready open) IDocument* origDoc = documents[document->url()]; if (origDoc->state() & IDocument::Modified) { // given that the file has been saved, close the saved file as the other instance will become conflicted on disk document->close(); controller->activateDocument( origDoc ); break; } // Otherwise close the original document origDoc->close(); } else { // Remove the original document it.remove(); } documents.insert(document->url(), document); if (!controller->isEmptyDocumentUrl(document->url())) { fileOpenRecent->addUrl(document->url()); } break; } } } KDevelop::IDocument* findBuddyDocument(const KUrl &url, IBuddyDocumentFinder* finder) { QList allDocs = controller->openDocuments(); foreach( KDevelop::IDocument* doc, allDocs ) { if(finder->areBuddies(url, doc->url())) { return doc; } } return 0; } IDocument* openDocumentInternal( const KUrl & inputUrl, const QString& prefName = QString(), const KTextEditor::Range& range = KTextEditor::Range::invalid(), const QString& encoding = "", DocumentController::DocumentActivationParams activationParams = 0, IDocument* buddy = 0) { IDocument* previousActiveDocument = controller->activeDocument(); KTextEditor::Cursor previousActivePosition; if(previousActiveDocument && previousActiveDocument->textDocument() && previousActiveDocument->textDocument()->activeView()) previousActivePosition = previousActiveDocument->textDocument()->activeView()->cursorPosition(); QString _encoding = encoding; KUrl url = inputUrl; if ( url.isEmpty() && (!activationParams.testFlag(IDocumentController::DoNotCreateView)) ) { KUrl dir; if( controller->activeDocument() ) { dir = controller->activeDocument()->url().upUrl(); }else { dir = KGlobal::config()->group("Open File").readEntry( "Last Open File Directory", Core::self()->projectController()->projectsBaseDirectory() ); } KEncodingFileDialog::Result res = KEncodingFileDialog::getOpenUrlAndEncoding( "", dir.url(), i18n( "*|Text File\n" ), Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "Open File" ) ); if( !res.URLs.isEmpty() ) url = res.URLs.first(); _encoding = res.encoding; } if ( url.isEmpty() ) //still no url return 0; KGlobal::config()->group("Open File").writeEntry( "Last Open File Directory", url.upUrl() ); // clean it and resolve possible symlink url.cleanPath( KUrl::SimplifyDirSeparators ); if ( url.isLocalFile() ) { QString path = QFileInfo( url.toLocalFile() ).canonicalFilePath(); if ( !path.isEmpty() ) url.setPath( path ); } //get a part document IDocument* doc=0; if (documents.contains(url)) doc=documents.value(url); else { KMimeType::Ptr mimeType; if (DocumentController::isEmptyDocumentUrl(url)) { mimeType = KMimeType::mimeType("text/plain"); } else { //make sure the URL exists if ( !url.isValid() || !KIO::NetAccess::exists( url, KIO::NetAccess::SourceSide, ICore::self()->uiController()->activeMainWindow() ) ) { kDebug() << "cannot find URL:" << url.url(); return 0; } mimeType = KMimeType::findByUrl( url ); if( !url.isLocalFile() && mimeType->isDefault() ) { // fall back to text/plain, for remote files without extension, i.e. COPYING, LICENSE, ... // using a syncronous KIO::MimetypeJob is hazardous and may lead to repeated calls to // this function without it having returned in the first place // and this function is *not* reentrant, see assert below: // Q_ASSERT(!documents.contains(url) || documents[url]==doc); mimeType = KMimeType::mimeType("text/plain"); } } // is the URL pointing to a directory? if ( mimeType->is( "inode/directory" ) ) { kDebug() << "cannot open directory:" << url.url(); return 0; } if( prefName.isEmpty() ) { // Try to find a plugin that handles this mimetype QString constraint = QString("'%1' in [X-KDevelop-SupportedMimeTypes]").arg(mimeType->name()); KPluginInfo::List plugins = IPluginController::queryPlugins( constraint ); if( !plugins.isEmpty() ) { KPluginInfo info = plugins.first(); Core::self()->pluginController()->loadPlugin( info.pluginName() ); } } if( factories.contains( mimeType->name() ) ) { doc = factories[mimeType->name()]->create(url, Core::self()); } if(!doc) { if( !prefName.isEmpty() ) { doc = new PartDocument(url, Core::self(), prefName); } else if ( Core::self()->partControllerInternal()->isTextType(mimeType)) { doc = new TextDocument(url, Core::self(), _encoding); } else if( Core::self()->partControllerInternal()->canCreatePart(url) ) { doc = new PartDocument(url, Core::self()); } else { int openAsText = KMessageBox::questionYesNo(0, i18n("KDevelop could not find the editor for file '%1' of type %2.\nDo you want to open it as plain text?", url.fileName(), mimeType->name()), i18nc("@title:window", "Could Not Find Editor")); if (openAsText == KMessageBox::Yes) doc = new TextDocument(url, Core::self(), _encoding); else return 0; } } } // The url in the document must equal the current url, else the housekeeping will get broken Q_ASSERT(!doc || doc->url() == url); if(doc && openDocumentInternal(doc, range, activationParams, buddy)) return doc; else return 0; } bool openDocumentInternal(IDocument* doc, const KTextEditor::Range& range, DocumentController::DocumentActivationParams activationParams, IDocument* buddy = 0) { IDocument* previousActiveDocument = controller->activeDocument(); KTextEditor::Cursor previousActivePosition; if(previousActiveDocument && previousActiveDocument->textDocument() && previousActiveDocument->textDocument()->activeView()) previousActivePosition = previousActiveDocument->textDocument()->activeView()->cursorPosition(); KUrl url=doc->url(); UiController *uiController = Core::self()->uiControllerInternal(); Sublime::Area *area = uiController->activeArea(); //We can't have the same url in many documents //so we check it's already the same if it exists //contains=>it's the same Q_ASSERT(!documents.contains(url) || documents[url]==doc); Sublime::Document *sdoc = dynamic_cast(doc); if( !sdoc ) { documents.remove(url); delete doc; return false; } //react on document deletion - we need to cleanup controller structures QObject::connect(sdoc, SIGNAL(aboutToDelete(Sublime::Document*)), controller, SLOT(notifyDocumentClosed(Sublime::Document*))); //We check if it was already opened before bool emitOpened = !documents.contains(url); if(emitOpened) documents[url]=doc; if (!activationParams.testFlag(IDocumentController::DoNotCreateView)) { //find a view if there's one already opened in this area Sublime::View *partView = 0; foreach (Sublime::View *view, sdoc->views()) { if (area->views().contains(view) && area->indexOf(view) == area->indexOf(uiController->activeSublimeWindow()->activeView())) { partView = view; break; } } bool addView = false, applyRange = true; if (!partView) { //no view currently shown for this url partView = sdoc->createView(); addView = true; } KDevelop::TextView* textView = dynamic_cast(partView); if(textView && textView->textView()) { applyRange = false; if (range.isEmpty()) doc->setCursorPosition( range.start() ); else doc->setTextSelection( range ); }else if(textView) { textView->setInitialRange(range); } if(addView) { // This code is never executed when restoring session on startup, // only when opening a file manually Sublime::View* buddyView = 0; bool placeAfterBuddy = true; if(Core::self()->uiControllerInternal()->arrangeBuddies()) { // If buddy is not set, look for a (usually) plugin which handles this URL's mimetype // and use its IBuddyDocumentFinder, if available, to find a buddy document if(!buddy && doc->mimeType()) { QString mime = doc->mimeType()->name(); IBuddyDocumentFinder* buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime); if(buddyFinder) { buddy = findBuddyDocument(url, buddyFinder); if(buddy) { placeAfterBuddy = buddyFinder->buddyOrder(buddy->url(), doc->url()); } } } if(buddy) { Sublime::Document* sublimeDocBuddy = dynamic_cast(buddy); if(sublimeDocBuddy) { Sublime::AreaIndex *pActiveViewIndex = area->indexOf(uiController->activeSublimeWindow()->activeView()); if(pActiveViewIndex) { // try to find existing View of buddy document in current active view's tab foreach (Sublime::View *pView, pActiveViewIndex->views()) { if(sublimeDocBuddy->views().contains(pView)) { buddyView = pView; break; } } } } } } // add view to the area if(buddyView && area->indexOf(buddyView)) { if(placeAfterBuddy) { // Adding new view after buddy view, simple case area->addView(partView, area->indexOf(buddyView), buddyView); } else { // First new view, then buddy view area->addView(partView, area->indexOf(buddyView), buddyView); // move buddyView tab after the new document area->removeView(buddyView); area->addView(buddyView, area->indexOf(partView), partView); } } else { // no buddy found for new document / plugin does not support buddies / buddy feature disabled Sublime::View *activeView = uiController->activeSublimeWindow()->activeView(); Sublime::UrlDocument *activeDoc = 0; IBuddyDocumentFinder *buddyFinder = 0; if(activeView) activeDoc = dynamic_cast(activeView->document()); if(activeDoc && Core::self()->uiControllerInternal()->arrangeBuddies()) { QString mime = KMimeType::findByUrl(activeDoc->url())->name(); buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime); } if(Core::self()->uiControllerInternal()->openAfterCurrent() && Core::self()->uiControllerInternal()->arrangeBuddies() && buddyFinder) { // Check if active document's buddy is directly next to it. // For example, we have the already-open tabs | *foo.h* | foo.cpp | , foo.h is active. // When we open a new document here (and the buddy feature is enabled), // we do not want to separate foo.h and foo.cpp, so we take care and avoid this. Sublime::AreaIndex *activeAreaIndex = area->indexOf(activeView); int pos = activeAreaIndex->views().indexOf(activeView); Sublime::View *afterActiveView = activeAreaIndex->views().value(pos+1, 0); Sublime::UrlDocument *activeDoc = 0, *afterActiveDoc = 0; if(activeView && afterActiveView) { activeDoc = dynamic_cast(activeView->document()); afterActiveDoc = dynamic_cast(afterActiveView->document()); } if(activeDoc && afterActiveDoc && buddyFinder->areBuddies(activeDoc->url(), afterActiveDoc->url())) { // don't insert in between of two buddies, but after them area->addView(partView, activeAreaIndex, afterActiveView); } else { // The active document's buddy is not directly after it // => no ploblem, insert after active document area->addView(partView, activeView); } } else { // Opening as last tab won't disturb our buddies // Same, if buddies are disabled, we needn't care about them. // this method places the tab according to openAfterCurrent() area->addView(partView, activeView); } } } if (!activationParams.testFlag(IDocumentController::DoNotActivate)) { uiController->activeSublimeWindow()->activateView(partView); } if (!controller->isEmptyDocumentUrl(url)) { fileOpenRecent->addUrl( url ); } if( applyRange && range.isValid() ) { if (range.isEmpty()) doc->setCursorPosition( range.start() ); else doc->setTextSelection( range ); } } // Deferred signals, wait until it's all ready first if( emitOpened ) { emit controller->documentOpened( doc ); } if (!activationParams.testFlag(IDocumentController::DoNotActivate) && doc != controller->activeDocument()) emit controller->documentActivated( doc ); saveAll->setEnabled(true); revertAll->setEnabled(true); close->setEnabled(true); closeAll->setEnabled(true); closeAllOthers->setEnabled(true); if(doc) { KTextEditor::Cursor activePosition; if(range.isValid()) activePosition = range.start(); else if(doc->textDocument() && doc->textDocument()->activeView()) activePosition = doc->textDocument()->activeView()->cursorPosition(); if (doc != previousActiveDocument || activePosition != previousActivePosition) emit controller->documentJumpPerformed(doc, activePosition, previousActiveDocument, previousActivePosition); } return true; } DocumentController* controller; QList backHistory; QList forwardHistory; bool isJumping; QPointer saveAll; QPointer revertAll; QPointer close; QPointer closeAll; QPointer closeAllOthers; KRecentFilesAction* fileOpenRecent; KTextEditor::Document* globalTextEditorInstance; /* HistoryEntry createHistoryEntry(); void addHistoryEntry(); void jumpTo( const HistoryEntry & );*/ }; DocumentController::DocumentController( QObject *parent ) : IDocumentController( parent ) { setObjectName("DocumentController"); d = new DocumentControllerPrivate(this); QDBusConnection::sessionBus().registerObject( "/org/kdevelop/DocumentController", this, QDBusConnection::ExportScriptableSlots ); connect(this, SIGNAL(documentUrlChanged(KDevelop::IDocument*)), this, SLOT(changeDocumentUrl(KDevelop::IDocument*))); if(!(Core::self()->setupFlags() & Core::NoUi)) setupActions(); #ifdef HAVE_KOMPARE registerDocumentForMimetype("text/x-patch", new PatchDocumentFactory); #endif } void KDevelop::DocumentController::initialize() { connect(ICore::self()->projectController(), SIGNAL(projectOpened(KDevelop::IProject*)), SLOT(slotProjectOpened(KDevelop::IProject*))); } void DocumentController::cleanup() { if (d->fileOpenRecent) d->fileOpenRecent->saveEntries( KConfigGroup(KGlobal::config(), "Recent Files" ) ); // Close all documents without checking if they should be saved. // This is because the user gets a chance to save them during MainWindow::queryClose. foreach (IDocument* doc, openDocuments()) doc->close(IDocument::Discard); } DocumentController::~DocumentController() { delete d; } void DocumentController::setupActions() { KActionCollection * ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); KAction *action; action = ac->addAction( "file_open" ); action->setIcon(KIcon("document-open")); action->setShortcut( Qt::CTRL + Qt::Key_O ); action->setText(i18n( "&Open..." ) ); connect( action, SIGNAL(triggered(bool)), SLOT(chooseDocument()) ); action->setToolTip( i18n( "Open file" ) ); action->setWhatsThis( i18n( "Open file

Opens a file for editing.

" ) ); d->fileOpenRecent = KStandardAction::openRecent(this, SLOT(slotOpenDocument(KUrl)), ac); d->fileOpenRecent->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again.")); d->fileOpenRecent->loadEntries( KConfigGroup(KGlobal::config(), "Recent Files" ) ); action = d->saveAll = ac->addAction( "file_save_all" ); action->setIcon(KIcon("document-save")); action->setText(i18n( "Save Al&l" ) ); connect( action, SIGNAL(triggered(bool)), SLOT(slotSaveAllDocuments()) ); action->setToolTip( i18n( "Save all open documents" ) ); action->setWhatsThis( i18n( "Save all documents

Save all open documents, prompting for additional information when necessary.

" ) ); action->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_L) ); action->setEnabled(false); action = d->revertAll = ac->addAction( "file_revert_all" ); action->setIcon(KIcon("document-revert")); action->setText(i18n( "Reload All" ) ); connect( action, SIGNAL(triggered(bool)), SLOT(reloadAllDocuments()) ); action->setToolTip( i18n( "Revert all open documents" ) ); action->setWhatsThis( i18n( "Revert all documents

Revert all open documents, returning to the previously saved state.

" ) ); action->setEnabled(false); action = d->close = ac->addAction( "file_close" ); action->setIcon(KIcon("window-close")); action->setShortcut( Qt::CTRL + Qt::Key_W ); action->setText( i18n( "&Close" ) ); connect( action, SIGNAL(triggered(bool)), SLOT(fileClose()) ); action->setToolTip( i18n( "Close File" ) ); action->setWhatsThis( i18n( "Close File

Closes current file.

" ) ); action->setEnabled(false); action = d->closeAll = ac->addAction( "file_close_all" ); action->setIcon(KIcon("window-close")); action->setText(i18n( "Clos&e All" ) ); connect( action, SIGNAL(triggered(bool)), SLOT(closeAllDocuments()) ); action->setToolTip( i18n( "Close all open documents" ) ); action->setWhatsThis( i18n( "Close all documents

Close all open documents, prompting for additional information when necessary.

" ) ); action->setEnabled(false); action = d->closeAllOthers = ac->addAction( "file_closeother" ); action->setIcon(KIcon("window-close")); action->setShortcut( Qt::CTRL + Qt::SHIFT + Qt::Key_W ); action->setText(i18n( "Close All Ot&hers" ) ); connect( action, SIGNAL(triggered(bool)), SLOT(closeAllOtherDocuments()) ); action->setToolTip( i18n( "Close all other documents" ) ); action->setWhatsThis( i18n( "Close all other documents

Close all open documents, with the exception of the currently active document.

" ) ); action->setEnabled(false); } void DocumentController::setEncoding( const QString &encoding ) { d->presetEncoding = encoding; } QString KDevelop::DocumentController::encoding() const { return d->presetEncoding; } void DocumentController::slotOpenDocument(const KUrl &url) { openDocument(url); } IDocument* DocumentController::openDocumentFromText( const QString& data ) { IDocument* d = openDocument(nextEmptyDocumentUrl()); Q_ASSERT(d->textDocument()); d->textDocument()->setText( data ); return d; } bool DocumentController::openDocumentFromTextSimple( QString text ) { return (bool)openDocumentFromText( text ); } bool DocumentController::openDocumentSimple( QString url ) { return (bool)openDocument( KUrl(url) ); } IDocument* DocumentController::openDocument( const KUrl& inputUrl, const QString& prefName ) { return d->openDocumentInternal( inputUrl, prefName ); } IDocument* DocumentController::openDocument( const KUrl & inputUrl, const KTextEditor::Range& range, DocumentActivationParams activationParams, const QString& encoding, IDocument* buddy) { return d->openDocumentInternal( inputUrl, "", range, encoding, activationParams, buddy); } bool DocumentController::openDocument(IDocument* doc, const KTextEditor::Range& range, DocumentActivationParams activationParams, IDocument* buddy) { return d->openDocumentInternal( doc, range, activationParams, buddy); } void DocumentController::fileClose() { IDocument *activeDoc = activeDocument(); if (activeDoc) { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::View *activeView = uiController->activeSublimeWindow()->activeView(); uiController->activeArea()->closeView(activeView); } } void DocumentController::closeDocument( const KUrl &url ) { if( !d->documents.contains(url) ) return; //this will remove all views and after the last view is removed, the //document will be self-destructed and removeDocument() slot will catch that //and clean up internal data structures d->documents[url]->close(); } void DocumentController::notifyDocumentClosed(Sublime::Document* doc_) { IDocument* doc = dynamic_cast(doc_); Q_ASSERT(doc); d->removeDocument(doc_); if (d->documents.isEmpty()) { if (d->saveAll) d->saveAll->setEnabled(false); if (d->revertAll) d->revertAll->setEnabled(false); if (d->close) d->close->setEnabled(false); if (d->closeAll) d->closeAll->setEnabled(false); if (d->closeAllOthers) d->closeAllOthers->setEnabled(false); } emit documentClosed(doc); } IDocument * DocumentController::documentForUrl( const KUrl & dirtyUrl ) const { //Fix urls that might not be absolute KUrl url(dirtyUrl); url.cleanPath(); return d->documents.value( url, 0 ); } QList DocumentController::openDocuments() const { QList opened; foreach (IDocument *doc, d->documents) { Sublime::Document *sdoc = dynamic_cast(doc); if( !sdoc ) { continue; } if (!sdoc->views().isEmpty()) opened << doc; } return opened; } void DocumentController::activateDocument( IDocument * document, const KTextEditor::Range& range ) { // TODO avoid some code in openDocument? Q_ASSERT(document); openDocument(document->url(), range); } void DocumentController::slotSaveAllDocuments() { saveAllDocuments(IDocument::Silent); } bool DocumentController::saveAllDocuments(IDocument::DocumentSaveMode mode) { return saveSomeDocuments(openDocuments(), mode); } bool KDevelop::DocumentController::saveSomeDocuments(const QList< IDocument * > & list, IDocument::DocumentSaveMode mode) { if (mode & IDocument::Silent) { foreach (IDocument* doc, modifiedDocuments(list)) { if( !DocumentController::isEmptyDocumentUrl(doc->url()) && !doc->save(mode) ) { if( doc ) qWarning() << "!! Could not save document:" << doc->url(); else qWarning() << "!! Could not save document as its NULL"; } // TODO if (!ret) showErrorDialog() ? } } else { // Ask the user which documents to save QList checkSave = modifiedDocuments(list); if (!checkSave.isEmpty()) { KSaveSelectDialog dialog(checkSave, qApp->activeWindow()); if (dialog.exec() == QDialog::Rejected) return false; } } return true; } QList< IDocument * > KDevelop::DocumentController::visibleDocumentsInWindow(MainWindow * mw) const { // Gather a list of all documents which do have a view in the given main window // Does not find documents which are open in inactive areas QList list; foreach (IDocument* doc, openDocuments()) { if (Sublime::Document* sdoc = dynamic_cast(doc)) { foreach (Sublime::View* view, sdoc->views()) { if (view->hasWidget() && view->widget()->window() == mw) { list.append(doc); break; } } } } return list; } QList< IDocument * > KDevelop::DocumentController::documentsExclusivelyInWindow(MainWindow * mw, bool currentAreaOnly) const { // Gather a list of all documents which have views only in the given main window QList checkSave; foreach (IDocument* doc, openDocuments()) { if (Sublime::Document* sdoc = dynamic_cast(doc)) { bool inOtherWindow = false; foreach (Sublime::View* view, sdoc->views()) { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) if(window->containsView(view) && (window != mw || (currentAreaOnly && window == mw && !mw->area()->views().contains(view)))) inOtherWindow = true; } if (!inOtherWindow) checkSave.append(doc); } } return checkSave; } QList< IDocument * > KDevelop::DocumentController::modifiedDocuments(const QList< IDocument * > & list) const { QList< IDocument * > ret; foreach (IDocument* doc, list) if (doc->state() == IDocument::Modified || doc->state() == IDocument::DirtyAndModified) ret.append(doc); return ret; } bool DocumentController::saveAllDocumentsForWindow(KParts::MainWindow* mw, KDevelop::IDocument::DocumentSaveMode mode, bool currentAreaOnly) { QList checkSave = documentsExclusivelyInWindow(dynamic_cast(mw), currentAreaOnly); return saveSomeDocuments(checkSave, mode); } void DocumentController::reloadAllDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { QList views = visibleDocumentsInWindow(dynamic_cast(mw)); if (!saveSomeDocuments(views, IDocument::Default)) // User cancelled or other error return; foreach (IDocument* doc, views) if(!isEmptyDocumentUrl(doc->url())) doc->reload(); } } void DocumentController::closeAllDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { QList views = visibleDocumentsInWindow(dynamic_cast(mw)); if (!saveSomeDocuments(views, IDocument::Default)) // User cancelled or other error return; foreach (IDocument* doc, views) doc->close(IDocument::Discard); } } void DocumentController::closeAllOtherDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { Sublime::View* activeView = mw->activeView(); if (!activeView) { kWarning() << "Shouldn't there always be an active view when this function is called?"; return; } // Deal with saving unsaved solo views QList soloViews = documentsExclusivelyInWindow(dynamic_cast(mw)); soloViews.removeAll(dynamic_cast(activeView->document())); if (!saveSomeDocuments(soloViews, IDocument::Default)) // User cancelled or other error return; foreach (Sublime::View* view, mw->area()->views()) { if (view != activeView) mw->area()->closeView(view); } activeView->widget()->setFocus(); } } IDocument* DocumentController::activeDocument() const { UiController *uiController = Core::self()->uiControllerInternal(); if( !uiController->activeSublimeWindow() || !uiController->activeSublimeWindow()->activeView() ) return 0; return dynamic_cast(uiController->activeSublimeWindow()->activeView()->document()); } QString DocumentController::activeDocumentPath( QString target ) const { if(target.size()) { foreach(IProject* project, Core::self()->projectController()->projects()) { - if(project->name().toLower() == target.toLower()) { + if(project->name().startsWith(target, Qt::CaseInsensitive)) { return project->folder().pathOrUrl() + "/."; } } } IDocument* doc = activeDocument(); if(!doc || target == "[selection]") { Context* selection = ICore::self()->selectionController()->currentSelection(); if(selection && selection->type() == Context::ProjectItemContext && static_cast(selection)->items().size()) { QString ret = static_cast(selection)->items()[0]->url().pathOrUrl(); if(static_cast(selection)->items()[0]->folder()) ret += "/."; return ret; } return QString(); } return doc->url().pathOrUrl(); } QStringList DocumentController::activeDocumentPaths() const { UiController *uiController = Core::self()->uiControllerInternal(); if( !uiController->activeSublimeWindow() ) return QStringList(); QSet documents; foreach(Sublime::View* view, uiController->activeSublimeWindow()->area()->views()) documents.insert(KUrl(view->document()->documentSpecifier()).pathOrUrl()); return documents.toList(); } void DocumentController::registerDocumentForMimetype( const QString& mimetype, KDevelop::IDocumentFactory* factory ) { if( !d->factories.contains( mimetype ) ) d->factories[mimetype] = factory; } QStringList DocumentController::documentTypes() const { return QStringList() << "Text"; } bool DocumentController::isEmptyDocumentUrl(const KUrl &url) { QRegExp r(QString("^%1(\\s\\(\\d+\\))?$").arg(EMPTY_DOCUMENT_URL)); return r.indexIn(url.prettyUrl()) != -1; } KUrl DocumentController::nextEmptyDocumentUrl() { int nextEmptyDocNumber = 0; foreach (IDocument *doc, Core::self()->documentControllerInternal()->openDocuments()) { if (DocumentController::isEmptyDocumentUrl(doc->url())) { QRegExp r(QString("^%1\\s\\((\\d+)\\)$").arg(EMPTY_DOCUMENT_URL)); if (r.indexIn(doc->url().prettyUrl()) != -1) nextEmptyDocNumber = qMax(nextEmptyDocNumber, r.cap(1).toInt()+1); else nextEmptyDocNumber = qMax(nextEmptyDocNumber, 1); } } KUrl url; if (nextEmptyDocNumber > 0) url = KUrl(QString("%1 (%2)").arg(EMPTY_DOCUMENT_URL).arg(nextEmptyDocNumber)); else url = KUrl(EMPTY_DOCUMENT_URL); return url; } IDocumentFactory* DocumentController::factory(const QString& mime) const { return d->factories.value(mime); } KTextEditor::Document* DocumentController::globalTextEditorInstance() { if(!d->globalTextEditorInstance) d->globalTextEditorInstance = Core::self()->partControllerInternal()->createTextPart(); return d->globalTextEditorInstance; } bool DocumentController::openDocumentsSimple( QStringList urls ) { Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea(); Sublime::AreaIndex* areaIndex = area->rootIndex(); if(Sublime::View* activeView = Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView()) areaIndex = area->indexOf(activeView); kDebug() << "opening " << urls << " to area " << area << " index " << areaIndex << " with children " << areaIndex->first() << " " << areaIndex->second(); bool isFirstView = true; bool ret = openDocumentsWithSplitSeparators( areaIndex, urls, isFirstView ); kDebug() << "area arch. after opening: " << areaIndex->print(); // Required because sublime sometimes doesn't update correctly when the area-index contents has been changed // (especially when views have been moved to other indices, through unsplit, split, etc.) static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->reconstructViews(); return ret; } bool DocumentController::openDocumentsWithSplitSeparators( Sublime::AreaIndex* index, QStringList urlsWithSeparators, bool& isFirstView ) { kDebug() << "opening " << urlsWithSeparators << " index " << index << " with children " << index->first() << " " << index->second() << " view-count " << index->viewCount(); if(urlsWithSeparators.isEmpty()) return true; Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea(); QList topLevelSeparators; // Indices of the top-level separators (with groups skipped) QStringList separators = QStringList() << "/" << "-"; QList groups; bool ret = true; { int parenDepth = 0; int groupStart = 0; for(int pos = 0; pos < urlsWithSeparators.size(); ++pos) { QString item = urlsWithSeparators[pos]; if(separators.contains(item)) { if(parenDepth == 0) topLevelSeparators << pos; }else if(item == "[") { if(parenDepth == 0) groupStart = pos+1; ++parenDepth; } else if(item == "]") { if(parenDepth > 0) { --parenDepth; if(parenDepth == 0) groups << urlsWithSeparators.mid(groupStart, pos-groupStart); } else{ kDebug() << "syntax error in " << urlsWithSeparators << ": parens do not match"; ret = false; } }else if(parenDepth == 0) { groups << (QStringList() << item); } } } if(topLevelSeparators.isEmpty()) { if(urlsWithSeparators.size() > 1) { foreach(QStringList group, groups) ret &= openDocumentsWithSplitSeparators( index, group, isFirstView ); }else{ while(index->isSplit()) index = index->first(); // Simply open the document into the area index IDocument* doc = Core::self()->documentControllerInternal()->openDocument(KUrl(urlsWithSeparators.front()), KTextEditor::Cursor::invalid(), IDocumentController::DoNotActivate | IDocumentController::DoNotCreateView); Sublime::Document *sublimeDoc = dynamic_cast(doc); if (sublimeDoc) { Sublime::View* view = sublimeDoc->createView(); area->addView(view, index); if(isFirstView) { static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->activateView(view); isFirstView = false; } }else{ ret = false; } } return ret; } // Pick a separator in the middle int pickSeparator = topLevelSeparators[topLevelSeparators.size()/2]; bool activeViewToSecondChild = false; if(pickSeparator == urlsWithSeparators.size()-1) { // There is no right child group, so the right side should be filled with the currently active views activeViewToSecondChild = true; }else{ QStringList separatorsAndParens = separators; separatorsAndParens << "[" << "]"; // Check if the second child-set contains an unterminated separator, which means that the active views should end up there for(int pos = pickSeparator+1; pos < urlsWithSeparators.size(); ++pos) if( separators.contains(urlsWithSeparators[pos]) && (pos == urlsWithSeparators.size()-1 || separatorsAndParens.contains(urlsWithSeparators[pos-1]) || separatorsAndParens.contains(urlsWithSeparators[pos-1])) ) activeViewToSecondChild = true; } Qt::Orientation orientation = urlsWithSeparators[pickSeparator] == "/" ? Qt::Horizontal : Qt::Vertical; if(!index->isSplit()) { kDebug() << "splitting " << index << "orientation" << orientation << "to second" << activeViewToSecondChild; index->split(orientation, activeViewToSecondChild); }else{ index->setOrientation(orientation); kDebug() << "WARNING: Area is already split (shouldn't be)" << urlsWithSeparators; } openDocumentsWithSplitSeparators( index->first(), urlsWithSeparators.mid(0, pickSeparator) , isFirstView ); if(pickSeparator != urlsWithSeparators.size() - 1) openDocumentsWithSplitSeparators( index->second(), urlsWithSeparators.mid(pickSeparator+1, urlsWithSeparators.size() - (pickSeparator+1) ), isFirstView ); // Clean up the child-indices, because document-loading may fail if(!index->first()->viewCount() && !index->first()->isSplit()) { kDebug() << "unsplitting first"; index->unsplit(index->first()); } else if(!index->second()->viewCount() && !index->second()->isSplit()) { kDebug() << "unsplitting second"; index->unsplit(index->second()); } return ret; } void DocumentController::slotProjectOpened(IProject* p) { connect(p->managerPlugin(), SIGNAL(fileRenamed(KUrl,KDevelop::ProjectFileItem*)), SLOT(slotFileRenamed(KUrl,KDevelop::ProjectFileItem*))); } void DocumentController::slotFileRenamed(const KUrl& oldname, ProjectFileItem* newitem) { IDocument* doc=documentForUrl(oldname); if(!doc) return; KTextEditor::Cursor c; KTextEditor::Document* textdoc=doc->textDocument(); if(textdoc && textdoc->activeView()) c = textdoc->activeView()->cursorPosition(); doc->close(IDocument::Default); ICore::self()->documentController()->openDocument(newitem->url(), c); } } #include "documentcontroller.moc" diff --git a/shell/projectcontroller.cpp b/shell/projectcontroller.cpp index bc557bda3d..b805edf888 100644 --- a/shell/projectcontroller.cpp +++ b/shell/projectcontroller.cpp @@ -1,1040 +1,1100 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat Copyright 2007 Andreas Pakulat This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectcontroller.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 #include #include #include #include #include #include #include #include #include "core.h" #include "project.h" #include "mainwindow.h" #include "shellextension.h" #include "plugincontroller.h" #include "uicontroller.h" #include "documentcontroller.h" #include "openprojectdialog.h" #include #include #include #include "sessioncontroller.h" #include "session.h" #include +#include #include #include #include namespace KDevelop { class ProjectControllerPrivate { public: QList m_projects; QMap< IProject*, QList > m_projectPlugins; QPointer m_recentAction; Core* m_core; // IProject* m_currentProject; ProjectModel* model; QItemSelectionModel* selectionModel; QPointer m_openProject; QPointer m_fetchProject; QPointer m_closeProject; QPointer m_openConfig; IProjectDialogProvider* dialog; QList m_currentlyOpening; // project-file urls that are being opened IProject* m_configuringProject; ProjectController* q; ProjectBuildSetModel* buildset; bool m_foundProjectFile; //Temporary flag used while searching the hierarchy for a project file bool m_cleaningUp; //Temporary flag enabled while destroying the project-controller QPointer m_changesModel; ProjectControllerPrivate( ProjectController* p ) : m_core(0), model(0), selectionModel(0), dialog(0), m_configuringProject(0), q(p), m_foundProjectFile(false), m_cleaningUp(false) { } void unloadAllProjectPlugins() { if( m_projects.isEmpty() ) m_core->pluginControllerInternal()->unloadProjectPlugins(); } void projectConfig( QObject * obj ) { if( !obj ) return; Project* proj = qobject_cast(obj); if( !proj ) return; //@FIXME: Currently it is important to set a parentApp on the kcms //that's different from the component name of the application, else //the plugin will show up on all projects settings dialogs. QStringList pluginsForPrj = findPluginsForProject( proj ); kDebug() << "Using pluginlist:" << pluginsForPrj; pluginsForPrj << "kdevplatformproject"; // for project-wide env settings. KSettings::Dialog cfgDlg( pluginsForPrj, m_core->uiController()->activeMainWindow() ); cfgDlg.setKCMArguments( QStringList() << proj->developerTempFile() << proj->projectTempFile() << proj->projectFileUrl().url() << proj->developerFileUrl().url() << proj->name() ); m_configuringProject = proj; cfgDlg.setWindowTitle( i18n("Configure Project %1", proj->name()) ); cfgDlg.exec(); proj->projectConfiguration()->sync(); m_configuringProject = 0; } void saveListOfOpenedProjects() { KSharedConfig::Ptr config = Core::self()->activeSession()->config(); KConfigGroup group = config->group( "General Options" ); KUrl::List openProjects; foreach( IProject* project, m_projects ) { openProjects.append(project->projectFileUrl()); } group.writeEntry( "Open Projects", openProjects.toStringList() ); group.sync(); } // Recursively collects builder dependencies for a project. static void collectBuilders( QList< IProjectBuilder* >& destination, IProjectBuilder* topBuilder, IProject* project ) { QList< IProjectBuilder* > auxBuilders = topBuilder->additionalBuilderPlugins( project ); destination.append( auxBuilders ); foreach( IProjectBuilder* auxBuilder, auxBuilders ) { collectBuilders( destination, auxBuilder, project ); } } QStringList findPluginsForProject( IProject* project ) { QList plugins = m_core->pluginController()->loadedPlugins(); QStringList pluginnames; QList< IProjectBuilder* > buildersForKcm; if( IBuildSystemManager* buildSystemManager = project->buildSystemManager() ) { collectBuilders( buildersForKcm, buildSystemManager->builder(), project ); } for( QList::iterator it = plugins.begin(); it != plugins.end(); ++it ) { IPlugin* plugin = *it; const KPluginInfo info = m_core->pluginController()->pluginInfo( plugin ); if (info.property("X-KDevelop-Category").toString() != "Project") continue; IProjectFileManager* manager = plugin->extension(); if( manager && manager != project->projectFileManager() ) { // current plugin is a manager but does not apply to given project, skip continue; } IProjectBuilder* builder = plugin->extension(); if ( builder && !buildersForKcm.contains( builder ) ) { continue; } pluginnames << info.pluginName(); } return pluginnames; } void notifyProjectConfigurationChanged() { if( m_configuringProject ) { emit q->projectConfigurationChanged( m_configuringProject ); } } void updateActionStates( Context* ctx ) { ProjectItemContext* itemctx = dynamic_cast(ctx); m_openConfig->setEnabled( itemctx && itemctx->items().count() == 1 ); m_closeProject->setEnabled( itemctx && itemctx->items().count() > 0 ); } void openProjectConfig() { ProjectItemContext* ctx = dynamic_cast( Core::self()->selectionController()->currentSelection() ); if( ctx && ctx->items().count() == 1 ) { q->configureProject( ctx->items().at(0)->project() ); } } void closeSelectedProjects() { ProjectItemContext* ctx = dynamic_cast( Core::self()->selectionController()->currentSelection() ); if( ctx && ctx->items().count() > 0 ) { QSet projects; foreach( ProjectBaseItem* item, ctx->items() ) { projects.insert( item->project() ); } foreach( IProject* project, projects ) { q->closeProject( project ); } } } void importProject(const KUrl& url_) { KUrl url(url_); if ( url.isLocalFile() ) { QString path = QFileInfo( url.toLocalFile() ).canonicalFilePath(); if ( !path.isEmpty() ) url.setPath( path ); } if ( !url.isValid() ) { KMessageBox::error(Core::self()->uiControllerInternal()->activeMainWindow(), i18n("Invalid Location: %1", url.prettyUrl())); return; } if ( m_currentlyOpening.contains(url)) { kDebug() << "Already opening " << url << ". Aborting."; KPassivePopup::message( i18n( "Project already being opened"), i18n( "Already opening %1, not opening again", url.prettyUrl() ), m_core->uiController()->activeMainWindow() ); return; } foreach( IProject* project, m_projects ) { if ( url == project->projectFileUrl() ) { if ( dialog->userWantsReopen() ) { // close first, then open again by falling through q->closeProject(project); } else { // abort return; } } } m_core->pluginControllerInternal()->loadProjectPlugins(); Project* project = new Project(); emit q->projectAboutToBeOpened( project ); if ( !project->open( url ) ) { q->abortOpeningProject(project); project->deleteLater(); return; } m_currentlyOpening << url; } }; IProjectDialogProvider::IProjectDialogProvider() {} IProjectDialogProvider::~IProjectDialogProvider() {} ProjectDialogProvider::ProjectDialogProvider(ProjectControllerPrivate* const p) : d(p) {} ProjectDialogProvider::~ProjectDialogProvider() {} bool writeNewProjectFile( const QString& localConfigFile, const QString& name, const QString& manager ) { KSharedConfig::Ptr cfg = KSharedConfig::openConfig( localConfigFile, KConfig::SimpleConfig ); if (!cfg->isConfigWritable(true)) { kDebug() << "can't write to configfile"; return false; } KConfigGroup grp = cfg->group( "Project" ); grp.writeEntry( "Name", name ); grp.writeEntry( "Manager", manager ); cfg->sync(); return true; } bool writeProjectSettingsToConfigFile(const KUrl& projectFileUrl, const QString& projectName, const QString& projectManager) { if ( !projectFileUrl.isLocalFile() ) { KTemporaryFile tmp; if ( !tmp.open() ) { return false; } if ( !writeNewProjectFile( tmp.fileName(), projectName, projectManager ) ) { return false; } // explicitly close file before uploading it, see also: https://bugs.kde.org/show_bug.cgi?id=254519 tmp.close(); return KIO::NetAccess::upload( tmp.fileName(), projectFileUrl, Core::self()->uiControllerInternal()->defaultMainWindow() ); } return writeNewProjectFile( projectFileUrl.toLocalFile(),projectName, projectManager ); } bool projectFileExists( const KUrl& u ) { if( u.isLocalFile() ) { return QFileInfo( u.toLocalFile() ).exists(); } else { return KIO::NetAccess::exists( u, KIO::NetAccess::DestinationSide, Core::self()->uiControllerInternal()->activeMainWindow() ); } } bool equalProjectFile( const QString& configPath, OpenProjectDialog* dlg ) { KSharedConfig::Ptr cfg = KSharedConfig::openConfig( configPath, KConfig::SimpleConfig ); KConfigGroup grp = cfg->group( "Project" ); QString defaultName = dlg->projectFileUrl().upUrl().fileName(); return (grp.readEntry( "Name", QString() ) == dlg->projectName() || dlg->projectName() == defaultName) && grp.readEntry( "Manager", QString() ) == dlg->projectManager(); } KUrl ProjectDialogProvider::askProjectConfigLocation(bool fetch, const KUrl& startUrl) { Q_ASSERT(d); OpenProjectDialog dlg( fetch, startUrl, Core::self()->uiController()->activeMainWindow() ); if(dlg.exec() == QDialog::Rejected) return KUrl(); KUrl projectFileUrl = dlg.projectFileUrl(); kDebug() << "selected project:" << projectFileUrl << dlg.projectName() << dlg.projectManager(); // controls if existing project file should be saved bool writeProjectConfigToFile = true; if( projectFileExists( projectFileUrl ) ) { // check whether config is equal bool shouldAsk = true; if( projectFileUrl.isLocalFile() ) { shouldAsk = !equalProjectFile( projectFileUrl.toLocalFile(), &dlg ); } else { QString tmpFile; if ( KIO::NetAccess::download( projectFileUrl, tmpFile, qApp->activeWindow() ) ) { shouldAsk = !equalProjectFile( tmpFile, &dlg ); QFile::remove(tmpFile); } else { shouldAsk = false; } } if ( shouldAsk ) { KGuiItem yes = KStandardGuiItem::yes(); yes.setText(i18n("Override")); yes.setToolTip(i18nc("@info:tooltip", "Continue to open the project and use the just provided project configuration.")); yes.setIcon(KIcon()); KGuiItem no = KStandardGuiItem::no(); no.setText(i18n("Open Existing File")); no.setToolTip(i18nc("@info:tooltip", "Continue to open the project but use the existing project configuration.")); no.setIcon(KIcon()); KGuiItem cancel = KStandardGuiItem::cancel(); cancel.setToolTip(i18nc("@info:tooltip", "Cancel and do not open the project.")); int ret = KMessageBox::questionYesNoCancel(qApp->activeWindow(), i18n("There already exists a project configuration file at %1.\n" "Do you want to override it or open the existing file?", projectFileUrl.pathOrUrl()), i18n("Override existing project configuration"), yes, no, cancel ); if ( ret == KMessageBox::No ) { writeProjectConfigToFile = false; } else if ( ret == KMessageBox::Cancel ) { return KUrl(); } // else fall through and write new file } else { writeProjectConfigToFile = false; } } if (writeProjectConfigToFile) { if (!writeProjectSettingsToConfigFile(projectFileUrl, dlg.projectName(), dlg.projectManager())) { KMessageBox::error(d->m_core->uiControllerInternal()->defaultMainWindow(), i18n("Unable to create configuration file %1", projectFileUrl.url())); return KUrl(); } } return projectFileUrl; } bool ProjectDialogProvider::userWantsReopen() { Q_ASSERT(d); return (KMessageBox::questionYesNo( d->m_core->uiControllerInternal()->defaultMainWindow(), i18n( "Reopen the current project?" ) ) == KMessageBox::No) ? false : true; } void ProjectController::setDialogProvider(IProjectDialogProvider* dialog) { Q_ASSERT(d->dialog); delete d->dialog; d->dialog = dialog; } ProjectController::ProjectController( Core* core ) : IProjectController( core ), d( new ProjectControllerPrivate( this ) ) { setObjectName("ProjectController"); d->m_core = core; d->model = new ProjectModel(); d->buildset = new ProjectBuildSetModel( this ); connect( this, SIGNAL(projectOpened(KDevelop::IProject*)), d->buildset, SLOT(loadFromProject(KDevelop::IProject*)) ); connect( this, SIGNAL(projectClosing(KDevelop::IProject*)), d->buildset, SLOT(saveToProject(KDevelop::IProject*)) ); connect( this, SIGNAL(projectClosed(KDevelop::IProject*)), d->buildset, SLOT(projectClosed(KDevelop::IProject*)) ); d->selectionModel = new QItemSelectionModel(d->model); if(!(Core::self()->setupFlags() & Core::NoUi)) setupActions(); loadSettings(false); d->dialog = new ProjectDialogProvider(d); KSettings::Dispatcher::registerComponent( KComponentData("kdevplatformproject"), this, "notifyProjectConfigurationChanged" ); + + QDBusConnection::sessionBus().registerObject( "/org/kdevelop/ProjectController", + this, QDBusConnection::ExportScriptableSlots ); } void ProjectController::setupActions() { KActionCollection * ac = d->m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); KAction *action; d->m_openProject = action = ac->addAction( "project_open" ); action->setText(i18nc( "@action", "Open / Import Project..." ) ); action->setToolTip( i18nc( "@info:tooltip", "Open / Import Project" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Open / Import project

Open an existing KDevelop 4 project or import an existing Project into KDevelop 4. This entry allows to select a KDevelop4 project file or an existing directory to open it in KDevelop. When opening an existing directory that does not yet have a KDevelop4 project file, the file will be created.

" ) ); action->setIcon(KIcon("project-open")); connect( action, SIGNAL(triggered(bool)), SLOT(openProject()) ); d->m_fetchProject = action = ac->addAction( "project_fetch" ); action->setText(i18nc( "@action", "Fetch Project..." ) ); action->setToolTip( i18nc( "@info:tooltip", "Fetch Project" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Fetch project

Guides the user through the project fetch and then imports it into KDevelop 4.

" ) ); // action->setIcon(KIcon("project-open")); connect( action, SIGNAL(triggered(bool)), SLOT(fetchProject()) ); // action = ac->addAction( "project_close" ); // action->setText( i18n( "C&lose Project" ) ); // connect( action, SIGNAL(triggered(bool)), SLOT(closeProject()) ); // action->setToolTip( i18n( "Close project" ) ); // action->setWhatsThis( i18n( "Close project

Closes the current project." ) ); // action->setEnabled( false ); d->m_closeProject = action = ac->addAction( "project_close" ); connect( action, SIGNAL(triggered(bool)), SLOT(closeSelectedProjects()) ); action->setText( i18nc( "@action", "Close Project(s)" ) ); action->setIcon( KIcon( "project-development-close" ) ); action->setToolTip( i18nc( "@info:tooltip", "Closes all currently selected projects" ) ); action->setEnabled( false ); d->m_openConfig = action = ac->addAction( "project_open_config" ); connect( action, SIGNAL(triggered(bool)), SLOT(openProjectConfig()) ); action->setText( i18n( "Open Configuration..." ) ); action->setIcon( KIcon("configure") ); action->setEnabled( false ); action = ac->addAction( "commit_current_project" ); connect( action, SIGNAL(triggered(bool)), SLOT(commitCurrentProject()) ); action->setText( i18n( "Commit Current Project..." ) ); action->setIconText( i18n( "Commit..." ) ); action->setIcon( KIcon("svn-commit") ); KSharedConfig * config = KGlobal::config().data(); // KConfigGroup group = config->group( "General Options" ); d->m_recentAction = new KRecentFilesAction( this ); connect( d->m_recentAction, SIGNAL(urlSelected(KUrl)), SLOT( openProject( const KUrl& ) )); ac->addAction( "project_open_recent", d->m_recentAction ); d->m_recentAction->setText( i18n( "Open Recent" ) ); d->m_recentAction->setToolTip( i18nc( "@info:tooltip", "Open recent project" ) ); d->m_recentAction->setWhatsThis( i18nc( "@info:whatsthis", "Open recent project

Opens recently opened project.

" ) ); d->m_recentAction->loadEntries( KConfigGroup(config, "RecentProjects") ); KAction* openProjectForFileAction = new KAction( this ); ac->addAction("project_open_for_file", openProjectForFileAction); openProjectForFileAction->setText(i18n("Open Project for Current File")); connect( openProjectForFileAction, SIGNAL(triggered(bool)), SLOT(openProjectForUrlSlot(bool))); } ProjectController::~ProjectController() { delete d->model; delete d->dialog; delete d; } void ProjectController::cleanup() { d->m_cleaningUp = true; KSharedConfig::Ptr config = Core::self()->activeSession()->config(); KConfigGroup group = config->group( "General Options" ); foreach( IProject* project, d->m_projects ) { closeProject( project ); } } void ProjectController::initialize() { KSharedConfig::Ptr config = Core::self()->activeSession()->config(); KConfigGroup group = config->group( "General Options" ); KUrl::List openProjects = group.readEntry( "Open Projects", QStringList() ); QMetaObject::invokeMethod(this, "openProjects", Qt::QueuedConnection, Q_ARG(KUrl::List, openProjects)); connect( Core::self()->selectionController(), SIGNAL(selectionChanged(KDevelop::Context*)), SLOT(updateActionStates(KDevelop::Context*)) ); } void ProjectController::openProjects(const KUrl::List& projects) { foreach (const KUrl& url, projects) openProject(url); } void ProjectController::loadSettings( bool projectIsLoaded ) { Q_UNUSED(projectIsLoaded) } void ProjectController::saveSettings( bool projectIsLoaded ) { Q_UNUSED( projectIsLoaded ); } int ProjectController::projectCount() const { return d->m_projects.count(); } IProject* ProjectController::projectAt( int num ) const { if( !d->m_projects.isEmpty() && num >= 0 && num < d->m_projects.count() ) return d->m_projects.at( num ); return 0; } QList ProjectController::projects() const { return d->m_projects; } void ProjectController::eventuallyOpenProjectFile(KIO::Job* _job, KIO::UDSEntryList entries ) { KIO::SimpleJob* job(dynamic_cast(_job)); Q_ASSERT(job); foreach(const KIO::UDSEntry& entry, entries) { if(d->m_foundProjectFile) break; if(!entry.isDir()) { QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); if(name.endsWith(".kdev4")) { //We have found a project-file, open it KUrl u(job->url()); u.addPath(name); openProject(u); d->m_foundProjectFile = true; } } } } void ProjectController::openProjectForUrlSlot(bool) { if(ICore::self()->documentController()->activeDocument()) { KUrl url = ICore::self()->documentController()->activeDocument()->url(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(!project) { openProjectForUrl(url); }else{ KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("Project already open: %1", project->name())); } }else{ KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("No active document")); } } void ProjectController::openProjectForUrl(const KUrl& sourceUrl) { KUrl dirUrl = sourceUrl.upUrl(); KUrl testAt = dirUrl; d->m_foundProjectFile = false; while(!testAt.path().isEmpty()) { KUrl testProjectFile(testAt); KIO::ListJob* job = KIO::listDir(testAt); connect(job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(eventuallyOpenProjectFile(KIO::Job*,KIO::UDSEntryList))); KIO::NetAccess::synchronousRun(job, ICore::self()->uiController()->activeMainWindow()); if(d->m_foundProjectFile) { //Fine! We have directly opened the project d->m_foundProjectFile = false; return; } KUrl oldTest = testAt; testAt = testAt.upUrl(); if(oldTest == testAt) break; } KUrl askForOpen = d->dialog->askProjectConfigLocation(false, dirUrl); if(askForOpen.isValid()) openProject(askForOpen); } void ProjectController::openProject( const KUrl &projectFile ) { KUrl url = projectFile; if(!Core::self()->sessionController()->activeSession()->containedProjects().contains(url)) { foreach( const Session* session, Core::self()->sessionController()->sessions()) { if(session->containedProjects().contains(url)) { int res = KMessageBox::questionYesNo(Core::self()->uiControllerInternal()->activeMainWindow(), i18n("The project you are opening is part of the session %1, do you want to open the session instead?", session->description())); if(res == KMessageBox::Yes) { Core::self()->sessionController()->loadSession(session->id().toString()); #if 0 ///@todo Think about this! Problem: The session might already contain files, the debugger might be active, etc. //If this session is empty, close it if(Core::self()->sessionController()->activeSession()->description().isEmpty()) { //Terminate this instance of kdevelop if the user agrees foreach(Sublime::MainWindow* window, Core::self()->uiController()->controller()->mainWindows()) window->close(); } #endif return; } } } } if ( url.isEmpty() ) { url = d->dialog->askProjectConfigLocation(false); } if ( !url.isEmpty() ) { d->importProject(url); } } void ProjectController::fetchProject() { KUrl url = d->dialog->askProjectConfigLocation(true); if ( !url.isEmpty() ) { d->importProject(url); } } void ProjectController::projectImportingFinished( IProject* project ) { if( !project ) { kWarning() << "OOOPS: 0-pointer project"; return; } IPlugin *managerPlugin = project->managerPlugin(); QList pluglist; pluglist.append( managerPlugin ); d->m_projectPlugins.insert( project, pluglist ); d->m_projects.append( project ); if(!Core::self()->sessionController()->activeSession()->containedProjects().contains(project->projectFileUrl())) d->saveListOfOpenedProjects(); // KActionCollection * ac = d->m_core->uiController()->defaultMainWindow()->actionCollection(); // QAction * action; //action = ac->action( "project_close" ); //action->setEnabled( true ); if (Core::self()->setupFlags() != Core::NoUi) { d->m_recentAction->addUrl( project->projectFileUrl() ); KSharedConfig * config = KGlobal::config().data(); KConfigGroup recentGroup = config->group("RecentProjects"); d->m_recentAction->saveEntries( recentGroup ); config->sync(); } Q_ASSERT(d->m_currentlyOpening.contains(project->projectFileUrl())); d->m_currentlyOpening.removeAll(project->projectFileUrl()); emit projectOpened( project ); if (parseAllProjectSources()) { KJob* parseProjectJob = new KDevelop::ParseProjectJob(project); ICore::self()->runController()->registerJob(parseProjectJob); } KUrl::List parseList; // Add all currently open files that belong to the project to the background-parser, // since more information may be available for parsing them now(Like include-paths). foreach(IDocument* document, Core::self()->documentController()->openDocuments()) { if(!project->filesForUrl(document->url()).isEmpty()) { parseList.append(document->url()); } } Core::self()->languageController()->backgroundParser()->addDocumentList( parseList, KDevelop::TopDUContext::AllDeclarationsContextsAndUses, 10 ); } // helper method for closeProject() void ProjectController::unloadUnusedProjectPlugins(IProject* proj) { QList pluginsForProj = d->m_projectPlugins.value( proj ); d->m_projectPlugins.remove( proj ); QList otherProjectPlugins; Q_FOREACH( const QList& _list, d->m_projectPlugins ) { otherProjectPlugins << _list; } QSet pluginsForProjSet = QSet::fromList( pluginsForProj ); QSet otherPrjPluginsSet = QSet::fromList( otherProjectPlugins ); // loaded - target = tobe unloaded. QSet tobeRemoved = pluginsForProjSet.subtract( otherPrjPluginsSet ); Q_FOREACH( IPlugin* _plugin, tobeRemoved ) { KPluginInfo _plugInfo = Core::self()->pluginController()->pluginInfo( _plugin ); if( _plugInfo.isValid() ) { QString _plugName = _plugInfo.pluginName(); kDebug() << "about to unloading :" << _plugName; Core::self()->pluginController()->unloadPlugin( _plugName ); } } } // helper method for closeProject() void ProjectController::closeAllOpenedFiles(IProject* proj) { Q_FOREACH( ProjectFileItem *fileItem, proj->files() ) { Core::self()->documentControllerInternal()->closeDocument( fileItem->url() ); } } // helper method for closeProject() void ProjectController::initializePluginCleanup(IProject* proj) { // Unloading (and thus deleting) these plugins is not a good idea just yet // as we're being called by the view part and it gets deleted when we unload the plugin(s) // TODO: find a better place to unload connect(proj, SIGNAL(destroyed(QObject*)), this, SLOT(unloadAllProjectPlugins())); } void ProjectController::closeProject(IProject* proj_) { if (!proj_) { return; } // loading might have failed d->m_currentlyOpening.removeAll(proj_->projectFileUrl()); Project* proj = dynamic_cast( proj_ ); if( !proj ) { kWarning() << "Unknown Project subclass found!"; return; } d->m_projects.removeAll(proj); emit projectClosing(proj); //Core::self()->saveSettings(); // The project file is being closed. // Now we can save settings for all of the Core // objects including this one!! unloadUnusedProjectPlugins(proj); closeAllOpenedFiles(proj); proj->close(); proj->deleteLater(); //be safe when deleting if (d->m_projects.isEmpty()) { initializePluginCleanup(proj); } if(!d->m_cleaningUp) d->saveListOfOpenedProjects(); emit projectClosed(proj); return; } void ProjectController::abortOpeningProject(IProject* proj) { d->m_currentlyOpening.removeAll(proj->projectFileUrl()); emit projectOpeningAborted(proj); } ProjectModel* ProjectController::projectModel() { return d->model; } IProject* ProjectController::findProjectForUrl( const KUrl& url ) const { Q_FOREACH( IProject* proj, d->m_projects ) { if( proj->inProject( url ) ) return proj; } return 0; } IProject* ProjectController::findProjectByName( const QString& name ) { Q_FOREACH( IProject* proj, d->m_projects ) { if( proj->name() == name ) { return proj; } } return 0; } void ProjectController::configureProject( IProject* project ) { d->projectConfig( project ); } void ProjectController::addProject(IProject* project) { d->m_projects.append( project ); } QItemSelectionModel* ProjectController::projectSelectionModel() { return d->selectionModel; } bool ProjectController::isProjectNameUsed( const QString& name ) const { foreach( IProject* p, projects() ) { if( p->name() == name ) { return true; } } return false; } KUrl ProjectController::projectsBaseDirectory() const { KConfigGroup group = Core::self()->activeSession()->config()->group( "Project Manager" ); return group.readEntry( "Projects Base Directory", KUrl( QDir::homePath()+"/projects" ) ); } QString ProjectController::prettyFilePath(KUrl url, FormattingOptions format) const { IProject* project = Core::self()->projectController()->findProjectForUrl(url); if(!project) { // Find a project with the correct base directory at least foreach(IProject* candidateProject, Core::self()->projectController()->projects()) { if(candidateProject->folder().isParentOf(url)) { project = candidateProject; break; } } } QString prefixText = url.upUrl().pathOrUrl(KUrl::AddTrailingSlash); if (project) { if (format == FormatHtml) { prefixText = "" + project->name() + "/"; } else { prefixText = project->name() + '/'; } QString relativePath = project->relativeUrl(url.upUrl()).path(KUrl::AddTrailingSlash); if(relativePath.startsWith("./")) relativePath = relativePath.mid(2); prefixText += relativePath; } return prefixText; } QString ProjectController::prettyFileName(KUrl url, FormattingOptions format) const { IProject* project = Core::self()->projectController()->findProjectForUrl(url); if(project && project->folder().equals(url, KUrl::CompareWithoutTrailingSlash)) { if (format == FormatHtml) { return "" + project->name() + ""; } else { return project->name(); } } QString prefixText = prettyFilePath( url, format ); if (format == FormatHtml) { return prefixText + "" + url.fileName() + ""; } else { return prefixText + url.fileName(); } } ContextMenuExtension ProjectController::contextMenuExtension ( Context* ctx ) { ContextMenuExtension ext; if ( ctx->type() != Context::ProjectItemContext || !static_cast(ctx)->items().isEmpty() ) { return ext; } ext.addAction(ContextMenuExtension::ProjectGroup, d->m_openProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_fetchProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_recentAction); return ext; } ProjectBuildSetModel* ProjectController::buildSetModel() { return d->buildset; } ProjectChangesModel* ProjectController::changesModel() { if(!d->m_changesModel) d->m_changesModel=new ProjectChangesModel(this); return d->m_changesModel; } void ProjectController::commitCurrentProject() { IDocument* doc=ICore::self()->documentController()->activeDocument(); if(!doc) return; KUrl url=doc->url(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(project && project->versionControlPlugin()) { KUrl baseUrl=project->projectItem()->url(); IPlugin* plugin = project->versionControlPlugin(); IBasicVersionControl* vcs=plugin->extension(); if(vcs) { ICore::self()->documentController()->saveAllDocuments(KDevelop::IDocument::Silent); VCSCommitDiffPatchSource* patchSource = new VCSCommitDiffPatchSource(new VCSStandardDiffUpdater(vcs, baseUrl)); bool ret = showVcsDiff(patchSource); if(!ret) { VcsCommitDialog *commitDialog = new VcsCommitDialog(patchSource); commitDialog->setCommitCandidates(patchSource->infos()); commitDialog->exec(); } } } } +QString ProjectController::mapSourceBuild( const QString& path, bool reverse, bool fallbackRoot ) const +{ + KUrl url(path); + IProject* sourceDirProject = 0, *buildDirProject = 0; + Q_FOREACH(IProject* proj, d->m_projects) + { + if(proj->folder().isParentOf(url)) + sourceDirProject = proj; + if(proj->buildSystemManager()) + { + KUrl buildDir = proj->buildSystemManager()->buildDirectory(proj->projectItem()); + if(buildDir.isValid() && buildDir.isParentOf(url)) + buildDirProject = proj; + } + } + + if(!reverse) + { + // Map-target is the build directory + if(sourceDirProject && sourceDirProject->buildSystemManager()) + { + // We're in the source, map into the build directory + QString relativePath = KUrl::relativeUrl(sourceDirProject->folder(), url); + + KUrl build = sourceDirProject->buildSystemManager()->buildDirectory(sourceDirProject->projectItem()); + build.addPath(relativePath); + while(!QFile::exists(build.path())) + build = build.upUrl(); + return build.pathOrUrl(); + }else if(buildDirProject && fallbackRoot) + { + // We're in the build directory, map to the build directory root + return buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()).pathOrUrl(); + } + }else{ + // Map-target is the source directory + if(buildDirProject) + { + KUrl build = buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()); + // We're in the source, map into the build directory + QString relativePath = KUrl::relativeUrl(build, url); + + KUrl source = buildDirProject->folder(); + source.addPath(relativePath); + while(!QFile::exists(source.path())) + source = source.upUrl(); + return source.pathOrUrl(); + }else if(sourceDirProject && fallbackRoot) + { + // We're in the source directory, map to the root + return sourceDirProject->folder().pathOrUrl(); + } + } + return QString(); +} + } #include "projectcontroller.moc" diff --git a/shell/projectcontroller.h b/shell/projectcontroller.h index e21345f24c..59b09d0d8b 100644 --- a/shell/projectcontroller.h +++ b/shell/projectcontroller.h @@ -1,153 +1,160 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat Copyright 2007 Andreas Pakulat This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPROJECTCONTROLLER_H #define KDEVPROJECTCONTROLLER_H #include #include #include "shellexport.h" class QModelIndex; namespace KIO { class Job; } namespace KDevelop { class IProject; class Core; class UiController; class Context; class ContextMenuExtension; class KDEVPLATFORMSHELL_EXPORT IProjectDialogProvider : public QObject { Q_OBJECT public: IProjectDialogProvider(); virtual ~IProjectDialogProvider(); public Q_SLOTS: /** * Displays some UI to ask the user for the project location. * * @param fetch will tell the UI that the user might want to fetch the project first * @param startUrl tells where to look first */ virtual KUrl askProjectConfigLocation(bool fetch, const KUrl& startUrl = KUrl()) = 0; virtual bool userWantsReopen() = 0; }; class KDEVPLATFORMSHELL_EXPORT ProjectController : public IProjectController { Q_OBJECT + Q_CLASSINFO( "D-Bus Interface", "org.kdevelop.ProjectController" ) friend class Core; friend class CorePrivate; friend class ProjectPreferences; public: ProjectController( Core* core ); virtual ~ProjectController(); virtual IProject* projectAt( int ) const; virtual int projectCount() const; virtual QList projects() const; virtual ProjectBuildSetModel* buildSetModel(); virtual ProjectModel* projectModel(); virtual ProjectChangesModel* changesModel(); virtual QItemSelectionModel* projectSelectionModel(); virtual IProject* findProjectByName( const QString& name ); IProject* findProjectForUrl( const KUrl& ) const; void addProject(IProject*); // IProject* currentProject() const; virtual bool isProjectNameUsed( const QString& name ) const; void setDialogProvider(IProjectDialogProvider*); KUrl projectsBaseDirectory() const; QString prettyFileName(KUrl url, FormattingOptions format = FormatHtml) const; QString prettyFilePath(KUrl url, FormattingOptions format = FormatHtml) const; ContextMenuExtension contextMenuExtension( KDevelop::Context* ctx ); public Q_SLOTS: virtual void openProjectForUrl( const KUrl &sourceUrl ); virtual void fetchProject(); virtual void openProject( const KUrl &KDev4ProjectFile = KUrl() ); virtual void abortOpeningProject( IProject* ); void projectImportingFinished( IProject* ); virtual void closeProject( IProject* ); virtual void configureProject( IProject* ); void eventuallyOpenProjectFile(KIO::Job*,KIO::UDSEntryList); void openProjectForUrlSlot(bool); // void changeCurrentProject( ProjectBaseItem* ); void openProjects(const KUrl::List& projects); void commitCurrentProject(); + // Maps the given path from the source to the equivalent path within the build directory + // of the corresponding project. If the path is already in the build directory and fallbackRoot is true, + // then it is mapped to the root of the build directory. + // If reverse is true, maps the opposite direction, from build to source. [ Used in kdevplatform_shell_environment.sh ] + Q_SCRIPTABLE QString mapSourceBuild( const QString& path, bool reverse = false, bool fallbackRoot = true ) const; + protected: virtual void loadSettings( bool projectIsLoaded ); virtual void saveSettings( bool projectIsLoaded ); private: //FIXME Do not load all of this just for the project being opened... //void legacyLoading(); void setupActions(); void cleanup(); void initialize(); // helper methods for closeProject() void unloadUnusedProjectPlugins(IProject* proj); void disableProjectCloseAction(); void closeAllOpenedFiles(IProject* proj); void initializePluginCleanup(IProject* proj); private: Q_PRIVATE_SLOT(d, void projectConfig( QObject* ) ) Q_PRIVATE_SLOT(d, void unloadAllProjectPlugins() ) Q_PRIVATE_SLOT(d, void notifyProjectConfigurationChanged() ) Q_PRIVATE_SLOT(d, void updateActionStates( KDevelop::Context* ) ) Q_PRIVATE_SLOT(d, void closeSelectedProjects() ) Q_PRIVATE_SLOT(d, void openProjectConfig() ) class ProjectControllerPrivate* const d; friend class ProjectControllerPrivate; }; class ProjectDialogProvider : public IProjectDialogProvider { Q_OBJECT public: ProjectDialogProvider(ProjectControllerPrivate* const p); virtual ~ProjectDialogProvider(); ProjectControllerPrivate* const d; public Q_SLOTS: virtual KUrl askProjectConfigLocation(bool fetch, const KUrl& sta); virtual bool userWantsReopen(); }; } #endif diff --git a/util/kdevplatform_shell_environment.sh b/util/kdevplatform_shell_environment.sh index e52f542c62..d2ece7f928 100755 --- a/util/kdevplatform_shell_environment.sh +++ b/util/kdevplatform_shell_environment.sh @@ -1,768 +1,804 @@ #!/bin/bash # This file is part of KDevelop # Copyright 2011 David Nolden # # This library 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 library 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 # Library General Public License for more details. # # You should have received a copy of the GNU Library General Public License # along with this library; see the file COPYING.LIB. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. if [ -e ~/.bashrc ]; then # Since this runs as a replacement for the init-file, we need to chain in the 'real' bash-rc source ~/.bashrc fi if ! [ "$APPLICATION_HOST" ]; then export APPLICATION_HOST=$(hostname) fi if ! [ "$KDEV_SHELL_ENVIRONMENT_ID" ]; then export KDEV_SHELL_ENVIRONMENT_ID="default" fi if ! [ "$KDEV_DBUS_ID" ]; then echo "The required environment variable KDEV_DBUS_ID is not set. This variable defines the dbus id of the application instance instance which is supposed to be attached." exit 5 fi # Eventually, if we are forwarding to another host, and kdevplatform_shell_environment.sh # has been located through "which kdevplatform_shell_environment.sh", then we need to update KDEV_BASEDIR. if ! [ -e "$KDEV_BASEDIR/kdevplatform_shell_environment.sh" ]; then KDEV_BASEDIR=$(dirname $(which kdevplatform_shell_environment.sh)) fi if ! [ -e "$KDEV_BASEDIR/kdev_dbus_socket_transformer" ]; then echo "The $KDEV_BASEDIR/kdev_dbus_socket_transformer utility is missing, controlling the application across ssh is not possible" fi # Takes a list of tools, and prints a warning of one of them is not available in the path function checkToolsInPath { for TOOL in $@; do if ! [ "$(which $TOOL 2> /dev/null)" ]; then echo "The utility $TOOL is not in your path, the shell integration will not work properly." fi done } # Check if all required tools are there (on the host machine) checkToolsInPath sed qdbus ls cut dirname mktemp basename readlink hostname if ! [ "$KDEV_SSH_FORWARD_CHAIN" ]; then # Check for additional utilities that are required on the client machine checkToolsInPath kioclient fi # Queries the session name from the running application instance function getSessionName { echo "$(qdbus $KDEV_DBUS_ID /kdevelop/SessionController org.kdevelop.kdevelop.KDevelop.SessionController.sessionName)" } function getSessionDir { echo "$(qdbus $KDEV_DBUS_ID /kdevelop/SessionController org.kdevelop.kdevelop.KDevelop.SessionController.sessionDir)" } function getCurrentShellEnvPath { local ENV_ID=$KDEV_SHELL_ENVIRONMENT_ID if [ "$1" ]; then ENV_ID=$1 fi echo "$(getSessionDir)/${ENV_ID}.sh" } function help! { echo "You are controlling the $APPLICATION session '$(getSessionName)'" echo "" if [ "$1" == "" ]; then echo "Standard commands:" echo "raise! - Raise the window." echo "sync! - Synchronize the working directory with the currently open document. See \"help! sync\"" echo "open! [file] ... - Open the file(s) within the attached application. See \"help! open\"" echo "eopen! [file] ... - Open the file(s) within an external application using kde-open." echo "create! [file] [[text]] - Create and open a new file." echo "search! [pattern] [[locations]] ... - Search for the given pattern here or at the optionally given location(s)." echo "dsearch! [pattern] [[locations]] ... - Same as search, but starts the search instantly instead of showing the dialog (using previous settings)." echo "ssh! [ssh arguments] - Connect to a remote host via ssh, keeping the control-connection alive. See \"help! remote\"" echo "" echo "help! - Show help." echo "help! open - Show extended help about file opening commands." echo "help! sync - Show extended help about path synchronization commands." echo "help! remote - Show extended help about remote shell-integration through ssh." echo "help! env - Show extended help about the environment." echo "" echo "Most commands can be abbreviated by the first character(s), eg. r! instead of raise!, and se! instead of search!." fi if [ "$1" == "open" ]; then echo "Extended opening:" echo "The open! command can also be used to open files in specific tool-view configurations, by adding split-separators:" echo "- Files around the / separator will be arranged horizontally by split-view." echo "- Files around the - separator will be arranged vertically by split-view." echo "- Parens [ ... ] can be used to disambiguate the hierarchy (there must be spaces between filename and paren)." echo "- If a file is missing around a separator, the currently active view is inserted into the position." echo "" echo "Examples:" echo "open! file1 / file2 - The active view is split horizontally." echo " file1 is opened in the left view, and file2 in the right view." echo "open! file1 / [ file2 - file3 ] - The active view is split horizontally, and the right split-view is split vertically." echo " file1 is opened in the left view, file2 in the right upper view, and file3 in the right lower view." echo "open! / file1 - The active view is split horizontally." echo " - The active document is kept in the left split-view, and file1 is opened in the right split-view." echo "" echo "Short forms: o! = open!, eo! = eopen!, c! = create!" fi if [ "$1" == "sync" ]; then echo "Extended syncing:" - echo "sync! [[projectname]] - If no project-name is given, then the sync! command synchronizes to the currently active document." + echo "sync! [[project-name]] - If no project-name is given, then the sync! command synchronizes to the currently active document." echo " If no document is active, then it synchronizes to the currently selected item in the project tree-view." - echo " If a project name is given, then it synchronizes to the base folder of that project." - echo "syncsel! - Synchronizes to the currently selected item in the project tree-view, independent of the active document." + echo " If a case-insensitive project name prefix is given, then it synchronizes to the base folder of the matching project." + echo "syncsel! - Synchronizes to the currently selected item in the project tree-view, independently of the active document." + echo "project! [[project-name]] - Map from a path within the build directory to the corresponding path in the source directory." + echo " If we're already in the source directory, map to the root of the surrounding project." + echo "bdir! [[project-name]] - Map from a path within the source directory to the corresponding path in the build directory." + echo " If we're already in the build directory, map to the root of the build directory." echo "" - echo "Short forms: s! = sync!, ss! = syncsel!" + echo "Short forms: s! = sync!, ss! = syncsel!, p! = project!, b! = bdir!" fi if [ "$1" == "remote" ]; then echo "Extended remote commands:" echo "ssh! [ssh arguments] - Connect to a remote host via ssh, keeping the control-connection alive." echo " - The whole dbus environment is forwarded, KDevelop needs to be installed on both sides." echo "ssw! [ssh arguments] - Like ssh!, but preserves the current working directory." echo "exec! [cmd] [args] [file] . .. - Execute the given command on the client machine, referencing any number of local files on the host machine." echo " - The file paths will be re-encoded as fish:// urls if required." echo "cexec! [cmd] [args] [file] . .. - Execute the given command on the client machine, referencing any number of local files on the host machine." echo " - The files will be COPIED to the client machine if required." echo "copytohost! [client path] [host path] - Copy a file/directory through the fish protocol from the client machine th the host machine." echo "copytoclient! [host path] [client path]- Copy a file/directory through the fish protocol from the host machine to the client machine." echo "" echo "Short forms: e! = exec!, ce! = cexec!, cth! = copytohost!, ctc! = copytoclient!" fi if [ "$1" == "env" ]; then echo "Environment management:" echo "The environment can be used to store session-specific macros and generally manipulate the shell environment" echo "for embedded shell sessions. The environment is sourced into the shell when the shell is initialized, and" echo "whenever setenv! is called." echo "" echo "env! - List all available shell environment-ids for this session." echo "setenv! [id] - Set the shell environmnet-id for this session to the given id, or update the current one." echo "editenv! [id] - Edit the current shell environment or the one with the optionally given id." echo "showenv! [id] - Show the current shell environment or the one with the optionally given id." echo "" echo "Short forms: sev! = setenv!, ee! = editenv!, shenv! = showenv!" fi - echo "" } # Short versions of the commands: function r! { raise! $@ } function s! { sync! $@ } function ss! { syncsel! } function syncsel! { sync! '[selection]' } +function p! { + if [ "$@" ]; then + s! $@ + fi + project! +} + +function b! { + if [ "$@" ]; then + s! $@ + fi + bdir! +} + function o! { open! $@ } function eo! { eopen! $@ } function e! { exec! $@ } function ce! { cexec! $@ } function c! { create! $@ } function se! { search! $@ } function ds! { dsearch! $@ } function h! { help! $@ } function cth! { copytohost! $@ } function ctc! { copytoclient! $@ } function sev! { setenv! $@ } function ee! { editenv! $@ } function shev! { showenv! $@ } # Internals: # Opens a document in internally in the application function openDocument { RESULT=$(qdbus $KDEV_DBUS_ID /org/kdevelop/DocumentController org.kdevelop.DocumentController.openDocumentSimple $1) if ! [ "$RESULT" == "true" ]; then echo "Failed to open $1" fi } # Opens a document in internally in the application function openDocuments { RESULT=$(qdbus $KDEV_DBUS_ID /org/kdevelop/DocumentController org.kdevelop.DocumentController.openDocumentsSimple "(" $1 ")") if ! [ "$RESULT" == "true" ]; then echo "Failed to open $1" fi } # Executes a command on the client machine using the custom-script integration. # First argument: The full command. Second argument: The working directory. function executeInApp { local CMD="$1" local WD=$2 if ! [ "$WD" ]; then WD=$(pwd) fi RESULT=$(qdbus $KDEV_DBUS_ID /org/kdevelop/ExternalScriptPlugin org.kdevelop.ExternalScriptPlugin.executeCommand "$CMD" "$WD") if ! [ "$RESULT" == "true" ]; then echo "Execution failed" fi } # First argument: The full command. Second argument: The working directory. # Executes the command silently and synchronously, and returns the output function executeInAppSync { local CMD=$1 local WD=$2 if ! [ "$WD" ]; then WD=$(pwd) fi RESULT=$(qdbus $KDEV_DBUS_ID /org/kdevelop/ExternalScriptPlugin org.kdevelop.ExternalScriptPlugin.executeCommandSync "$CMD" "$WD") echo "$RESULT" } # Getter functions: function getActiveDocument { qdbus $KDEV_DBUS_ID /org/kdevelop/DocumentController org.kdevelop.DocumentController.activeDocumentPath $@ } function getOpenDocuments { qdbus $KDEV_DBUS_ID /org/kdevelop/DocumentController org.kdevelop.DocumentController.activeDocumentPaths } function raise! { qdbus $KDEV_DBUS_ID /kdevelop/MainWindow org.kdevelop.MainWindow.ensureVisible } +function bdir! { + TARG=$(qdbus $KDEV_DBUS_ID /org/kdevelop/ProjectController org.kdevelop.ProjectController.mapSourceBuild "$(pwd)" false) + if [ "$TARG" ]; then + cd $TARG + else + echo "Got no path" + fi +} + +function project! { + TARG=$(qdbus $KDEV_DBUS_ID /org/kdevelop/ProjectController org.kdevelop.ProjectController.mapSourceBuild "$(pwd)" true) + if [ "$TARG" ]; then + cd $TARG + else + echo "Got no path" + fi +} + # Main functions: function raise! { qdbus $KDEV_DBUS_ID /kdevelop/MainWindow org.kdevelop.MainWindow.ensureVisible } function sync! { local P=$(getActiveDocument $@) if [ "$P" ]; then if [[ "$P" == fish://* ]]; then # This regular expression filters the user@host:port out of fish:///user@host:port/path/... LOGIN=$(echo $P | sed "s/fish\:\/\/*\([^\/]*\)\(\/.*\)/\1/") P_ON_HOST=$(echo $P | sed "s/fish\:\/\/*\([^\/]*\)\(\/.*\)/\2/") if [ "$KDEV_SSH_FORWARD_CHAIN" == "$LOGIN" ]; then P="$P_ON_HOST" else if [ "$KDEV_SSH_FORWARD_CHAIN" == "" ]; then # Try to ssh to the host machine # We need to split away the optional ":port" suffix, because the ssh command does not allow that syntax HOST=$(echo $LOGIN | cut --delimiter=':' -f 1) CMD="ssh!" if [[ "$LOGIN" == *:* ]]; then # If there is a port, extract it PORT=$(echo $LOGIN | cut --delimiter=':' -f 2) CMD="$CMD -p $PORT" fi CMD="$CMD $HOST" # Execute the ssh command echo "Executing $CMD" KDEV_WORKING_DIR="$(dirname $P_ON_HOST)" $CMD return else echo "Cannot synchronize the working directory, because the host-names do not match (app: $LOGIN, shell: $KDEV_SSH_FORWARD_CHAIN)" return fi fi elif [ "$KDEV_SSH_FORWARD_CHAIN" ]; then # This session is being forwarded to another machine, but the current document is not # However, we won't complain, because it's possible that the machines share the same file-system if [ $(isEqualFileOnHostAndClient $P) != "yes" ]; then echo "Cannot synchronize the working directory, because the file systems do not match" return fi fi - - cd $(dirname $P) + + [ -d "$P" ] || P=$(dirname "$P") + cd "$P" else echo "Got no path" fi } # Take a path, and returns "yes" if the equal file is available on the host and the client # The check is performed by comparing inode-numbers function isEqualFileOnHostAndClient { function trimWhiteSpace() { echo $1 } FILE=$1 INODE_HOST=$(trimWhiteSpace $(ls --color=never -i $FILE | cut -d' ' -f1)) INODE_CLIENT=$(trimWhiteSpace $(executeInAppSync "ls --color=never -i $FILE | cut -d' ' -f1" "$(dirname $FILE)")) if [ "$INODE_HOST" == "$INODE_CLIENT" ]; then echo "yes" else echo "" fi } # Takes a relative file, returns an absolute file/url that should be valid on the client. function mapFileToClient { local RELATIVE_FILE=$1 FILE=$(readlink -f $RELATIVE_FILE) if ! [ -e "$FILE" ]; then # Try opening the file anyway, it might be an url or something else we don't understand here FILE=$RELATIVE_FILE else # We are referencing an absolute file, available on the file-system. if [ "$KDEV_SSH_FORWARD_CHAIN" ]; then # If we are forwarding, map it to the client somehow. if [ "$(isEqualFileOnHostAndClient "$FILE")" != "yes" ]; then # We can eventually map the file using the fish protocol FISH_HOST=$KDEV_SSH_FORWARD_CHAIN if [[ "$FISH_HOST" == *\,* ]]; then # Extracts everything before the first comma FISH_HOST=$(echo $FISH_HOST | sed 's/\([^,]*\),\(.*\)/\1/') echo "ssh chain is too long: $KDEV_SSH_FORWARD_CHAIN mapping anyway using $FISH_HOST" 1>&2 fi # Theoretically, we can only map through fish if the forward-chains contains no comma, which means that # we forward only once. Try anyway, there might be the same filesystem on the whole forward-chain. FILE="fish://$FISH_HOST$FILE" fi fi fi echo $FILE } function open! { FILES=$@ NEWFILES="" for RELATIVE_FILE in $FILES; do if [ "$RELATIVE_FILE" == "/" ]; then FILE=$RELATIVE_FILE else FILE=$(mapFileToClient $RELATIVE_FILE) fi NEWFILES="$NEWFILES $FILE" done openDocuments "$NEWFILES" } function eopen! { FILES=$@ for RELATIVE_FILE in $FILES; do FILE=$(mapFileToClient $RELATIVE_FILE) executeInApp "kde-open $FILE" done } function exec! { FILES=$@ ARGS="" for RELATIVE_FILE in $FILES; do if [ "$ARGS" == "" ]; then # Do not transform the command-name ARGS=$RELATIVE_FILE else FILE=$(mapFileToClient $RELATIVE_FILE) ARGS=$ARGS" "$FILE fi done echo "Executing: " $ARGS executeInApp "$ARGS" } function copytohost! { executeInApp "kioclient copy $1 $(mapFileToClient $2)" } function copytoclient! { executeInApp "kioclient copy $(mapFileToClient $1) $2" } function cexec! { FILES=$@ ARGS="" PREFIX="" TMP=1 for RELATIVE_FILE in $FILES; do if [ "$ARGS" == "" ]; then # Do not transform the command-name ARGS=$RELATIVE_FILE else FILE=$(mapFileToClient $RELATIVE_FILE) if [[ "$FILE" == fish://* ]]; then # Add a prefix to copy the file into a temporary file # Keep the baseline as suffix, so that applications can easily recognize the mimetype PREFIX+="FILE$TMP=\$(mktemp).$(basename $FILE); kioclient copy $FILE \$FILE$TMP;" # Use the temporary variable instead of the name FILE="\$FILE$TMP" TMP=$(($TMP+1)) fi ARGS=$ARGS" "$FILE fi done echo "Executing: " $ARGS executeInApp "$PREFIX $ARGS" } function create! { FILE=$(readlink -f $1) if ! [ "$FILE" ]; then echo "Error: Bad arguments." return 1 fi if [ -e "$FILE" ]; then echo "The file $FILE already exists" return 2 fi echo $2 > $FILE openDocument $(mapFileToClient $FILE) } function search! { PATTERN=$1 # if ! [ "$PATTERN" ]; then # echo "Error: No pattern given." # return 1 # fi LOCATION=$2 if ! [ "$LOCATION" ]; then LOCATION="." fi LOCATION=$(mapFileToClient $LOCATION) for LOC in $*; do if [ "$LOC" == "$1" ]; then continue; fi if [ "$LOC" == "$2" ]; then continue; fi LOCATION="$LOCATION;$(mapFileToClient $LOC)" done qdbus $KDEV_DBUS_ID /org/kdevelop/GrepViewPlugin org.kdevelop.kdevelop.GrepViewPlugin.startSearch "$PATTERN" "$LOCATION" true } function dsearch! { PATTERN=$1 if ! [ "$PATTERN" ]; then echo "Error: No pattern given." return 1 fi LOCATION=$2 if ! [ "$LOCATION" ]; then LOCATION="." fi LOCATION=$(mapFileToClient $LOCATION) for LOC in $*; do if [ "$LOC" == "$1" ]; then continue; fi if [ "$LOC" == "$2" ]; then continue; fi LOCATION="$LOCATION;$(mapFileToClient $LOC)" done qdbus $KDEV_DBUS_ID /org/kdevelop/GrepViewPlugin org.kdevelop.kdevelop.GrepViewPlugin.startSearch "$PATTERN" "$LOCATION" false } ##### SSH DBUS FORWARDING -------------------------------------------------------------------------------------------------------------------- DBUS_SOCKET_TRANSFORMER=$KDEV_BASEDIR/kdev_dbus_socket_transformer # We need this, to make sure that our forwarding-loops won't get out of control # This configures the shell to kill background jobs when it is terminated shopt -s huponexit export DBUS_ABSTRACT_SOCKET_TARGET_BASE_PATH=/tmp/dbus-forwarded-$USER-$APPLICATION_HOST export DBUS_FORWARDING_TCP_LOCAL_PORT=9000 export DBUS_FORWARDING_TCP_MAX_LOCAL_PORT=10000 export DBUS_ABSTRACT_SOCKET_TARGET_INDEX=1 export DBUS_ABSTRACT_SOCKET_MAX_TARGET_INDEX=1000 function getPortFromSSHCommand { # The port is given to ssh exclusively in the format "-p PORT" # This regular expression extracts the "4821" from "ssh -q bla1 -p 4821 bla2" local ARGS=$@ local RET=$(echo "$@" | sed "s/.*-p \+\([0-9]*\).*/\1/") if [ "$ARGS" == "$RET" ]; then # There was no match echo "" else echo ":$RET" fi } function getLoginFromSSHCommand { # The login name can be given to ssh in the format "-l NAME" # This regular expression extracts the "NAME" from "ssh -q bla1 -l NAME bla2" local ARGS=$@ local RET=$(echo "$ARGS" | sed "s/.*-l \+\([a-z,A-Z,_,0-9]*\).*/\1/") if [ "$RET" == "$ARGS" ] || [ "$RET" == "" ]; then # There was no match echo "" else echo "$RET@" fi } function getHostFromSSHCommand { # This regular expression extracts the "bla2" from "echo "ssh -q bla1 -p 4821 bla2" # Specifically, it finds the first argument which is not preceded by a "-x" parameter kind specification. local CLEANED="" local NEWCLEANED="$@" while ! [ "$NEWCLEANED" == "$CLEANED" ]; do CLEANED="$NEWCLEANED" # This expression removes one "-x ARG" parameter NEWCLEANED="$(echo $CLEANED | sed "s/\(.*\)\(-[a-z,A-Z] \+[a-z,0-9]*\)\ \(.*\)/\1\3/")" done # After cleaning, the result should only consist of the host-name followed by an optional command. # Select the host-name, by extracting the forst column. echo $CLEANED | cut --delimiter=" " -f 1 } function getSSHForwardOptionsFromCommand { HOST="$(getLoginFromSSHCommand "$@")$(getHostFromSSHCommand "$@")$(getPortFromSSHCommand "$@")" if [ "$KDEV_SSH_FORWARD_CHAIN" ]; then # We are already forwarding, so we deal with a chain of multiple ssh commands. # We still record it, although it's not sure if we can use it somehow. echo "KDEV_SSH_FORWARD_CHAIN=\"$KDEV_SSH_FORWARD_CHAIN,$HOST\""; else echo "KDEV_SSH_FORWARD_CHAIN=$HOST" fi } function getDBusAbstractSocketSuffix { # From something like DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-wYmSkVH7FE,guid=b214dad39e0292a4299778d64d761a5b # extract the /tmp/dbus-wYmSkVH7FE echo $DBUS_SESSION_BUS_ADDRESS | sed 's/unix\:abstract\=.*\(,guid\=.*\)/\1/' } function keepForwardingDBusToTCPSocket { while ! $KDEV_BASEDIR/kdev_dbus_socket_transformer $DBUS_FORWARDING_TCP_LOCAL_PORT --bind-only; do if (($DBUS_FORWARDING_TCP_LOCAL_PORT<$DBUS_FORWARDING_TCP_MAX_LOCAL_PORT)); then export DBUS_FORWARDING_TCP_LOCAL_PORT=$(($DBUS_FORWARDING_TCP_LOCAL_PORT+1)) # echo "Increased local port to " $DBUS_FORWARDING_TCP_LOCAL_PORT; else echo "Failed to allocate a local TCP port"; return 1; fi done $KDEV_BASEDIR/kdev_dbus_socket_transformer $DBUS_FORWARDING_TCP_LOCAL_PORT& return 0; } function keepForwardingDBusFromTCPSocket { while ! $KDEV_BASEDIR/kdev_dbus_socket_transformer $FORWARD_DBUS_FROM_PORT ${DBUS_ABSTRACT_SOCKET_TARGET_BASE_PATH}-${DBUS_ABSTRACT_SOCKET_TARGET_INDEX} --bind-only; do if ((${DBUS_ABSTRACT_SOCKET_TARGET_INDEX}<${DBUS_ABSTRACT_SOCKET_MAX_TARGET_INDEX})); then export DBUS_ABSTRACT_SOCKET_TARGET_INDEX=$(($DBUS_ABSTRACT_SOCKET_TARGET_INDEX+1)) else echo "Failed to allocate a local path for the abstract dbus socket"; return 1; fi done local PATH=${DBUS_ABSTRACT_SOCKET_TARGET_BASE_PATH}-${DBUS_ABSTRACT_SOCKET_TARGET_INDEX} export DBUS_SESSION_BUS_ADDRESS=unix:abstract=$PATH${DBUS_SOCKET_SUFFIX} $KDEV_BASEDIR/kdev_dbus_socket_transformer $FORWARD_DBUS_FROM_PORT $PATH& } function ssh! { keepForwardingDBusToTCPSocket # Start the dbus forwarding subprocess DBUS_FORWARDING_TCP_TARGET_PORT=$((5000+($RANDOM%50000))) ssh $@ -t -R localhost:$DBUS_FORWARDING_TCP_TARGET_PORT:localhost:$DBUS_FORWARDING_TCP_LOCAL_PORT \ " APPLICATION=$APPLICATION \ KDEV_BASEDIR=$KDEV_BASEDIR \ KDEV_DBUS_ID=$KDEV_DBUS_ID \ FORWARD_DBUS_FROM_PORT=$DBUS_FORWARDING_TCP_TARGET_PORT \ APPLICATION_HOST=$APPLICATION_HOST \ KDEV_WORKING_DIR=$KDEV_WORKING_DIR \ KDEV_SHELL_ENVIRONMENT_ID=$KDEV_SHELL_ENVIRONMENT_ID \ DBUS_SOCKET_SUFFIX=$(getDBusAbstractSocketSuffix) \ $(getSSHForwardOptionsFromCommand "$@") \ bash --init-file \ \$(if [ -e \"$KDEV_BASEDIR/kdevplatform_shell_environment.sh\" ]; \ then echo \"$KDEV_BASEDIR/kdevplatform_shell_environment.sh\"; \ elif [ -e \"$(which kdevplatform_shell_environment.sh)\" ]; then echo \"$(which kdevplatform_shell_environment.sh)\"; \ else \ echo \"~/.kdevplatform_shell_environment.sh\"; \ fi) \ -i" if [ "$FORWARD_DBUS_FROM_PORT" ]; then # We created the 2nd subprocess kill %2 # Stop the dbus forwarding subprocess else # We created the 1st subprocess kill %1 # Stop the dbus forwarding subprocess fi } # A version of ssh! that preserves the current working directory function ssw! { KDEV_WORKING_DIR=$(pwd) ssh! $@ } function env! { FILES="$(executeInAppSync "ls $(getSessionDir)/*.sh" "")" for FILE in $FILES; do FILE=$(basename $FILE) ID=${FILE%.sh} # This ugly construct strips away the .sh suffix if [ "$ID" == "$KDEV_SHELL_ENVIRONMENT_ID" ]; then echo "$ID [current]" else echo "$ID" fi done } function editenv! { local ENV_ID=$KDEV_SHELL_ENVIRONMENT_ID if [ "$1" ]; then ENV_ID=$1 fi # If the environment-file doesn't exist yet, create it executeInAppSync "if ! [ -e $(getCurrentShellEnvPath $ENV_ID) ]; then touch $(getCurrentShellEnvPath $ENV_ID); fi" "" # Open it openDocument "$(getCurrentShellEnvPath $ENV_ID)" } function setenv! { if [ "$1" ]; then KDEV_SHELL_ENVIRONMENT_ID=$1 fi # Execute the contents of the shell-environment local TEMP=$(mktemp) RESULT=$(executeInAppSync "cat \"$(getCurrentShellEnvPath)\"" "") echo "$RESULT" > $TEMP if ! [ "$RESULT" ]; then # If the environment shell file doesn't exist, create it executeInAppSync "if ! [ -e $(getCurrentShellEnvPath) ]; then touch $(getCurrentShellEnvPath); fi" "" fi source $TEMP rm $TEMP } function showenv! { local ENV_ID=$KDEV_SHELL_ENVIRONMENT_ID if [ "$1" ]; then ENV_ID=$1 fi echo "Environment $ENV_ID:" # Execute the contents of the shell-environment echo $(executeInAppSync "cat \"$(getCurrentShellEnvPath $ENV_ID)\"" "") } if [ "$FORWARD_DBUS_FROM_PORT" ]; then # Start the target-side dbus forwarding, transforming from the ssh pipe to the abstract unix domain socket export DBUS_SESSION_BUS_ADDRESS=unix:abstract=${DBUS_ABSTRACT_SOCKET_TARGET_BASE_PATH}-${DBUS_ABSTRACT_SOCKET_TARGET_INDEX}${DBUS_SOCKET_SUFFIX} keepForwardingDBusFromTCPSocket fi setenv! ##### INITIALIZATION -------------------------------------------------------------------------------------------------------------------- # Mark that this session is attached, by prepending a '!' character PS1="!$PS1" echo "You are controlling the $APPLICATION session '$(getSessionName)'. Type help! for more information." if [ "$KDEV_WORKING_DIR" ]; then cd $KDEV_WORKING_DIR fi