Browse Source

basic syntax highlighting

cl-refactor
arkpar 10 years ago
parent
commit
e70bf144c6
  1. 15
      mix/CodeEditorExtensionManager.cpp
  2. 4
      mix/CodeEditorExtensionManager.h
  3. 139
      mix/CodeHighlighter.cpp
  4. 94
      mix/CodeHighlighter.h
  5. 66
      mix/CodeModel.cpp
  6. 24
      mix/CodeModel.h
  7. 6
      mix/qml/MainContent.qml

15
mix/CodeEditorExtensionManager.cpp

@ -25,13 +25,13 @@
#include <QQmlEngine> #include <QQmlEngine>
#include <QQmlComponent> #include <QQmlComponent>
#include <QQuickTextDocument> #include <QQuickTextDocument>
#include <libevm/VM.h>
#include "ConstantCompilationControl.h" #include "ConstantCompilationControl.h"
#include "AssemblyDebuggerControl.h" #include "AssemblyDebuggerControl.h"
#include "StateListView.h" #include "StateListView.h"
#include "AppContext.h" #include "AppContext.h"
#include "MixApplication.h" #include "MixApplication.h"
#include "CodeModel.h" #include "CodeModel.h"
#include "CodeHighlighter.h"
#include "CodeEditorExtensionManager.h" #include "CodeEditorExtensionManager.h"
using namespace dev::mix; using namespace dev::mix;
@ -67,8 +67,9 @@ void CodeEditorExtensionManager::initExtensions()
std::shared_ptr<ConstantCompilationControl> output = std::make_shared<ConstantCompilationControl>(m_appContext); std::shared_ptr<ConstantCompilationControl> output = std::make_shared<ConstantCompilationControl>(m_appContext);
std::shared_ptr<AssemblyDebuggerControl> debug = std::make_shared<AssemblyDebuggerControl>(m_appContext); std::shared_ptr<AssemblyDebuggerControl> debug = std::make_shared<AssemblyDebuggerControl>(m_appContext);
std::shared_ptr<StateListView> stateList = std::make_shared<StateListView>(m_appContext); std::shared_ptr<StateListView> stateList = std::make_shared<StateListView>(m_appContext);
QObject::connect(m_doc, &QTextDocument::contentsChanged, [=]() { m_appContext->codeModel()->registerCodeChange(m_doc->toPlainText()); }); QObject::connect(m_doc, &QTextDocument::contentsChange, this, &CodeEditorExtensionManager::onCodeChange);
QObject::connect(debug.get(), &AssemblyDebuggerControl::runFailed, output.get(), &ConstantCompilationControl::displayError); QObject::connect(debug.get(), &AssemblyDebuggerControl::runFailed, output.get(), &ConstantCompilationControl::displayError);
QObject::connect(m_appContext->codeModel(), &CodeModel::compilationComplete, this, &CodeEditorExtensionManager::applyCodeHighlight);
initExtension(output); initExtension(output);
initExtension(debug); initExtension(debug);
@ -111,6 +112,16 @@ void CodeEditorExtensionManager::setEditor(QQuickItem* _editor)
} }
} }
void CodeEditorExtensionManager::onCodeChange()
{
m_appContext->codeModel()->registerCodeChange(m_doc->toPlainText());
}
void CodeEditorExtensionManager::applyCodeHighlight()
{
m_appContext->codeModel()->updateFormatting(m_doc);
}
void CodeEditorExtensionManager::setRightTabView(QQuickItem* _tabView) void CodeEditorExtensionManager::setRightTabView(QQuickItem* _tabView)
{ {
m_rightTabView = _tabView; m_rightTabView = _tabView;

4
mix/CodeEditorExtensionManager.h

@ -61,6 +61,10 @@ public:
/// Set current right tab view. /// Set current right tab view.
void setRightTabView(QQuickItem*); void setRightTabView(QQuickItem*);
private slots:
void onCodeChange();
void applyCodeHighlight();
private: private:
QQuickItem* m_editor; QQuickItem* m_editor;
QVector<std::shared_ptr<Extension>> m_features; QVector<std::shared_ptr<Extension>> m_features;

139
mix/CodeHighlighter.cpp

@ -0,0 +1,139 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
cpp-ethereum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file CodeHighlighter.cpp
* @author Arkadiy Paronyan arkadiy@ethdev.com
* @date 2015
* Ethereum IDE client.
*/
#include <algorithm>
#include <QRegularExpression>
#include <QTextDocument>
#include <QTextBlock>
#include <QTextLayout>
#include "CodeHighlighter.h"
#include "libsolidity/ASTVisitor.h"
#include "libsolidity/AST.h"
#include "libsolidity/Scanner.h"
namespace dev
{
namespace mix
{
CodeHighlighterSettings::CodeHighlighterSettings()
{
bg = QColor(0x00, 0x2b, 0x36);
fg = QColor (0xee, 0xe8, 0xd5);
formats[Keyword].setForeground(QColor(0x93, 0xa1, 0xa1));
//formats[Type].setForeground(Qt::darkCyan);
formats[Comment].setForeground(QColor(0x85, 0x99, 0x00));
formats[StringLiteral].setForeground(QColor(0xdc, 0x32, 0x2f));
formats[NumLiteral].setForeground(fg);
formats[Import].setForeground(QColor(0x6c, 0x71, 0xc4));
}
namespace
{
using namespace dev::solidity;
class HighlightVisitor : public solidity::ASTConstVisitor
{
public:
HighlightVisitor(CodeHighlighter::Formats* _formats) { m_formats = _formats; }
private:
CodeHighlighter::Formats* m_formats;
virtual bool visit(ImportDirective const& _node)
{
m_formats->push_back(CodeHighlighter::FormatRange(CodeHighlighterSettings::Import, _node.getLocation()));
return true;
}
};
}
CodeHighlighter::FormatRange::FormatRange(CodeHighlighterSettings::Token _t, solidity::Location const& _location):
token(_t), start(_location.start), length(_location.end - _location.start)
{}
void CodeHighlighter::processSource(solidity::Scanner* _scanner)
{
solidity::Token::Value token = _scanner->getCurrentToken();
while (token != Token::EOS)
{
if ((token >= Token::BREAK && token < Token::TYPES_END) ||
token == Token::IN || token == Token::DELETE || token == Token::NULL_LITERAL || token == Token::TRUE_LITERAL || token == Token::FALSE_LITERAL)
m_formats.push_back(FormatRange(CodeHighlighterSettings::Keyword, _scanner->getCurrentLocation()));
else if (token == Token::STRING_LITERAL)
m_formats.push_back(FormatRange(CodeHighlighterSettings::StringLiteral, _scanner->getCurrentLocation()));
else if (token == Token::COMMENT_LITERAL)
m_formats.push_back(FormatRange(CodeHighlighterSettings::Comment, _scanner->getCurrentLocation()));
else if (token == Token::NUMBER)
m_formats.push_back(FormatRange(CodeHighlighterSettings::NumLiteral, _scanner->getCurrentLocation()));
token = _scanner->next();
}
std::sort(m_formats.begin(), m_formats.end());
}
void CodeHighlighter::processAST(solidity::ASTNode const& _ast)
{
HighlightVisitor visitor(&m_formats);
_ast.accept(visitor);
std::sort(m_formats.begin(), m_formats.end());
}
void CodeHighlighter::updateFormatting(QTextDocument* _document, CodeHighlighterSettings const& _settings)
{
QTextBlock block = _document->firstBlock();
QList<QTextLayout::FormatRange> ranges;
Formats::const_iterator format = m_formats.begin();
while (true)
{
while ((format == m_formats.end() || (block.position() + block.length() <= format->start)) && block.isValid())
{
auto layout = block.layout();
layout->clearAdditionalFormats();
layout->setAdditionalFormats(ranges);
_document->markContentsDirty(block.position(), block.length());
block = block.next();
ranges.clear();
}
if (!block.isValid())
break;
int intersectionStart = std::max(format->start, block.position());
int intersectionLength = std::min(format->start + format->length, block.position() + block.length()) - intersectionStart;
if (intersectionLength > 0)
{
QTextLayout::FormatRange range;
range.format = _settings.formats[format->token];
range.start = format->start - block.position();
range.length = format->length;
ranges.append(range);
}
++format;
}
}
}
}

94
mix/CodeHighlighter.h

@ -0,0 +1,94 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
cpp-ethereum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file CodeHighlighter.h
* @author Arkadiy Paronyan arkadiy@ethdev.com
* @date 2015
* Ethereum IDE client.
*/
#pragma once
#include <vector>
#include <QString>
#include <QTextCharFormat>
class QTextDocument;
namespace dev
{
namespace solidity
{
class ASTNode;
class Scanner;
struct Location;
}
namespace mix
{
class CodeHighlighterSettings
{
public:
enum Token
{
Import,
Keyword,
Comment,
StringLiteral,
NumLiteral,
Size, //this must be kept last
};
CodeHighlighterSettings();
///Format for each token
QTextCharFormat formats[Size];
///Background color
QColor bg;
///Foreground color
QColor fg;
};
class CodeHighlighter
{
public:
struct FormatRange
{
FormatRange(CodeHighlighterSettings::Token _t, int _start, int _length): token(_t), start(_start), length(_length) {}
FormatRange(CodeHighlighterSettings::Token _t, solidity::Location const& _location);
bool operator<(FormatRange const& _other) { return start < _other.start || (start == _other.start && length < _other.length); }
CodeHighlighterSettings::Token token;
int start;
int length;
};
typedef std::vector<FormatRange> Formats; // Sorted by start position
public:
/// Collect highligting information
void processSource(solidity::Scanner* _scanner);
void processAST(solidity::ASTNode const& _ast);
void updateFormatting(QTextDocument* _document, CodeHighlighterSettings const& _settings);
private:
Formats m_formats;
};
}
}

66
mix/CodeModel.cpp

@ -24,12 +24,14 @@
#include <QDebug> #include <QDebug>
#include <QApplication> #include <QApplication>
#include <QtQml> #include <QtQml>
#include <libsolidity/Scanner.h>
#include <libsolidity/CompilerStack.h> #include <libsolidity/CompilerStack.h>
#include <libsolidity/SourceReferenceFormatter.h> #include <libsolidity/SourceReferenceFormatter.h>
#include <libevmcore/Instruction.h> #include <libevmcore/Instruction.h>
#include "QContractDefinition.h" #include "QContractDefinition.h"
#include "QFunctionDefinition.h" #include "QFunctionDefinition.h"
#include "QVariableDeclaration.h" #include "QVariableDeclaration.h"
#include "CodeHighlighter.h"
#include "CodeModel.h" #include "CodeModel.h"
namespace dev namespace dev
@ -42,28 +44,36 @@ void BackgroundWorker::queueCodeChange(int _jobId, QString const& _content)
m_model->runCompilationJob(_jobId, _content); m_model->runCompilationJob(_jobId, _content);
} }
CompilationResult::CompilationResult(QObject *_parent): CompilationResult::CompilationResult():
QObject(_parent), m_successfull(false), QObject(nullptr), m_successfull(false),
m_contract(new QContractDefinition()) m_contract(new QContractDefinition()),
m_codeHighlighter(new CodeHighlighter())
{} {}
CompilationResult::CompilationResult(const solidity::CompilerStack& _compiler, QObject *_parent): CompilationResult::CompilationResult(const solidity::CompilerStack& _compiler):
QObject(_parent), m_successfull(true), QObject(nullptr), m_successfull(true)
m_contract(new QContractDefinition(&_compiler.getContractDefinition(std::string()))), {
m_bytes(_compiler.getBytecode()), if (!_compiler.getContractNames().empty())
m_assemblyCode(QString::fromStdString((dev::eth::disassemble(m_bytes)))) {
{} m_contract.reset(new QContractDefinition(&_compiler.getContractDefinition(std::string())));
m_bytes = _compiler.getBytecode();
m_assemblyCode = QString::fromStdString(dev::eth::disassemble(m_bytes));
}
else
m_contract.reset(new QContractDefinition());
}
CompilationResult::CompilationResult(CompilationResult const& _prev, QString const& _compilerMessage, QObject* _parent): CompilationResult::CompilationResult(CompilationResult const& _prev, QString const& _compilerMessage):
QObject(_parent), m_successfull(false), QObject(nullptr), m_successfull(false),
m_contract(_prev.m_contract), m_contract(_prev.m_contract),
m_compilerMessage(_compilerMessage), m_compilerMessage(_compilerMessage),
m_bytes(_prev.m_bytes), m_bytes(_prev.m_bytes),
m_assemblyCode(_prev.m_assemblyCode) m_assemblyCode(_prev.m_assemblyCode),
m_codeHighlighter(_prev.m_codeHighlighter)
{} {}
CodeModel::CodeModel(QObject* _parent) : QObject(_parent), CodeModel::CodeModel(QObject* _parent) : QObject(_parent),
m_compiling(false), m_result(new CompilationResult(nullptr)), m_backgroundWorker(this), m_backgroundJobId(0) m_compiling(false), m_result(new CompilationResult()), m_codeHighlighterSettings(new CodeHighlighterSettings()), m_backgroundWorker(this), m_backgroundJobId(0)
{ {
m_backgroundWorker.moveToThread(&m_backgroundThread); m_backgroundWorker.moveToThread(&m_backgroundThread);
connect(this, &CodeModel::scheduleCompilationJob, &m_backgroundWorker, &BackgroundWorker::queueCodeChange, Qt::QueuedConnection); connect(this, &CodeModel::scheduleCompilationJob, &m_backgroundWorker, &BackgroundWorker::queueCodeChange, Qt::QueuedConnection);
@ -105,22 +115,35 @@ void CodeModel::runCompilationJob(int _jobId, QString const& _code)
return; //obsolete job return; //obsolete job
solidity::CompilerStack cs; solidity::CompilerStack cs;
std::unique_ptr<CompilationResult> result;
std::string source = _code.toStdString();
// run syntax highlighting first
// @todo combine this with compilation step
auto codeHighlighter = std::make_shared<CodeHighlighter>();
solidity::CharStream stream(source);
solidity::Scanner scanner(stream);
codeHighlighter->processSource(&scanner);
// run compilation
try try
{ {
cs.setSource(_code.toStdString()); cs.setSource(source);
cs.compile(false); cs.compile(false);
std::unique_ptr<CompilationResult> result(new CompilationResult(cs, nullptr)); codeHighlighter->processAST(cs.getAST());
result.reset(new CompilationResult(cs));
qDebug() << QString(QApplication::tr("compilation succeeded")); qDebug() << QString(QApplication::tr("compilation succeeded"));
emit compilationCompleteInternal(result.release());
} }
catch (dev::Exception const& _exception) catch (dev::Exception const& _exception)
{ {
std::ostringstream error; std::ostringstream error;
solidity::SourceReferenceFormatter::printExceptionInformation(error, _exception, "Error", cs); solidity::SourceReferenceFormatter::printExceptionInformation(error, _exception, "Error", cs);
std::unique_ptr<CompilationResult> result(new CompilationResult(*m_result, QString::fromStdString(error.str()), nullptr)); result.reset(new CompilationResult(*m_result, QString::fromStdString(error.str())));
qDebug() << QString(QApplication::tr("compilation failed") + " " + m_result->compilerMessage()); qDebug() << QString(QApplication::tr("compilation failed:") + " " + m_result->compilerMessage());
emit compilationCompleteInternal(result.release());
} }
result->m_codeHighlighter = codeHighlighter;
emit compilationCompleteInternal(result.release());
} }
void CodeModel::onCompilationComplete(CompilationResult*_newResult) void CodeModel::onCompilationComplete(CompilationResult*_newResult)
@ -138,5 +161,10 @@ bool CodeModel::hasContract() const
return m_result->contract()->functionsList().size() > 0; return m_result->contract()->functionsList().size() > 0;
} }
void CodeModel::updateFormatting(QTextDocument* _document)
{
m_result->codeHighlighter()->updateFormatting(_document, *m_codeHighlighterSettings);
}
} }
} }

