glsleditor.cpp 12.3 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8 9 10 11 12 13 14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15 16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22 23 24 25
** 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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
29 30 31 32

#include "glsleditor.h"
#include "glsleditorconstants.h"
#include "glsleditorplugin.h"
33
#include "glslhighlighter.h"
34
#include "glslautocompleter.h"
Leandro Melo's avatar
Leandro Melo committed
35
#include "glslcompletionassist.h"
Daniel Teske's avatar
Daniel Teske committed
36
#include "glslindenter.h"
37

Roberto Raggi's avatar
Roberto Raggi committed
38 39 40
#include <glsl/glsllexer.h>
#include <glsl/glslparser.h>
#include <glsl/glslengine.h>
41
#include <glsl/glslsemantic.h>
Roberto Raggi's avatar
Roberto Raggi committed
42
#include <glsl/glslsymbols.h>
Roberto Raggi's avatar
Roberto Raggi committed
43

44 45 46 47 48
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
49
#include <coreplugin/id.h>
50
#include <coreplugin/mimedatabase.h>
51

52
#include <extensionsystem/pluginmanager.h>
53 54
#include <extensionsystem/pluginspec.h>

55
#include <texteditor/refactoroverlay.h>
56 57 58 59 60
#include <texteditor/syntaxhighlighter.h>
#include <texteditor/texteditoractionhandler.h>
#include <texteditor/texteditorconstants.h>
#include <texteditor/texteditorsettings.h>

61
#include <qmldesigner/qmldesignerconstants.h>
62

63
#include <utils/changeset.h>
64
#include <utils/qtcassert.h>
65 66
#include <utils/uncommentselection.h>

67 68
#include <QCoreApplication>
#include <QSettings>
69
#include <QComboBox>
70
#include <QFileInfo>
71
#include <QHeaderView>
72 73
#include <QTextBlock>
#include <QTimer>
74
#include <QTreeView>
75

76
using namespace TextEditor;
77
using namespace GLSL;
78

79
namespace GlslEditor {
80
namespace Internal {
81 82 83 84 85

enum {
    UPDATE_DOCUMENT_DEFAULT_INTERVAL = 150
};

86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
class CreateRanges: protected GLSL::Visitor
{
    QTextDocument *textDocument;
    Document::Ptr glslDocument;

public:
    CreateRanges(QTextDocument *textDocument, Document::Ptr glslDocument)
        : textDocument(textDocument), glslDocument(glslDocument) {}

    void operator()(GLSL::AST *ast) { accept(ast); }

protected:
    using GLSL::Visitor::visit;

