diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt
index 8051db6e59f..1f5c2e8342a 100644
--- a/src/plugins/CMakeLists.txt
+++ b/src/plugins/CMakeLists.txt
@@ -22,6 +22,7 @@ add_subdirectory(silversearcher)
# Level 3: (only depends on Level 2 and below)
add_subdirectory(bookmarks)
add_subdirectory(cppeditor)
+add_subdirectory(haskell)
add_subdirectory(help)
add_subdirectory(resourceeditor)
add_subdirectory(nim)
diff --git a/src/plugins/haskell/CMakeLists.txt b/src/plugins/haskell/CMakeLists.txt
new file mode 100644
index 00000000000..8f6cc4f1d85
--- /dev/null
+++ b/src/plugins/haskell/CMakeLists.txt
@@ -0,0 +1,27 @@
+add_qtc_plugin(Haskell
+ PLUGIN_DEPENDS
+ QtCreator::Core QtCreator::TextEditor QtCreator::ProjectExplorer
+ DEPENDS Qt5::Widgets
+ SOURCES
+ haskell.qrc
+ haskell_global.h
+ haskellbuildconfiguration.cpp haskellbuildconfiguration.h
+ haskellconstants.h
+ haskelleditorfactory.cpp haskelleditorfactory.h
+ haskellhighlighter.cpp haskellhighlighter.h
+ haskellmanager.cpp haskellmanager.h
+ haskellplugin.cpp haskellplugin.h
+ haskellproject.cpp haskellproject.h
+ haskellrunconfiguration.cpp haskellrunconfiguration.h
+ haskelltokenizer.cpp haskelltokenizer.h
+ optionspage.cpp optionspage.h
+ stackbuildstep.cpp stackbuildstep.h
+)
+
+qtc_add_resources(Haskell haskell_wizards
+ PREFIX "/haskell"
+ BASE share/wizards
+ FILES
+ module/file.hs
+ module/wizard.json
+)
diff --git a/src/plugins/haskell/Haskell.json.in b/src/plugins/haskell/Haskell.json.in
new file mode 100644
index 00000000000..f9062c88b27
--- /dev/null
+++ b/src/plugins/haskell/Haskell.json.in
@@ -0,0 +1,23 @@
+{
+ \"Name\" : \"Haskell\",
+ \"Version\" : \"$$QTCREATOR_VERSION\",
+ \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
+ \"DisabledByDefault\" : true,
+ \"Vendor\" : \"Eike Ziller\",
+ \"Copyright\" : \"(C) Eike Ziller\",
+ \"License\" : \"MIT\",
+ \"Description\" : \"Haskell support\",
+ \"Url\" : \"https://haskell.org\",
+ $$dependencyList,
+
+ \"Mimetypes\" : [
+ \"\",
+ \"\",
+ \" \",
+ \" \",
+ \" Haskell Cabal project file\",
+ \" \",
+ \" \",
+ \"\"
+ ]
+}
diff --git a/src/plugins/haskell/haskell.qbs b/src/plugins/haskell/haskell.qbs
new file mode 100644
index 00000000000..30eb8328f1a
--- /dev/null
+++ b/src/plugins/haskell/haskell.qbs
@@ -0,0 +1,28 @@
+import qbs 1.0
+
+QtcPlugin {
+ name: "Haskell"
+
+ Depends { name: "Qt.widgets" }
+ Depends { name: "Utils" }
+
+ Depends { name: "Core" }
+ Depends { name: "TextEditor" }
+ Depends { name: "ProjectExplorer" }
+
+ files: [
+ "haskell.qrc",
+ "haskellbuildconfiguration.cpp", "haskellbuildconfiguration.h",
+ "haskellconstants.h",
+ "haskelleditorfactory.cpp", "haskelleditorfactory.h",
+ "haskell_global.h",
+ "haskellhighlighter.cpp", "haskellhighlighter.h",
+ "haskellmanager.cpp", "haskellmanager.h",
+ "haskellplugin.cpp", "haskellplugin.h",
+ "haskellproject.cpp", "haskellproject.h",
+ "haskellrunconfiguration.cpp", "haskellrunconfiguration.h",
+ "haskelltokenizer.cpp", "haskelltokenizer.h",
+ "optionspage.cpp", "optionspage.h",
+ "stackbuildstep.cpp", "stackbuildstep.h"
+ ]
+}
diff --git a/src/plugins/haskell/haskell.qrc b/src/plugins/haskell/haskell.qrc
new file mode 100644
index 00000000000..654f45818c3
--- /dev/null
+++ b/src/plugins/haskell/haskell.qrc
@@ -0,0 +1,5 @@
+
+
+ images/category_haskell.png
+
+
diff --git a/src/plugins/haskell/haskell_global.h b/src/plugins/haskell/haskell_global.h
new file mode 100644
index 00000000000..9452e44653c
--- /dev/null
+++ b/src/plugins/haskell/haskell_global.h
@@ -0,0 +1,34 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include
+
+#if defined(HASKELL_LIBRARY)
+# define HASKELLSHARED_EXPORT Q_DECL_EXPORT
+#else
+# define HASKELLSHARED_EXPORT Q_DECL_IMPORT
+#endif
diff --git a/src/plugins/haskell/haskellbuildconfiguration.cpp b/src/plugins/haskell/haskellbuildconfiguration.cpp
new file mode 100644
index 00000000000..77bee060a77
--- /dev/null
+++ b/src/plugins/haskell/haskellbuildconfiguration.cpp
@@ -0,0 +1,132 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "haskellbuildconfiguration.h"
+
+#include "haskellconstants.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+using namespace ProjectExplorer;
+
+const char C_HASKELL_BUILDCONFIGURATION_ID[] = "Haskell.BuildConfiguration";
+
+namespace Haskell {
+namespace Internal {
+
+HaskellBuildConfigurationFactory::HaskellBuildConfigurationFactory()
+{
+ registerBuildConfiguration(C_HASKELL_BUILDCONFIGURATION_ID);
+ setSupportedProjectType(Constants::C_HASKELL_PROJECT_ID);
+ setSupportedProjectMimeTypeName(Constants::C_HASKELL_PROJECT_MIMETYPE);
+
+ setBuildGenerator([](const Kit *k, const Utils::FilePath &projectPath, bool forSetup) {
+ BuildInfo info;
+ info.typeName = HaskellBuildConfiguration::tr("Release");
+ if (forSetup) {
+ info.displayName = info.typeName;
+ info.buildDirectory = projectPath.parentDir().pathAppended(".stack-work");
+ }
+ info.kitId = k->id();
+ info.buildType = BuildConfiguration::BuildType::Release;
+ return QList{info};
+ });
+}
+
+HaskellBuildConfiguration::HaskellBuildConfiguration(Target *target, Utils::Id id)
+ : BuildConfiguration(target, id)
+{
+ setInitializer([this](const BuildInfo &info) {
+ setBuildDirectory(info.buildDirectory);
+ setBuildType(info.buildType);
+ setDisplayName(info.displayName);
+ });
+ appendInitialBuildStep(Constants::C_STACK_BUILD_STEP_ID);
+}
+
+NamedWidget *HaskellBuildConfiguration::createConfigWidget()
+{
+ return new HaskellBuildConfigurationWidget(this);
+}
+
+BuildConfiguration::BuildType HaskellBuildConfiguration::buildType() const
+{
+ return m_buildType;
+}
+
+void HaskellBuildConfiguration::setBuildType(BuildConfiguration::BuildType type)
+{
+ m_buildType = type;
+}
+
+HaskellBuildConfigurationWidget::HaskellBuildConfigurationWidget(HaskellBuildConfiguration *bc)
+ : NamedWidget(tr("General"))
+ , m_buildConfiguration(bc)
+{
+ setLayout(new QVBoxLayout);
+ layout()->setContentsMargins(0, 0, 0, 0);
+ auto box = new Utils::DetailsWidget;
+ box->setState(Utils::DetailsWidget::NoSummary);
+ layout()->addWidget(box);
+ auto details = new QWidget;
+ box->setWidget(details);
+ details->setLayout(new QHBoxLayout);
+ details->layout()->setContentsMargins(0, 0, 0, 0);
+ details->layout()->addWidget(new QLabel(tr("Build directory:")));
+
+ auto buildDirectoryInput = new Utils::PathChooser;
+ buildDirectoryInput->setExpectedKind(Utils::PathChooser::Directory);
+ buildDirectoryInput->setFilePath(m_buildConfiguration->buildDirectory());
+ details->layout()->addWidget(buildDirectoryInput);
+
+ connect(m_buildConfiguration,
+ &BuildConfiguration::buildDirectoryChanged,
+ buildDirectoryInput,
+ [this, buildDirectoryInput] {
+ buildDirectoryInput->setFilePath(m_buildConfiguration->buildDirectory());
+ });
+ connect(buildDirectoryInput,
+ &Utils::PathChooser::textChanged,
+ m_buildConfiguration,
+ [this, buildDirectoryInput](const QString &) {
+ m_buildConfiguration->setBuildDirectory(buildDirectoryInput->rawFilePath());
+ });
+}
+
+} // namespace Internal
+} // namespace Haskell
diff --git a/src/plugins/haskell/haskellbuildconfiguration.h b/src/plugins/haskell/haskellbuildconfiguration.h
new file mode 100644
index 00000000000..c93161e5353
--- /dev/null
+++ b/src/plugins/haskell/haskellbuildconfiguration.h
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include
+#include
+
+namespace Haskell {
+namespace Internal {
+
+class HaskellBuildConfigurationFactory : public ProjectExplorer::BuildConfigurationFactory
+{
+public:
+ HaskellBuildConfigurationFactory();
+};
+
+class HaskellBuildConfiguration : public ProjectExplorer::BuildConfiguration
+{
+ Q_OBJECT
+
+public:
+ HaskellBuildConfiguration(ProjectExplorer::Target *target, Utils::Id id);
+
+ ProjectExplorer::NamedWidget *createConfigWidget() override;
+ BuildType buildType() const override;
+ void setBuildType(BuildType type);
+
+private:
+ BuildType m_buildType = BuildType::Release;
+};
+
+class HaskellBuildConfigurationWidget : public ProjectExplorer::NamedWidget
+{
+ Q_OBJECT
+
+public:
+ HaskellBuildConfigurationWidget(HaskellBuildConfiguration *bc);
+
+private:
+ HaskellBuildConfiguration *m_buildConfiguration;
+};
+
+} // namespace Internal
+} // namespace Haskell
diff --git a/src/plugins/haskell/haskellconstants.h b/src/plugins/haskell/haskellconstants.h
new file mode 100644
index 00000000000..8b63761ada0
--- /dev/null
+++ b/src/plugins/haskell/haskellconstants.h
@@ -0,0 +1,41 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+namespace Haskell {
+namespace Constants {
+
+const char C_HASKELLEDITOR_ID[] = "Haskell.HaskellEditor";
+const char C_HASKELLSNIPPETSGROUP_ID[] = "Haskell";
+const char C_HASKELL_PROJECT_MIMETYPE[] = "text/x-haskell-project";
+const char C_HASKELL_PROJECT_ID[] = "Haskell.Project";
+const char C_HASKELL_RUNCONFIG_ID[] = "Haskell.RunConfiguration";
+const char C_STACK_BUILD_STEP_ID[] = "Haskell.Stack.Build";
+const char OPTIONS_GENERAL[] = "Haskell.A.General";
+const char A_RUN_GHCI[] = "Haskell.RunGHCi";
+
+} // namespace Haskell
+} // namespace Constants
diff --git a/src/plugins/haskell/haskelleditorfactory.cpp b/src/plugins/haskell/haskelleditorfactory.cpp
new file mode 100644
index 00000000000..206677915ba
--- /dev/null
+++ b/src/plugins/haskell/haskelleditorfactory.cpp
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "haskelleditorfactory.h"
+
+#include "haskellconstants.h"
+#include "haskellhighlighter.h"
+#include "haskellmanager.h"
+
+#include
+#include
+#include
+#include
+
+#include
+
+namespace Haskell {
+namespace Internal {
+
+static QWidget *createEditorWidget()
+{
+ auto widget = new TextEditor::TextEditorWidget;
+ auto ghciButton = new Core::CommandButton(Constants::A_RUN_GHCI, widget);
+ ghciButton->setText(HaskellManager::tr("GHCi"));
+ QObject::connect(ghciButton, &QToolButton::clicked, HaskellManager::instance(), [widget] {
+ HaskellManager::openGhci(widget->textDocument()->filePath());
+ });
+ widget->insertExtraToolBarWidget(TextEditor::TextEditorWidget::Left, ghciButton);
+ return widget;
+}
+
+HaskellEditorFactory::HaskellEditorFactory()
+{
+ setId(Constants::C_HASKELLEDITOR_ID);
+ setDisplayName(QCoreApplication::translate("OpenWith::Editors", "Haskell Editor"));
+ addMimeType("text/x-haskell");
+ setEditorActionHandlers(TextEditor::TextEditorActionHandler::UnCommentSelection
+ | TextEditor::TextEditorActionHandler::FollowSymbolUnderCursor);
+ setDocumentCreator([] { return new TextEditor::TextDocument(Constants::C_HASKELLEDITOR_ID); });
+ setIndenterCreator([](QTextDocument *doc) { return new TextEditor::TextIndenter(doc); });
+ setEditorWidgetCreator(createEditorWidget);
+ setCommentDefinition(Utils::CommentDefinition("--", "{-", "-}"));
+ setParenthesesMatchingEnabled(true);
+ setMarksVisible(true);
+ setSyntaxHighlighterCreator([] { return new HaskellHighlighter(); });
+}
+
+} // Internal
+} // Haskell
diff --git a/src/plugins/haskell/haskelleditorfactory.h b/src/plugins/haskell/haskelleditorfactory.h
new file mode 100644
index 00000000000..a48632d289d
--- /dev/null
+++ b/src/plugins/haskell/haskelleditorfactory.h
@@ -0,0 +1,40 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include
+
+namespace Haskell {
+namespace Internal {
+
+class HaskellEditorFactory : public TextEditor::TextEditorFactory
+{
+public:
+ HaskellEditorFactory();
+};
+
+} // Internal
+} // Haskell
diff --git a/src/plugins/haskell/haskellhighlighter.cpp b/src/plugins/haskell/haskellhighlighter.cpp
new file mode 100644
index 00000000000..8a5b6cae76e
--- /dev/null
+++ b/src/plugins/haskell/haskellhighlighter.cpp
@@ -0,0 +1,152 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "haskellhighlighter.h"
+
+#include "haskelltokenizer.h"
+
+#include
+#include
+#include
+
+#include
+#include
+
+Q_GLOBAL_STATIC_WITH_ARGS(QSet, IMPORT_HIGHLIGHTS, ({
+ "qualified",
+ "as",
+ "hiding"
+}));
+
+using namespace TextEditor;
+
+namespace Haskell {
+namespace Internal {
+
+HaskellHighlighter::HaskellHighlighter()
+{
+ setDefaultTextFormatCategories();
+ updateFormats(TextEditorSettings::fontSettings());
+}
+
+void HaskellHighlighter::highlightBlock(const QString &text)
+{
+ const Tokens tokens = HaskellTokenizer::tokenize(text, previousBlockState());
+ setCurrentBlockState(tokens.state);
+ const Token *firstNonWS = 0;
+ const Token *secondNonWS = 0;
+ bool inType = false;
+ bool inImport = false;
+ for (const Token & token : tokens) {
+ switch (token.type) {
+ case TokenType::Variable:
+ if (inType)
+ setTokenFormat(token, C_LOCAL);
+ else if (inImport && IMPORT_HIGHLIGHTS->contains(token.text.toString()))
+ setTokenFormat(token, C_KEYWORD);
+// else
+// setTokenFormat(token, C_TEXT);
+ break;
+ case TokenType::Constructor:
+ case TokenType::OperatorConstructor:
+ setTokenFormat(token, C_TYPE);
+ break;
+ case TokenType::Operator:
+ setTokenFormat(token, C_OPERATOR);
+ break;
+ case TokenType::Whitespace:
+ setTokenFormat(token, C_VISUAL_WHITESPACE);
+ break;
+ case TokenType::Keyword:
+ if (token.text == QLatin1String("::") && firstNonWS && !secondNonWS) { // toplevel declaration
+ setFormat(firstNonWS->startCol, firstNonWS->length, m_toplevelDeclFormat);
+ inType = true;
+ } else if (token.text == QLatin1String("import")) {
+ inImport = true;
+ }
+ setTokenFormat(token, C_KEYWORD);
+ break;
+ case TokenType::Integer:
+ case TokenType::Float:
+ setTokenFormat(token, C_NUMBER);
+ break;
+ case TokenType::String:
+ setTokenFormatWithSpaces(text, token, C_STRING);
+ break;
+ case TokenType::Char:
+ setTokenFormatWithSpaces(text, token, C_STRING);
+ break;
+ case TokenType::EscapeSequence:
+ setTokenFormat(token, C_PRIMITIVE_TYPE);
+ break;
+ case TokenType::SingleLineComment:
+ setTokenFormatWithSpaces(text, token, C_COMMENT);
+ break;
+ case TokenType::MultiLineComment:
+ setTokenFormatWithSpaces(text, token, C_COMMENT);
+ break;
+ case TokenType::Special:
+// setTokenFormat(token, C_TEXT);
+ break;
+ case TokenType::StringError:
+ case TokenType::CharError:
+ case TokenType::Unknown:
+ setTokenFormat(token, C_PARENTHESES_MISMATCH);
+ break;
+ }
+ if (token.type != TokenType::Whitespace) {
+ if (!firstNonWS)
+ firstNonWS = &token;
+ else if (!secondNonWS)
+ secondNonWS = &token;
+ }
+ }
+}
+
+void HaskellHighlighter::setFontSettings(const FontSettings &fontSettings)
+{
+ SyntaxHighlighter::setFontSettings(fontSettings);
+ updateFormats(fontSettings);
+}
+
+void HaskellHighlighter::updateFormats(const FontSettings &fontSettings)
+{
+ m_toplevelDeclFormat = fontSettings.toTextCharFormat(
+ TextStyles::mixinStyle(C_FUNCTION, C_DECLARATION));
+}
+
+void HaskellHighlighter::setTokenFormat(const Token &token, TextStyle style)
+{
+ setFormat(token.startCol, token.length, formatForCategory(style));
+}
+
+void HaskellHighlighter::setTokenFormatWithSpaces(const QString &text, const Token &token,
+ TextStyle style)
+{
+ setFormatWithSpaces(text, token.startCol, token.length, formatForCategory(style));
+}
+
+} // Internal
+} // Haskell
diff --git a/src/plugins/haskell/haskellhighlighter.h b/src/plugins/haskell/haskellhighlighter.h
new file mode 100644
index 00000000000..621333341bc
--- /dev/null
+++ b/src/plugins/haskell/haskellhighlighter.h
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include
+
+#include
+#include
+
+namespace Haskell {
+namespace Internal {
+
+class Token;
+
+class HaskellHighlighter : public TextEditor::SyntaxHighlighter
+{
+ Q_OBJECT
+
+public:
+ HaskellHighlighter();
+
+protected:
+ void highlightBlock(const QString &text) override;
+
+private:
+ void setFontSettings(const TextEditor::FontSettings &fontSettings) override;
+ void updateFormats(const TextEditor::FontSettings &fontSettings);
+ void setTokenFormat(const Token &token, TextEditor::TextStyle style);
+ void setTokenFormatWithSpaces(const QString &text, const Token &token,
+ TextEditor::TextStyle style);
+ QTextCharFormat m_toplevelDeclFormat;
+};
+
+} // Internal
+} // Haskell
diff --git a/src/plugins/haskell/haskellmanager.cpp b/src/plugins/haskell/haskellmanager.cpp
new file mode 100644
index 00000000000..dd6a3712b95
--- /dev/null
+++ b/src/plugins/haskell/haskellmanager.cpp
@@ -0,0 +1,141 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "haskellmanager.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+
+static const char kStackExecutableKey[] = "Haskell/StackExecutable";
+
+using namespace Utils;
+
+namespace Haskell {
+namespace Internal {
+
+class HaskellManagerPrivate
+{
+public:
+ FilePath stackExecutable;
+};
+
+Q_GLOBAL_STATIC(HaskellManagerPrivate, m_d)
+Q_GLOBAL_STATIC(HaskellManager, m_instance)
+
+HaskellManager *HaskellManager::instance()
+{
+ return m_instance;
+}
+
+FilePath HaskellManager::findProjectDirectory(const FilePath &filePath)
+{
+ if (filePath.isEmpty())
+ return {};
+
+ QDir directory(filePath.toFileInfo().isDir() ? filePath.toString()
+ : filePath.parentDir().toString());
+ directory.setNameFilters({"stack.yaml", "*.cabal"});
+ directory.setFilter(QDir::Files | QDir::Readable);
+ do {
+ if (!directory.entryList().isEmpty())
+ return FilePath::fromString(directory.path());
+ } while (!directory.isRoot() && directory.cdUp());
+ return {};
+}
+
+FilePath defaultStackExecutable()
+{
+ // stack from brew or the installer script from https://docs.haskellstack.org
+ // install to /usr/local/bin.
+ if (HostOsInfo::isAnyUnixHost())
+ return FilePath::fromString("/usr/local/bin/stack");
+ return FilePath::fromString("stack");
+}
+
+FilePath HaskellManager::stackExecutable()
+{
+ return m_d->stackExecutable;
+}
+
+void HaskellManager::setStackExecutable(const FilePath &filePath)
+{
+ if (filePath == m_d->stackExecutable)
+ return;
+ m_d->stackExecutable = filePath;
+ emit m_instance->stackExecutableChanged(m_d->stackExecutable);
+}
+
+void HaskellManager::openGhci(const FilePath &haskellFile)
+{
+ const QList mimeTypes = mimeTypesForFileName(haskellFile.toString());
+ const bool isHaskell = Utils::anyOf(mimeTypes, [](const MimeType &mt) {
+ return mt.inherits("text/x-haskell") || mt.inherits("text/x-literate-haskell");
+ });
+ const auto args = QStringList{"ghci"}
+ + (isHaskell ? QStringList{haskellFile.fileName()} : QStringList());
+ auto p = new QtcProcess(m_instance);
+ p->setTerminalMode(TerminalMode::On);
+ p->setCommand({stackExecutable(), args});
+ p->setWorkingDirectory(haskellFile.absolutePath());
+ connect(p, &QtcProcess::done, p, [p] {
+ if (p->result() != ProcessResult::FinishedWithSuccess) {
+ Core::MessageManager::writeDisrupting(
+ tr("Failed to run GHCi: \"%1\".").arg(p->errorString()));
+ }
+ p->deleteLater();
+ });
+ p->start();
+}
+
+void HaskellManager::readSettings(QSettings *settings)
+{
+ m_d->stackExecutable = FilePath::fromString(
+ settings->value(kStackExecutableKey,
+ defaultStackExecutable().toString()).toString());
+ emit m_instance->stackExecutableChanged(m_d->stackExecutable);
+}
+
+void HaskellManager::writeSettings(QSettings *settings)
+{
+ if (m_d->stackExecutable == defaultStackExecutable())
+ settings->remove(kStackExecutableKey);
+ else
+ settings->setValue(kStackExecutableKey, m_d->stackExecutable.toString());
+}
+
+} // namespace Internal
+} // namespace Haskell
diff --git a/src/plugins/haskell/haskellmanager.h b/src/plugins/haskell/haskellmanager.h
new file mode 100644
index 00000000000..361b8f8e966
--- /dev/null
+++ b/src/plugins/haskell/haskellmanager.h
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include
+
+#include
+
+QT_BEGIN_NAMESPACE
+class QSettings;
+QT_END_NAMESPACE
+
+namespace Haskell {
+namespace Internal {
+
+class HaskellManager : public QObject
+{
+ Q_OBJECT
+
+public:
+ static HaskellManager *instance();
+
+ static Utils::FilePath findProjectDirectory(const Utils::FilePath &filePath);
+ static Utils::FilePath stackExecutable();
+ static void setStackExecutable(const Utils::FilePath &filePath);
+ static void openGhci(const Utils::FilePath &haskellFile);
+ static void readSettings(QSettings *settings);
+ static void writeSettings(QSettings *settings);
+
+signals:
+ void stackExecutableChanged(const Utils::FilePath &filePath);
+};
+
+} // namespace Internal
+} // namespace Haskell
diff --git a/src/plugins/haskell/haskellplugin.cpp b/src/plugins/haskell/haskellplugin.cpp
new file mode 100644
index 00000000000..c3ae400629c
--- /dev/null
+++ b/src/plugins/haskell/haskellplugin.cpp
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "haskellplugin.h"
+
+#include "haskellbuildconfiguration.h"
+#include "haskellconstants.h"
+#include "haskelleditorfactory.h"
+#include "haskellmanager.h"
+#include "haskellproject.h"
+#include "haskellrunconfiguration.h"
+#include "optionspage.h"
+#include "stackbuildstep.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+namespace Haskell {
+namespace Internal {
+
+class HaskellPluginPrivate
+{
+public:
+ HaskellEditorFactory editorFactory;
+ OptionsPage optionsPage;
+ HaskellBuildConfigurationFactory buildConfigFactory;
+ StackBuildStepFactory stackBuildStepFactory;
+ HaskellRunConfigurationFactory runConfigFactory;
+ ProjectExplorer::SimpleTargetRunnerFactory runWorkerFactory{{Constants::C_HASKELL_RUNCONFIG_ID}};
+};
+
+HaskellPlugin::~HaskellPlugin()
+{
+ delete d;
+}
+
+static void registerGhciAction()
+{
+ QAction *action = new QAction(HaskellManager::tr("Run GHCi"), HaskellManager::instance());
+ Core::ActionManager::registerAction(action, Constants::A_RUN_GHCI);
+ QObject::connect(action, &QAction::triggered, HaskellManager::instance(), [] {
+ if (Core::IDocument *doc = Core::EditorManager::currentDocument())
+ HaskellManager::openGhci(doc->filePath());
+ });
+}
+
+bool HaskellPlugin::initialize(const QStringList &arguments, QString *errorString)
+{
+ Q_UNUSED(arguments)
+ Q_UNUSED(errorString)
+
+ d = new HaskellPluginPrivate;
+
+ ProjectExplorer::ProjectManager::registerProjectType(
+ Constants::C_HASKELL_PROJECT_MIMETYPE);
+ TextEditor::SnippetProvider::registerGroup(Constants::C_HASKELLSNIPPETSGROUP_ID,
+ tr("Haskell", "SnippetProvider"));
+
+ connect(Core::ICore::instance(), &Core::ICore::saveSettingsRequested, this, [] {
+ HaskellManager::writeSettings(Core::ICore::settings());
+ });
+
+ registerGhciAction();
+
+ HaskellManager::readSettings(Core::ICore::settings());
+
+ ProjectExplorer::JsonWizardFactory::addWizardPath(":/haskell/share/wizards/");
+ return true;
+}
+
+} // namespace Internal
+} // namespace Haskell
diff --git a/src/plugins/haskell/haskellplugin.h b/src/plugins/haskell/haskellplugin.h
new file mode 100644
index 00000000000..163f40b959b
--- /dev/null
+++ b/src/plugins/haskell/haskellplugin.h
@@ -0,0 +1,52 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "haskell_global.h"
+
+#include
+
+namespace Haskell {
+namespace Internal {
+
+class HaskellPlugin : public ExtensionSystem::IPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Haskell.json")
+
+public:
+ HaskellPlugin() = default;
+ ~HaskellPlugin() final;
+
+private:
+ bool initialize(const QStringList &arguments, QString *errorString) final;
+ void extensionsInitialized() final {}
+
+ class HaskellPluginPrivate *d = nullptr;
+};
+
+} // namespace Internal
+} // namespace Haskell
diff --git a/src/plugins/haskell/haskellproject.cpp b/src/plugins/haskell/haskellproject.cpp
new file mode 100644
index 00000000000..248f898aeed
--- /dev/null
+++ b/src/plugins/haskell/haskellproject.cpp
@@ -0,0 +1,135 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "haskellproject.h"
+
+#include "haskellconstants.h"
+
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+using namespace ProjectExplorer;
+using namespace Utils;
+
+namespace Haskell {
+namespace Internal {
+
+static QVector parseExecutableNames(const FilePath &projectFilePath)
+{
+ static const QString EXECUTABLE = "executable";
+ static const int EXECUTABLE_LEN = EXECUTABLE.length();
+ QVector result;
+ QFile file(projectFilePath.toString());
+ if (file.open(QFile::ReadOnly)) {
+ QTextStream stream(&file);
+ while (!stream.atEnd()) {
+ const QString line = stream.readLine().trimmed();
+ if (line.length() > EXECUTABLE_LEN && line.startsWith(EXECUTABLE)
+ && line.at(EXECUTABLE_LEN).isSpace())
+ result.append(line.mid(EXECUTABLE_LEN + 1).trimmed());
+ }
+ }
+ return result;
+}
+
+HaskellProject::HaskellProject(const Utils::FilePath &fileName)
+ : Project(Constants::C_HASKELL_PROJECT_MIMETYPE, fileName)
+{
+ setId(Constants::C_HASKELL_PROJECT_ID);
+ setDisplayName(fileName.toFileInfo().completeBaseName());
+ setBuildSystemCreator([](Target *t) { return new HaskellBuildSystem(t); });
+}
+
+bool HaskellProject::isHaskellProject(Project *project)
+{
+ return project && project->id() == Constants::C_HASKELL_PROJECT_ID;
+}
+
+HaskellBuildSystem::HaskellBuildSystem(Target *t)
+ : BuildSystem(t)
+{
+ connect(&m_scanner, &TreeScanner::finished, this, [this] {
+ auto root = std::make_unique(projectDirectory());
+ root->setDisplayName(target()->project()->displayName());
+ std::vector> nodePtrs
+ = Utils::transform(m_scanner.release().allFiles, [](FileNode *fn) {
+ return std::unique_ptr(fn);
+ });
+ root->addNestedNodes(std::move(nodePtrs));
+ setRootProjectNode(std::move(root));
+
+ updateApplicationTargets();
+
+ m_parseGuard.markAsSuccess();
+ m_parseGuard = {};
+
+ emitBuildSystemUpdated();
+ });
+
+ connect(target()->project(),
+ &Project::projectFileIsDirty,
+ this,
+ &BuildSystem::requestDelayedParse);
+
+ requestDelayedParse();
+}
+
+void HaskellBuildSystem::triggerParsing()
+{
+ m_parseGuard = guardParsingRun();
+ m_scanner.asyncScanForFiles(target()->project()->projectDirectory());
+}
+
+void HaskellBuildSystem::updateApplicationTargets()
+{
+ const QVector executables = parseExecutableNames(projectFilePath());
+ const Utils::FilePath projFilePath = projectFilePath();
+ const QList appTargets
+ = Utils::transform(executables, [projFilePath](const QString &executable) {
+ BuildTargetInfo bti;
+ bti.displayName = executable;
+ bti.buildKey = executable;
+ bti.targetFilePath = FilePath::fromString(executable);
+ bti.projectFilePath = projFilePath;
+ bti.isQtcRunnable = true;
+ return bti;
+ });
+ setApplicationTargets(appTargets);
+ target()->updateDefaultRunConfigurations();
+}
+
+} // namespace Internal
+} // namespace Haskell
diff --git a/src/plugins/haskell/haskellproject.h b/src/plugins/haskell/haskellproject.h
new file mode 100644
index 00000000000..0ee05d90333
--- /dev/null
+++ b/src/plugins/haskell/haskellproject.h
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+namespace Haskell {
+namespace Internal {
+
+class HaskellProject : public ProjectExplorer::Project
+{
+ Q_OBJECT
+
+public:
+ explicit HaskellProject(const Utils::FilePath &fileName);
+
+ static bool isHaskellProject(Project *project);
+};
+
+class HaskellBuildSystem : public ProjectExplorer::BuildSystem
+{
+ Q_OBJECT
+
+public:
+ HaskellBuildSystem(ProjectExplorer::Target *t);
+
+ void triggerParsing() override;
+ QString name() const final { return QLatin1String("haskell"); }
+
+private:
+ void updateApplicationTargets();
+ void refresh();
+
+private:
+ ParseGuard m_parseGuard;
+ ProjectExplorer::TreeScanner m_scanner;
+};
+
+} // namespace Internal
+} // namespace Haskell
diff --git a/src/plugins/haskell/haskellrunconfiguration.cpp b/src/plugins/haskell/haskellrunconfiguration.cpp
new file mode 100644
index 00000000000..74574710f6f
--- /dev/null
+++ b/src/plugins/haskell/haskellrunconfiguration.cpp
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "haskellrunconfiguration.h"
+
+#include "haskellconstants.h"
+#include "haskellmanager.h"
+#include "haskellproject.h"
+
+#include
+#include
+#include
+#include
+#include
+
+using namespace ProjectExplorer;
+
+namespace Haskell {
+namespace Internal {
+
+HaskellRunConfigurationFactory::HaskellRunConfigurationFactory()
+{
+ registerRunConfiguration(Constants::C_HASKELL_RUNCONFIG_ID);
+ addSupportedProjectType(Constants::C_HASKELL_PROJECT_ID);
+ addSupportedTargetDeviceType(ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE);
+}
+
+HaskellExecutableAspect::HaskellExecutableAspect()
+{
+ setSettingsKey("Haskell.Executable");
+ setLabelText(tr("Executable"));
+}
+
+HaskellRunConfiguration::HaskellRunConfiguration(Target *target, Utils::Id id)
+ : RunConfiguration(target, id)
+{
+ auto envAspect = addAspect(target);
+
+ addAspect();
+ addAspect(macroExpander());
+
+ auto workingDirAspect = addAspect(macroExpander(), envAspect);
+ workingDirAspect->setDefaultWorkingDirectory(target->project()->projectDirectory());
+ workingDirAspect->setVisible(false);
+
+ addAspect();
+
+ setUpdater([this] { aspect()->setValue(buildTargetInfo().buildKey); });
+ connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update);
+ update();
+}
+
+Runnable HaskellRunConfiguration::runnable() const
+{
+ const Utils::FilePath projectDirectory = target()->project()->projectDirectory();
+ Runnable r;
+ QStringList args;
+ if (BuildConfiguration *buildConfiguration = target()->activeBuildConfiguration()) {
+ args << "--work-dir"
+ << QDir(projectDirectory.toString()).relativeFilePath(
+ buildConfiguration->buildDirectory().toString());
+ }
+ args << "exec" << aspect()->value();
+ const QString arguments = aspect()->arguments();
+ if (!arguments.isEmpty())
+ args << "--" << arguments;
+
+ r.workingDirectory = projectDirectory;
+ r.environment = aspect()->environment();
+ r.command = {r.environment.searchInPath(HaskellManager::stackExecutable().toString()), args};
+ return r;
+}
+
+} // namespace Internal
+} // namespace Haskell
diff --git a/src/plugins/haskell/haskellrunconfiguration.h b/src/plugins/haskell/haskellrunconfiguration.h
new file mode 100644
index 00000000000..27e4a83f9ad
--- /dev/null
+++ b/src/plugins/haskell/haskellrunconfiguration.h
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+namespace Haskell {
+namespace Internal {
+
+class HaskellRunConfiguration : public ProjectExplorer::RunConfiguration
+{
+ Q_OBJECT
+
+public:
+ HaskellRunConfiguration(ProjectExplorer::Target *target, Utils::Id id);
+
+private:
+ ProjectExplorer::Runnable runnable() const final;
+};
+
+class HaskellRunConfigurationFactory : public ProjectExplorer::RunConfigurationFactory
+{
+public:
+ HaskellRunConfigurationFactory();
+};
+
+class HaskellExecutableAspect : public Utils::StringAspect
+{
+ Q_OBJECT
+
+public:
+ HaskellExecutableAspect();
+};
+
+} // namespace Internal
+} // namespace Haskell
diff --git a/src/plugins/haskell/haskelltokenizer.cpp b/src/plugins/haskell/haskelltokenizer.cpp
new file mode 100644
index 00000000000..6f2556d2475
--- /dev/null
+++ b/src/plugins/haskell/haskelltokenizer.cpp
@@ -0,0 +1,643 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "haskelltokenizer.h"
+
+#include
+
+#include
+#include
+
+Q_GLOBAL_STATIC_WITH_ARGS(QSet, RESERVED_OP, ({
+ "..",
+ ":",
+ "::",
+ "=",
+ "\\",
+ "|",
+ "<-",
+ "->",
+ "@",
+ "~",
+ "=>",
+
+ // Arrows GHC extension
+ "-<",
+ "-<<",
+ ">-",
+ ">>-",
+ "(|",
+ "|)"
+}));
+
+Q_GLOBAL_STATIC_WITH_ARGS(QSet, RESERVED_ID, ({
+ "case",
+ "class",
+ "data",
+ "default",
+ "deriving",
+ "do",
+ "else",
+ "foreign",
+ "if",
+ "import",
+ "in",
+ "infix",
+ "infixl",
+ "infixr",
+ "instance",
+ "let",
+ "module",
+ "newtype",
+ "of",
+ "then",
+ "type",
+ "where",
+ "_",
+
+ // from GHC extensions
+ "family",
+ "forall",
+ "mdo",
+ "proc",
+ "rec"
+}));
+
+Q_GLOBAL_STATIC_WITH_ARGS(QSet, SPECIAL, ({
+ '(',
+ ')',
+ ',',
+ ';',
+ '[',
+ ']',
+ '`',
+ '{',
+ '}',
+}));
+
+Q_GLOBAL_STATIC_WITH_ARGS(QSet, CHAR_ESCAPES,
+ ({'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"', '\'', '&'}));
+
+Q_GLOBAL_STATIC_WITH_ARGS(QVector, ASCII_ESCAPES, ({
+ "NUL",
+ "SOH", // must be before "SO" to match
+ "STX",
+ "ETX",
+ "EOT",
+ "ENQ",
+ "ACK",
+ "BEL",
+ "BS",
+ "HT",
+ "LF",
+ "VT",
+ "FF",
+ "CR",
+ "SO",
+ "SI",
+ "DLE",
+ "DC1",
+ "DC2",
+ "DC3",
+ "DC4",
+ "NAK",
+ "SYN",
+ "ETB",
+ "CAN",
+ "EM",
+ "SUB",
+ "ESC",
+ "FS",
+ "GS",
+ "RS",
+ "US",
+ "SP",
+ "DEL"
+}));
+
+namespace Haskell {
+namespace Internal {
+
+Token token(TokenType type, std::shared_ptr line, int start, int end)
+{
+ return {type, start, end - start, QStringView(*line).mid(start, end - start), line};
+}
+
+Tokens::Tokens(std::shared_ptr source)
+ : source(source)
+{
+}
+
+Token Tokens::tokenAtColumn(int col) const
+{
+ auto it = std::upper_bound(begin(), end(), col, [](int c, const Token &i) {
+ return c < i.startCol;
+ });
+ if (it == begin())
+ return Token();
+ --it;
+ if (it->startCol + it->length > col)
+ return *it;
+ return Token();
+}
+
+static int grab(const QString &line, int begin,
+ const std::function &test)
+{
+ const int length = line.length();
+ int current = begin;
+ while (current < length && test(line.at(current)))
+ ++current;
+ return current - begin;
+};
+
+static bool isIdentifierChar(const QChar &c)
+{
+ return c.isLetterOrNumber() || c == '\'' || c == '_';
+}
+
+static bool isVariableIdentifierStart(const QChar &c)
+{
+ return c == '_' || c.isLower();
+}
+
+static bool isAscSymbol(const QChar &c)
+{
+ return c == '!'
+ || c == '#'
+ || c == '$'
+ || c == '%'
+ || c == '&'
+ || c == '*'
+ || c == '+'
+ || c == '.'
+ || c == '/'
+ || c == '<'
+ || c == '='
+ || c == '>'
+ || c == '?'
+ || c == '@'
+ || c == '\\'
+ || c == '^'
+ || c == '|'
+ || c == '-'
+ || c == '~'
+ || c == ':';
+}
+
+static bool isSymbol(const QChar &c)
+{
+ return isAscSymbol(c)
+ || ((c.isSymbol() || c.isPunct()) && c != '_' && c != '"' && c != '\''
+ && !SPECIAL->contains(c));
+}
+
+static bool isDigit(const QChar &c)
+{
+ return c.isDigit();
+}
+
+static bool isOctit(const QChar &c)
+{
+ return c >= '0' && c <= '7';
+}
+
+static bool isHexit(const QChar &c)
+{
+ return c.isDigit()
+ || (c >= 'A' && c <= 'F')
+ || (c >= 'a' && c <= 'f');
+}
+
+static bool isCntrl(const QChar &c)
+{
+ return (c >= 'A' && c <= 'Z')
+ || c == '@'
+ || c == '['
+ || c == '\\'
+ || c == ']'
+ || c == '^'
+ || c == '_';
+}
+
+static QVector getSpace(std::shared_ptr line, int start)
+{
+ const auto lineEnd = line->cend();
+ const auto tokenStart = line->cbegin() + start;
+ auto current = tokenStart;
+ while (current != lineEnd && (*current).isSpace())
+ ++current;
+ const int length = int(std::distance(tokenStart, current));
+ if (current > tokenStart)
+ return {{TokenType::Whitespace, start, length, QStringView(*line).mid(start, length), line}};
+ return {};
+}
+
+static QVector getNumber(std::shared_ptr line, int start)
+{
+ const QChar &startC = line->at(start);
+ if (!startC.isDigit())
+ return {};
+ const int length = line->length();
+ int current = start + 1;
+ TokenType type = TokenType::Integer;
+ if (current < length) {
+ if (startC == '0') {
+ // check for octal or hexadecimal
+ const QChar &secondC = line->at(current);
+ if (secondC == 'o' || secondC == 'O') {
+ const int numLen = grab(*line, current + 1, isOctit);
+ if (numLen > 0)
+ return {token(TokenType::Integer, line, start, current + numLen + 1)};
+ } else if (secondC == 'x' || secondC == 'X') {
+ const int numLen = grab(*line, current + 1, isHexit);
+ if (numLen > 0)
+ return {token(TokenType::Integer, line, start, current + numLen + 1)};
+ }
+ }
+ // starts with decimal
+ const int numLen = grab(*line, start, isDigit);
+ current = start + numLen;
+ // check for floating point
+ if (current < length && line->at(current) == '.') {
+ const int numLen = grab(*line, current + 1, isDigit);
+ if (numLen > 0) {
+ current += numLen + 1;
+ type = TokenType::Float;
+ }
+ }
+ // check for exponent
+ if (current + 1 < length /*for at least 'e' and digit*/
+ && (line->at(current) == 'e' || line->at(current) == 'E')) {
+ int expEnd = current + 1;
+ if (line->at(expEnd) == '+' || line->at(expEnd) == '-')
+ ++expEnd;
+ const int numLen = grab(*line, expEnd, isDigit);
+ if (numLen > 0) {
+ current = expEnd + numLen;
+ type = TokenType::Float;
+ }
+ }
+ }
+ return {token(type, line, start, current)};
+}
+
+static QVector getIdOrOpOrSingleLineComment(std::shared_ptr line, int start)
+{
+ const int length = line->length();
+ if (start >= length)
+ return {};
+ int current = start;
+ // check for {conid.}conid
+ int conidEnd = start;
+ bool canOnlyBeConstructor = false;
+ while (current < length && line->at(current).isUpper()) {
+ current += grab(*line, current, isIdentifierChar);
+ conidEnd = current;
+ // it is definitely a constructor id if it is not followed by a '.'
+ canOnlyBeConstructor = current >= length || line->at(current) != '.';
+ // otherwise it might be a module id, and we skip the dot to check for qualified thing
+ if (!canOnlyBeConstructor)
+ ++current;
+ }
+ if (canOnlyBeConstructor)
+ return {token(TokenType::Constructor, line, start, conidEnd)};
+
+ // check for variable or reserved id
+ if (current < length && isVariableIdentifierStart(line->at(current))) {
+ const int varLen = grab(*line, current, isIdentifierChar);
+ // check for reserved id
+ if (RESERVED_ID->contains(line->mid(current, varLen))) {
+ QVector result;
+ // possibly add constructor + op '.'
+ if (conidEnd > start) {
+ result.append(token(TokenType::Constructor, line, start, conidEnd));
+ result.append(token(TokenType::Operator, line, conidEnd, current));
+ }
+ result.append(token(TokenType::Keyword, line, current, current + varLen));
+ return result;
+ }
+ return {token(TokenType::Variable, line, start, current + varLen)};
+ }
+ // check for operator
+ if (current < length && isSymbol(line->at(current))) {
+ const int opLen = grab(*line, current, isSymbol);
+ // check for reserved op
+ if (RESERVED_OP->contains(line->mid(current, opLen))) {
+ // because of the case of F... (constructor + op '...') etc
+ // we only add conid if we have one, handling the rest in next iteration
+ if (conidEnd > start)
+ return {token(TokenType::Constructor, line, start, conidEnd)};
+ return {token(TokenType::Keyword, line, start, current + opLen)};
+ }
+ // check for single line comment
+ if (opLen >= 2 && std::all_of(line->begin() + current, line->begin() + current + opLen,
+ [](const QChar c) { return c == '-'; })) {
+ QVector result;
+ // possibly add constructor + op '.'
+ if (conidEnd > start) {
+ result.append(token(TokenType::Constructor, line, start, conidEnd));
+ result.append(token(TokenType::Operator, line, conidEnd, current));
+ }
+ // rest is comment
+ result.append(token(TokenType::SingleLineComment, line, current, length));
+ return result;
+ }
+ // check for (qualified?) operator constructor
+ if (line->at(current) == ':')
+ return {token(TokenType::OperatorConstructor, line, start, current + opLen)};
+ return {token(TokenType::Operator, line, start, current + opLen)};
+ }
+ // Foo.Blah.
+ if (conidEnd > start)
+ return {token(TokenType::Constructor, line, start, conidEnd)};
+ return {};
+}
+
+static int getEscape(const QString &line, int start)
+{
+ if (CHAR_ESCAPES->contains(line.at(start)))
+ return 1;
+
+ // decimal
+ if (line.at(start).isDigit())
+ return grab(line, start + 1, isDigit) + 1;
+ // octal
+ if (line.at(start) == 'o') {
+ const int count = grab(line, start + 1, isOctit);
+ if (count < 1) // no octal number after 'o'
+ return 0;
+ return count + 1;
+ }
+ // hexadecimal
+ if (line.at(start) == 'x') {
+ const int count = grab(line, start + 1, isHexit);
+ if (count < 1) // no octal number after 'o'
+ return 0;
+ return count + 1;
+ }
+ // ascii cntrl
+ if (line.at(start) == '^') {
+ const int count = grab(line, start + 1, isCntrl);
+ if (count < 1) // no octal number after 'o'
+ return 0;
+ return count + 1;
+ }
+ const QStringView s = QStringView(line).mid(start);
+ for (const QString &esc : *ASCII_ESCAPES) {
+ if (s.startsWith(esc))
+ return esc.length();
+ }
+ return 0;
+}
+
+static QVector getString(std::shared_ptr line, int start, bool *inStringGap/*in-out*/)
+{
+ // Haskell has the specialty of using \\ within strings for multiline strings
+ const int length = line->length();
+ if (start >= length)
+ return {};
+ QVector result;
+ int tokenStart = start;
+ int current = tokenStart;
+ bool inString = *inStringGap;
+ do {
+ const QChar c = line->at(current);
+ if (*inStringGap && !c.isSpace() && c != '\\') {
+ // invalid non-whitespace in string gap
+ // add previous string as token, this is at least a whitespace
+ result.append(token(TokenType::String, line, tokenStart, current));
+ // then add wrong non-whitespace
+ tokenStart = current;
+ do { ++current; } while (current < length && !line->at(current).isSpace());
+ result.append(token(TokenType::StringError, line, tokenStart, current));
+ tokenStart = current;
+ } else if (c == '"') {
+ inString = !inString;
+ ++current;
+ } else if (inString) {
+ if (c == '\\') {
+ ++current;
+ if (*inStringGap) {
+ // ending string gap
+ *inStringGap = false;
+ } else if (current >= length || line->at(current).isSpace()) {
+ // starting string gap
+ *inStringGap = true;
+ current = std::min(current + 1, length);
+ } else { // there is at least one character after current
+ const int escapeLength = getEscape(*line, current);
+ if (escapeLength > 0) {
+ // valid escape
+ // add previous string as token without backslash, if necessary
+ if (tokenStart < current - 1/*backslash*/)
+ result.append(token(TokenType::String, line, tokenStart, current - 1));
+ tokenStart = current - 1; // backslash
+ current += escapeLength;
+ result.append(token(TokenType::EscapeSequence, line, tokenStart, current));
+ tokenStart = current;
+ } else { // invalid escape sequence
+ // add previous string as token, this is at least backslash
+ result.append(token(TokenType::String, line, tokenStart, current));
+ result.append(token(TokenType::StringError, line, current, current + 1));
+ ++current;
+ tokenStart = current;
+ }
+ }
+ } else {
+ ++current;
+ }
+ }
+ } while (current < length && inString);
+ if (current > tokenStart)
+ result.append(token(TokenType::String, line, tokenStart, current));
+ if (inString && !*inStringGap) { // unterminated string
+ // mark last character of last token as Unknown as an error hint
+ if (!result.isEmpty()) { // should actually never be different
+ Token &lastRef = result.last();
+ if (lastRef.length == 1) {
+ lastRef.type = TokenType::StringError;
+ } else {
+ --lastRef.length;
+ lastRef.text = QStringView(*line).mid(lastRef.startCol, lastRef.length);
+ result.append(token(TokenType::StringError, line, current - 1, current));
+ }
+ }
+ }
+ return result;
+}
+
+static QVector getMultiLineComment(std::shared_ptr line, int start,
+ int *commentLevel/*in_out*/)
+{
+ // Haskell multiline comments can be nested {- foo {- bar -} blah -}
+ const int length = line->length();
+ int current = start;
+ do {
+ const QStringView test = QStringView(*line).mid(current, 2);
+ if (test == QLatin1String("{-")) {
+ ++(*commentLevel);
+ current += 2;
+ } else if (test == QLatin1String("-}") && *commentLevel > 0) {
+ --(*commentLevel);
+ current += 2;
+ } else if (*commentLevel > 0) {
+ ++current;
+ }
+ } while (current < length && *commentLevel > 0);
+ if (current > start) {
+ return {token(TokenType::MultiLineComment, line, start, current)};
+ }
+ return {};
+}
+
+static QVector getChar(std::shared_ptr line, int start)
+{
+ if (line->at(start) != '\'')
+ return {};
+ QVector result;
+ const int length = line->length();
+ int tokenStart = start;
+ int current = tokenStart + 1;
+ bool inChar = true;
+ int count = 0;
+ while (current < length && inChar) {
+ if (line->at(current) == '\'') {
+ inChar = false;
+ ++current;
+ } else if (count == 1) {
+ // we already have one character, so start Unknown token
+ if (current > tokenStart)
+ result.append(token(TokenType::Char, line, tokenStart, current));
+ tokenStart = current;
+ ++count;
+ ++current;
+ } else if (count > 1) {
+ ++count;
+ ++current;
+ } else if (line->at(current) == '\\') {
+ if (current + 1 < length) {
+ ++current;
+ ++count;
+ const int escapeLength = getEscape(*line, current);
+ if (line->at(current) != '&' && escapeLength > 0) { // no & escape for chars
+ // valid escape
+ // add previous string as token without backslash, if necessary
+ if (tokenStart < current - 1/*backslash*/)
+ result.append(token(TokenType::Char, line, tokenStart, current - 1));
+ tokenStart = current - 1; // backslash
+ current += escapeLength;
+ result.append(token(TokenType::EscapeSequence, line, tokenStart, current));
+ tokenStart = current;
+ } else { // invalid escape sequence
+ // add previous string as token, this is at least backslash
+ result.append(token(TokenType::Char, line, tokenStart, current));
+ result.append(token(TokenType::CharError, line, current, current + 1));
+ ++current;
+ tokenStart = current;
+ }
+ } else {
+ ++current;
+ }
+ } else {
+ ++count;
+ ++current;
+ }
+ }
+ if (count > 1 && inChar) {
+ // too long and unterminated, just add Unknown token till end
+ result.append(token(TokenType::CharError, line, tokenStart, current));
+ } else if (count > 1) {
+ // too long but terminated, add Unknown up to ending quote, then quote
+ result.append(token(TokenType::CharError, line, tokenStart, current - 1));
+ result.append(token(TokenType::Char, line, current - 1, current));
+ } else if (inChar || count < 1) {
+ // unterminated, or no character inside, mark last character as error
+ if (current > tokenStart + 1)
+ result.append(token(TokenType::Char, line, tokenStart, current - 1));
+ result.append(token(TokenType::CharError, line, current - 1, current));
+ } else {
+ result.append(token(TokenType::Char, line, tokenStart, current));
+ }
+ return result;
+}
+
+static QVector getSpecial(std::shared_ptr line, int start)
+{
+ if (SPECIAL->contains(line->at(start)))
+ return {{TokenType::Special, start, 1, QStringView(*line).mid(start, 1), line}};
+ return {};
+}
+
+Tokens HaskellTokenizer::tokenize(const QString &line, int startState)
+{
+ Tokens result(std::make_shared(line));
+ const int length = result.source->length();
+ bool inStringGap = startState == int(Tokens::State::StringGap);
+ int multiLineCommentLevel = std::max(startState - int(Tokens::State::MultiLineCommentGuard), 0);
+ int currentStart = 0;
+ QVector tokens;
+ while (currentStart < length) {
+ if (multiLineCommentLevel <= 0 &&
+ !(tokens = getString(result.source, currentStart, &inStringGap)).isEmpty()) {
+ result.append(tokens);
+ } else if (!(tokens = getMultiLineComment(result.source, currentStart,
+ &multiLineCommentLevel)).isEmpty()) {
+ result.append(tokens);
+ } else if (!(tokens = getChar(result.source, currentStart)).isEmpty()) {
+ result.append(tokens);
+ } else if (!(tokens = getSpace(result.source, currentStart)).isEmpty()) {
+ result.append(tokens);
+ } else if (!(tokens = getNumber(result.source, currentStart)).isEmpty()) {
+ result.append(tokens);
+ } else if (!(tokens = getIdOrOpOrSingleLineComment(result.source, currentStart)).isEmpty()) {
+ result.append(tokens);
+ } else if (!(tokens = getSpecial(result.source, currentStart)).isEmpty()) {
+ result.append(tokens);
+ } else {
+ tokens = {{TokenType::Unknown,
+ currentStart,
+ 1,
+ QStringView(*result.source).mid(currentStart, 1),
+ result.source}};
+ result.append(tokens);
+ }
+ currentStart += std::accumulate(tokens.cbegin(), tokens.cend(), 0,
+ [](int s, const Token &t) { return s + t.length; });
+ }
+ if (inStringGap)
+ result.state = int(Tokens::State::StringGap);
+ else if (multiLineCommentLevel > 0)
+ result.state = int(Tokens::State::MultiLineCommentGuard) + multiLineCommentLevel;
+ return result;
+}
+
+bool Token::isValid() const
+{
+ return type != TokenType::Unknown;
+}
+
+} // Internal
+} // Haskell
diff --git a/src/plugins/haskell/haskelltokenizer.h b/src/plugins/haskell/haskelltokenizer.h
new file mode 100644
index 00000000000..63d82d960b0
--- /dev/null
+++ b/src/plugins/haskell/haskelltokenizer.h
@@ -0,0 +1,93 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include
+#include
+#include
+
+#include
+
+namespace Haskell {
+namespace Internal {
+
+enum class TokenType {
+ Variable,
+ Constructor,
+ Operator,
+ OperatorConstructor,
+ Whitespace,
+ String,
+ StringError,
+ Char,
+ CharError,
+ EscapeSequence,
+ Integer,
+ Float,
+ Keyword,
+ Special,
+ SingleLineComment,
+ MultiLineComment,
+ Unknown
+};
+
+class Token {
+public:
+ bool isValid() const;
+
+ TokenType type = TokenType::Unknown;
+ int startCol = -1;
+ int length = -1;
+ QStringView text;
+
+ std::shared_ptr source; // keep the string ref alive
+};
+
+class Tokens : public QVector
+{
+public:
+ enum class State {
+ None = -1,
+ StringGap = 0, // gap == two backslashes enclosing only whitespace
+ MultiLineCommentGuard // nothing may follow that
+ };
+
+ Tokens(std::shared_ptr source);
+
+ Token tokenAtColumn(int col) const;
+
+ std::shared_ptr source;
+ int state = int(State::None);
+};
+
+class HaskellTokenizer
+{
+public:
+ static Tokens tokenize(const QString &line, int startState);
+};
+
+} // Internal
+} // Haskell
diff --git a/src/plugins/haskell/images/category_haskell.png b/src/plugins/haskell/images/category_haskell.png
new file mode 100644
index 00000000000..947f948bd91
Binary files /dev/null and b/src/plugins/haskell/images/category_haskell.png differ
diff --git a/src/plugins/haskell/optionspage.cpp b/src/plugins/haskell/optionspage.cpp
new file mode 100644
index 00000000000..63d159443b0
--- /dev/null
+++ b/src/plugins/haskell/optionspage.cpp
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "optionspage.h"
+
+#include "haskellconstants.h"
+#include "haskellmanager.h"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace Haskell {
+namespace Internal {
+
+OptionsPage::OptionsPage()
+{
+ setId(Constants::OPTIONS_GENERAL);
+ setDisplayName(tr("General"));
+ setCategory("J.Z.Haskell");
+ setDisplayCategory(tr("Haskell"));
+ setCategoryIcon(Utils::Icon(":/haskell/images/category_haskell.png"));
+}
+
+QWidget *OptionsPage::widget()
+{
+ using namespace Utils;
+ if (!m_widget) {
+ m_widget = new QWidget;
+ auto topLayout = new QVBoxLayout;
+ m_widget->setLayout(topLayout);
+ auto generalBox = new QGroupBox(tr("General"));
+ topLayout->addWidget(generalBox);
+ topLayout->addStretch(10);
+ auto boxLayout = new QHBoxLayout;
+ generalBox->setLayout(boxLayout);
+ boxLayout->addWidget(new QLabel(tr("Stack executable:")));
+ m_stackPath = new PathChooser();
+ m_stackPath->setExpectedKind(PathChooser::ExistingCommand);
+ m_stackPath->setPromptDialogTitle(tr("Choose Stack Executable"));
+ m_stackPath->setFilePath(HaskellManager::stackExecutable());
+ m_stackPath->setCommandVersionArguments({"--version"});
+ boxLayout->addWidget(m_stackPath);
+ }
+ return m_widget;
+}
+
+void OptionsPage::apply()
+{
+ if (!m_widget)
+ return;
+ HaskellManager::setStackExecutable(m_stackPath->rawFilePath());
+}
+
+void OptionsPage::finish()
+{
+}
+
+} // namespace Internal
+} // namespace Haskell
diff --git a/src/plugins/haskell/optionspage.h b/src/plugins/haskell/optionspage.h
new file mode 100644
index 00000000000..c1663c7a47e
--- /dev/null
+++ b/src/plugins/haskell/optionspage.h
@@ -0,0 +1,53 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include
+#include
+
+#include
+
+namespace Haskell {
+namespace Internal {
+
+class OptionsPage : public Core::IOptionsPage
+{
+ Q_OBJECT
+
+public:
+ OptionsPage();
+
+ QWidget *widget() override;
+ void apply() override;
+ void finish() override;
+
+private:
+ QPointer m_widget;
+ QPointer m_stackPath;
+};
+
+} // namespace Internal
+} // namespace Haskell
diff --git a/src/plugins/haskell/share/wizards/module/file.hs b/src/plugins/haskell/share/wizards/module/file.hs
new file mode 100644
index 00000000000..e3f85b3f775
--- /dev/null
+++ b/src/plugins/haskell/share/wizards/module/file.hs
@@ -0,0 +1 @@
+module %{BaseName} where
diff --git a/src/plugins/haskell/share/wizards/module/wizard.json b/src/plugins/haskell/share/wizards/module/wizard.json
new file mode 100644
index 00000000000..37f6ebf975b
--- /dev/null
+++ b/src/plugins/haskell/share/wizards/module/wizard.json
@@ -0,0 +1,42 @@
+{
+ "version": 1,
+ "supportedProjectTypes": [ ],
+ "id": "C.Module",
+ "category": "S.Haskell",
+ "trDescription": "Creates a Haskell module.",
+ "trDisplayName": "Module",
+ "trDisplayCategory": "Haskell",
+ "iconText": "hs",
+ "enabled": "%{JS: value('Plugins').indexOf('Haskell') >= 0}",
+
+ "options": [
+ { "key": "FileName", "value": "%{JS: Util.fileName(value('TargetPath'), 'hs')}" },
+ { "key": "BaseName", "value": "%{JS: Util.baseName(value('FileName'))}" }
+ ],
+
+ "pages" :
+ [
+ {
+ "trDisplayName": "Location",
+ "trShortTitle": "Location",
+ "typeId": "File"
+ },
+ {
+ "trDisplayName": "Project Management",
+ "trShortTitle": "Summary",
+ "typeId": "Summary"
+ }
+ ],
+ "generators" :
+ [
+ {
+ "typeId": "File",
+ "data":
+ {
+ "source": "file.hs",
+ "target": "%{FileName}",
+ "openInEditor": true
+ }
+ }
+ ]
+}
diff --git a/src/plugins/haskell/stackbuildstep.cpp b/src/plugins/haskell/stackbuildstep.cpp
new file mode 100644
index 00000000000..52ed64f2834
--- /dev/null
+++ b/src/plugins/haskell/stackbuildstep.cpp
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "stackbuildstep.h"
+
+#include "haskellconstants.h"
+#include "haskellmanager.h"
+
+#include
+#include
+#include
+#include
+
+using namespace ProjectExplorer;
+
+namespace Haskell {
+namespace Internal {
+
+StackBuildStep::StackBuildStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id)
+ : AbstractProcessStep(bsl, id)
+{
+ setDefaultDisplayName(trDisplayName());
+}
+
+QWidget *StackBuildStep::createConfigWidget()
+{
+ return new QWidget;
+}
+
+QString StackBuildStep::trDisplayName()
+{
+ return tr("Stack Build");
+}
+
+bool StackBuildStep::init()
+{
+ if (AbstractProcessStep::init()) {
+ const auto projectDir = QDir(project()->projectDirectory().toString());
+ processParameters()->setCommandLine(
+ {HaskellManager::stackExecutable(),
+ {"build", "--work-dir", projectDir.relativeFilePath(buildDirectory().toString())}});
+ processParameters()->setEnvironment(buildEnvironment());
+ }
+ return true;
+}
+
+StackBuildStepFactory::StackBuildStepFactory()
+{
+ registerStep(Constants::C_STACK_BUILD_STEP_ID);
+ setDisplayName(StackBuildStep::StackBuildStep::trDisplayName());
+ setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_BUILD);
+}
+
+} // namespace Internal
+} // namespace Haskell
diff --git a/src/plugins/haskell/stackbuildstep.h b/src/plugins/haskell/stackbuildstep.h
new file mode 100644
index 00000000000..d74d155b708
--- /dev/null
+++ b/src/plugins/haskell/stackbuildstep.h
@@ -0,0 +1,55 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include
+
+namespace Haskell {
+namespace Internal {
+
+class StackBuildStep : public ProjectExplorer::AbstractProcessStep
+{
+ Q_OBJECT
+
+public:
+ StackBuildStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id);
+
+ QWidget *createConfigWidget() override;
+
+ static QString trDisplayName();
+
+protected:
+ bool init() override;
+};
+
+class StackBuildStepFactory : public ProjectExplorer::BuildStepFactory
+{
+public:
+ StackBuildStepFactory();
+};
+
+} // namespace Internal
+} // namespace Haskell
diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt
index 36c9c2c59cf..c71cbafdb2d 100644
--- a/tests/auto/CMakeLists.txt
+++ b/tests/auto/CMakeLists.txt
@@ -9,6 +9,7 @@ add_subdirectory(environment)
add_subdirectory(extensionsystem)
add_subdirectory(externaltool)
add_subdirectory(filesearch)
+add_subdirectory(haskell)
add_subdirectory(json)
add_subdirectory(languageserverprotocol)
add_subdirectory(mapreduce)
diff --git a/tests/auto/haskell/CMakeLists.txt b/tests/auto/haskell/CMakeLists.txt
new file mode 100644
index 00000000000..cdcccae5edb
--- /dev/null
+++ b/tests/auto/haskell/CMakeLists.txt
@@ -0,0 +1,8 @@
+add_qtc_test(tst_tokenizer
+ DEPENDS Qt::Core Qt::Test
+ INCLUDES ../../../src/plugins/haskell
+ SOURCES
+ tst_tokenizer.cpp
+ ../../../src/plugins/haskell/haskelltokenizer.cpp
+ ../../../src/plugins/haskell/haskelltokenizer.h
+)
diff --git a/tests/auto/haskell/tst_tokenizer.cpp b/tests/auto/haskell/tst_tokenizer.cpp
new file mode 100644
index 00000000000..ffa34b205b8
--- /dev/null
+++ b/tests/auto/haskell/tst_tokenizer.cpp
@@ -0,0 +1,730 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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 The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include
+
+#include
+#include
+
+using namespace Haskell::Internal;
+
+const QSet escapes{'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"', '\'', '&'};
+
+struct TokenInfo
+{
+ TokenType type;
+ int column;
+ QString text;
+};
+
+Q_DECLARE_METATYPE(TokenInfo)
+
+bool operator==(const TokenInfo &info, const Token &token)
+{
+ return info.type == token.type
+ && info.column == token.startCol
+ && info.text.length() == token.length
+ && info.text == token.text.toString();
+}
+
+bool operator==(const Token &token, const TokenInfo &info)
+{
+ return info == token;
+}
+
+class tst_Tokenizer : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void singleLineComment_data();
+ void singleLineComment();
+
+ void multiLineComment_data();
+ void multiLineComment();
+
+ void string_data();
+ void string();
+
+ void character_data();
+ void character();
+
+ void number_data();
+ void number();
+
+ void keyword_data();
+ void keyword();
+
+ void variable_data();
+ void variable();
+
+ void constructor_data();
+ void constructor();
+
+ void op_data();
+ void op();
+
+private:
+ void setupData();
+ void addRow(const char *name,
+ const QString &input,
+ const QList &tokens,
+ Tokens::State startState = Tokens::State::None,
+ Tokens::State endState = Tokens::State::None);
+ void checkData();
+};
+
+void tst_Tokenizer::setupData()
+{
+ QTest::addColumn("input");
+ QTest::addColumn>("output");
+ QTest::addColumn("startState");
+ QTest::addColumn("endState");
+}
+
+void tst_Tokenizer::addRow(const char *name,
+ const QString &input,
+ const QList &tokens,
+ Tokens::State startState,
+ Tokens::State endState)
+{
+ QTest::newRow(name) << input << tokens << int(startState) << int(endState);
+}
+
+void tst_Tokenizer::checkData()
+{
+ QFETCH(QString, input);
+ QFETCH(QList, output);
+ QFETCH(int, startState);
+ QFETCH(int, endState);
+ const Tokens tokens = HaskellTokenizer::tokenize(input, startState);
+ QCOMPARE(tokens.length(), output.length());
+ QCOMPARE(tokens.state, endState);
+ for (int i = 0; i < tokens.length(); ++i) {
+ const Token t = tokens.at(i);
+ const TokenInfo ti = output.at(i);
+ QVERIFY2(t == ti, QString("Token at index %1 does not match, {%2, %3, \"%4\"} != {%5, %6, \"%7\"}")
+ .arg(i)
+ .arg(int(t.type)).arg(t.startCol).arg(t.text.toString())
+ .arg(int(ti.type)).arg(ti.column).arg(ti.text)
+ .toUtf8().constData());
+ }
+}
+
+void tst_Tokenizer::singleLineComment_data()
+{
+ setupData();
+
+ addRow("simple", " -- foo", {
+ {TokenType::Whitespace, 0, " "},
+ {TokenType::SingleLineComment, 1, "-- foo"}
+ });
+ addRow("dash, id", "--foo", {
+ {TokenType::SingleLineComment, 0, "--foo"}
+ });
+ addRow("dash, space, op", "-- |foo", {
+ {TokenType::SingleLineComment, 0, "-- |foo"}
+ });
+ addRow("multi-dash, space", "---- foo", {
+ {TokenType::SingleLineComment, 0, "---- foo"}
+ });
+ addRow("dash, op", "--| foo", {
+ {TokenType::Operator, 0, "--|"},
+ {TokenType::Whitespace, 3, " "},
+ {TokenType::Variable, 4, "foo"}
+ });
+ addRow("dash, special", "--(foo", {
+ {TokenType::SingleLineComment, 0, "--(foo"}
+ });
+ addRow("not a qualified varsym", "F.-- foo", {
+ {TokenType::Constructor, 0, "F"},
+ {TokenType::Operator, 1, "."},
+ {TokenType::SingleLineComment, 2, "-- foo"}
+ });
+}
+
+void tst_Tokenizer::singleLineComment()
+{
+ checkData();
+}
+
+void tst_Tokenizer::multiLineComment_data()
+{
+ setupData();
+
+ addRow("trailing dashes", "{---foo -}", {
+ {TokenType::MultiLineComment, 0, "{---foo -}"}
+ });
+ addRow("multiline", "{- foo", {
+ {TokenType::MultiLineComment, 0, "{- foo"}
+ },
+ Tokens::State::None,
+ Tokens::State(int(Tokens::State::MultiLineCommentGuard) + 1));
+ addRow("multiline2", "bar -}", {
+ {TokenType::MultiLineComment, 0, "bar -}"}
+ },
+ Tokens::State(int(Tokens::State::MultiLineCommentGuard) + 1),
+ Tokens::State::None);
+ addRow("nested", "{- fo{-o", {
+ {TokenType::MultiLineComment, 0, "{- fo{-o"}
+ },
+ Tokens::State::None,
+ Tokens::State(int(Tokens::State::MultiLineCommentGuard) + 2));
+ addRow("nested2", "bar -}", {
+ {TokenType::MultiLineComment, 0, "bar -}"}
+ },
+ Tokens::State(int(Tokens::State::MultiLineCommentGuard) + 2),
+ Tokens::State(int(Tokens::State::MultiLineCommentGuard) + 1));
+ addRow("nested3", "bar -}", {
+ {TokenType::MultiLineComment, 0, "bar -}"}
+ },
+ Tokens::State(int(Tokens::State::MultiLineCommentGuard) + 1),
+ Tokens::State::None);
+}
+
+void tst_Tokenizer::multiLineComment()
+{
+ checkData();
+}
+
+void tst_Tokenizer::string_data()
+{
+ setupData();
+
+ addRow("simple", "\"foo\"", {
+ {TokenType::String, 0, "\"foo\""}
+ });
+
+ addRow("unterminated", "\"", {
+ {TokenType::StringError, 0, "\""}
+ });
+ addRow("unterminated2", "\"foo", {
+ {TokenType::String, 0, "\"fo"},
+ {TokenType::StringError, 3, "o"}
+ });
+ addRow("unterminated with escape", "\"\\\\", {
+ {TokenType::String, 0, "\""},
+ {TokenType::EscapeSequence, 1, "\\"},
+ {TokenType::StringError, 2, "\\"}
+ });
+
+ // gaps
+ addRow("gap", "\" \\ \\\"", {
+ {TokenType::String, 0, "\" \\ \\\""}
+ });
+ addRow("gap over endline", "\"foo\\", {
+ {TokenType::String, 0, "\"foo\\"}
+ },
+ Tokens::State::None, Tokens::State::StringGap);
+ addRow("gap over endline2", "\\foo\"", {
+ {TokenType::String, 0, "\\foo\""}
+ },
+ Tokens::State::StringGap, Tokens::State::None);
+ addRow("gap error", "\"\\ ab \\\"", {
+ {TokenType::String, 0, "\"\\ "},
+ {TokenType::StringError, 3, "ab"},
+ {TokenType::String, 5, " \\\""}
+ });
+ addRow("gap error with quote", "\"\\ \"", {
+ {TokenType::String, 0, "\"\\ "},
+ {TokenType::StringError, 3, "\""}
+ },
+ Tokens::State::None, Tokens::State::StringGap);
+
+ // char escapes (including wrong ones)
+ for (char c = '!'; c <= '~'; ++c) {
+ // skip uppercase and '^', since these can be part of ascii escapes
+ // and 'o' and 'x' since they start octal and hex escapes
+ // and digits as part of decimal escape
+ if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '^' || c == 'o' || c == 'x')
+ continue;
+ const QChar qc(c);
+ const QByteArray name = QString("charesc '%1'").arg(qc).toUtf8();
+ const QString input = QString("\"\\%1\"").arg(qc);
+ if (escapes.contains(c)) {
+ addRow(name.constData(), input, {
+ {TokenType::String, 0, "\""},
+ {TokenType::EscapeSequence, 1, QLatin1String("\\") + qc},
+ {TokenType::String, 3, "\""}
+ });
+ } else {
+ addRow(name.constData(), input, {
+ {TokenType::String, 0, "\"\\"},
+ {TokenType::StringError, 2, qc},
+ {TokenType::String, 3, "\""}
+ });
+ }
+ }
+
+ addRow("decimal escape", "\"\\1234a\"", {
+ {TokenType::String, 0, "\""},
+ {TokenType::EscapeSequence, 1, "\\1234"},
+ {TokenType::String, 6, "a\""}
+ });
+
+ addRow("octal escape", "\"\\o0678a\"", {
+ {TokenType::String, 0, "\""},
+ {TokenType::EscapeSequence, 1, "\\o067"},
+ {TokenType::String, 6, "8a\""}
+ });
+ addRow("octal escape error", "\"\\o8a\"", {
+ {TokenType::String, 0, "\"\\"},
+ {TokenType::StringError, 2, "o"},
+ {TokenType::String, 3, "8a\""}
+ });
+
+ addRow("hexadecimal escape", "\"\\x0678Abg\"", {
+ {TokenType::String, 0, "\""},
+ {TokenType::EscapeSequence, 1, "\\x0678Ab"},
+ {TokenType::String, 9, "g\""}
+ });
+ addRow("hexadecimal escape error", "\"\\xg\"", {
+ {TokenType::String, 0, "\"\\"},
+ {TokenType::StringError, 2, "x"},
+ {TokenType::String, 3, "g\""}
+ });
+
+ // ascii cntrl escapes (including wrong ones)
+ for (char c = '!'; c <= '~'; ++c) {
+ if (c == '"') // is special because it also ends the string
+ continue;
+ const QChar qc(c);
+ const QByteArray name = QString("ascii cntrl '^%1'").arg(qc).toUtf8();
+ const QString input = QString("\"\\^%1\"").arg(qc);
+ if ((qc >= 'A' && qc <= 'Z') || qc == '@' || qc == '[' || qc == '\\' || qc == ']'
+ || qc == '^' || qc == '_') {
+ addRow(name.constData(), input, {
+ {TokenType::String, 0, "\""},
+ {TokenType::EscapeSequence, 1, QLatin1String("\\^") + qc},
+ {TokenType::String, 4, "\""}
+ });
+ } else {
+ addRow(name.constData(), input, {
+ {TokenType::String, 0, "\"\\"},
+ {TokenType::StringError, 2, "^"},
+ {TokenType::String, 3, QString(qc) + "\""}
+ });
+ }
+ }
+
+ addRow("ascii escape SOH", "\"\\SOHN\"", {
+ {TokenType::String, 0, "\""},
+ {TokenType::EscapeSequence, 1, "\\SOH"},
+ {TokenType::String, 5, "N\""}
+ });
+ addRow("ascii escape SO", "\"\\SON\"", {
+ {TokenType::String, 0, "\""},
+ {TokenType::EscapeSequence, 1, "\\SO"},
+ {TokenType::String, 4, "N\""}
+ });
+ addRow("ascii escape error", "\"\\TON\"", {
+ {TokenType::String, 0, "\"\\"},
+ {TokenType::StringError, 2, "T"},
+ {TokenType::String, 3, "ON\""}
+ });
+ addRow("ascii escape error 2", "\"\\STO\"", {
+ {TokenType::String, 0, "\"\\"},
+ {TokenType::StringError, 2, "S"},
+ {TokenType::String, 3, "TO\""}
+ });
+}
+
+void tst_Tokenizer::string()
+{
+ checkData();
+}
+
+void tst_Tokenizer::character_data()
+{
+ setupData();
+
+ addRow("simple", "'a'", {
+ {TokenType::Char, 0, "'a'"}
+ });
+ addRow("too many", "'abc'", {
+ {TokenType::Char, 0, "'a"},
+ {TokenType::CharError, 2, "bc"},
+ {TokenType::Char, 4, "'"}
+ });
+ addRow("too few", "''", {
+ {TokenType::Char, 0, "'"},
+ {TokenType::CharError, 1, "'"}
+ });
+ addRow("only quote", "'", {
+ {TokenType::CharError, 0, "'"}
+ });
+ addRow("unterminated", "'a", {
+ {TokenType::Char, 0, "'"},
+ {TokenType::CharError, 1, "a"}
+ });
+ addRow("unterminated too many", "'abc", {
+ {TokenType::Char, 0, "'a"},
+ {TokenType::CharError, 2, "bc"}
+ });
+ addRow("unterminated backslash", "'\\", {
+ {TokenType::Char, 0, "'"},
+ {TokenType::CharError, 1, "\\"}
+ });
+
+ // char escapes (including wrong ones)
+ for (char c = '!'; c <= '~'; ++c) {
+ // skip uppercase and '^', since these can be part of ascii escapes
+ // and 'o' and 'x' since they start octal and hex escapes
+ // and digits as part of decimal escape
+ if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '^' || c == 'o' || c == 'x')
+ continue;
+ const QChar qc(c);
+ const QByteArray name = QString("charesc '%1'").arg(qc).toUtf8();
+ const QString input = QString("'\\%1'").arg(qc);
+ if (c != '&' && escapes.contains(c)) {
+ addRow(name.constData(), input, {
+ {TokenType::Char, 0, "'"},
+ {TokenType::EscapeSequence, 1, QLatin1String("\\") + qc},
+ {TokenType::Char, 3, "'"}
+ });
+ } else {
+ addRow(name.constData(), input, {
+ {TokenType::Char, 0, "'\\"},
+ {TokenType::CharError, 2, qc},
+ {TokenType::Char, 3, "'"}
+ });
+ }
+ }
+
+ addRow("decimal escape", "'\\1234'", {
+ {TokenType::Char, 0, "'"},
+ {TokenType::EscapeSequence, 1, "\\1234"},
+ {TokenType::Char, 6, "'"}
+ });
+ addRow("decimal escape too long", "'\\1234a'", {
+ {TokenType::Char, 0, "'"},
+ {TokenType::EscapeSequence, 1, "\\1234"},
+ {TokenType::CharError, 6, "a"},
+ {TokenType::Char, 7, "'"}
+ });
+
+ addRow("octal escape", "'\\o067'", {
+ {TokenType::Char, 0, "'"},
+ {TokenType::EscapeSequence, 1, "\\o067"},
+ {TokenType::Char, 6, "'"}
+ });
+ addRow("octal escape error", "'\\o8'", {
+ {TokenType::Char, 0, "'\\"},
+ {TokenType::CharError, 2, "o"},
+ {TokenType::CharError, 3, "8"},
+ {TokenType::Char, 4, "'"}
+ });
+
+ addRow("hexadecimal escape", "'\\x0678Ab'", {
+ {TokenType::Char, 0, "'"},
+ {TokenType::EscapeSequence, 1, "\\x0678Ab"},
+ {TokenType::Char, 9, "'"}
+ });
+ addRow("hexadecimal escape error", "'\\xg'", {
+ {TokenType::Char, 0, "'\\"},
+ {TokenType::CharError, 2, "x"},
+ {TokenType::CharError, 3, "g"},
+ {TokenType::Char, 4, "'"}
+ });
+
+ // ascii cntrl escapes (including wrong ones)
+ for (char c = '!'; c <= '~'; ++c) {
+ if (c == '\'') // is special because it also ends the string
+ continue;
+ const QChar qc(c);
+ const QByteArray name = QString("ascii cntrl '^%1'").arg(qc).toUtf8();
+ const QString input = QString("'\\^%1'").arg(qc);
+ if ((qc >= 'A' && qc <= 'Z') || qc == '@' || qc == '[' || qc == '\\' || qc == ']'
+ || qc == '^' || qc == '_') {
+ addRow(name.constData(), input, {
+ {TokenType::Char, 0, "'"},
+ {TokenType::EscapeSequence, 1, QLatin1String("\\^") + qc},
+ {TokenType::Char, 4, "'"}
+ });
+ } else {
+ addRow(name.constData(), input, {
+ {TokenType::Char, 0, "'\\"},
+ {TokenType::CharError, 2, "^"},
+ {TokenType::CharError, 3, qc},
+ {TokenType::Char, 4, "'"}
+ });
+ }
+ }
+
+ addRow("ascii escape SOH", "'\\SOH'", {
+ {TokenType::Char, 0, "'"},
+ {TokenType::EscapeSequence, 1, "\\SOH"},
+ {TokenType::Char, 5, "'"}
+ });
+ addRow("ascii escape SO, too long", "'\\SON'", {
+ {TokenType::Char, 0, "'"},
+ {TokenType::EscapeSequence, 1, "\\SO"},
+ {TokenType::CharError, 4, "N"},
+ {TokenType::Char, 5, "'"}
+ });
+ addRow("ascii escape error", "'\\TON'", {
+ {TokenType::Char, 0, "'\\"},
+ {TokenType::CharError, 2, "T"},
+ {TokenType::CharError, 3, "ON"},
+ {TokenType::Char, 5, "'"}
+ });
+}
+
+void tst_Tokenizer::character()
+{
+ checkData();
+}
+
+void tst_Tokenizer::number_data()
+{
+ setupData();
+
+ addRow("decimal", "012345", {
+ {TokenType::Integer, 0, "012345"}
+ });
+ addRow("single digit decimal", "0", {
+ {TokenType::Integer, 0, "0"}
+ });
+ addRow("octal", "0o1234", {
+ {TokenType::Integer, 0, "0o1234"}
+ });
+ // this is a bit weird, but correct: octal 1 followed by decimal 8
+ addRow("number after octal", "0O18", {
+ {TokenType::Integer, 0, "0O1"},
+ {TokenType::Integer, 3, "8"}
+ });
+ addRow("not octal", "0o9", {
+ {TokenType::Integer, 0, "0"},
+ {TokenType::Variable, 1, "o9"},
+ });
+ addRow("hexadecimal", "0x9fA", {
+ {TokenType::Integer, 0, "0x9fA"}
+ });
+ // hex number followed by identifier 'g'
+ addRow("hexadecimal", "0X9fg", {
+ {TokenType::Integer, 0, "0X9f"},
+ {TokenType::Variable, 4, "g"}
+ });
+
+ // 0 followed by identifier
+ addRow("decimal followed by identifier", "0z6", {
+ {TokenType::Integer, 0, "0"},
+ {TokenType::Variable, 1, "z6"}
+ });
+
+ addRow("float", "0123.45", {
+ {TokenType::Float, 0, "0123.45"}
+ });
+ addRow("decimal + operator '.'", "0123.", {
+ {TokenType::Integer, 0, "0123"},
+ {TokenType::Operator, 4, "."}
+ });
+ addRow("operator '.' + decimal", ".0123", {
+ {TokenType::Operator, 0, "."},
+ {TokenType::Integer, 1, "0123"}
+ });
+ addRow("without '.', with exp 'e'", "0123e45", {
+ {TokenType::Float, 0, "0123e45"}
+ });
+ addRow("without '.', with exp 'E'", "0123E45", {
+ {TokenType::Float, 0, "0123E45"}
+ });
+ addRow("without '.', with '+'", "0123e+45", {
+ {TokenType::Float, 0, "0123e+45"}
+ });
+ addRow("without '.', with '-'", "0123e-45", {
+ {TokenType::Float, 0, "0123e-45"}
+ });
+ addRow("without '.', with '+', missing decimal", "0123e+", {
+ {TokenType::Integer, 0, "0123"},
+ {TokenType::Variable, 4, "e"},
+ {TokenType::Operator, 5, "+"}
+ });
+ addRow("without '.', missing decimal", "0123e", {
+ {TokenType::Integer, 0, "0123"},
+ {TokenType::Variable, 4, "e"}
+ });
+ addRow("exp 'e'", "01.23e45", {
+ {TokenType::Float, 0, "01.23e45"}
+ });
+ addRow("exp 'E'", "01.23E45", {
+ {TokenType::Float, 0, "01.23E45"}
+ });
+ addRow("with '+'", "01.23e+45", {
+ {TokenType::Float, 0, "01.23e+45"}
+ });
+ addRow("with '-'", "01.23e-45", {
+ {TokenType::Float, 0, "01.23e-45"}
+ });
+ addRow("with '+', missing decimal", "01.23e+", {
+ {TokenType::Float, 0, "01.23"},
+ {TokenType::Variable, 5, "e"},
+ {TokenType::Operator, 6, "+"}
+ });
+ addRow("missing decimal", "01.23e", {
+ {TokenType::Float, 0, "01.23"},
+ {TokenType::Variable, 5, "e"}
+ });
+}
+
+void tst_Tokenizer::number()
+{
+ checkData();
+}
+
+void tst_Tokenizer::keyword_data()
+{
+ setupData();
+
+ addRow("data", "data", {
+ {TokenType::Keyword, 0, "data"}
+ });
+ addRow("not a qualified varid", "Foo.case", {
+ {TokenType::Constructor, 0, "Foo"},
+ {TokenType::Operator, 3, "."},
+ {TokenType::Keyword, 4, "case"}
+ });
+ addRow(":", ":", {
+ {TokenType::Keyword, 0, ":"}
+ });
+ addRow("->", "->", {
+ {TokenType::Keyword, 0, "->"}
+ });
+ addRow("not a qualified varsym", "Foo...", {
+ {TokenType::Constructor, 0, "Foo"},
+ {TokenType::Operator, 3, "..."}
+ });
+}
+
+void tst_Tokenizer::keyword()
+{
+ checkData();
+}
+
+void tst_Tokenizer::variable_data()
+{
+ setupData();
+
+ addRow("simple", "fOo_1'", {
+ {TokenType::Variable, 0, "fOo_1'"}
+ });
+ addRow("start with '_'", "_1", {
+ {TokenType::Variable, 0, "_1"}
+ });
+ addRow("not a keyword", "cases", {
+ {TokenType::Variable, 0, "cases"}
+ });
+ addRow("not a keyword 2", "qualified", {
+ {TokenType::Variable, 0, "qualified"}
+ });
+ addRow("not a keyword 3", "as", {
+ {TokenType::Variable, 0, "as"}
+ });
+ addRow("not a keyword 4", "hiding", {
+ {TokenType::Variable, 0, "hiding"}
+ });
+ addRow(".variable", ".foo", {
+ {TokenType::Operator, 0, "."},
+ {TokenType::Variable, 1, "foo"}
+ });
+ addRow("variable.", "foo.", {
+ {TokenType::Variable, 0, "foo"},
+ {TokenType::Operator, 3, "."}
+ });
+ addRow("variable.variable", "blah.foo", {
+ {TokenType::Variable, 0, "blah"},
+ {TokenType::Operator, 4, "."},
+ {TokenType::Variable, 5, "foo"}
+ });
+ addRow("qualified", "Blah.foo", {
+ {TokenType::Variable, 0, "Blah.foo"}
+ });
+ addRow("qualified2", "Goo.Blah.foo", {
+ {TokenType::Variable, 0, "Goo.Blah.foo"}
+ });
+ addRow("variable + op '..'", "foo..", {
+ {TokenType::Variable, 0, "foo"},
+ {TokenType::Keyword, 3, ".."}
+ });
+ addRow("variable + op '...'", "foo...", {
+ {TokenType::Variable, 0, "foo"},
+ {TokenType::Operator, 3, "..."}
+ });
+}
+
+void tst_Tokenizer::variable()
+{
+ checkData();
+}
+
+void tst_Tokenizer::constructor_data()
+{
+ setupData();
+
+ addRow("simple", "Foo", {
+ {TokenType::Constructor, 0, "Foo"}
+ });
+ addRow("qualified", "Foo.Bar", {
+ {TokenType::Constructor, 0, "Foo.Bar"}
+ });
+ addRow("followed by op '.'", "Foo.Bar.", {
+ {TokenType::Constructor, 0, "Foo.Bar"},
+ {TokenType::Operator, 7, "."}
+ });
+
+}
+
+void tst_Tokenizer::constructor()
+{
+ checkData();
+}
+
+void tst_Tokenizer::op_data()
+{
+ setupData();
+
+ addRow("simple", "+-=", {
+ {TokenType::Operator, 0, "+-="}
+ });
+ addRow("qualified", "Foo.+-=", {
+ {TokenType::Operator, 0, "Foo.+-="}
+ });
+ addRow("qualified '.'", "Foo..", {
+ {TokenType::Operator, 0, "Foo.."}
+ });
+ addRow("constructor plus op", "Foo+", {
+ {TokenType::Constructor, 0, "Foo"},
+ {TokenType::Operator, 3, "+"}
+ });
+}
+
+void tst_Tokenizer::op()
+{
+ checkData();
+}
+
+QTEST_MAIN(tst_Tokenizer)
+
+#include "tst_tokenizer.moc"
diff --git a/tests/manual/haskell/simple/README.md b/tests/manual/haskell/simple/README.md
new file mode 100644
index 00000000000..7011e6ed4e9
--- /dev/null
+++ b/tests/manual/haskell/simple/README.md
@@ -0,0 +1 @@
+# simple test project
diff --git a/tests/manual/haskell/simple/Setup.hs b/tests/manual/haskell/simple/Setup.hs
new file mode 100644
index 00000000000..9a994af677b
--- /dev/null
+++ b/tests/manual/haskell/simple/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/tests/manual/haskell/simple/simple.cabal b/tests/manual/haskell/simple/simple.cabal
new file mode 100644
index 00000000000..dd2a85e6001
--- /dev/null
+++ b/tests/manual/haskell/simple/simple.cabal
@@ -0,0 +1,14 @@
+name: simple
+version: 0.1.0.0
+-- synopsis:
+-- description:
+category: Test
+build-type: Simple
+cabal-version: >=1.10
+extra-source-files: README.md
+
+executable simple
+ hs-source-dirs: src
+ main-is: Main.hs
+ default-language: Haskell2010
+ build-depends: base >= 4.7 && < 5
diff --git a/tests/manual/haskell/simple/src/Main.hs b/tests/manual/haskell/simple/src/Main.hs
new file mode 100644
index 00000000000..9cd992d9e53
--- /dev/null
+++ b/tests/manual/haskell/simple/src/Main.hs
@@ -0,0 +1,5 @@
+module Main where
+
+main :: IO ()
+main = do
+ putStrLn "hello world"
diff --git a/tests/manual/haskell/simple/stack.yaml b/tests/manual/haskell/simple/stack.yaml
new file mode 100644
index 00000000000..9f5c8c628b2
--- /dev/null
+++ b/tests/manual/haskell/simple/stack.yaml
@@ -0,0 +1,66 @@
+# This file was automatically generated by 'stack init'
+#
+# Some commonly used options have been documented as comments in this file.
+# For advanced use and comprehensive documentation of the format, please see:
+# https://docs.haskellstack.org/en/stable/yaml_configuration/
+
+# Resolver to choose a 'specific' stackage snapshot or a compiler version.
+# A snapshot resolver dictates the compiler version and the set of packages
+# to be used for project dependencies. For example:
+#
+# resolver: lts-3.5
+# resolver: nightly-2015-09-21
+# resolver: ghc-7.10.2
+# resolver: ghcjs-0.1.0_ghc-7.10.2
+# resolver:
+# name: custom-snapshot
+# location: "./custom-snapshot.yaml"
+resolver: lts-7.24
+
+# User packages to be built.
+# Various formats can be used as shown in the example below.
+#
+# packages:
+# - some-directory
+# - https://example.com/foo/bar/baz-0.0.2.tar.gz
+# - location:
+# git: https://github.com/commercialhaskell/stack.git
+# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
+# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a
+# extra-dep: true
+# subdirs:
+# - auto-update
+# - wai
+#
+# A package marked 'extra-dep: true' will only be built if demanded by a
+# non-dependency (i.e. a user package), and its test suites and benchmarks
+# will not be run. This is useful for tweaking upstream packages.
+packages:
+- .
+# Dependency packages to be pulled from upstream that are not in the resolver
+# (e.g., acme-missiles-0.3)
+# extra-deps: []
+
+# Override default flag values for local packages and extra-deps
+# flags: {}
+
+# Extra package databases containing global packages
+# extra-package-dbs: []
+
+# Control whether we use the GHC we find on the path
+# system-ghc: true
+#
+# Require a specific version of stack, using version ranges
+# require-stack-version: -any # Default
+# require-stack-version: ">=1.6"
+#
+# Override the architecture used by stack, especially useful on Windows
+# arch: i386
+# arch: x86_64
+#
+# Extra directories used by stack for building
+# extra-include-dirs: [/path/to/dir]
+# extra-lib-dirs: [/path/to/dir]
+#
+# Allow a newer minor version of GHC than the snapshot specifies
+# compiler-check: newer-minor