cpphoverhandler.cpp 12.7 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
5
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
7
8
**
** Contact:  Qt Software Information (qt-info@nokia.com)
**
9
** Commercial Usage
10
**
11
12
13
14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
15
**
16
** GNU Lesser General Public License Usage
17
**
18
19
20
21
22
23
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24
**
25
26
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
con's avatar
con committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
#include "cpphoverhandler.h"
31
32
#include "cppeditor.h"
#include "cppplugin.h"
con's avatar
con committed
33
34
35

#include <coreplugin/icore.h>
#include <coreplugin/uniqueidmanager.h>
36
37
#include <coreplugin/editormanager/editormanager.h>
#include <cpptools/cppmodelmanagerinterface.h>
38
#include <extensionsystem/pluginmanager.h>
con's avatar
con committed
39
#include <texteditor/itexteditor.h>
Roberto Raggi's avatar
Roberto Raggi committed
40
#include <texteditor/basetexteditor.h>
con's avatar
con committed
41
42
43
44
45
#include <debugger/debuggerconstants.h>

#include <CoreTypes.h>
#include <FullySpecifiedType.h>
#include <Literals.h>
46
#include <Control.h>
con's avatar
con committed
47
48
49
50
51
52
53
#include <Names.h>
#include <Scope.h>
#include <Symbol.h>
#include <Symbols.h>
#include <cplusplus/ExpressionUnderCursor.h>
#include <cplusplus/Overview.h>
#include <cplusplus/TypeOfExpression.h>
54
#include <cplusplus/SimpleLexer.h>
con's avatar
con committed
55

56
57
58
59
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QSettings>
con's avatar
con committed
60
61
62
63
64
#include <QtGui/QToolTip>
#include <QtGui/QTextCursor>
#include <QtGui/QTextBlock>
#include <QtHelp/QHelpEngineCore>

65
using namespace CppEditor::Internal;
Roberto Raggi's avatar
Roberto Raggi committed
66
using namespace CPlusPlus;
67
using namespace Core;
con's avatar
con committed
68

69
70
71
CppHoverHandler::CppHoverHandler(QObject *parent)
    : QObject(parent)
    , m_helpEngineNeedsSetup(false)
con's avatar
con committed
72
{
73
    m_modelManager = ExtensionSystem::PluginManager::instance()->getObject<CppTools::CppModelManagerInterface>();
74

75
76
    ICore *core = ICore::instance();
    QFileInfo fi(core->settings()->fileName());
77
78
79
80
81
82
    // FIXME shouldn't the help engine create the directory if it doesn't exist?
    QDir directory(fi.absolutePath()+"/qtcreator");
    if (!directory.exists())
        directory.mkpath(directory.absolutePath());

    m_helpEngine = new QHelpEngineCore(directory.absolutePath()
con's avatar
con committed
83
84
                                       + QLatin1String("/helpcollection.qhc"), this);
    //m_helpEngine->setAutoSaveFilter(false);
85
86
    if (!m_helpEngine->setupData())
        qWarning() << "Could not initialize help engine:" << m_helpEngine->error();
con's avatar
con committed
87
    m_helpEngine->setCurrentFilter(tr("Unfiltered"));
88
    m_helpEngineNeedsSetup = m_helpEngine->registeredDocumentations().count() == 0;
89
90

    // Listen for editor opened events in order to connect to tooltip/helpid requests
91
    connect(core->editorManager(), SIGNAL(editorOpened(Core::IEditor *)),
92
            this, SLOT(editorOpened(Core::IEditor *)));
con's avatar
con committed
93
94
95
96
97
98
99
}

void CppHoverHandler::updateContextHelpId(TextEditor::ITextEditor *editor, int pos)
{
    updateHelpIdAndTooltip(editor, pos);
}

100
void CppHoverHandler::editorOpened(IEditor *editor)
con's avatar
con committed
101
{
102
103
104
105
106
107
108
109
110
111
    CPPEditorEditable *cppEditor = qobject_cast<CPPEditorEditable *>(editor);
    if (!cppEditor)
        return;

    connect(cppEditor, SIGNAL(tooltipRequested(TextEditor::ITextEditor*, QPoint, int)),
            this, SLOT(showToolTip(TextEditor::ITextEditor*, QPoint, int)));

    connect(cppEditor, SIGNAL(contextHelpIdRequested(TextEditor::ITextEditor*, int)),
            this, SLOT(updateContextHelpId(TextEditor::ITextEditor*, int)));
}
con's avatar
con committed
112

113
114
115
void CppHoverHandler::showToolTip(TextEditor::ITextEditor *editor, const QPoint &point, int pos)
{
    if (!editor)
con's avatar
con committed
116
117
        return;

118
119
    ICore *core = ICore::instance();
    const int dbgcontext = core->uniqueIDManager()->uniqueIdentifier(Debugger::Constants::C_GDBDEBUGGER);
120

121
    if (core->hasContext(dbgcontext))
con's avatar
con committed
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
        return;

    updateHelpIdAndTooltip(editor, pos);

    if (m_toolTip.isEmpty())
        QToolTip::hideText();
    else {
        const QPoint pnt = point - QPoint(0,
#ifdef Q_WS_WIN
        24
#else
        16
#endif
        );

        QToolTip::showText(pnt, m_toolTip);
    }
}