    virtual void endVisit(GLSL::CompoundStatementAST *ast)
    {
        if (ast->symbol) {
            QTextCursor tc(textDocument);
            tc.setPosition(ast->start);
            tc.setPosition(ast->end, QTextCursor::KeepAnchor);
            glslDocument->addRange(tc, ast->symbol);
        }
    }
};

111 112 113
//
//  GlslEditorWidget
//
114

115
class GlslEditorWidget : public BaseTextEditorWidget
116
{
117 118
public:
    GlslEditorWidget();
119

120 121
    int editorRevision() const;
    bool isOutdated() const;
122

123 124 125 126 127 128 129 130 131 132 133 134 135
    QSet<QString> identifiers() const;

    IAssistInterface *createAssistInterface(AssistKind assistKind, AssistReason reason) const;

private:
    void updateDocumentNow();
    void setSelectedElements();
    QString wordUnderCursor() const;

    QTimer m_updateDocumentTimer;
    QComboBox *m_outlineCombo;
    Document::Ptr m_glslDocument;
};
136

137
GlslEditorWidget::GlslEditorWidget()
138
{
139
    setCompletionAssistProvider(ExtensionSystem::PluginManager::getObject<GlslCompletionAssistProvider>());
hjk's avatar
hjk committed
140
    setAutoCompleter(new GlslCompleter);
141
    m_outlineCombo = 0;
142 143 144 145
    setParenthesesMatchingEnabled(true);
    setMarksVisible(true);
    setCodeFoldingSupported(true);

146 147 148 149
    m_updateDocumentTimer.setInterval(UPDATE_DOCUMENT_DEFAULT_INTERVAL);
    m_updateDocumentTimer.setSingleShot(true);
    connect(&m_updateDocumentTimer, &QTimer::timeout,
            this, &GlslEditorWidget::updateDocumentNow);
150

151 152
    connect(this, &QPlainTextEdit::textChanged,
            [this]() { m_updateDocumentTimer.start(); });
153

154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
    m_outlineCombo = new QComboBox;
    m_outlineCombo->setMinimumContentsLength(22);

    // ### m_outlineCombo->setModel(m_outlineModel);

    QTreeView *treeView = new QTreeView;
    treeView->header()->hide();
    treeView->setItemsExpandable(false);
    treeView->setRootIsDecorated(false);
    m_outlineCombo->setView(treeView);
    treeView->expandAll();

    //m_outlineCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);

    // Make the combo box prefer to expand
    QSizePolicy policy = m_outlineCombo->sizePolicy();
    policy.setHorizontalPolicy(QSizePolicy::Expanding);
    m_outlineCombo->setSizePolicy(policy);

173
    insertExtraToolBarWidget(BaseTextEditorWidget::Left, m_outlineCombo);
174

175 176 177 178 179 180 181 182 183 184
//    if (m_modelManager) {
//        m_semanticHighlighter->setModelManager(m_modelManager);
//        connect(m_modelManager, SIGNAL(documentUpdated(GLSL::Document::Ptr)),
//                this, SLOT(onDocumentUpdated(GLSL::Document::Ptr)));
//        connect(m_modelManager, SIGNAL(libraryInfoUpdated(QString,GLSL::LibraryInfo)),
//                this, SLOT(forceSemanticRehighlight()));
//        connect(this->document(), SIGNAL(modificationChanged(bool)), this, SLOT(modificationChanged(bool)));
//    }
}

185
int GlslEditorWidget::editorRevision() const
186 187 188 189 190
{
    //return document()->revision();
    return 0;
}

191
bool GlslEditorWidget::isOutdated() const
192 193 194 195 196 197 198
{
//    if (m_semanticInfo.revision() != editorRevision())
//        return true;

    return false;
}

199
QString GlslEditorWidget::wordUnderCursor() const
200 201
{
    QTextCursor tc = textCursor();
202
    const QChar ch = document()->characterAt(tc.position() - 1);
203 204 205 206 207 208 209 210 211
    // make sure that we're not at the start of the next word.
    if (ch.isLetterOrNumber() || ch == QLatin1Char('_'))
        tc.movePosition(QTextCursor::Left);
    tc.movePosition(QTextCursor::StartOfWord);
    tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
    const QString word = tc.selectedText();
    return word;
}

212
void GlslEditorWidget::updateDocumentNow()
213
{
214
    m_updateDocumentTimer.stop();
Roberto Raggi's avatar
Roberto Raggi committed
215

216
    int variant = languageVariant(textDocument()->mimeType());
Roberto Raggi's avatar
Roberto Raggi committed
217 218 219
    const QString contents = toPlainText(); // get the code from the editor
    const QByteArray preprocessedCode = contents.toLatin1(); // ### use the QtCreator C++ preprocessor.

220
    Document::Ptr doc(new Document());
221
    Engine *engine = new GLSL::Engine();
222 223
    doc->_engine = new GLSL::Engine();
    Parser parser(doc->_engine, preprocessedCode.constData(), preprocessedCode.size(), variant);
224
    TranslationUnitAST *ast = parser.parse();
Roberto Raggi's avatar
Roberto Raggi committed
225 226
    if (ast != 0 || extraSelections(CodeWarningsSelection).isEmpty()) {
        Semantic sem;
227 228
        Scope *globalScope = engine->newNamespace();
        doc->_globalScope = globalScope;
hjk's avatar
hjk committed
229
        const GlslEditorPlugin::InitFile *file = GlslEditorPlugin::shaderInit(variant);
hjk's avatar
hjk committed
230 231
        sem.translationUnit(file->ast, globalScope, file->engine);
        if (variant & Lexer::Variant_VertexShader) {
hjk's avatar
hjk committed
232
            file = GlslEditorPlugin::vertexShaderInit(variant);
hjk's avatar
hjk committed
233 234 235
            sem.translationUnit(file->ast, globalScope, file->engine);
        }
        if (variant & Lexer::Variant_FragmentShader) {
hjk's avatar
hjk committed
236
            file = GlslEditorPlugin::fragmentShaderInit(variant);
hjk's avatar
hjk committed
237 238
            sem.translationUnit(file->ast, globalScope, file->engine);
        }
239 240 241 242
        sem.translationUnit(ast, globalScope, engine);

        CreateRanges createRanges(document(), doc);
        createRanges(ast);
Roberto Raggi's avatar
Roberto Raggi committed
243 244 245 246 247 248 249 250 251 252 253 254

        QTextCharFormat errorFormat;
        errorFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline);
        errorFormat.setUnderlineColor(Qt::red);

        QTextCharFormat warningFormat;
        warningFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline);
        warningFormat.setUnderlineColor(Qt::darkYellow);

        QList<QTextEdit::ExtraSelection> sels;
        QSet<int> errors;

255
        foreach (const DiagnosticMessage &m, engine->diagnosticMessages()) {
Roberto Raggi's avatar
Roberto Raggi committed
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
            if (! m.line())
                continue;
            else if (errors.contains(m.line()))
                continue;

            errors.insert(m.line());

            QTextCursor cursor(document()->findBlockByNumber(m.line() - 1));
            cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);