24
mix/CodeModel.h

@ -23,10 +23,13 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <atomic>
#include <QObject> #include <QObject>
#include <QThread> #include <QThread>
#include <libdevcore/Common.h> #include <libdevcore/Common.h>
class QTextDocument;
namespace dev namespace dev
{ {
@ -39,6 +42,8 @@ namespace mix
{ {
class CodeModel; class CodeModel;
class CodeHighlighter;
class CodeHighlighterSettings;
class QContractDefinition; class QContractDefinition;
//utility class to perform tasks in background thread //utility class to perform tasks in background thread
@ -63,28 +68,26 @@ class CompilationResult : public QObject
public: public:
/// Empty compilation result constructor /// Empty compilation result constructor
CompilationResult(QObject* parent); CompilationResult();
/// Successfull compilation result constructor /// Successfull compilation result constructor
CompilationResult(solidity::CompilerStack const& _compiler, QObject* parent); CompilationResult(solidity::CompilerStack const& _compiler);
/// Failed compilation result constructor /// Failed compilation result constructor
CompilationResult(CompilationResult const& _prev, QString const& _compilerMessage, QObject* parent); CompilationResult(CompilationResult const& _prev, QString const& _compilerMessage);
/// @returns contract definition for QML property /// @returns contract definition for QML property
QContractDefinition* contract() { return m_contract.get(); } QContractDefinition* contract() { return m_contract.get(); }
/// @returns contract definition /// @returns contract definition
std::shared_ptr<QContractDefinition> sharedContract() { return m_contract; } std::shared_ptr<QContractDefinition> sharedContract() { return m_contract; }
/// Indicates if the compilation was successfull /// Indicates if the compilation was successfull
bool successfull() const { return m_successfull; } bool successfull() const { return m_successfull; }
/// @returns compiler error message in case of unsuccessfull compilation /// @returns compiler error message in case of unsuccessfull compilation
QString compilerMessage() const { return m_compilerMessage; } QString compilerMessage() const { return m_compilerMessage; }
/// @returns contract bytecode /// @returns contract bytecode
dev::bytes const& bytes() const { return m_bytes; } dev::bytes const& bytes() const { return m_bytes; }
/// @returns contract bytecode in human-readable form /// @returns contract bytecode in human-readable form
QString assemblyCode() const { return m_assemblyCode; } QString assemblyCode() const { return m_assemblyCode; }
/// Get code highlighter
std::shared_ptr<CodeHighlighter> codeHighlighter() { return m_codeHighlighter; }
private: private:
bool m_successfull; bool m_successfull;
@ -92,7 +95,9 @@ private:
QString m_compilerMessage; ///< @todo: use some structure here QString m_compilerMessage; ///< @todo: use some structure here
dev::bytes m_bytes; dev::bytes m_bytes;
QString m_assemblyCode; QString m_assemblyCode;
std::shared_ptr<CodeHighlighter> m_codeHighlighter;
///@todo syntax highlighting, etc ///@todo syntax highlighting, etc
friend class CodeModel;
}; };
/// Background code compiler /// Background code compiler
@ -117,6 +122,8 @@ public:
bool isCompiling() const { return m_compiling; } bool isCompiling() const { return m_compiling; }
/// @returns true if contract has at least one function /// @returns true if contract has at least one function
bool hasContract() const; bool hasContract() const;
/// Apply text document formatting. @todo Move this to editor module
void updateFormatting(QTextDocument* _document);
signals: signals:
/// Emited on compilation state change /// Emited on compilation state change
@ -141,8 +148,9 @@ private:
void runCompilationJob(int _jobId, QString const& _content); void runCompilationJob(int _jobId, QString const& _content);
void stop(); void stop();
bool m_compiling; std::atomic<bool> m_compiling;
std::unique_ptr<CompilationResult> m_result; std::unique_ptr<CompilationResult> m_result;
std::unique_ptr<CodeHighlighterSettings> m_codeHighlighterSettings;
QThread m_backgroundThread; QThread m_backgroundThread;
BackgroundWorker m_backgroundWorker; BackgroundWorker m_backgroundWorker;
int m_backgroundJobId = 0; //protects from starting obsolete compilation job int m_backgroundJobId = 0; //protects from starting obsolete compilation job

6
mix/qml/MainContent.qml

@ -30,7 +30,11 @@ Rectangle {
width: parent.width width: parent.width
height: parent.height * 0.7 height: parent.height * 0.7
TextArea { TextArea {
id: codeEditor id: codeEditor
textColor: "#EEE8D5"
style: TextAreaStyle {
backgroundColor: "#002B36"
}
height: parent.height height: parent.height
font.family: "Monospace" font.family: "Monospace"
font.pointSize: 12 font.pointSize: 12

Loading…
Cancel
Save