projectfilequickopen.cpp 10.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* This file is part of the KDE libraries
   Copyright (C) 2007 David Nolden <david.nolden.kdevelop@art-master.de>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License version 2 as published by the Free Software Foundation.

   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.
Kevin Funk's avatar
Kevin Funk committed
17
 */
18

Andreas Pakulat's avatar
Andreas Pakulat committed
19
#include "projectfilequickopen.h"
Milian Wolff's avatar
Milian Wolff committed
20

21
#include <QApplication>
22 23
#include <QIcon>
#include <QTextBrowser>
Milian Wolff's avatar
Milian Wolff committed
24

25
#include <KIconLoader>
26
#include <KLocalizedString>
Milian Wolff's avatar
Milian Wolff committed
27

28 29 30 31
#include <interfaces/iprojectcontroller.h>
#include <interfaces/idocumentcontroller.h>
#include <interfaces/iproject.h>
#include <interfaces/icore.h>
Milian Wolff's avatar
Milian Wolff committed
32

33 34 35
#include <language/duchain/topducontext.h>
#include <language/duchain/duchain.h>
#include <language/duchain/duchainlock.h>
36
#include <serialization/indexedstring.h>
37
#include <language/duchain/parsingenvironment.h>
38
#include <util/texteditorhelpers.h>
Milian Wolff's avatar
Milian Wolff committed
39

40
#include <project/projectmodel.h>
Kevin Funk's avatar
Kevin Funk committed
41
#include <project/projectutils.h>
42

43 44
#include "../openwith/iopenwith.h"

45 46
using namespace KDevelop;

47 48 49 50 51 52
namespace {
QSet<IndexedString> openFiles()
{
    QSet<IndexedString> openFiles;
    const QList<IDocument*>& docs = ICore::self()->documentController()->openDocuments();
    openFiles.reserve(docs.size());
53
    for (IDocument* doc : docs) {
Milian Wolff's avatar
Milian Wolff committed
54
        openFiles << IndexedString(doc->url());
55
    }
Kevin Funk's avatar
Kevin Funk committed
56

57 58 59 60 61 62
    return openFiles;
}

QString iconNameForUrl(const IndexedString& url)
{
    if (url.isEmpty()) {
63
        return QStringLiteral("tab-duplicate");
64
    }
65
    ProjectBaseItem* item = ICore::self()->projectController()->projectModel()->itemForPath(url);
66 67 68
    if (item) {
        return item->iconName();
    }
69
    return QStringLiteral("unknown");
70 71 72
}
}

Kevin Funk's avatar
Kevin Funk committed
73 74
ProjectFileData::ProjectFileData(const ProjectFile& file)
    : m_file(file)
75
{
76 77
}

78 79
QString ProjectFileData::text() const
{
80
    return m_file.projectPath.relativePath(m_file.path);
81
}
82

83 84
QString ProjectFileData::htmlDescription() const
{
85 86 87 88
    return
        QLatin1String("<small><small>") +
        i18nc("%1: project name", "Project %1", project()) +
        QLatin1String("</small></small>");
89 90
}

Kevin Funk's avatar
Kevin Funk committed
91
bool ProjectFileData::execute(QString& filterText)
92
{
Milian Wolff's avatar
Milian Wolff committed
93 94
    const QUrl url = m_file.path.toUrl();
    IOpenWith::openFiles(QList<QUrl>() << url);
95 96 97

    auto cursor = KTextEditorHelpers::extractCursor(filterText);
    if (cursor.isValid()) {
98
        IDocument* doc = ICore::self()->documentController()->documentForUrl(url);
99
        if (doc) {
100
            doc->setCursorPosition(cursor);
101 102 103
        }
    }
    return true;
104 105
}

106 107 108
bool ProjectFileData::isExpandable() const
{
    return true;
109 110
}

111 112 113 114 115 116 117 118
QList<QVariant> ProjectFileData::highlighting() const
{
    QTextCharFormat boldFormat;
    boldFormat.setFontWeight(QFont::Bold);
    QTextCharFormat normalFormat;

    QString txt = text();

119
    int fileNameLength = m_file.path.lastPathSegment().length();
120

121 122 123 124 125 126 127 128
    const QList<QVariant> ret{
        0,
        txt.length() - fileNameLength,
        QVariant(normalFormat),
        txt.length() - fileNameLength,
        fileNameLength,
        QVariant(boldFormat),
    };
129 130 131
    return ret;
}