141
static QString buildHelpId(Symbol *symbol, Name *name)
con's avatar
con committed
142
143
144
{
    Scope *scope = 0;

145
146
147
    if (symbol) {
        scope = symbol->scope();
        name = symbol->name();
con's avatar
con committed
148
149
    }

Roberto Raggi's avatar
Roberto Raggi committed
150
151
152
    if (! name)
        return QString();

con's avatar
con committed
153
154
155
156
157
158
159
160
    Overview overview;
    overview.setShowArgumentNames(false);
    overview.setShowReturnTypes(false);

    QStringList qualifiedNames;
    qualifiedNames.prepend(overview.prettyName(name));

    for (; scope; scope = scope->enclosingScope()) {
161
162
163
164
        Symbol *owner = scope->owner();

        if (owner && owner->name() && ! scope->isEnumScope()) {
            Name *name = owner->name();
con's avatar
con committed
165
            Identifier *id = 0;
166

167
            if (NameId *nameId = name->asNameId())
con's avatar
con committed
168
                id = nameId->identifier();
169
170

            else if (TemplateNameId *nameId = name->asTemplateNameId())
con's avatar
con committed
171
                id = nameId->identifier();
172

con's avatar
con committed
173
174
175
176
177
178
179
180
            if (id)
                qualifiedNames.prepend(QString::fromLatin1(id->chars(), id->size()));
        }
    }

    return qualifiedNames.join(QLatin1String("::"));
}

181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// ### move me
static FullySpecifiedType resolve(const FullySpecifiedType &ty,
                                  const LookupContext &context,
                                  Symbol **resolvedSymbol,
                                  Name **resolvedName)
{
    Control *control = context.control();

    if (const PointerType *ptrTy = ty->asPointerType()) {
        return control->pointerType(resolve(ptrTy->elementType(), context,
                                            resolvedSymbol, resolvedName));

    } else if (const ReferenceType *refTy = ty->asReferenceType()) {
        return control->referenceType(resolve(refTy->elementType(), context,
                                              resolvedSymbol, resolvedName));

    } else if (const PointerToMemberType *ptrToMemTy = ty->asPointerToMemberType()) {
        return control->pointerToMemberType(ptrToMemTy->memberName(),
                                            resolve(ptrToMemTy->elementType(), context,
                                                    resolvedSymbol, resolvedName));

    } else if (const NamedType *namedTy = ty->asNamedType()) {
        if (resolvedName)
            *resolvedName = namedTy->name();

        const QList<Symbol *> candidates = context.resolve(namedTy->name());

        foreach (Symbol *c, candidates) {
            if (c->isClass() || c->isEnum()) {
                if (resolvedSymbol)
                    *resolvedSymbol = c;

                return c->type();
            }
        }

    } else if (const Namespace *nsTy = ty->asNamespaceType()) {
        if (resolvedName)
            *resolvedName = nsTy->name();

221
222
223
        if (resolvedSymbol)
            *resolvedSymbol = const_cast<Namespace *>(nsTy);

224
225
226
227
228
229
230
231
232
233
234
    } else if (const Class *classTy = ty->asClassType()) {
        if (resolvedName)
            *resolvedName = classTy->name();

        if (resolvedSymbol)
            *resolvedSymbol = const_cast<Class *>(classTy);

    } else if (const ForwardClassDeclaration *fwdClassTy = ty->asForwardClassDeclarationType()) {
        if (resolvedName)
            *resolvedName = fwdClassTy->name();

235
236
237
        if (resolvedSymbol)
            *resolvedSymbol = const_cast<ForwardClassDeclaration *>(fwdClassTy);

238
239
240
241
    } else if (const Enum *enumTy = ty->asEnumType()) {
        if (resolvedName)
            *resolvedName = enumTy->name();

242
243
244
        if (resolvedSymbol)
            *resolvedSymbol = const_cast<Enum *>(enumTy);

245
246
247
248
    } else if (const Function *funTy = ty->asFunctionType()) {
        if (resolvedName)
            *resolvedName = funTy->name();

249
250
251
        if (resolvedSymbol)
            *resolvedSymbol = const_cast<Function *>(funTy);

252
253
254
255
256
    }

    return ty;
}

