diff options
46 files changed, 708 insertions, 1927 deletions
diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..107410c --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,41 @@ +# GitHub Actions & Workflows + +The `build_cmake.yml` in this directory adds a [GitHub action][1] and workflow that builds +your plugin anytime you push commits to GitHub on Windows, Linux and macOS. + +The build artifacts can be downloaded from GitHub and be installed into an existing Qt Creator +installation. + +When you push a tag, the workflow also creates a new release on GitHub. + +## Keeping it up to date + +Near the top of the file you find a section starting with `env:`. + +The value for `QT_VERSION` specifies the Qt version to use for building the plugin. + +The value for `QT_CREATOR_VERSION` specifies the Qt Creator version to use for building the plugin. + +The value for `QT_CREATOR_SNAPSHOT` can either be `NO` or `latest` or the build ID of a specific +snapshot build for the Qt Creator version that you specified. + +You need to keep these values updated for different versions of your plugin, and take care +that the Qt version and Qt Creator version you specify are compatible. + +## What it does + +The build job consists of several steps: + +* Install required packages on the build host +* Download, unpack and install the binary for the Qt version +* Download and unpack the binary for the Qt Creator version +* Build the plugin and upload the plugin libraries to GitHub +* If a tag is pushed, create a release on GitHub for the tag, including zipped plugin libraries + for download + +## Limitations + +If your plugin requires additional resources besides the plugin library, you need to adapt the +script accordingly. + +[1]: https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-github-actions diff --git a/.github/workflows/build_cmake.yml b/.github/workflows/build_cmake.yml new file mode 100644 index 0000000..21a889b --- /dev/null +++ b/.github/workflows/build_cmake.yml @@ -0,0 +1,335 @@ +name: QMake Build Matrix + +on: [push] + +env: + PLUGIN_NAME: Haskell + QT_VERSION: 6.4.2 + MACOS_DEPLOYMENT_TARGET: 10.14 + QT_CREATOR_VERSION: 10.0.0-beta1 + QT_CREATOR_SNAPSHOT: latest + CMAKE_VERSION: 3.21.1 + NINJA_VERSION: 1.10.2 + +jobs: + build: + name: ${{ matrix.config.name }} + runs-on: ${{ matrix.config.os }} + strategy: + matrix: + config: + - { + name: "Windows MSVC 2019", artifact: "Windows-x64", + os: windows-2019, + cc: "cl", cxx: "cl", + environment_script: "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Auxiliary/Build/vcvars64.bat", + } + - { + name: "Ubuntu Latest GCC", artifact: "Linux-x64", + os: ubuntu-latest, + cc: "gcc", cxx: "g++" + } + - { + name: "macOS Latest Clang", artifact: "macOS-x64", + os: macos-latest, + cc: "clang", cxx: "clang++" + } + + steps: + - uses: actions/checkout@v2 + + - name: Download Ninja and CMake + shell: cmake -P {0} + run: | + set(cmake_version "$ENV{CMAKE_VERSION}") + set(ninja_version "$ENV{NINJA_VERSION}") + + if ("${{ runner.os }}" STREQUAL "Windows") + set(ninja_suffix "win.zip") + set(cmake_suffix "windows-x86_64.zip") + set(cmake_dir "cmake-${cmake_version}-windows-x86_64/bin") + elseif ("${{ runner.os }}" STREQUAL "Linux") + set(ninja_suffix "linux.zip") + set(cmake_suffix "linux-x86_64.tar.gz") + set(cmake_dir "cmake-${cmake_version}-linux-x86_64/bin") + elseif ("${{ runner.os }}" STREQUAL "macOS") + set(ninja_suffix "mac.zip") + set(cmake_suffix "macos-universal.tar.gz") + set(cmake_dir "cmake-${cmake_version}-macos-universal/CMake.app/Contents/bin") + endif() + + set(ninja_url "https://github.com/ninja-build/ninja/releases/download/v${ninja_version}/ninja-${ninja_suffix}") + file(DOWNLOAD "${ninja_url}" ./ninja.zip SHOW_PROGRESS) + execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./ninja.zip) + + set(cmake_url "https://github.com/Kitware/CMake/releases/download/v${cmake_version}/cmake-${cmake_version}-${cmake_suffix}") + file(DOWNLOAD "${cmake_url}" ./cmake.zip SHOW_PROGRESS) + execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./cmake.zip) + + # Add to PATH environment variable + file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/${cmake_dir}" cmake_dir) + set(path_separator ":") + if ("${{ runner.os }}" STREQUAL "Windows") + set(path_separator ";") + endif() + file(APPEND "$ENV{GITHUB_PATH}" "$ENV{GITHUB_WORKSPACE}${path_separator}${cmake_dir}") + + if (NOT "${{ runner.os }}" STREQUAL "Windows") + execute_process( + COMMAND chmod +x ninja + COMMAND chmod +x ${cmake_dir}/cmake + ) + endif() + + - name: Install system libs + shell: cmake -P {0} + run: | + if ("${{ runner.os }}" STREQUAL "Linux") + execute_process( + COMMAND sudo apt update + ) + execute_process( + COMMAND sudo apt install libgl1-mesa-dev + RESULT_VARIABLE result + ) + if (NOT result EQUAL 0) + message(FATAL_ERROR "Failed to install dependencies") + endif() + endif() + + - name: Download Qt + id: qt + shell: cmake -P {0} + run: | + set(qt_version "$ENV{QT_VERSION}") + + string(REPLACE "." "" qt_version_dotless "${qt_version}") + if ("${{ runner.os }}" STREQUAL "Windows") + set(url_os "windows_x86") + set(qt_package_arch_suffix "win64_msvc2019_64") + set(qt_dir_prefix "${qt_version}/msvc2019_64") + set(qt_package_suffix "-Windows-Windows_10_21H2-MSVC2019-Windows-Windows_10_21H2-X86_64") + elseif ("${{ runner.os }}" STREQUAL "Linux") + set(url_os "linux_x64") + set(qt_package_arch_suffix "gcc_64") + set(qt_dir_prefix "${qt_version}/gcc_64") + set(qt_package_suffix "-Linux-RHEL_8_4-GCC-Linux-RHEL_8_4-X86_64") + elseif ("${{ runner.os }}" STREQUAL "macOS") + set(url_os "mac_x64") + set(qt_package_arch_suffix "clang_64") + set(qt_dir_prefix "${qt_version}/macos") + set(qt_package_suffix "-MacOS-MacOS_12-Clang-MacOS-MacOS_12-X86_64-ARM64") + endif() + + set(qt_base_url "https://download.qt.io/online/qtsdkrepository/${url_os}/desktop/qt6_${qt_version_dotless}") + file(DOWNLOAD "${qt_base_url}/Updates.xml" ./Updates.xml SHOW_PROGRESS) + + file(READ ./Updates.xml updates_xml) + string(REGEX MATCH "<Name>qt.qt6.*<Version>([0-9+-.]+)</Version>" updates_xml_output "${updates_xml}") + set(qt_package_version ${CMAKE_MATCH_1}) + + file(MAKE_DIRECTORY qt) + + # Save the path for other steps + file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/qt/${qt_dir_prefix}" qt_dir) + message("::set-output name=qt_dir::${qt_dir}") + + message("Downloading Qt to ${qt_dir}") + function(downloadAndExtract url archive) + message("Downloading ${url}") + file(DOWNLOAD "${url}" ./${archive} SHOW_PROGRESS) + execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ../${archive} WORKING_DIRECTORY qt) + endfunction() + + foreach(package qtbase qtdeclarative) + downloadAndExtract( + "${qt_base_url}/qt.qt6.${qt_version_dotless}.${qt_package_arch_suffix}/${qt_package_version}${package}${qt_package_suffix}.7z" + ${package}.7z + ) + endforeach() + + foreach(package qt5compat qtshadertools) + downloadAndExtract( + "${qt_base_url}/qt.qt6.${qt_version_dotless}.${package}.${qt_package_arch_suffix}/${qt_package_version}${package}${qt_package_suffix}.7z" + ${package}.7z + ) + endforeach() + + # uic depends on libicu56.so + if ("${{ runner.os }}" STREQUAL "Linux") + downloadAndExtract( + "${qt_base_url}/qt.qt6.${qt_version_dotless}.${qt_package_arch_suffix}/${qt_package_version}icu-linux-Rhel7.2-x64.7z" + icu.7z + ) + endif() + + - name: Download Qt Creator + id: qt_creator + shell: cmake -P {0} + run: | + string(REGEX MATCH "([0-9]+.[0-9]+).[0-9]+" outvar "$ENV{QT_CREATOR_VERSION}") + + set(qtc_base_url "https://download.qt.io/official_releases/qtcreator/${CMAKE_MATCH_1}/$ENV{QT_CREATOR_VERSION}/installer_source") + set(qtc_snapshot "$ENV{QT_CREATOR_SNAPSHOT}") + if (qtc_snapshot) + set(qtc_base_url "https://download.qt.io/snapshots/qtcreator/${CMAKE_MATCH_1}/$ENV{QT_CREATOR_VERSION}/installer_source/${qtc_snapshot}") + endif() + + if ("${{ runner.os }}" STREQUAL "Windows") + set(qtc_platform "windows_x64") + elseif ("${{ runner.os }}" STREQUAL "Linux") + set(qtc_platform "linux_x64") + elseif ("${{ runner.os }}" STREQUAL "macOS") + set(qtc_platform "mac_x64") + endif() + + file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/qtcreator" qtc_dir) + # Save the path for other steps + message("::set-output name=qtc_dir::${qtc_dir}") + + file(MAKE_DIRECTORY qtcreator) + + message("Downloading Qt Creator from ${qtc_base_url}/${qtc_platform}") + + foreach(package qtcreator qtcreator_dev) + file(DOWNLOAD + "${qtc_base_url}/${qtc_platform}/${package}.7z" ./${package}.7z SHOW_PROGRESS) + execute_process(COMMAND + ${CMAKE_COMMAND} -E tar xvf ../${package}.7z WORKING_DIRECTORY qtcreator) + endforeach() + + - name: Build + shell: cmake -P {0} + run: | + set(ENV{CC} ${{ matrix.config.cc }}) + set(ENV{CXX} ${{ matrix.config.cxx }}) + set(ENV{MACOSX_DEPLOYMENT_TARGET} "${{ env.MACOS_DEPLOYMENT_TARGET }}") + + if ("${{ runner.os }}" STREQUAL "Windows" AND NOT "x${{ matrix.config.environment_script }}" STREQUAL "x") + execute_process( + COMMAND "${{ matrix.config.environment_script }}" && set + OUTPUT_FILE environment_script_output.txt + ) + file(STRINGS environment_script_output.txt output_lines) + foreach(line IN LISTS output_lines) + if (line MATCHES "^([a-zA-Z0-9_-]+)=(.*)$") + set(ENV{${CMAKE_MATCH_1}} "${CMAKE_MATCH_2}") + endif() + endforeach() + endif() + + set(ENV{NINJA_STATUS} "[%f/%t %o/sec] ") + + set(build_plugin_py "scripts/build_plugin.py") + foreach(dir "share/qtcreator/scripts" "Qt Creator.app/Contents/Resources/scripts" "Contents/Resources/scripts") + if(EXISTS "${{ steps.qt_creator.outputs.qtc_dir }}/${dir}/build_plugin.py") + set(build_plugin_py "${dir}/build_plugin.py") + break() + endif() + endforeach() + + if("${{ runner.os }}" STREQUAL "macOS") + set(architecture_config "--add-config=-DCMAKE_OSX_ARCHITECTURES=x86_64\;arm64") + endif() + + execute_process( + COMMAND python + -u + ${{ steps.qt_creator.outputs.qtc_dir }}/${build_plugin_py} + --name "$ENV{PLUGIN_NAME}-$ENV{QT_CREATOR_VERSION}-${{ matrix.config.artifact }}" + --src . + --build build + --qt-path "${{ steps.qt.outputs.qt_dir }}" + --qtc-path "${{ steps.qt_creator.outputs.qtc_dir }}" + --output-path "$ENV{GITHUB_WORKSPACE}" + ${architecture_config} + RESULT_VARIABLE result + ) + if (NOT result EQUAL 0) + string(REGEX MATCH "FAILED:.*$" error_message "${output}") + string(REPLACE "\n" "%0A" error_message "${error_message}") + message("::error::${error_message}") + message(FATAL_ERROR "Build failed") + endif() + + - uses: actions/upload-artifact@v2 + id: upload_artifact + with: + path: ./${{ env.PLUGIN_NAME }}-${{ env.QT_CREATOR_VERSION }}-${{ matrix.config.artifact }}.7z + name: ${{ env.PLUGIN_NAME}}-${{ env.QT_CREATOR_VERSION }}-${{ matrix.config.artifact }}.7z + + release: + if: contains(github.ref, 'tags/v') + runs-on: ubuntu-latest + needs: build + + steps: + - name: Create Release + id: create_release + uses: actions/create-release@v1.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + + - name: Store Release url + run: | + echo "${{ steps.create_release.outputs.upload_url }}" > ./upload_url + + - uses: actions/upload-artifact@v1 + with: + path: ./upload_url + name: upload_url + + publish: + if: contains(github.ref, 'tags/v') + + name: ${{ matrix.config.name }} + runs-on: ${{ matrix.config.os }} + strategy: + matrix: + config: + - { + name: "Windows Latest x64", artifact: "Windows-x64.7z", + os: ubuntu-latest + } + - { + name: "Linux Latest x64", artifact: "Linux-x64.7z", + os: ubuntu-latest + } + - { + name: "macOS Latest x64", artifact: "macOS-x64.7z", + os: macos-latest + } + needs: release + + steps: + - name: Download artifact + uses: actions/download-artifact@v1 + with: + name: ${{ env.PLUGIN_NAME }}-${{ env.QT_CREATOR_VERSION }}-${{ matrix.config.artifact }} + path: ./ + + - name: Download URL + uses: actions/download-artifact@v1 + with: + name: upload_url + path: ./ + - id: set_upload_url + run: | + upload_url=`cat ./upload_url` + echo ::set-output name=upload_url::$upload_url + + - name: Upload to Release + id: upload_to_release + uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.set_upload_url.outputs.upload_url }} + asset_path: ./${{ env.PLUGIN_NAME }}-${{ env.QT_CREATOR_VERSION }}-${{ matrix.config.artifact }} + asset_name: ${{ env.PLUGIN_NAME }}-${{ env.QT_CREATOR_VERSION }}-${{ matrix.config.artifact }} + asset_content_type: application/x-7z-compressed @@ -1 +1,3 @@ .stack-work +CMakeLists.txt.user* +/build*/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2ab32c3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.10) + +project(Haskell) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_CXX_STANDARD 17) + +find_package(QtCreator COMPONENTS Core REQUIRED) +find_package(Qt5 COMPONENTS Widgets REQUIRED) + +add_subdirectory(plugins/haskell) +add_subdirectory(tests/auto/tokenizer) @@ -1,24 +1,42 @@ -### Haskell Support for Qt Creator +# Haskell Support for Qt Creator This Qt Creator plugin adds basic support for the Haskell programming language. -#### Features +## Features -* Code highlighting -* Editor tool tips with symbol information -* Follow symbol +* Syntax highlighting * Basic .cabal project support * Basic build configuration * Basic run configuration -#### Requirements +Other editing features like code completion and navigation are provided via +[haskell-ide-engine](https://github.com/haskell/haskell-ide-engine) and Qt Creator's +Language Server Protocol client. + +## Requirements + +### Projects The plugin currently only supports projects using [Haskell Stack](https://haskellstack.org). -The project must already be set up, if something mysteriously does not work check the following: +* The plugin looks for the `stack` executable in the default installation directory of the Haskell + Stack installers. If this is not correct for you, adapt the path in *Options* > *Haskell*. + +Linux: Note that Haskell Stack from the Ubuntu distribution and probably others is hopelessly +outdated. Use the installers provided by the [Haskell Stack](https://haskellstack.org) project. + +### Editing + +Install [haskell-ide-engine](https://github.com/haskell/haskell-ide-engine) for the GHC version +that your project uses and [configure it](https://doc.qt.io/qtcreator/creator-language-servers.html) +in Qt Creator's language client: -* The project's resolver must be installed. Ensure this by running `stack setup` in the project directory. -* For code info and navigation to work, `ghc-mod` is required to be built for the project's resolver. Ensure this by running `stack build ghc-mod` in the project directory. -* The plugin looks for the `stack` executable in the default installation directory of the Haskell Stack installers. If this is not correct for you, adapt the path in *Options* > *Haskell*. +* Open *Options* > *Language Client* +* Add a new server +* Set *Language* to the MIME types `text/x-haskell`, `text/x-haskell-project` and + `text/x-literate-haskell` +* Set *Startup behavior* to *Start Server per Project* +* Set *Executable* to `hie-wrapper`, for example to `/home/myself/.local/bin/hie-wrapper` +* Set *Arguments* to `-lsp` -Linux: Note that Haskell Stack from the Ubuntu distribution and probably others is hopelessly outdated. Use the installers provided by the [Haskell Stack](https://haskellstack.org) project.
\ No newline at end of file +Note that HIE compiles your project before providing any information, so it might take some time. diff --git a/haskell.pro b/haskell.pro deleted file mode 100644 index a8601b4..0000000 --- a/haskell.pro +++ /dev/null @@ -1,7 +0,0 @@ -TEMPLATE = subdirs - -SUBDIRS += \ - plugins/haskell - -!isEmpty(BUILD_TESTS): SUBDIRS += tests/auto - diff --git a/plugins/haskell/CMakeLists.txt b/plugins/haskell/CMakeLists.txt new file mode 100644 index 0000000..8f6cc4f --- /dev/null +++ b/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/plugins/haskell/config.pri b/plugins/haskell/config.pri deleted file mode 100644 index 0e52377..0000000 --- a/plugins/haskell/config.pri +++ /dev/null @@ -1,12 +0,0 @@ -# Qt Creator linking - -## Either set the IDE_SOURCE_TREE when running qmake, -## or set the QTC_SOURCE environment variable, to override the default setting -isEmpty(IDE_SOURCE_TREE): IDE_SOURCE_TREE = $$(QTC_SOURCE) -isEmpty(IDE_SOURCE_TREE): IDE_SOURCE_TREE = "$$PWD/../../../qtcreator_src" - -## Either set the IDE_BUILD_TREE when running qmake, -## or set the QTC_BUILD environment variable, to override the default setting -isEmpty(IDE_BUILD_TREE): IDE_BUILD_TREE = $$(QTC_BUILD) -isEmpty(IDE_BUILD_TREE): IDE_BUILD_TREE = "$$PWD/../../../qtcreator_build" - diff --git a/plugins/haskell/filecache.cpp b/plugins/haskell/filecache.cpp deleted file mode 100644 index 5d931c9..0000000 --- a/plugins/haskell/filecache.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/**************************************************************************** -** -** 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 "filecache.h" - -#include <coreplugin/idocument.h> -#include <texteditor/textdocument.h> -#include <utils/algorithm.h> - -#include <QFile> -#include <QLoggingCategory> -#include <QTemporaryFile> -#include <QTextDocument> - -Q_LOGGING_CATEGORY(cacheLog, "qtc.haskell.filecache", QtWarningMsg) - -using namespace Core; -using namespace TextEditor; -using namespace Utils; - -namespace Haskell { -namespace Internal { - -FileCache::FileCache(const QString &id, - const std::function<QList<Core::IDocument *>()> &documentsToUpdate) - : m_tempDir(id), - m_documentsToUpdate(documentsToUpdate) -{ - qCDebug(cacheLog) << "creating cache path at" << m_tempDir.path(); -} - - -void FileCache::update() -{ - const QList<IDocument *> documents = m_documentsToUpdate(); - for (IDocument *document : documents) { - const Utils::FilePath filePath = document->filePath(); - if (m_fileMap.contains(filePath)) { - // update the existing cached file - // check revision if possible - if (auto textDocument = qobject_cast<TextDocument *>(document)) { - if (m_fileRevision.value(filePath) != textDocument->document()->revision()) - writeFile(document); - } else { - writeFile(document); - } - } else { - // save file if it is modified - if (document->isModified()) - writeFile(document); - } - } - cleanUp(documents); -} - -QHash<FilePath, FilePath> FileCache::fileMap() const -{ - return m_fileMap; -} - -void FileCache::writeFile(IDocument *document) -{ - FilePath cacheFilePath = m_fileMap.value(document->filePath()); - if (cacheFilePath.isEmpty()) { - cacheFilePath = createCacheFile(document->filePath()); - m_fileMap.insert(document->filePath(), cacheFilePath); - } - qCDebug(cacheLog) << "writing" << cacheFilePath; - if (auto baseTextDocument = qobject_cast<BaseTextDocument *>(document)) { - QString errorMessage; - if (baseTextDocument->write(cacheFilePath.toString(), - QString::fromUtf8(baseTextDocument->contents()), - &errorMessage)) { - if (auto textDocument = qobject_cast<TextDocument *>(document)) { - m_fileRevision.insert(document->filePath(), textDocument->document()->revision()); - } else { - m_fileRevision.insert(document->filePath(), -1); - } - } else { - qCDebug(cacheLog) << "!!! writing file failed:" << errorMessage; - } - - } else { - QFile file(cacheFilePath.toString()); - if (file.open(QIODevice::WriteOnly)) { - file.write(document->contents()); - } else { - qCDebug(cacheLog) << "!!! opening file for writing failed"; - } - } -} - -void FileCache::cleanUp(const QList<IDocument *> &documents) -{ - const QSet<FilePath> files = Utils::transform<QSet>(documents, &IDocument::filePath); - auto it = QMutableHashIterator<FilePath, FilePath>(m_fileMap); - while (it.hasNext()) { - it.next(); - if (!files.contains(it.key())) { - qCDebug(cacheLog) << "deleting" << it.value(); - QFile::remove(it.value().toString()); - m_fileRevision.remove(it.key()); - it.remove(); - } - } -} - -FilePath FileCache::createCacheFile(const FilePath &filePath) -{ - QTemporaryFile tempFile(m_tempDir.path() + "/XXXXXX-" + filePath.fileName()); - tempFile.setAutoRemove(false); - tempFile.open(); - return FilePath::fromString(tempFile.fileName()); -} - -} // namespace Internal -} // namespace Haskell diff --git a/plugins/haskell/filecache.h b/plugins/haskell/filecache.h deleted file mode 100644 index 3c53d40..0000000 --- a/plugins/haskell/filecache.h +++ /dev/null @@ -1,61 +0,0 @@ -/**************************************************************************** -** -** 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 <utils/fileutils.h> -#include <utils/temporarydirectory.h> - -#include <QHash> - -#include <functional> - -namespace Core { class IDocument; } - -namespace Haskell { -namespace Internal { - -class FileCache -{ -public: - FileCache(const QString &id, - const std::function<QList<Core::IDocument *>()> &documentsToUpdate); - - void update(); - QHash<Utils::FilePath, Utils::FilePath> fileMap() const; - -private: - void writeFile(Core::IDocument *document); - void cleanUp(const QList<Core::IDocument *> &documents); - Utils::FilePath createCacheFile(const Utils::FilePath &filePath); - - Utils::TemporaryDirectory m_tempDir; - QHash<Utils::FilePath, Utils::FilePath> m_fileMap; - QHash<Utils::FilePath, int> m_fileRevision; - std::function<QList<Core::IDocument *>()> m_documentsToUpdate; -}; - -} // namespace Internal -} // namespace Haskell diff --git a/plugins/haskell/followsymbol.cpp b/plugins/haskell/followsymbol.cpp deleted file mode 100644 index ab32614..0000000 --- a/plugins/haskell/followsymbol.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/**************************************************************************** -** -** 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 "followsymbol.h" - -#include "haskelleditorwidget.h" -#include "haskellmanager.h" -#include "haskelltokenizer.h" - -#include <coreplugin/editormanager/editormanager.h> -#include <texteditor/codeassist/assistinterface.h> -#include <texteditor/codeassist/assistproposalitem.h> -#include <texteditor/codeassist/genericproposal.h> -#include <texteditor/codeassist/genericproposalmodel.h> - -#include <utils/qtcassert.h> - -using namespace TextEditor; -using namespace Utils; - -namespace Haskell { -namespace Internal { - -IAssistProvider::RunType FollowSymbolAssistProvider::runType() const -{ - return AsynchronousWithThread; -} - -IAssistProcessor *FollowSymbolAssistProvider::createProcessor() const -{ - return new FollowSymbolAssistProcessor(m_inNextSplit); -} - -void FollowSymbolAssistProvider::setOpenInNextSplit(bool inNextSplit) -{ - m_inNextSplit = inNextSplit; -} - -FollowSymbolAssistProcessor::FollowSymbolAssistProcessor(bool inNextSplit) - : m_inNextSplit(inNextSplit) -{ -} - -IAssistProposal *FollowSymbolAssistProcessor::immediateProposal(const AssistInterface *interface) -{ - int line, column; - const optional<Token> symbol - = HaskellEditorWidget::symbolAt(interface->textDocument(), interface->position(), - &line, &column); - QTC_ASSERT(symbol, return nullptr); // should have been checked before - const auto filePath = FilePath::fromString(interface->fileName()); - m_ghcmod = HaskellManager::ghcModForFile(filePath); - m_symbolFuture = m_ghcmod->findSymbol(filePath, symbol->text.toString()); - - auto item = new AssistProposalItem(); - item->setText(HaskellManager::trLookingUp(symbol->text.toString())); - item->setData(QString()); - item->setOrder(-1000); - - const QList<TextEditor::AssistProposalItemInterface *> list = {item}; - auto proposal = new GenericProposal(interface->position(), list); - proposal->setFragile(true); - return proposal; -} - -IAssistProposal *FollowSymbolAssistProcessor::perform(const AssistInterface *interface) -{ - const int position = interface->position(); - delete interface; - const SymbolInfoOrError info = m_symbolFuture.result(); - auto item = new FollowSymbolAssistProposalItem(m_ghcmod->basePath(), info, m_inNextSplit); - return new InstantProposal(position, {item}); -} - -FollowSymbolAssistProposalItem::FollowSymbolAssistProposalItem(const FilePath &basePath, - const SymbolInfoOrError &info, - bool inNextSplit) - : m_basePath(basePath), - m_inNextSplit(inNextSplit) -{ - const SymbolInfo *info_p = Utils::get_if<SymbolInfo>(&info); - if (info_p && !info_p->file.isEmpty()) { - m_info = info; - setText(m_basePath.toString() + '/' + info_p->file.toString()); - } -} - -void FollowSymbolAssistProposalItem::apply(TextDocumentManipulatorInterface &, int) const -{ - const SymbolInfo *info_p = Utils::get_if<SymbolInfo>(&m_info); - if (info_p) - Core::EditorManager::openEditorAt(m_basePath.toString() + '/' + info_p->file.toString(), - info_p->line, info_p->col - 1, Core::Id(), - m_inNextSplit ? Core::EditorManager::OpenInOtherSplit - : Core::EditorManager::NoFlags); -} - -void InstantActivationProposalWidget::showProposal(const QString &prefix) -{ - if (model() && model()->size() == 1) { - emit proposalItemActivated(model()->proposalItem(0)); - deleteLater(); - return; - } - GenericProposalWidget::showProposal(prefix); -} - -InstantProposal::InstantProposal(int cursorPos, const QList<AssistProposalItemInterface *> &items) - : GenericProposal(cursorPos, items) -{ -} - -IAssistProposalWidget *InstantProposal::createWidget() const -{ - return new InstantActivationProposalWidget(); -} - -} // namespace Internal -} // namespace Haskell diff --git a/plugins/haskell/followsymbol.h b/plugins/haskell/followsymbol.h deleted file mode 100644 index a4f9051..0000000 --- a/plugins/haskell/followsymbol.h +++ /dev/null @@ -1,94 +0,0 @@ -/**************************************************************************** -** -** 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 "ghcmod.h" - -#include <texteditor/codeassist/assistproposalitem.h> -#include <texteditor/codeassist/genericproposal.h> -#include <texteditor/codeassist/genericproposalwidget.h> -#include <texteditor/codeassist/iassistprocessor.h> -#include <texteditor/codeassist/iassistprovider.h> - -namespace Haskell { -namespace Internal { - -class FollowSymbolAssistProposalItem : public TextEditor::AssistProposalItem -{ -public: - FollowSymbolAssistProposalItem(const Utils::FilePath &basePath, - const SymbolInfoOrError &info, - bool inNextSplit); - - void apply(TextEditor::TextDocumentManipulatorInterface &, int) const override; - -private: - Utils::FilePath m_basePath; - SymbolInfoOrError m_info; - bool m_inNextSplit; -}; - -class InstantActivationProposalWidget : public TextEditor::GenericProposalWidget -{ -protected: - void showProposal(const QString &prefix) override; -}; - -class InstantProposal : public TextEditor::GenericProposal -{ -public: - InstantProposal(int cursorPos, const QList<TextEditor::AssistProposalItemInterface *> &items); - - TextEditor::IAssistProposalWidget *createWidget() const override; -}; - -class FollowSymbolAssistProvider : public TextEditor::IAssistProvider -{ -public: - RunType runType() const override; - TextEditor::IAssistProcessor *createProcessor() const override; - void setOpenInNextSplit(bool inNextSplit); - -private: - bool m_inNextSplit = false; -}; - -class FollowSymbolAssistProcessor : public TextEditor::IAssistProcessor -{ -public: - FollowSymbolAssistProcessor(bool inNextSplit); - - TextEditor::IAssistProposal *immediateProposal(const TextEditor::AssistInterface *interface) override; - TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface) override; - -private: - std::shared_ptr<AsyncGhcMod> m_ghcmod; - QFuture<SymbolInfoOrError> m_symbolFuture; - bool m_inNextSplit; -}; - -} // namespace Internal -} // namespace Haskell diff --git a/plugins/haskell/ghcmod.cpp b/plugins/haskell/ghcmod.cpp deleted file mode 100644 index 1ca7713..0000000 --- a/plugins/haskell/ghcmod.cpp +++ /dev/null @@ -1,408 +0,0 @@ -/**************************************************************************** -** -** 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 "ghcmod.h" - -#include <coreplugin/editormanager/documentmodel.h> -#include <coreplugin/idocument.h> -#include <utils/algorithm.h> -#include <utils/environment.h> -#include <utils/qtcassert.h> - -#include <QFileInfo> -#include <QFutureWatcher> -#include <QLoggingCategory> -#include <QMetaObject> -#include <QMutexLocker> -#include <QProcess> -#include <QRegularExpression> -#include <QTime> -#include <QTimer> - -#include <functional> - -Q_LOGGING_CATEGORY(ghcModLog, "qtc.haskell.ghcmod", QtWarningMsg) -Q_LOGGING_CATEGORY(asyncGhcModLog, "qtc.haskell.ghcmod.async", QtWarningMsg) - -// TODO do not hardcode -const int kTimeoutMS = 10 * 1000; - -using namespace Utils; - -namespace Haskell { -namespace Internal { - -FilePath GhcMod::m_stackExecutable = Utils::FilePath::fromString("stack"); -QMutex GhcMod::m_mutex; - -GhcMod::GhcMod(const Utils::FilePath &path) - : m_path(path) -{ -} - -GhcMod::~GhcMod() -{ - shutdown(); -} - -FilePath GhcMod::basePath() const -{ - return m_path; -} - -void GhcMod::setFileMap(const QHash<FilePath, FilePath> &fileMap) -{ - if (fileMap != m_fileMap) { - log("setting new file map"); - m_fileMap = fileMap; - shutdown(); - } -} - -static QString toUnicode(QByteArray data) -{ - // clean zero bytes which let QString think that the string ends there - data.replace('\x0', QByteArray()); - return QString::fromUtf8(data); -} - -SymbolInfoOrError GhcMod::findSymbol(const FilePath &filePath, const QString &symbol) -{ - return parseFindSymbol(runFindSymbol(filePath, symbol)); -} - -QStringOrError GhcMod::typeInfo(const FilePath &filePath, int line, int col) -{ - return parseTypeInfo(runTypeInfo(filePath, line, col)); -} - -static QStringList fileMapArgs(const QHash<FilePath, FilePath> &map) -{ - QStringList result; - const auto end = map.cend(); - for (auto it = map.cbegin(); it != end; ++it) - result << "--map-file" << it.key().toString() + "=" + it.value().toString(); - return result; -} - -Utils::optional<Error> GhcMod::ensureStarted() -{ - m_mutex.lock(); - const FilePath plainStackExecutable = m_stackExecutable; - m_mutex.unlock(); - Environment env = Environment::systemEnvironment(); - const FilePath stackExecutable = env.searchInPath(plainStackExecutable.toString()); - if (m_process) { - if (m_process->state() == QProcess::NotRunning) { - log("is no longer running"); - m_process.reset(); - } else if (FilePath::fromString(m_process->program()) != stackExecutable) { - log("stack settings changed"); - shutdown(); - } - } - if (m_process) - return Utils::nullopt; - log("starting"); - // for ghc-mod finding stack back: - env.prependOrSetPath(stackExecutable.toFileInfo().absolutePath()); - m_process.reset(new QProcess); - m_process->setWorkingDirectory(m_path.toString()); - m_process->setEnvironment(env.toStringList()); - m_process->start(stackExecutable.toString(), - QStringList{"exec", "ghc-mod", "--"} + fileMapArgs(m_fileMap) - << "legacy-interactive"); - if (!m_process->waitForStarted(kTimeoutMS)) { - log("failed to start"); - m_process.reset(); - return Error({Error::Type::FailedToStartStack, plainStackExecutable.toUserOutput()}); - } - log("started"); - m_process->setReadChannel(QProcess::StandardOutput); - return Utils::nullopt; -} - -void GhcMod::shutdown() -{ - if (!m_process) - return; - log("shutting down"); - m_process->write("\n"); - m_process->closeWriteChannel(); - m_process->waitForFinished(300); - m_process.reset(); -} - -void GhcMod::log(const QString &message) -{ - qCDebug(ghcModLog) << "ghcmod for" << m_path.toString() << ":" << qPrintable(message); -} - -QByteArrayOrError GhcMod::runQuery(const QString &query) -{ - const Utils::optional<Error> error = ensureStarted(); - if (error) - return error.value(); - log("query \"" + query + "\""); - m_process->write(query.toUtf8() + "\n"); - bool ok = false; - QByteArray response; - QTime readTime; - readTime.start(); - while (!ok && readTime.elapsed() < kTimeoutMS) { - m_process->waitForReadyRead(kTimeoutMS - readTime.elapsed() + 10); - response += m_process->read(2048); - ok = response.endsWith("OK\n") || response.endsWith("OK\r\n"); - } - if (ghcModLog().isDebugEnabled()) - qCDebug(ghcModLog) << "response" << QTextCodec::codecForLocale()->toUnicode(response); - if (!ok) { - log("failed"); - shutdown(); - return Error({Error::Type::Other, QString()}); - } - log("success"); - // convert to unix line endings - response.replace("\r\n", "\n"); - response.chop(3); // cut off "OK\n" - return response; -} - -QByteArrayOrError GhcMod::runFindSymbol(const FilePath &filePath, const QString &symbol) -{ - return runQuery(QString("info %1 %2").arg(filePath.toString()) // TODO toNative? quoting? - .arg(symbol)); -} - -QByteArrayOrError GhcMod::runTypeInfo(const FilePath &filePath, int line, int col) -{ - return runQuery(QString("type %1 %2 %3").arg(filePath.toString()) // TODO toNative? quoting? - .arg(line) - .arg(col + 1)); -} - -SymbolInfoOrError GhcMod::parseFindSymbol(const QByteArrayOrError &response) -{ - QRegularExpression infoRegEx("^\\s*(.*?)\\s+--\\sDefined ((at (.+?)(:(\\d+):(\\d+))?)|(in ‘(.+)’.*))$"); - const QByteArray *bytes = Utils::get_if<QByteArray>(&response); - if (!bytes) - return Utils::get<Error>(response); - SymbolInfo info; - bool hasFileOrModule = false; - const QString str = toUnicode(QByteArray(*bytes).replace('\x0', '\n')); - for (const QString &line : str.split('\n')) { - if (hasFileOrModule) { - info.additionalInfo += line; - } else { - QRegularExpressionMatch result = infoRegEx.match(line); - if (result.hasMatch()) { - hasFileOrModule = true; - info.definition += result.captured(1); - if (result.lastCapturedIndex() == 7) { // Defined at <file:line:col> - info.file = FilePath::fromString(result.captured(4)); - bool ok; - int num = result.captured(6).toInt(&ok); - if (ok) - info.line = num; - num = result.captured(7).toInt(&ok); - if (ok) - info.col = num; - } else if (result.lastCapturedIndex() == 9) { // Defined in <module> - info.module = result.captured(9); - } - } else { - info.definition += line; - } - } - } - if (hasFileOrModule) - return info; - return Error({Error::Type::Other, QString()}); -} - -QStringOrError GhcMod::parseTypeInfo(const QByteArrayOrError &response) -{ - QRegularExpression typeRegEx("^\\d+\\s+\\d+\\s+\\d+\\s+\\d+\\s+\"(.*)\"$", - QRegularExpression::MultilineOption); - const QByteArray *bytes = Utils::get_if<QByteArray>(&response); - if (!bytes) - return Utils::get<Error>(response); - QRegularExpressionMatch result = typeRegEx.match(toUnicode(*bytes)); - if (result.hasMatch()) - return result.captured(1); - return Error({Error::Type::Other, QString()}); -} - -void GhcMod::setStackExecutable(const FilePath &filePath) -{ - QMutexLocker lock(&m_mutex); - m_stackExecutable = filePath; -} - -static QList<Core::IDocument *> getOpenDocuments(const FilePath &path) -{ - return Utils::filtered(Core::DocumentModel::openedDocuments(), [path] (Core::IDocument *doc) { - return path.isEmpty() || doc->filePath().isChildOf(path); - }); -} - -AsyncGhcMod::AsyncGhcMod(const FilePath &path) - : m_ghcmod(path), - m_fileCache("haskell", std::bind(getOpenDocuments, path)) -{ - qCDebug(asyncGhcModLog) << "starting thread for" << m_ghcmod.basePath().toString(); - m_thread.start(); - m_threadTarget.moveToThread(&m_thread); -} - -AsyncGhcMod::~AsyncGhcMod() -{ - qCDebug(asyncGhcModLog) << "stopping thread for" << m_ghcmod.basePath().toString(); - m_mutex.lock(); - for (Operation &op : m_queue) - op.fi.cancel(); - m_queue.clear(); - m_mutex.unlock(); - m_thread.quit(); - m_thread.wait(); -} - -FilePath AsyncGhcMod::basePath() const -{ - return m_ghcmod.basePath(); -} - -template <typename Result> -QFuture<Result> createFuture(AsyncGhcMod::Operation op, - const std::function<Result(const QByteArrayOrError &)> &postOp) -{ - auto fi = new QFutureInterface<Result>; - fi->reportStarted(); - - // propagate inner events to outside future - auto opWatcher = new QFutureWatcher<QByteArrayOrError>(); - QObject::connect(opWatcher, &QFutureWatcherBase::canceled, [fi] { fi->cancel(); }); - QObject::connect(opWatcher, &QFutureWatcherBase::finished, opWatcher, &QObject::deleteLater); - QObject::connect(opWatcher, &QFutureWatcherBase::finished, [fi] { - fi->reportFinished(); - delete fi; - }); - QObject::connect(opWatcher, &QFutureWatcherBase::resultReadyAt, - [fi, opWatcher, postOp](int index) { - fi->reportResult(postOp(opWatcher->future().resultAt(index))); - }); - opWatcher->setFuture(op.fi.future()); - - // propagate cancel from outer future to inner future - auto fiWatcher = new QFutureWatcher<Result>(); - QObject::connect(fiWatcher, &QFutureWatcherBase::canceled, [op] { op.fi.cancel(); }); - QObject::connect(fiWatcher, &QFutureWatcherBase::finished, fiWatcher, &QObject::deleteLater); - fiWatcher->setFuture(fi->future()); - - return fi->future(); -} - -/*! - Asynchronously looks up the \a symbol in the context of \a filePath. - - Returns a QFuture handle for the asynchronous operation. You may not block the main event loop - while waiting for it to finish - doing so will result in a deadlock. -*/ -QFuture<SymbolInfoOrError> AsyncGhcMod::findSymbol(const FilePath &filePath, - const QString &symbol) -{ - QMutexLocker lock(&m_mutex); - Operation op([this, filePath, symbol] { return m_ghcmod.runFindSymbol(filePath, symbol); }); - m_queue.append(op); - QTimer::singleShot(0, &m_threadTarget, [this] { reduceQueue(); }); - return createFuture<SymbolInfoOrError>(op, &GhcMod::parseFindSymbol); -} - -/*! - Asynchronously looks up the type at \a line and \a col in \a filePath. - - Returns a QFuture handle for the asynchronous operation. You may not block the main event loop - while waiting for it to finish - doing so will result in a deadlock. -*/ -QFuture<QStringOrError> AsyncGhcMod::typeInfo(const FilePath &filePath, int line, int col) -{ - QMutexLocker lock(&m_mutex); - Operation op([this, filePath, line, col] { return m_ghcmod.runTypeInfo(filePath, line, col); }); - m_queue.append(op); - QTimer::singleShot(0, &m_threadTarget, [this] { reduceQueue(); }); - return createFuture<QStringOrError>(op, &GhcMod::parseTypeInfo); -} - -/*! - Synchronously runs an update of the cache of modified files. - This must be run on the main thread. - - \internal -*/ -void AsyncGhcMod::updateCache() -{ - m_fileCache.update(); -} - -/*! - Takes operations from the queue and executes them, until the queue is empty. - This must be run within the internal thread whenever an operation is added to the queue. - Canceled operations are not executed, but removed from the queue. - Before each operation, the cache of modified files is updated on the main thread. - - \internal -*/ -void AsyncGhcMod::reduceQueue() -{ - QTC_ASSERT(QThread::currentThread() != thread(), return); - const auto takeFirst = [this]() { - Operation op; - m_mutex.lock(); - if (!m_queue.isEmpty()) - op = m_queue.takeFirst(); - m_mutex.unlock(); - return op; - }; - - Operation op; - while ((op = takeFirst()).op) { - if (!op.fi.isCanceled()) { - QMetaObject::invokeMethod(this, "updateCache", Qt::BlockingQueuedConnection); - m_ghcmod.setFileMap(m_fileCache.fileMap()); - QByteArrayOrError result = op.op(); - op.fi.reportResult(result); - } - op.fi.reportFinished(); - } -} - -AsyncGhcMod::Operation::Operation(const std::function<QByteArrayOrError()> &op) - : op(op) -{ - fi.reportStarted(); -} - -} // Internal -} // Haskell diff --git a/plugins/haskell/ghcmod.h b/plugins/haskell/ghcmod.h deleted file mode 100644 index fb3dd3f..0000000 --- a/plugins/haskell/ghcmod.h +++ /dev/null @@ -1,150 +0,0 @@ -/**************************************************************************** -** -** 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 "filecache.h" - -#include <utils/fileutils.h> -#include <utils/optional.h> -#include <utils/synchronousprocess.h> -#include <utils/variant.h> - -#include <QFuture> -#include <QMutex> -#include <QThread> - -#include <memory> - -QT_BEGIN_NAMESPACE -class QProcess; -QT_END_NAMESPACE - -namespace Haskell { -namespace Internal { - -class Error { -public: - enum class Type { - FailedToStartStack, - Other // TODO get rid of it - }; - Type type; - QString details; -}; - -class SymbolInfo { -public: - QStringList definition; - QStringList additionalInfo; - Utils::FilePath file; - int line = -1; - int col = -1; - QString module; -}; - -using QByteArrayOrError = Utils::variant<QByteArray, Error>; -using QStringOrError = Utils::variant<QString, Error>; -using SymbolInfoOrError = Utils::variant<SymbolInfo, Error>; - -template <typename T> class ghcmod_deleter; -template <> class ghcmod_deleter<QProcess> -{ -public: - void operator()(QProcess *p) { Utils::SynchronousProcess::stopProcess(*p); delete p; } -}; -using unique_ghcmod_process = std::unique_ptr<QProcess, ghcmod_deleter<QProcess>>; - -class GhcMod -{ -public: - GhcMod(const Utils::FilePath &path); - ~GhcMod(); - - Utils::FilePath basePath() const; - void setFileMap(const QHash<Utils::FilePath, Utils::FilePath> &fileMap); - - SymbolInfoOrError findSymbol(const Utils::FilePath &filePath, const QString &symbol); - QStringOrError typeInfo(const Utils::FilePath &filePath, int line, int col); - - QByteArrayOrError runQuery(const QString &query); - - QByteArrayOrError runFindSymbol(const Utils::FilePath &filePath, const QString &symbol); - QByteArrayOrError runTypeInfo(const Utils::FilePath &filePath, int line, int col); - - static SymbolInfoOrError parseFindSymbol(const QByteArrayOrError &response); - static QStringOrError parseTypeInfo(const QByteArrayOrError &response); - - static void setStackExecutable(const Utils::FilePath &filePath); - -private: - Utils::optional<Error> ensureStarted(); - void shutdown(); - void log(const QString &message); - - static Utils::FilePath m_stackExecutable; - static QMutex m_mutex; - - Utils::FilePath m_path; - unique_ghcmod_process m_process; // kills process on reset - QHash<Utils::FilePath, Utils::FilePath> m_fileMap; -}; - -class AsyncGhcMod : public QObject -{ - Q_OBJECT - -public: - struct Operation { - Operation() = default; - Operation(const std::function<QByteArrayOrError()> &op); - mutable QFutureInterface<QByteArrayOrError> fi; - std::function<QByteArrayOrError()> op; - }; - - AsyncGhcMod(const Utils::FilePath &path); - ~AsyncGhcMod(); - - Utils::FilePath basePath() const; - - QFuture<SymbolInfoOrError> findSymbol(const Utils::FilePath &filePath, const QString &symbol); - QFuture<QStringOrError> typeInfo(const Utils::FilePath &filePath, int line, int col); - -private slots: - void updateCache(); // called through QMetaObject::invokeMethod - -private: - void reduceQueue(); - - QThread m_thread; - QObject m_threadTarget; // used to run methods in m_thread - GhcMod m_ghcmod; // only use in m_thread - FileCache m_fileCache; // only update through reduceQueue - QVector<Operation> m_queue; - QMutex m_mutex; -}; - -} // Internal -} // Haskell diff --git a/plugins/haskell/haskell.pro b/plugins/haskell/haskell.pro deleted file mode 100644 index 92d0307..0000000 --- a/plugins/haskell/haskell.pro +++ /dev/null @@ -1,75 +0,0 @@ -DEFINES += HASKELL_LIBRARY - -# Haskell files - -SOURCES += \ - haskellcompletionassist.cpp \ - haskelleditorfactory.cpp \ - haskellhoverhandler.cpp \ - haskellplugin.cpp \ - haskellhighlighter.cpp \ - haskelltokenizer.cpp \ - ghcmod.cpp \ - haskellmanager.cpp \ - haskelldocument.cpp \ - optionspage.cpp \ - filecache.cpp \ - haskelleditorwidget.cpp \ - followsymbol.cpp \ - haskellproject.cpp \ - haskellbuildconfiguration.cpp \ - stackbuildstep.cpp \ - haskellrunconfiguration.cpp - -HEADERS += \ - haskell_global.h \ - haskellcompletionassist.h \ - haskellconstants.h \ - haskelleditorfactory.h \ - haskellhoverhandler.h \ - haskellplugin.h \ - haskellhighlighter.h \ - haskelltokenizer.h \ - ghcmod.h \ - haskellmanager.h \ - haskelldocument.h \ - optionspage.h \ - filecache.h \ - haskelleditorwidget.h \ - followsymbol.h \ - haskellproject.h \ - haskellbuildconfiguration.h \ - stackbuildstep.h \ - haskellrunconfiguration.h - -## uncomment to build plugin into user config directory -## <localappdata>/plugins/<ideversion> -## where <localappdata> is e.g. -## "%LOCALAPPDATA%\QtProject\qtcreator" on Windows Vista and later -## "$XDG_DATA_HOME/data/QtProject/qtcreator" or "~/.local/share/data/QtProject/qtcreator" on Linux -## "~/Library/Application Support/QtProject/Qt Creator" on OS X -#USE_USER_DESTDIR = yes - -###### If the plugin can be depended upon by other plugins, this code needs to be outsourced to -###### <dirname>_dependencies.pri, where <dirname> is the name of the directory containing the -###### plugin's sources. - -QTC_PLUGIN_NAME = Haskell -QTC_LIB_DEPENDS += \ - # nothing here at this time - -QTC_PLUGIN_DEPENDS += \ - coreplugin \ - projectexplorer \ - texteditor - -QTC_PLUGIN_RECOMMENDS += \ - # optional plugin dependencies. nothing here at this time - -###### End _dependencies.pri contents ###### - -include(config.pri) -include($$IDE_SOURCE_TREE/src/qtcreatorplugin.pri) - -RESOURCES += \ - haskell.qrc diff --git a/plugins/haskell/haskell.qbs b/plugins/haskell/haskell.qbs index d2296a9..30eb832 100644 --- a/plugins/haskell/haskell.qbs +++ b/plugins/haskell/haskell.qbs @@ -11,19 +11,12 @@ QtcPlugin { Depends { name: "ProjectExplorer" } files: [ - "filecache.cpp", "filecache.h", - "followsymbol.cpp", "followsymbol.h", - "ghcmod.cpp", "ghcmod.h", "haskell.qrc", "haskellbuildconfiguration.cpp", "haskellbuildconfiguration.h", - "haskellcompletionassist.cpp", "haskellcompletionassist.h", "haskellconstants.h", - "haskelldocument.cpp", "haskelldocument.h", "haskelleditorfactory.cpp", "haskelleditorfactory.h", - "haskelleditorwidget.cpp", "haskelleditorwidget.h", "haskell_global.h", "haskellhighlighter.cpp", "haskellhighlighter.h", - "haskellhoverhandler.cpp", "haskellhoverhandler.h", "haskellmanager.cpp", "haskellmanager.h", "haskellplugin.cpp", "haskellplugin.h", "haskellproject.cpp", "haskellproject.h", diff --git a/plugins/haskell/haskellbuildconfiguration.cpp b/plugins/haskell/haskellbuildconfiguration.cpp index 0233e15..c133d2d 100644 --- a/plugins/haskell/haskellbuildconfiguration.cpp +++ b/plugins/haskell/haskellbuildconfiguration.cpp @@ -27,7 +27,6 @@ #include "haskellconstants.h" #include "haskellproject.h" -#include "stackbuildstep.h" #include <projectexplorer/buildinfo.h> #include <projectexplorer/buildsteplist.h> @@ -36,7 +35,7 @@ #include <projectexplorer/target.h> #include <utils/algorithm.h> #include <utils/detailswidget.h> -#include <utils/mimetypes/mimedatabase.h> +#include <utils/mimeutils.h> #include <utils/pathchooser.h> #include <utils/qtcassert.h> @@ -56,41 +55,31 @@ HaskellBuildConfigurationFactory::HaskellBuildConfigurationFactory() registerBuildConfiguration<HaskellBuildConfiguration>(C_HASKELL_BUILDCONFIGURATION_ID); setSupportedProjectType(Constants::C_HASKELL_PROJECT_ID); setSupportedProjectMimeTypeName(Constants::C_HASKELL_PROJECT_MIMETYPE); -} - -static QList<BuildInfo> createInfos(const HaskellBuildConfigurationFactory *factory, - const Kit *k, - const Utils::FilePath &projectFilePath) -{ - BuildInfo info(factory); - info.typeName = HaskellBuildConfigurationFactory::tr("Release"); - info.displayName = info.typeName; - info.buildDirectory = projectFilePath.parentDir().pathAppended(".stack-work"); - info.kitId = k->id(); - info.buildType = BuildConfiguration::BuildType::Release; - return {info}; -} -QList<BuildInfo> HaskellBuildConfigurationFactory::availableBuilds(const Target *parent) const -{ - // Entries that are available in add build configuration dropdown - return Utils::transform(createInfos(this, parent->kit(), parent->project()->projectFilePath()), - [](BuildInfo info) { - info.displayName.clear(); - return info; - }); + 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<BuildInfo>{info}; + }); } -QList<BuildInfo> HaskellBuildConfigurationFactory::availableSetups( - const Kit *k, const QString &projectPath) const +HaskellBuildConfiguration::HaskellBuildConfiguration(Target *target, Utils::Id id) + : BuildConfiguration(target, id) { - return createInfos(this, k, Utils::FilePath::fromString(projectPath)); + setInitializer([this](const BuildInfo &info) { + setBuildDirectory(info.buildDirectory); + setBuildType(info.buildType); + setDisplayName(info.displayName); + }); + appendInitialBuildStep(Constants::C_STACK_BUILD_STEP_ID); } -HaskellBuildConfiguration::HaskellBuildConfiguration(Target *target, Core::Id id) - : BuildConfiguration(target, id) -{} - NamedWidget *HaskellBuildConfiguration::createConfigWidget() { return new HaskellBuildConfigurationWidget(this); @@ -106,50 +95,37 @@ void HaskellBuildConfiguration::setBuildType(BuildConfiguration::BuildType type) m_buildType = type; } -void HaskellBuildConfiguration::initialize(const BuildInfo &info) -{ - BuildConfiguration::initialize(info); - setBuildDirectory(info.buildDirectory); - setBuildType(info.buildType); - setDisplayName(info.displayName); - - BuildStepList *buildSteps = stepList(ProjectExplorer::Constants::BUILDSTEPS_BUILD); - auto stackBuildStep = new StackBuildStep(buildSteps); - buildSteps->appendStep(stackBuildStep); -} - HaskellBuildConfigurationWidget::HaskellBuildConfigurationWidget(HaskellBuildConfiguration *bc) - : NamedWidget() + : NamedWidget(tr("General")) , m_buildConfiguration(bc) { - setDisplayName(tr("General")); setLayout(new QVBoxLayout); - layout()->setMargin(0); + 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()->setMargin(0); + 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->setFileName(m_buildConfiguration->buildDirectory()); + buildDirectoryInput->setFilePath(m_buildConfiguration->buildDirectory()); details->layout()->addWidget(buildDirectoryInput); connect(m_buildConfiguration, &BuildConfiguration::buildDirectoryChanged, buildDirectoryInput, [this, buildDirectoryInput] { - buildDirectoryInput->setFileName(m_buildConfiguration->buildDirectory()); + buildDirectoryInput->setFilePath(m_buildConfiguration->buildDirectory()); }); connect(buildDirectoryInput, - &Utils::PathChooser::pathChanged, + &Utils::PathChooser::textChanged, m_buildConfiguration, [this, buildDirectoryInput](const QString &) { - m_buildConfiguration->setBuildDirectory(buildDirectoryInput->rawFileName()); + m_buildConfiguration->setBuildDirectory(buildDirectoryInput->rawFilePath()); }); } diff --git a/plugins/haskell/haskellbuildconfiguration.h b/plugins/haskell/haskellbuildconfiguration.h index c45c35f..c93161e 100644 --- a/plugins/haskell/haskellbuildconfiguration.h +++ b/plugins/haskell/haskellbuildconfiguration.h @@ -33,15 +33,8 @@ namespace Internal { class HaskellBuildConfigurationFactory : public ProjectExplorer::BuildConfigurationFactory { - Q_OBJECT - public: HaskellBuildConfigurationFactory(); - - QList<ProjectExplorer::BuildInfo> availableBuilds( - const ProjectExplorer::Target *parent) const override; - QList<ProjectExplorer::BuildInfo> availableSetups(const ProjectExplorer::Kit *k, - const QString &projectPath) const override; }; class HaskellBuildConfiguration : public ProjectExplorer::BuildConfiguration @@ -49,12 +42,11 @@ class HaskellBuildConfiguration : public ProjectExplorer::BuildConfiguration Q_OBJECT public: - HaskellBuildConfiguration(ProjectExplorer::Target *target, Core::Id id); + HaskellBuildConfiguration(ProjectExplorer::Target *target, Utils::Id id); ProjectExplorer::NamedWidget *createConfigWidget() override; BuildType buildType() const override; void setBuildType(BuildType type); - void initialize(const ProjectExplorer::BuildInfo &info) override; private: BuildType m_buildType = BuildType::Release; diff --git a/plugins/haskell/haskellcompletionassist.cpp b/plugins/haskell/haskellcompletionassist.cpp deleted file mode 100644 index aa053f5..0000000 --- a/plugins/haskell/haskellcompletionassist.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/**************************************************************************** -** -** 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 "haskellcompletionassist.h" - -#include "haskellconstants.h" - -#include <coreplugin/id.h> -#include <texteditor/codeassist/keywordscompletionassist.h> - -namespace Haskell { -namespace Internal { - -TextEditor::IAssistProcessor *HaskellCompletionAssistProvider::createProcessor() const -{ - auto processor = new TextEditor::KeywordsCompletionAssistProcessor(TextEditor::Keywords()); - processor->setSnippetGroup(Constants::C_HASKELLSNIPPETSGROUP_ID); - return processor; -} - -} // namespace Internal -} // namespace Haskell diff --git a/plugins/haskell/haskellcompletionassist.h b/plugins/haskell/haskellcompletionassist.h deleted file mode 100644 index 9b3f0ca..0000000 --- a/plugins/haskell/haskellcompletionassist.h +++ /dev/null @@ -1,45 +0,0 @@ -/**************************************************************************** -** -** 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. -** -****************************************************************************/ - -#ifndef HASKELLCOMPLETIONASSIST_H -#define HASKELLCOMPLETIONASSIST_H - -#include <texteditor/codeassist/completionassistprovider.h> - -namespace Haskell { -namespace Internal { - -class HaskellCompletionAssistProvider : public TextEditor::CompletionAssistProvider -{ - Q_OBJECT - -public: - TextEditor::IAssistProcessor *createProcessor() const override; -}; - -} // namespace Internal -} // namespace Haskell - -#endif // HASKELLCOMPLETIONASSIST_H diff --git a/plugins/haskell/haskellconstants.h b/plugins/haskell/haskellconstants.h index 23463eb..8b63761 100644 --- a/plugins/haskell/haskellconstants.h +++ b/plugins/haskell/haskellconstants.h @@ -32,7 +32,10 @@ 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/plugins/haskell/haskelldocument.cpp b/plugins/haskell/haskelldocument.cpp deleted file mode 100644 index b2cc8e0..0000000 --- a/plugins/haskell/haskelldocument.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/**************************************************************************** -** -** 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 "haskelldocument.h" - -#include "haskellconstants.h" -#include "haskellmanager.h" - -using namespace TextEditor; -using namespace Utils; - -namespace Haskell { -namespace Internal { - -HaskellDocument::HaskellDocument() - : TextDocument(Constants::C_HASKELLEDITOR_ID) -{ - connect(this, &IDocument::filePathChanged, this, [this](const FilePath &, const FilePath &fn) { - m_ghcmod = HaskellManager::ghcModForFile(fn); - }); -} - -std::shared_ptr<AsyncGhcMod> HaskellDocument::ghcMod() const -{ - return m_ghcmod; -} - -} // namespace Internal -} // namespace Haskell diff --git a/plugins/haskell/haskelldocument.h b/plugins/haskell/haskelldocument.h deleted file mode 100644 index 72af5e9..0000000 --- a/plugins/haskell/haskelldocument.h +++ /dev/null @@ -1,51 +0,0 @@ -/**************************************************************************** -** -** 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 <texteditor/textdocument.h> - -#include <memory> - -namespace Haskell { -namespace Internal { - -class AsyncGhcMod; - -class HaskellDocument : public TextEditor::TextDocument -{ - Q_OBJECT - -public: - HaskellDocument(); - - std::shared_ptr<AsyncGhcMod> ghcMod() const; - -private: - std::shared_ptr<AsyncGhcMod> m_ghcmod; -}; - -} // namespace Internal -} // namespace Haskell diff --git a/plugins/haskell/haskelleditorfactory.cpp b/plugins/haskell/haskelleditorfactory.cpp index 8e5d837..2066779 100644 --- a/plugins/haskell/haskelleditorfactory.cpp +++ b/plugins/haskell/haskelleditorfactory.cpp @@ -25,21 +25,32 @@ #include "haskelleditorfactory.h" -#include "haskellcompletionassist.h" #include "haskellconstants.h" -#include "haskelldocument.h" -#include "haskelleditorwidget.h" #include "haskellhighlighter.h" -#include "haskellhoverhandler.h" +#include "haskellmanager.h" +#include <coreplugin/actionmanager/commandbutton.h> #include <texteditor/textdocument.h> #include <texteditor/texteditoractionhandler.h> +#include <texteditor/textindenter.h> #include <QCoreApplication> 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); @@ -47,13 +58,12 @@ HaskellEditorFactory::HaskellEditorFactory() addMimeType("text/x-haskell"); setEditorActionHandlers(TextEditor::TextEditorActionHandler::UnCommentSelection | TextEditor::TextEditorActionHandler::FollowSymbolUnderCursor); - addHoverHandler(new HaskellHoverHandler); - setDocumentCreator([] { return new HaskellDocument(); }); - setEditorWidgetCreator([] { return new HaskellEditorWidget; }); + 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); - setCompletionAssistProvider(new HaskellCompletionAssistProvider); setSyntaxHighlighterCreator([] { return new HaskellHighlighter(); }); } diff --git a/plugins/haskell/haskelleditorwidget.cpp b/plugins/haskell/haskelleditorwidget.cpp deleted file mode 100644 index d4154ae..0000000 --- a/plugins/haskell/haskelleditorwidget.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/**************************************************************************** -** -** 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 "haskelleditorwidget.h" - -#include "haskellconstants.h" -#include "haskelltokenizer.h" - -#include <coreplugin/icore.h> -#include <coreplugin/infobar.h> -#include <texteditor/textdocument.h> -#include <utils/textutils.h> - -#include <QTextBlock> -#include <QTimer> - -using namespace TextEditor; - -namespace Haskell { -namespace Internal { - -HaskellEditorWidget::HaskellEditorWidget(QWidget *parent) - : TextEditorWidget(parent) -{ -} - -Utils::optional<Token> HaskellEditorWidget::symbolAt(QTextDocument *doc, int position, - int *line, int *column) -{ - Utils::Text::convertPosition(doc, position, line, column); - if (*line < 0 || *column < 0) - return Utils::nullopt; - const QTextBlock block = doc->findBlockByNumber(*line - 1); - if (block.text().isEmpty()) - return Utils::nullopt; - const int startState = block.previous().isValid() ? block.previous().userState() : -1; - const Tokens tokens = HaskellTokenizer::tokenize(block.text(), startState); - const Token token = tokens.tokenAtColumn(*column); - if (token.isValid() - && (token.type == TokenType::Variable - || token.type == TokenType::Operator - || token.type == TokenType::Constructor - || token.type == TokenType::OperatorConstructor)) { - return token; - } - return Utils::nullopt; -} - -void HaskellEditorWidget::showFailedToStartStackError(const QString &stackExecutable, - TextEditorWidget *widget) -{ - static const char id[] = "Haskell.FailedToStartStack"; - Core::IDocument *document = widget->textDocument(); - if (!document->infoBar()->containsInfo(id)) { - Core::InfoBarEntry info( - id, - tr("Failed to start Haskell Stack \"%1\". Make sure you have stack installed and configured in the options.") - .arg(stackExecutable)); - info.setCustomButtonInfo(Core::ICore::msgShowOptionsDialog(), [document] { - QTimer::singleShot(0, Core::ICore::instance(), [document] { - document->infoBar()->removeInfo(id); - Core::ICore::showOptionsDialog(Constants::OPTIONS_GENERAL); - }); - }); - document->infoBar()->addInfo(info); - } -} - -void HaskellEditorWidget::findLinkAt(const QTextCursor &cursor, - Utils::ProcessLinkCallback &&processLinkCallback, - bool resolveTarget, - bool inNextSplit) -{ - Utils::Link link; - int line, column; - const Utils::optional<Token> symbol = symbolAt(document(), cursor.position(), &line, &column); - if (symbol) { - const QTextBlock block = document()->findBlockByNumber(line - 1); - Utils::Link link; - link.linkTextStart = block.position() + symbol->startCol; - link.linkTextEnd = link.linkTextStart + symbol->length; - if (resolveTarget) { - m_followSymbolAssistProvider.setOpenInNextSplit(inNextSplit); - invokeAssist(FollowSymbol, &m_followSymbolAssistProvider); - } - } - processLinkCallback(link); -} - -} // namespace Internal -} // namespace Haskell diff --git a/plugins/haskell/haskelleditorwidget.h b/plugins/haskell/haskelleditorwidget.h deleted file mode 100644 index 24af561..0000000 --- a/plugins/haskell/haskelleditorwidget.h +++ /dev/null @@ -1,60 +0,0 @@ -/**************************************************************************** -** -** 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 "followsymbol.h" - -#include <texteditor/texteditor.h> -#include <utils/optional.h> - -namespace Haskell { -namespace Internal { - -class Token; - -class HaskellEditorWidget : public TextEditor::TextEditorWidget -{ -public: - HaskellEditorWidget(QWidget *parent = 0); - - static Utils::optional<Token> symbolAt(QTextDocument *doc, int position, - int *line, int *column); - - static void showFailedToStartStackError(const QString &stackExecutable, - TextEditor::TextEditorWidget *widget); - -protected: - void findLinkAt(const QTextCursor &cursor, - Utils::ProcessLinkCallback &&processLinkCallback, - bool resolveTarget = true, - bool inNextSplit = false) override; - -private: - FollowSymbolAssistProvider m_followSymbolAssistProvider; -}; - -} // namespace Internal -} // namespace Haskell diff --git a/plugins/haskell/haskellhighlighter.cpp b/plugins/haskell/haskellhighlighter.cpp index 9899cc4..8a5b6ca 100644 --- a/plugins/haskell/haskellhighlighter.cpp +++ b/plugins/haskell/haskellhighlighter.cpp @@ -80,10 +80,10 @@ void HaskellHighlighter::highlightBlock(const QString &text) setTokenFormat(token, C_VISUAL_WHITESPACE); break; case TokenType::Keyword: - if (token.text == "::" && firstNonWS && !secondNonWS) { // toplevel declaration + if (token.text == QLatin1String("::") && firstNonWS && !secondNonWS) { // toplevel declaration setFormat(firstNonWS->startCol, firstNonWS->length, m_toplevelDeclFormat); inType = true; - } else if (token.text == "import") { + } else if (token.text == QLatin1String("import")) { inImport = true; } setTokenFormat(token, C_KEYWORD); diff --git a/plugins/haskell/haskellhoverhandler.cpp b/plugins/haskell/haskellhoverhandler.cpp deleted file mode 100644 index 1719297..0000000 --- a/plugins/haskell/haskellhoverhandler.cpp +++ /dev/null @@ -1,166 +0,0 @@ -/**************************************************************************** -** -** 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 "haskellhoverhandler.h" - -#include "haskelldocument.h" -#include "haskelleditorwidget.h" -#include "haskellmanager.h" -#include "haskelltokenizer.h" - -#include <texteditor/textdocument.h> -#include <texteditor/texteditor.h> -#include <utils/qtcassert.h> -#include <utils/runextensions.h> -#include <utils/synchronousprocess.h> -#include <utils/tooltip/tooltip.h> - -using namespace Utils; - -static QString toCode(const QString &s) -{ - if (s.isEmpty()) - return s; - return "<pre>" + s.toHtmlEscaped() + "</pre>"; -} - -namespace Haskell { -namespace Internal { - -QString symbolToHtml(const SymbolInfo &info) -{ - if (info.definition.isEmpty()) - return QString(); - QString result = "<pre>" + info.definition.join("\n") + "</pre>"; - if (!info.file.isEmpty()) { - result += "<div align=\"right\"><i>-- " + info.file.toString(); - if (info.line >= 0) { - result += ":" + QString::number(info.line); - if (info.col >= 0) - result += ":" + QString::number(info.col); - } - result += "</i></div>"; - } else if (!info.module.isEmpty()) { - result += "<div align=\"right\"><i>-- Module \"" + info.module + "\"</i></div>"; - } - if (!info.additionalInfo.isEmpty()) - result += "<pre>" + info.additionalInfo.join("\n") + "</pre>"; - return result; -} - -void HaskellHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget, - int pos, - ReportPriority report) -{ - cancel(); - m_name.clear(); - m_filePath.clear(); - const Utils::optional<Token> token = HaskellEditorWidget::symbolAt(editorWidget->document(), - pos, &m_line, &m_col); - if (token) { - m_filePath = editorWidget->textDocument()->filePath(); - m_name = token->text.toString(); - setPriority(Priority_Tooltip); - } else { - setPriority(-1); - } - report(priority()); -} - -static void showError(const QPointer<TextEditor::TextEditorWidget> &widget, - const Error &typeError, const Error &infoError) -{ - if (typeError.type == Error::Type::FailedToStartStack - || infoError.type == Error::Type::FailedToStartStack) { - const QString stackExecutable = typeError.type == Error::Type::FailedToStartStack - ? typeError.details - : infoError.details; - HaskellEditorWidget::showFailedToStartStackError(stackExecutable, widget); - } -} - -static void tryShowToolTip(const QPointer<TextEditor::TextEditorWidget> &widget, const QPoint &point, - QFuture<QStringOrError> typeFuture, - QFuture<SymbolInfoOrError> symbolFuture) -{ - if (Utils::ToolTip::isVisible() && widget - && symbolFuture.isResultReadyAt(0) && typeFuture.isResultReadyAt(0)) { - const QStringOrError typeOrError = typeFuture.result(); - const SymbolInfoOrError infoOrError = symbolFuture.result(); - if (const Error *typeError = Utils::get_if<Error>(&typeOrError)) - if (const Error *infoError = Utils::get_if<Error>(&infoOrError)) - showError(widget, *typeError, *infoError); - const QString *type = Utils::get_if<QString>(&typeOrError); - const SymbolInfo *info = Utils::get_if<SymbolInfo>(&infoOrError); - const QString typeString = !type || type->isEmpty() - ? QString() - : toCode(":: " + *type); - const QString infoString = info ? symbolToHtml(*info) : QString(); - const QString tip = typeString + infoString; - Utils::ToolTip::show(point, tip, widget); - } -} - -void HaskellHoverHandler::operateTooltip(TextEditor::TextEditorWidget *editorWidget, - const QPoint &point) -{ - cancel(); - if (m_name.isEmpty()) { - Utils::ToolTip::hide(); - return; - } - Utils::ToolTip::show(point, HaskellManager::trLookingUp(m_name), editorWidget); - - QPointer<TextEditor::TextEditorWidget> widget = editorWidget; - std::shared_ptr<AsyncGhcMod> ghcmod; - auto doc = qobject_cast<HaskellDocument *>(editorWidget->textDocument()); - QTC_ASSERT(doc, return); - ghcmod = doc->ghcMod(); - m_typeFuture = ghcmod->typeInfo(m_filePath, m_line, m_col); - m_symbolFuture = ghcmod->findSymbol(m_filePath, m_name); - Utils::onResultReady(m_typeFuture, - [typeFuture = m_typeFuture, symbolFuture = m_symbolFuture, - ghcmod, widget, point] // hold shared ghcmod pointer - (const QStringOrError &) { - tryShowToolTip(widget, point, typeFuture, symbolFuture); - }); - Utils::onResultReady(m_symbolFuture, - [typeFuture = m_typeFuture, symbolFuture = m_symbolFuture, - ghcmod, widget, point] // hold shared ghcmod pointer - (const SymbolInfoOrError &) { - tryShowToolTip(widget, point, typeFuture, symbolFuture); - }); -} - -void HaskellHoverHandler::cancel() -{ - if (m_symbolFuture.isRunning()) - m_symbolFuture.cancel(); - if (m_typeFuture.isRunning()) - m_typeFuture.cancel(); -} - -} // namespace Internal -} // namespace Haskell diff --git a/plugins/haskell/haskellhoverhandler.h b/plugins/haskell/haskellhoverhandler.h deleted file mode 100644 index a85da8e..0000000 --- a/plugins/haskell/haskellhoverhandler.h +++ /dev/null @@ -1,57 +0,0 @@ -/**************************************************************************** -** -** 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 "ghcmod.h" - -#include <texteditor/basehoverhandler.h> -#include <utils/fileutils.h> -#include <utils/optional.h> - -namespace Haskell { -namespace Internal { - -class HaskellHoverHandler : public TextEditor::BaseHoverHandler -{ -private: - void identifyMatch(TextEditor::TextEditorWidget *editorWidget, - int pos, - ReportPriority report) override; - void operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) override; - - void cancel(); - - Utils::FilePath m_filePath; - int m_line = -1; - int m_col = -1; - QString m_name; - - QFuture<SymbolInfoOrError> m_symbolFuture; - QFuture<QStringOrError> m_typeFuture; -}; - -} // namespace Internal -} // namespace Haskell diff --git a/plugins/haskell/haskellmanager.cpp b/plugins/haskell/haskellmanager.cpp index a2eadc6..dd6a371 100644 --- a/plugins/haskell/haskellmanager.cpp +++ b/plugins/haskell/haskellmanager.cpp @@ -25,9 +25,13 @@ #include "haskellmanager.h" -#include "ghcmod.h" - +#include <coreplugin/messagemanager.h> +#include <utils/algorithm.h> +#include <utils/commandline.h> #include <utils/hostosinfo.h> +#include <utils/mimeutils.h> +#include <utils/processenums.h> +#include <utils/qtcprocess.h> #include <QCoreApplication> #include <QDir> @@ -46,7 +50,6 @@ namespace Internal { class HaskellManagerPrivate { public: - std::unordered_map<FilePath, std::weak_ptr<AsyncGhcMod>> ghcModCache; FilePath stackExecutable; }; @@ -74,21 +77,6 @@ FilePath HaskellManager::findProjectDirectory(const FilePath &filePath) return {}; } -std::shared_ptr<AsyncGhcMod> HaskellManager::ghcModForFile(const FilePath &filePath) -{ - const FilePath projectPath = findProjectDirectory(filePath); - const auto cacheEntry = m_d->ghcModCache.find(projectPath); - if (cacheEntry != m_d->ghcModCache.cend()) { - if (cacheEntry->second.expired()) - m_d->ghcModCache.erase(cacheEntry); - else - return cacheEntry->second.lock(); - } - auto ghcmod = std::make_shared<AsyncGhcMod>(projectPath); - m_d->ghcModCache.insert(std::make_pair(projectPath, ghcmod)); - return ghcmod; -} - FilePath defaultStackExecutable() { // stack from brew or the installer script from https://docs.haskellstack.org @@ -111,6 +99,28 @@ void HaskellManager::setStackExecutable(const FilePath &filePath) emit m_instance->stackExecutableChanged(m_d->stackExecutable); } +void HaskellManager::openGhci(const FilePath &haskellFile) +{ + const QList<MimeType> 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( @@ -127,10 +137,5 @@ void HaskellManager::writeSettings(QSettings *settings) settings->setValue(kStackExecutableKey, m_d->stackExecutable.toString()); } -QString HaskellManager::trLookingUp(const QString &name) -{ - return QCoreApplication::translate("HaskellManager", "Looking up \"%1\"...").arg(name); -} - } // namespace Internal } // namespace Haskell diff --git a/plugins/haskell/haskellmanager.h b/plugins/haskell/haskellmanager.h index b4a2e35..361b8f8 100644 --- a/plugins/haskell/haskellmanager.h +++ b/plugins/haskell/haskellmanager.h @@ -36,8 +36,6 @@ QT_END_NAMESPACE namespace Haskell { namespace Internal { -class AsyncGhcMod; - class HaskellManager : public QObject { Q_OBJECT @@ -46,14 +44,12 @@ public: static HaskellManager *instance(); static Utils::FilePath findProjectDirectory(const Utils::FilePath &filePath); - static std::shared_ptr<AsyncGhcMod> ghcModForFile(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); - static QString trLookingUp(const QString &name); - signals: void stackExecutableChanged(const Utils::FilePath &filePath); }; diff --git a/plugins/haskell/haskellplugin.cpp b/plugins/haskell/haskellplugin.cpp index d92c4f8..c3ae400 100644 --- a/plugins/haskell/haskellplugin.cpp +++ b/plugins/haskell/haskellplugin.cpp @@ -25,7 +25,6 @@ #include "haskellplugin.h" -#include "ghcmod.h" #include "haskellbuildconfiguration.h" #include "haskellconstants.h" #include "haskelleditorfactory.h" @@ -35,10 +34,15 @@ #include "optionspage.h" #include "stackbuildstep.h" +#include <coreplugin/actionmanager/actionmanager.h> +#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/icore.h> #include <projectexplorer/projectmanager.h> +#include <projectexplorer/jsonwizard/jsonwizardfactory.h> #include <texteditor/snippets/snippetprovider.h> +#include <QAction> + namespace Haskell { namespace Internal { @@ -50,6 +54,7 @@ public: HaskellBuildConfigurationFactory buildConfigFactory; StackBuildStepFactory stackBuildStepFactory; HaskellRunConfigurationFactory runConfigFactory; + ProjectExplorer::SimpleTargetRunnerFactory runWorkerFactory{{Constants::C_HASKELL_RUNCONFIG_ID}}; }; HaskellPlugin::~HaskellPlugin() @@ -57,6 +62,16 @@ 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) @@ -72,10 +87,12 @@ bool HaskellPlugin::initialize(const QStringList &arguments, QString *errorStrin connect(Core::ICore::instance(), &Core::ICore::saveSettingsRequested, this, [] { HaskellManager::writeSettings(Core::ICore::settings()); }); - connect(HaskellManager::instance(), &HaskellManager::stackExecutableChanged, - &GhcMod::setStackExecutable); + + registerGhciAction(); HaskellManager::readSettings(Core::ICore::settings()); + + ProjectExplorer::JsonWizardFactory::addWizardPath(":/haskell/share/wizards/"); return true; } diff --git a/plugins/haskell/haskellproject.cpp b/plugins/haskell/haskellproject.cpp index 92b30e6..248f898 100644 --- a/plugins/haskell/haskellproject.cpp +++ b/plugins/haskell/haskellproject.cpp @@ -65,17 +65,12 @@ static QVector<QString> parseExecutableNames(const FilePath &projectFilePath) return result; } -HaskellProjectNode::HaskellProjectNode(const FilePath &projectFilePath) - : ProjectNode(projectFilePath) -{} - HaskellProject::HaskellProject(const Utils::FilePath &fileName) - : Project(Constants::C_HASKELL_PROJECT_MIMETYPE, fileName, [this] { refresh(); }) + : Project(Constants::C_HASKELL_PROJECT_MIMETYPE, fileName) { setId(Constants::C_HASKELL_PROJECT_ID); setDisplayName(fileName.toFileInfo().completeBaseName()); - updateFiles(); - connect(this, &Project::activeTargetChanged, this, &HaskellProject::updateApplicationTargets); + setBuildSystemCreator([](Target *t) { return new HaskellBuildSystem(t); }); } bool HaskellProject::isHaskellProject(Project *project) @@ -83,34 +78,43 @@ bool HaskellProject::isHaskellProject(Project *project) return project && project->id() == Constants::C_HASKELL_PROJECT_ID; } -void HaskellProject::updateFiles() +HaskellBuildSystem::HaskellBuildSystem(Target *t) + : BuildSystem(t) { - emitParsingStarted(); - FilePath projectDir = projectDirectory(); - QFuture<QList<FileNode *>> future = Utils::runAsync([this, projectDir] { - return FileNode::scanForFiles(projectDir, [this](const FilePath &fn) -> FileNode * { - if (fn != FilePath::fromString(projectFilePath().toString() + ".user")) - return new FileNode(fn, FileType::Source); - else - return nullptr; - }); - }); - Utils::onResultReady(future, this, [this](const QList<FileNode *> &nodes) { - auto root = new HaskellProjectNode(projectDirectory()); - root->setDisplayName(displayName()); + connect(&m_scanner, &TreeScanner::finished, this, [this] { + auto root = std::make_unique<ProjectNode>(projectDirectory()); + root->setDisplayName(target()->project()->displayName()); std::vector<std::unique_ptr<FileNode>> nodePtrs - = Utils::transform<std::vector>(nodes, [](FileNode *fn) { + = Utils::transform<std::vector>(m_scanner.release().allFiles, [](FileNode *fn) { return std::unique_ptr<FileNode>(fn); }); root->addNestedNodes(std::move(nodePtrs)); - setRootProjectNode(std::unique_ptr<ProjectNode>(root)); - emitParsingFinished(true); + setRootProjectNode(std::move(root)); + + updateApplicationTargets(); + + m_parseGuard.markAsSuccess(); + m_parseGuard = {}; + + emitBuildSystemUpdated(); }); + + connect(target()->project(), + &Project::projectFileIsDirty, + this, + &BuildSystem::requestDelayedParse); + + requestDelayedParse(); } -void HaskellProject::updateApplicationTargets(Target *target) +void HaskellBuildSystem::triggerParsing() +{ + m_parseGuard = guardParsingRun(); + m_scanner.asyncScanForFiles(target()->project()->projectDirectory()); +} + +void HaskellBuildSystem::updateApplicationTargets() { - QTC_ASSERT(target, return); const QVector<QString> executables = parseExecutableNames(projectFilePath()); const Utils::FilePath projFilePath = projectFilePath(); const QList<BuildTargetInfo> appTargets @@ -123,15 +127,8 @@ void HaskellProject::updateApplicationTargets(Target *target) bti.isQtcRunnable = true; return bti; }); - target->setApplicationTargets(appTargets); - target->updateDefaultRunConfigurations(); -} - -void HaskellProject::refresh() -{ - updateFiles(); - if (activeTarget()) - updateApplicationTargets(activeTarget()); + setApplicationTargets(appTargets); + target()->updateDefaultRunConfigurations(); } } // namespace Internal diff --git a/plugins/haskell/haskellproject.h b/plugins/haskell/haskellproject.h index 0afb717..0ee05d9 100644 --- a/plugins/haskell/haskellproject.h +++ b/plugins/haskell/haskellproject.h @@ -25,31 +25,41 @@ #pragma once +#include <projectexplorer/buildsystem.h> #include <projectexplorer/project.h> #include <projectexplorer/projectnodes.h> +#include <projectexplorer/treescanner.h> namespace Haskell { namespace Internal { -class HaskellProjectNode : public ProjectExplorer::ProjectNode +class HaskellProject : public ProjectExplorer::Project { + Q_OBJECT + public: - HaskellProjectNode(const Utils::FilePath &projectFilePath); + explicit HaskellProject(const Utils::FilePath &fileName); + + static bool isHaskellProject(Project *project); }; -class HaskellProject : public ProjectExplorer::Project +class HaskellBuildSystem : public ProjectExplorer::BuildSystem { Q_OBJECT public: - explicit HaskellProject(const Utils::FilePath &fileName); + HaskellBuildSystem(ProjectExplorer::Target *t); - static bool isHaskellProject(Project *project); + void triggerParsing() override; + QString name() const final { return QLatin1String("haskell"); } private: - void updateFiles(); - void updateApplicationTargets(ProjectExplorer::Target *target); + void updateApplicationTargets(); void refresh(); + +private: + ParseGuard m_parseGuard; + ProjectExplorer::TreeScanner m_scanner; }; } // namespace Internal diff --git a/plugins/haskell/haskellrunconfiguration.cpp b/plugins/haskell/haskellrunconfiguration.cpp index 78ea231..7457471 100644 --- a/plugins/haskell/haskellrunconfiguration.cpp +++ b/plugins/haskell/haskellrunconfiguration.cpp @@ -31,7 +31,6 @@ #include <projectexplorer/buildconfiguration.h> #include <projectexplorer/localenvironmentaspect.h> -#include <projectexplorer/projectexplorerconstants.h> #include <projectexplorer/runconfigurationaspects.h> #include <projectexplorer/runcontrol.h> #include <projectexplorer/target.h> @@ -43,7 +42,7 @@ namespace Internal { HaskellRunConfigurationFactory::HaskellRunConfigurationFactory() { - registerRunConfiguration<HaskellRunConfiguration>("Haskell.RunConfiguration"); + registerRunConfiguration<HaskellRunConfiguration>(Constants::C_HASKELL_RUNCONFIG_ID); addSupportedProjectType(Constants::C_HASKELL_PROJECT_ID); addSupportedTargetDeviceType(ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE); } @@ -54,49 +53,43 @@ HaskellExecutableAspect::HaskellExecutableAspect() setLabelText(tr("Executable")); } -HaskellRunConfiguration::HaskellRunConfiguration(Target *target, Core::Id id) +HaskellRunConfiguration::HaskellRunConfiguration(Target *target, Utils::Id id) : RunConfiguration(target, id) { - addAspect<LocalEnvironmentAspect>(target); + auto envAspect = addAspect<LocalEnvironmentAspect>(target); - auto executableAspect = addAspect<HaskellExecutableAspect>(); - connect(target, &Target::applicationTargetsChanged, this, [this, target, executableAspect] { - BuildTargetInfo bti = target->buildTarget(buildKey()); - executableAspect->setValue(bti.targetFilePath.toString()); - }); + addAspect<HaskellExecutableAspect>(); + addAspect<ArgumentsAspect>(macroExpander()); - addAspect<ArgumentsAspect>(); - - auto workingDirAspect = addAspect<WorkingDirectoryAspect>(); + auto workingDirAspect = addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect); workingDirAspect->setDefaultWorkingDirectory(target->project()->projectDirectory()); workingDirAspect->setVisible(false); addAspect<TerminalAspect>(); -} -void HaskellRunConfiguration::doAdditionalSetup(const RunConfigurationCreationInfo &info) -{ - aspect<HaskellExecutableAspect>()->setValue(info.buildKey); + setUpdater([this] { aspect<HaskellExecutableAspect>()->setValue(buildTargetInfo().buildKey); }); + connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update); + update(); } Runnable HaskellRunConfiguration::runnable() const { - const QString projectDirectory = target()->project()->projectDirectory().toString(); + const Utils::FilePath projectDirectory = target()->project()->projectDirectory(); Runnable r; - if (BuildConfiguration *buildConfiguration = target()->activeBuildConfiguration()) - r.commandLineArguments += "--work-dir \"" - + QDir(projectDirectory) - .relativeFilePath( - buildConfiguration->buildDirectory().toString()) - + "\" "; - const QString executable = aspect<HaskellExecutableAspect>()->value(); - r.commandLineArguments += "exec \"" + executable + "\""; - const QString arguments = aspect<ArgumentsAspect>()->arguments(macroExpander()); + QStringList args; + if (BuildConfiguration *buildConfiguration = target()->activeBuildConfiguration()) { + args << "--work-dir" + << QDir(projectDirectory.toString()).relativeFilePath( + buildConfiguration->buildDirectory().toString()); + } + args << "exec" << aspect<HaskellExecutableAspect>()->value(); + const QString arguments = aspect<ArgumentsAspect>()->arguments(); if (!arguments.isEmpty()) - r.commandLineArguments += " -- " + arguments; + args << "--" << arguments; + r.workingDirectory = projectDirectory; r.environment = aspect<LocalEnvironmentAspect>()->environment(); - r.executable = r.environment.searchInPath(HaskellManager::stackExecutable().toString()).toString(); + r.command = {r.environment.searchInPath(HaskellManager::stackExecutable().toString()), args}; return r; } diff --git a/plugins/haskell/haskellrunconfiguration.h b/plugins/haskell/haskellrunconfiguration.h index 62bbd3d..27e4a83 100644 --- a/plugins/haskell/haskellrunconfiguration.h +++ b/plugins/haskell/haskellrunconfiguration.h @@ -25,8 +25,10 @@ #pragma once +#include <projectexplorer/projectexplorerconstants.h> #include <projectexplorer/runconfigurationaspects.h> #include <projectexplorer/runcontrol.h> +#include <utils/aspects.h> namespace Haskell { namespace Internal { @@ -36,10 +38,9 @@ class HaskellRunConfiguration : public ProjectExplorer::RunConfiguration Q_OBJECT public: - HaskellRunConfiguration(ProjectExplorer::Target *target, Core::Id id); + HaskellRunConfiguration(ProjectExplorer::Target *target, Utils::Id id); private: - void doAdditionalSetup(const ProjectExplorer::RunConfigurationCreationInfo &info) final; ProjectExplorer::Runnable runnable() const final; }; @@ -47,14 +48,9 @@ class HaskellRunConfigurationFactory : public ProjectExplorer::RunConfigurationF { public: HaskellRunConfigurationFactory(); - -private: - ProjectExplorer::SimpleRunWorkerFactory<ProjectExplorer::SimpleTargetRunner, - HaskellRunConfiguration> - runWorkerFactory; }; -class HaskellExecutableAspect : public ProjectExplorer::BaseStringAspect +class HaskellExecutableAspect : public Utils::StringAspect { Q_OBJECT diff --git a/plugins/haskell/haskelltokenizer.cpp b/plugins/haskell/haskelltokenizer.cpp index b59b48b..6f2556d 100644 --- a/plugins/haskell/haskelltokenizer.cpp +++ b/plugins/haskell/haskelltokenizer.cpp @@ -142,7 +142,7 @@ namespace Internal { Token token(TokenType type, std::shared_ptr<QString> line, int start, int end) { - return {type, start, end - start, line->midRef(start, end - start), line}; + return {type, start, end - start, QStringView(*line).mid(start, end - start), line}; } Tokens::Tokens(std::shared_ptr<QString> source) @@ -251,7 +251,7 @@ static QVector<Token> getSpace(std::shared_ptr<QString> line, int start) ++current; const int length = int(std::distance(tokenStart, current)); if (current > tokenStart) - return {{TokenType::Whitespace, start, length, line->midRef(start, length), line}}; + return {{TokenType::Whitespace, start, length, QStringView(*line).mid(start, length), line}}; return {}; } @@ -405,7 +405,7 @@ static int getEscape(const QString &line, int start) return 0; return count + 1; } - const QStringRef s = line.midRef(start); + const QStringView s = QStringView(line).mid(start); for (const QString &esc : *ASCII_ESCAPES) { if (s.startsWith(esc)) return esc.length(); @@ -481,7 +481,7 @@ static QVector<Token> getString(std::shared_ptr<QString> line, int start, bool * lastRef.type = TokenType::StringError; } else { --lastRef.length; - lastRef.text = line->midRef(lastRef.startCol, lastRef.length); + lastRef.text = QStringView(*line).mid(lastRef.startCol, lastRef.length); result.append(token(TokenType::StringError, line, current - 1, current)); } } @@ -496,11 +496,11 @@ static QVector<Token> getMultiLineComment(std::shared_ptr<QString> line, int sta const int length = line->length(); int current = start; do { - const QStringRef test = line->midRef(current, 2); - if (test == "{-") { + const QStringView test = QStringView(*line).mid(current, 2); + if (test == QLatin1String("{-")) { ++(*commentLevel); current += 2; - } else if (test == "-}" && *commentLevel > 0) { + } else if (test == QLatin1String("-}") && *commentLevel > 0) { --(*commentLevel); current += 2; } else if (*commentLevel > 0) { @@ -587,7 +587,7 @@ static QVector<Token> getChar(std::shared_ptr<QString> line, int start) static QVector<Token> getSpecial(std::shared_ptr<QString> line, int start) { if (SPECIAL->contains(line->at(start))) - return {{TokenType::Special, start, 1, line->midRef(start, 1), line}}; + return {{TokenType::Special, start, 1, QStringView(*line).mid(start, 1), line}}; return {}; } @@ -620,7 +620,7 @@ Tokens HaskellTokenizer::tokenize(const QString &line, int startState) tokens = {{TokenType::Unknown, currentStart, 1, - result.source->midRef(currentStart, 1), + QStringView(*result.source).mid(currentStart, 1), result.source}}; result.append(tokens); } diff --git a/plugins/haskell/haskelltokenizer.h b/plugins/haskell/haskelltokenizer.h index 29b10df..63d82d9 100644 --- a/plugins/haskell/haskelltokenizer.h +++ b/plugins/haskell/haskelltokenizer.h @@ -61,7 +61,7 @@ public: TokenType type = TokenType::Unknown; int startCol = -1; int length = -1; - QStringRef text; + QStringView text; std::shared_ptr<QString> source; // keep the string ref alive }; diff --git a/plugins/haskell/optionspage.cpp b/plugins/haskell/optionspage.cpp index 04cab79..63d1594 100644 --- a/plugins/haskell/optionspage.cpp +++ b/plugins/haskell/optionspage.cpp @@ -62,7 +62,7 @@ QWidget *OptionsPage::widget() m_stackPath = new PathChooser(); m_stackPath->setExpectedKind(PathChooser::ExistingCommand); m_stackPath->setPromptDialogTitle(tr("Choose Stack Executable")); - m_stackPath->setFileName(HaskellManager::stackExecutable()); + m_stackPath->setFilePath(HaskellManager::stackExecutable()); m_stackPath->setCommandVersionArguments({"--version"}); boxLayout->addWidget(m_stackPath); } @@ -73,7 +73,7 @@ void OptionsPage::apply() { if (!m_widget) return; - HaskellManager::setStackExecutable(m_stackPath->rawFileName()); + HaskellManager::setStackExecutable(m_stackPath->rawFilePath()); } void OptionsPage::finish() diff --git a/plugins/haskell/share/wizards/module/file.hs b/plugins/haskell/share/wizards/module/file.hs new file mode 100644 index 0000000..e3f85b3 --- /dev/null +++ b/plugins/haskell/share/wizards/module/file.hs @@ -0,0 +1 @@ +module %{BaseName} where diff --git a/plugins/haskell/share/wizards/module/wizard.json b/plugins/haskell/share/wizards/module/wizard.json new file mode 100644 index 0000000..37f6ebf --- /dev/null +++ b/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/plugins/haskell/stackbuildstep.cpp b/plugins/haskell/stackbuildstep.cpp index 931fa32..52ed64f 100644 --- a/plugins/haskell/stackbuildstep.cpp +++ b/plugins/haskell/stackbuildstep.cpp @@ -25,6 +25,7 @@ #include "stackbuildstep.h" +#include "haskellconstants.h" #include "haskellmanager.h" #include <projectexplorer/buildconfiguration.h> @@ -34,42 +35,18 @@ using namespace ProjectExplorer; -const char C_STACK_BUILD_STEP_ID[] = "Haskell.Stack.Build"; - namespace Haskell { namespace Internal { -StackBuildStep::StackBuildStep(ProjectExplorer::BuildStepList *bsl) - : AbstractProcessStep(bsl, C_STACK_BUILD_STEP_ID) +StackBuildStep::StackBuildStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id) + : AbstractProcessStep(bsl, id) { setDefaultDisplayName(trDisplayName()); - - const auto updateArguments = [this] { - const auto projectDir = QDir(project()->projectDirectory().toString()); - processParameters()->setArguments( - "build --work-dir \"" - + projectDir.relativeFilePath(buildConfiguration()->buildDirectory().toString()) + "\""); - }; - const auto updateEnvironment = [this] { - processParameters()->setEnvironment(buildConfiguration()->environment()); - }; - processParameters()->setCommand(HaskellManager::stackExecutable()); - updateArguments(); - processParameters()->setWorkingDirectory(project()->projectDirectory()); - updateEnvironment(); - connect(HaskellManager::instance(), - &HaskellManager::stackExecutableChanged, - this, - [this](const Utils::FilePath &stackExe) { - processParameters()->setCommand(stackExe); - }); - connect(buildConfiguration(), &BuildConfiguration::buildDirectoryChanged, this, updateArguments); - connect(buildConfiguration(), &BuildConfiguration::environmentChanged, this, updateEnvironment); } -BuildStepConfigWidget *StackBuildStep::createConfigWidget() +QWidget *StackBuildStep::createConfigWidget() { - return new BuildStepConfigWidget(this); + return new QWidget; } QString StackBuildStep::trDisplayName() @@ -77,9 +54,21 @@ 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<StackBuildStep>(C_STACK_BUILD_STEP_ID); + registerStep<StackBuildStep>(Constants::C_STACK_BUILD_STEP_ID); setDisplayName(StackBuildStep::StackBuildStep::trDisplayName()); setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_BUILD); } diff --git a/plugins/haskell/stackbuildstep.h b/plugins/haskell/stackbuildstep.h index e03ab97..d74d155 100644 --- a/plugins/haskell/stackbuildstep.h +++ b/plugins/haskell/stackbuildstep.h @@ -35,11 +35,14 @@ class StackBuildStep : public ProjectExplorer::AbstractProcessStep Q_OBJECT public: - StackBuildStep(ProjectExplorer::BuildStepList *bsl); + StackBuildStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id); - ProjectExplorer::BuildStepConfigWidget *createConfigWidget() override; + QWidget *createConfigWidget() override; static QString trDisplayName(); + +protected: + bool init() override; }; class StackBuildStepFactory : public ProjectExplorer::BuildStepFactory diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro deleted file mode 100644 index 200dc2f..0000000 --- a/tests/auto/auto.pro +++ /dev/null @@ -1,2 +0,0 @@ -TEMPLATE = subdirs -SUBDIRS += tokenizer diff --git a/tests/auto/tokenizer/CMakeLists.txt b/tests/auto/tokenizer/CMakeLists.txt new file mode 100644 index 0000000..90474ca --- /dev/null +++ b/tests/auto/tokenizer/CMakeLists.txt @@ -0,0 +1,8 @@ +add_qtc_test(tst_tokenizer + DEPENDS Qt5::Core Qt5::Test + INCLUDES ../../../plugins/haskell + SOURCES + tst_tokenizer.cpp + ../../../plugins/haskell/haskelltokenizer.cpp + ../../../plugins/haskell/haskelltokenizer.h +) diff --git a/tests/auto/tokenizer/tokenizer.pro b/tests/auto/tokenizer/tokenizer.pro deleted file mode 100644 index a9ec439..0000000 --- a/tests/auto/tokenizer/tokenizer.pro +++ /dev/null @@ -1,11 +0,0 @@ -include(../../../plugins/haskell/config.pri) - -include($$IDE_SOURCE_TREE/tests/auto/qttest.pri) - -SOURCES += tst_tokenizer.cpp \ - $$PWD/../../../plugins/haskell/haskelltokenizer.cpp - -HEADERS += \ - $$PWD/../../../plugins/haskell/haskelltokenizer.h - -INCLUDEPATH += $$PWD/../../../plugins/haskell |