132 133
QWidget* ProjectFileData::expandingWidget() const
{
Milian Wolff's avatar
Milian Wolff committed
134
    const QUrl url = m_file.path.toUrl();
135 136 137
    DUChainReadLocker lock;

    ///Find a du-chain for the document
138
    const QList<TopDUContext*> contexts = DUChain::self()->chainsForDocument(url);
139 140

    ///Pick a non-proxy context
141
    TopDUContext* chosen = nullptr;
142
    for (TopDUContext* ctx : contexts) {
Kevin Funk's avatar
Kevin Funk committed
143
        if (!(ctx->parsingEnvironmentFile() && ctx->parsingEnvironmentFile()->isProxyContext())) {
144 145 146 147
            chosen = ctx;
        }
    }

Kevin Funk's avatar
Kevin Funk committed
148
    if (chosen) {
149 150
        // TODO: show project name, by introducing a generic wrapper widget that supports QuickOpenEmbeddedWidgetInterface
        return chosen->createNavigationWidget();
151
    } else {
152
        auto* ret = new QTextBrowser();
153 154
        ret->resize(400, 100);
        ret->setText(
155
            QLatin1String("<small><small>")
Kevin Funk's avatar
Kevin Funk committed
156
            + i18nc("%1: project name", "Project %1", project())
157 158
            + QLatin1String("<br>") + i18n("Not parsed yet")
            + QLatin1String("</small></small>"));
159 160 161
        return ret;
    }

162
    return nullptr;
163 164 165 166
}

QIcon ProjectFileData::icon() const
{
167
    const QString& iconName = iconNameForUrl(m_file.indexedPath);
168 169 170 171 172 173 174 175 176

    /**
     * FIXME: Move this cache into a more central place and reuse it elsewhere.
     *        The project model e.g. could reuse this as well.
     *
     * Note: We cache here since otherwise displaying and esp. scrolling
     *       in a large list of quickopen items becomes very slow.
     */
    static QHash<QString, QPixmap> iconCache;
Kevin Funk's avatar
Kevin Funk committed
177
    QHash<QString, QPixmap>::const_iterator it = iconCache.constFind(iconName);
178 179
    if (it != iconCache.constEnd()) {
        return it.value();
180
    }
181 182 183 184

    const QPixmap& pixmap = KIconLoader::global()->loadIcon(iconName, KIconLoader::Small);
    iconCache.insert(iconName, pixmap);
    return pixmap;
185 186
}

187 188
QString ProjectFileData::project() const
{
189
    const IProject* project = ICore::self()->projectController()->findProjectForUrl(m_file.path.toUrl());
190 191 192 193 194 195 196
    if (project) {
        return project->name();
    } else {
        return i18n("none");
    }
}

197 198 199 200 201
Path ProjectFileData::projectPath() const
{
    return m_file.projectPath;
}

202 203
BaseFileDataProvider::BaseFileDataProvider()
{
204 205
}

Kevin Funk's avatar
Kevin Funk committed
206
void BaseFileDataProvider::setFilterText(const QString& text)
207
{
208 209 210
    int pathLength;
    KTextEditorHelpers::extractCursor(text, &pathLength);
    QString path(text.mid(0, pathLength));
Kevin Funk's avatar
Kevin Funk committed
211
    if (path.startsWith(QLatin1String("./")) || path.startsWith(QLatin1String("../"))) {
212 213 214
        // assume we want to filter relative to active document's url
        IDocument* doc = ICore::self()->documentController()->activeDocument();
        if (doc) {
Milian Wolff's avatar
Milian Wolff committed
215
            path = Path(Path(doc->url()).parent(), path).pathOrUrl();
216
        }
217
    }
218
    setFilter(path.split(QLatin1Char('/'), QString::SkipEmptyParts));
219 220
}

221 222
uint BaseFileDataProvider::itemCount() const
{
223
    return filteredItems().count();
224 225
}

226 227
uint BaseFileDataProvider::unfilteredItemCount() const
{
228
    return items().count();
229 230
}

231
QuickOpenDataPointer BaseFileDataProvider::data(uint row) const
232
{
Kevin Funk's avatar
Kevin Funk committed
233
    return QuickOpenDataPointer(new ProjectFileData(filteredItems().at(row)));
234 235 236 237
}

ProjectFileDataProvider::ProjectFileDataProvider()
{
238 239
    auto projectController = ICore::self()->projectController();
    connect(projectController, &IProjectController::projectClosing,
240
            this, &ProjectFileDataProvider::projectClosing);
241
    connect(projectController, &IProjectController::projectOpened,
242
            this, &ProjectFileDataProvider::projectOpened);
243
    foreach (const auto project, projectController->projects()) {
244 245
        projectOpened(project);
    }
246 247
}

Kevin Funk's avatar
Kevin Funk committed
248
void ProjectFileDataProvider::projectClosing(IProject* project)
249
{
Kevin Funk's avatar
Kevin Funk committed
250
    foreach (ProjectFileItem* file, KDevelop::allFiles(project->projectItem())) {
251
        fileRemovedFromSet(file);
252 253 254
    }
}