            QTextEdit::ExtraSelection sel;
            sel.cursor = cursor;
            sel.format = m.isError() ? errorFormat : warningFormat;
            sel.format.setToolTip(m.message());
            sels.append(sel);
        }

        setExtraSelections(CodeWarningsSelection, sels);
274
        m_glslDocument = doc;
Roberto Raggi's avatar
Roberto Raggi committed
275
    }
276
}
Roberto Raggi's avatar
Roberto Raggi committed
277

278
int languageVariant(const QString &type)
Roberto Raggi's avatar
Roberto Raggi committed
279
{
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
    int variant = 0;
    bool isVertex = false;
    bool isFragment = false;
    bool isDesktop = false;
    if (type.isEmpty()) {
        // ### Before file has been opened, so don't know the mime type.
        isVertex = true;
        isFragment = true;
    } else if (type == QLatin1String("text/x-glsl") ||
               type == QLatin1String("application/x-glsl")) {
        isVertex = true;
        isFragment = true;
        isDesktop = true;
    } else if (type == QLatin1String("text/x-glsl-vert")) {
        isVertex = true;
        isDesktop = true;
    } else if (type == QLatin1String("text/x-glsl-frag")) {
        isFragment = true;
        isDesktop = true;
    } else if (type == QLatin1String("text/x-glsl-es-vert")) {
        isVertex = true;
    } else if (type == QLatin1String("text/x-glsl-es-frag")) {
        isFragment = true;
    }
    if (isDesktop)
        variant |= Lexer::Variant_GLSL_120;
    else
307
        variant |= Lexer::Variant_GLSL_ES_100;
308 309 310 311 312
    if (isVertex)
        variant |= Lexer::Variant_VertexShader;
    if (isFragment)
        variant |= Lexer::Variant_FragmentShader;
    return variant;
Roberto Raggi's avatar
Roberto Raggi committed
313
}
314

315 316
IAssistInterface *GlslEditorWidget::createAssistInterface(
    AssistKind kind, AssistReason reason) const
Leandro Melo's avatar
Leandro Melo committed
317
{
318
    if (kind == Completion)
hjk's avatar
hjk committed
319
        return new GlslCompletionAssistInterface(document(),
Leandro Melo's avatar
Leandro Melo committed
320
                                                 position(),
321
                                                 textDocument()->filePath(),
Leandro Melo's avatar
Leandro Melo committed
322
                                                 reason,
323
                                                 textDocument()->mimeType(),
hjk's avatar
hjk committed
324
                                                 m_glslDocument);
Leandro Melo's avatar
Leandro Melo committed
325 326
    return BaseTextEditorWidget::createAssistInterface(kind, reason);
}
327 328 329 330 331 332


//
//  GlslEditor
//

333
class GlslEditor : public TextEditor::BaseTextEditor
334
{
335 336 337 338 339
public:
    GlslEditor()
    {
        setDuplicateSupported(true);
    }
340

341 342 343 344 345 346 347
    bool open(QString *errorString, const QString &fileName, const QString &realFileName)
    {
        textDocument()->setMimeType(Core::MimeDatabase::findByFile(QFileInfo(fileName)).type());
        bool b = BaseTextEditor::open(errorString, fileName, realFileName);
        return b;
    }
};
348 349 350 351 352 353 354 355


//
//  GlslEditorFactory
//

GlslEditorFactory::GlslEditorFactory()
{
356 357 358 359 360 361 362 363 364 365 366 367
    setId(Constants::C_GLSLEDITOR_ID);
    setDisplayName(qApp->translate("OpenWith::Editors", Constants::C_GLSLEDITOR_DISPLAY_NAME));
    addMimeType(Constants::GLSL_MIMETYPE);
    addMimeType(Constants::GLSL_MIMETYPE_VERT);
    addMimeType(Constants::GLSL_MIMETYPE_FRAG);
    addMimeType(Constants::GLSL_MIMETYPE_VERT_ES);
    addMimeType(Constants::GLSL_MIMETYPE_FRAG_ES);

    setDocumentCreator([]() { return new BaseTextDocument(Constants::C_GLSLEDITOR_ID); });
    setEditorWidgetCreator([]() { return new GlslEditorWidget; });
    setEditorCreator([]() { return new GlslEditor; });
    setIndenterCreator([]() { return new GlslIndenter; });
368
    setSyntaxHighlighterCreator([]() { return new GlslHighlighter; });
369
    setCommentStyle(Utils::CommentDefinition::CppStyle);
370

371
    setEditorActionHandlers(TextEditorActionHandler::Format
372 373
                          | TextEditorActionHandler::UnCommentSelection
                          | TextEditorActionHandler::UnCollapseAll);
374 375 376 377

}

} // namespace Internal
378
} // namespace GlslEditor