con's avatar
con committed
257
258
259
260
261
void CppHoverHandler::updateHelpIdAndTooltip(TextEditor::ITextEditor *editor, int pos)
{
    m_helpId.clear();
    m_toolTip.clear();

262
263
264
    if (!m_modelManager)
        return;

Roberto Raggi's avatar
Roberto Raggi committed
265
    TextEditor::BaseTextEditor *edit = qobject_cast<TextEditor::BaseTextEditor *>(editor->widget());
con's avatar
con committed
266
267
268
    if (!edit)
        return;

269
270
271
272
273
274
    const Snapshot documents = m_modelManager->snapshot();
    const QString fileName = editor->file()->fileName();
    Document::Ptr doc = documents.value(fileName);
    if (! doc)
        return; // nothing to do

con's avatar
con committed
275
276
    QTextCursor tc(edit->document());
    tc.setPosition(pos);
277
    const unsigned lineNumber = tc.block().blockNumber() + 1;
con's avatar
con committed
278

279
280
281
282
    // Find the last symbol up to the cursor position
    int line = 0, column = 0;
    editor->convertPosition(tc.position(), &line, &column);
    Symbol *lastSymbol = doc->findSymbolAt(line, column);
283

284
285
286
287
288
289
290
    TypeOfExpression typeOfExpression;
    typeOfExpression.setSnapshot(documents);

    foreach (Document::DiagnosticMessage m, doc->diagnosticMessages()) {
        if (m.line() == lineNumber) {
            m_toolTip = m.text();
            break;
con's avatar
con committed
291
        }
292
    }
con's avatar
con committed
293

294
295
296
    if (m_toolTip.isEmpty()) {
        foreach (const Document::Include &incl, doc->includes()) {
            if (incl.line() == lineNumber) {
297
                m_toolTip = QDir::toNativeSeparators(incl.fileName());
298
                break;
299
300
301
302
            }
        }
    }

con's avatar
con committed
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
    if (m_toolTip.isEmpty()) {
        // Move to the end of a qualified name
        bool stop = false;
        while (!stop) {
            const QChar ch = editor->characterAt(tc.position());
            if (ch.isLetterOrNumber() || ch == QLatin1Char('_'))
                tc.setPosition(tc.position() + 1);
            else if (ch == QLatin1Char(':') && editor->characterAt(tc.position() + 1) == QLatin1Char(':')) {
                tc.setPosition(tc.position() + 2);
            } else {
                stop = true;
            }
        }

        // Fetch the expression's code.
        ExpressionUnderCursor expressionUnderCursor;
        const QString expression = expressionUnderCursor(tc);

321
322
323
        const QList<TypeOfExpression::Result> types =
                typeOfExpression(expression, doc, lastSymbol);

324
        if (!types.isEmpty()) {
325
            const TypeOfExpression::Result result = types.first();
326

327
328
            FullySpecifiedType firstType = result.first; // result of `type of expression'.
            Symbol *lookupSymbol = result.second;        // lookup symbol
329

330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
            Symbol *resolvedSymbol = 0;
            Name *resolvedName = 0;
            firstType = resolve(firstType, typeOfExpression.lookupContext(),
                                &resolvedSymbol, &resolvedName);

            m_helpId = buildHelpId(resolvedSymbol, resolvedName);

            Symbol *symbol = result.second;
            if (resolvedSymbol)
                symbol = resolvedSymbol;

            Overview overview;
            overview.setShowArgumentNames(true);
            overview.setShowReturnTypes(true);
            overview.setShowFullyQualifiedNamed(true);

            if (lookupSymbol && lookupSymbol->isDeclaration()) {
                Declaration *decl = lookupSymbol->asDeclaration();
                m_toolTip = overview.prettyType(firstType, decl->name());

            } else if (firstType->isClassType() || firstType->isEnumType() ||
                       firstType->isForwardClassDeclarationType()) {
352
                m_toolTip = m_helpId;
353
354
355
356

            } else {
                m_toolTip = overview.prettyType(firstType, m_helpId);

con's avatar
con committed
357
358
359
360
            }
        }
    }

361
    if (m_toolTip.isEmpty()) {
362
363
        foreach (const Document::MacroUse &use, doc->macroUses()) {
            if (use.contains(pos)) {
364
365
366
                const Macro m = use.macro();
                m_toolTip = m.toString();
                m_helpId = m.name();
367
368
369
370
371
                break;
            }
        }
    }

372
373
374
375
376
377
    if (m_helpEngineNeedsSetup
        && m_helpEngine->registeredDocumentations().count() > 0) {
        m_helpEngine->setupData();
        m_helpEngineNeedsSetup = false;
    }

378
    if (!m_toolTip.isEmpty())
379
380
        m_toolTip = Qt::escape(m_toolTip);

con's avatar
con committed
381
382
    if (!m_helpId.isEmpty() && !m_helpEngine->linksForIdentifier(m_helpId).isEmpty()) {
        m_toolTip = QString(QLatin1String("<table><tr><td valign=middle><nobr>%1</td>"
383
                                          "<td><img src=\":/cppeditor/images/f1.svg\"></td></tr></table>"))
384
                    .arg(m_toolTip);
con's avatar
con committed
385
386
        editor->setContextHelpId(m_helpId);
    } else if (!m_toolTip.isEmpty()) {
387
        m_toolTip = QString(QLatin1String("<nobr>%1")).arg(m_toolTip);
con's avatar
con committed
388
389
    }
}