Kevin Funk's avatar
Kevin Funk committed
255
void ProjectFileDataProvider::projectOpened(IProject* project)
256 257 258
{
    const int processAfter = 1000;
    int processed = 0;
Kevin Funk's avatar
Kevin Funk committed
259
    foreach (ProjectFileItem* file, KDevelop::allFiles(project->projectItem())) {
260
        fileAddedToSet(file);
261 262 263 264 265 266 267
        if (++processed == processAfter) {
            // prevent UI-lockup when a huge project was imported
            QApplication::processEvents();
            processed = 0;
        }
    }

268 269 270 271
    connect(project, &IProject::fileAddedToSet,
            this, &ProjectFileDataProvider::fileAddedToSet);
    connect(project, &IProject::fileRemovedFromSet,
            this, &ProjectFileDataProvider::fileRemovedFromSet);
272 273
}

Kevin Funk's avatar
Kevin Funk committed
274
void ProjectFileDataProvider::fileAddedToSet(ProjectFileItem* file)
275 276
{
    ProjectFile f;
277 278
    f.projectPath = file->project()->path();
    f.path = file->path();
279 280
    f.indexedPath = file->indexedPath();
    f.outsideOfProject = !f.projectPath.isParentOf(f.path);
Kevin Funk's avatar
Kevin Funk committed
281
    auto it = std::lower_bound(m_projectFiles.begin(), m_projectFiles.end(), f);
282
    if (it == m_projectFiles.end() || it->path != f.path) {
283
        m_projectFiles.insert(it, f);
284
    }
285
}
286

Kevin Funk's avatar
Kevin Funk committed
287
void ProjectFileDataProvider::fileRemovedFromSet(ProjectFileItem* file)
288
{
289
    ProjectFile item;
290
    item.path = file->path();
291 292 293 294

    // fast-path for non-generated files
    // NOTE: figuring out whether something is generated is expensive... and since
    // generated files are rare we apply this two-step algorithm here
Kevin Funk's avatar
Kevin Funk committed
295 296
    auto it = std::lower_bound(m_projectFiles.begin(), m_projectFiles.end(), item);
    if (it != m_projectFiles.end() && !(item < *it)) {
297
        m_projectFiles.erase(it);
298 299 300 301 302
        return;
    }

    // last try: maybe it was generated
    item.outsideOfProject = true;
Kevin Funk's avatar
Kevin Funk committed
303 304
    it = std::lower_bound(m_projectFiles.begin(), m_projectFiles.end(), item);
    if (it != m_projectFiles.end() && !(item < *it)) {
305 306
        m_projectFiles.erase(it);
        return;
307
    }
308
}
309

310 311
void ProjectFileDataProvider::reset()
{
312
    clearFilter();
313

314
    QVector<ProjectFile> projectFiles = m_projectFiles;
315

316
    const auto& open = openFiles();
317
    for (QVector<ProjectFile>::iterator it = projectFiles.begin();
Kevin Funk's avatar
Kevin Funk committed
318
         it != projectFiles.end(); ) {
319
        if (open.contains(it->indexedPath)) {
320 321 322
            it = projectFiles.erase(it);
        } else {
            ++it;
323
        }
324 325
    }

326
    setItems(projectFiles);
327 328
}

329 330 331
QSet<IndexedString> ProjectFileDataProvider::files() const
{
    QSet<IndexedString> ret;
332

Kevin Funk's avatar
Kevin Funk committed
333
    foreach (IProject* project, ICore::self()->projectController()->projects()) {
334
        ret += project->fileSet();
Kevin Funk's avatar
Kevin Funk committed
335
    }
336

337
    return ret - openFiles();
338 339
}

340 341
void OpenFilesDataProvider::reset()
{
342
    clearFilter();
343 344
    IProjectController* projCtrl = ICore::self()->projectController();
    IDocumentController* docCtrl = ICore::self()->documentController();
345
    const QList<IDocument*>& docs = docCtrl->openDocuments();
346

347
    QVector<ProjectFile> currentFiles;
348
    currentFiles.reserve(docs.size());
349
    for (IDocument* doc : docs) {
350
        ProjectFile f;
351
        f.path = Path(doc->url());
352 353
        IProject* project = projCtrl->findProjectForUrl(doc->url());
        if (project) {
354
            f.projectPath = project->path();
355 356
        }
        currentFiles << f;
357 358
    }

Kevin Funk's avatar
Kevin Funk committed
359
    std::sort(currentFiles.begin(), currentFiles.end());
360

361
    setItems(currentFiles);
362 363
}

364
QSet<IndexedString> OpenFilesDataProvider::files() const
365
{
366
    return openFiles();
367 368
}