forked from qt-creator/qt-creator
rename PythonEditor plugin to Python
The plugin does not only contain a pure editor, but all kind of support for a programming language like project and run support. Change-Id: I1251367c8db2e7a54986415ffc5b860cb210de3c Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
11
src/plugins/python/CMakeLists.txt
Normal file
11
src/plugins/python/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
add_qtc_plugin(Python
|
||||
PLUGIN_DEPENDS Core QtSupport ProjectExplorer TextEditor
|
||||
SOURCES
|
||||
pythoneditor.cpp pythoneditor.h
|
||||
pythonconstants.h
|
||||
pythonplugin.cpp pythonplugin.h
|
||||
pythonformattoken.h
|
||||
pythonhighlighter.cpp pythonhighlighter.h
|
||||
pythonindenter.cpp pythonindenter.h
|
||||
pythonscanner.cpp pythonscanner.h
|
||||
)
|
||||
36
src/plugins/python/Python.json.in
Normal file
36
src/plugins/python/Python.json.in
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
\"Name\" : \"Python\",
|
||||
\"Version\" : \"$$QTCREATOR_VERSION\",
|
||||
\"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
|
||||
\"Vendor\" : \"The Qt Company Ltd\",
|
||||
\"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\",
|
||||
\"License\" : [ \"Commercial Usage\",
|
||||
\"\",
|
||||
\"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\",
|
||||
\"\",
|
||||
\"GNU General Public License Usage\",
|
||||
\"\",
|
||||
\"Alternatively, this plugin 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 plugin. 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.\"
|
||||
],
|
||||
\"Category\" : \"Other Languages\",
|
||||
\"Description\" : \"Plugin for supporting the Python language.\",
|
||||
\"Url\" : \"http://www.qt.io\",
|
||||
$$dependencyList,
|
||||
|
||||
\"Mimetypes\" : [
|
||||
\"<?xml version=\'1.0\'?>\",
|
||||
\"<mime-info xmlns=\'http://www.freedesktop.org/standards/shared-mime-info\'>\",
|
||||
\" <mime-type type=\'text/x-python-gui\'>\",
|
||||
\" <sub-class-of type=\'text/x-python\'/>\",
|
||||
\" <comment>Python source file without console</comment>\",
|
||||
\" <glob pattern=\'*.pyw\'/>\",
|
||||
\" </mime-type>\",
|
||||
\" <mime-type type=\'text/x-python-project\'>\",
|
||||
\" <sub-class-of type=\'text/x-python\'/>\",
|
||||
\" <comment>Qt Creator Python project file</comment>\",
|
||||
\" <glob pattern=\'*.pyproject\'/>\",
|
||||
\" <glob pattern=\'*.pyqtc\'/>\",
|
||||
\" </mime-type>\",
|
||||
\"</mime-info>\"
|
||||
]
|
||||
}
|
||||
20
src/plugins/python/python.pro
Normal file
20
src/plugins/python/python.pro
Normal file
@@ -0,0 +1,20 @@
|
||||
include(../../qtcreatorplugin.pri)
|
||||
|
||||
DEFINES += \
|
||||
PYTHON_LIBRARY
|
||||
|
||||
HEADERS += \
|
||||
pythonplugin.h \
|
||||
pythoneditor.h \
|
||||
pythonconstants.h \
|
||||
pythonhighlighter.h \
|
||||
pythonindenter.h \
|
||||
pythonformattoken.h \
|
||||
pythonscanner.h \
|
||||
|
||||
SOURCES += \
|
||||
pythonplugin.cpp \
|
||||
pythoneditor.cpp \
|
||||
pythonhighlighter.cpp \
|
||||
pythonindenter.cpp \
|
||||
pythonscanner.cpp
|
||||
26
src/plugins/python/python.qbs
Normal file
26
src/plugins/python/python.qbs
Normal file
@@ -0,0 +1,26 @@
|
||||
import qbs 1.0
|
||||
|
||||
QtcPlugin {
|
||||
name: "Python"
|
||||
|
||||
Depends { name: "Qt.widgets" }
|
||||
Depends { name: "Utils" }
|
||||
|
||||
Depends { name: "Core" }
|
||||
Depends { name: "TextEditor" }
|
||||
Depends { name: "QtSupport" }
|
||||
Depends { name: "ProjectExplorer" }
|
||||
|
||||
Group {
|
||||
name: "General"
|
||||
files: [
|
||||
"pythoneditor.cpp", "pythoneditor.h",
|
||||
"pythonconstants.h",
|
||||
"pythonplugin.cpp", "pythonplugin.h",
|
||||
"pythonhighlighter.h", "pythonhighlighter.cpp",
|
||||
"pythonindenter.cpp", "pythonindenter.h",
|
||||
"pythonformattoken.h",
|
||||
"pythonscanner.h", "pythonscanner.cpp",
|
||||
]
|
||||
}
|
||||
}
|
||||
9
src/plugins/python/python_dependencies.pri
Normal file
9
src/plugins/python/python_dependencies.pri
Normal file
@@ -0,0 +1,9 @@
|
||||
QTC_PLUGIN_NAME = Python
|
||||
QTC_LIB_DEPENDS += \
|
||||
extensionsystem \
|
||||
utils
|
||||
QTC_PLUGIN_DEPENDS += \
|
||||
coreplugin \
|
||||
texteditor \
|
||||
qtsupport \
|
||||
projectexplorer
|
||||
44
src/plugins/python/pythonconstants.h
Normal file
44
src/plugins/python/pythonconstants.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 <QtGlobal>
|
||||
|
||||
namespace Python {
|
||||
namespace Constants {
|
||||
|
||||
const char C_PYTHONEDITOR_ID[] = "PythonEditor.PythonEditor";
|
||||
const char C_EDITOR_DISPLAY_NAME[] =
|
||||
QT_TRANSLATE_NOOP("OpenWith::Editors", "Python Editor");
|
||||
|
||||
/*******************************************************************************
|
||||
* MIME type
|
||||
******************************************************************************/
|
||||
const char C_PY_MIMETYPE[] = "text/x-python";
|
||||
const char C_PY_MIME_ICON[] = "text-x-python";
|
||||
|
||||
} // namespace Constants
|
||||
} // namespace Python
|
||||
65
src/plugins/python/pythoneditor.cpp
Normal file
65
src/plugins/python/pythoneditor.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 "pythoneditor.h"
|
||||
#include "pythonconstants.h"
|
||||
#include "pythonplugin.h"
|
||||
#include "pythonindenter.h"
|
||||
#include "pythonhighlighter.h"
|
||||
|
||||
#include <texteditor/texteditoractionhandler.h>
|
||||
#include <texteditor/texteditorconstants.h>
|
||||
#include <texteditor/textdocument.h>
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
using namespace TextEditor;
|
||||
|
||||
namespace Python {
|
||||
namespace Internal {
|
||||
|
||||
PythonEditorFactory::PythonEditorFactory()
|
||||
{
|
||||
setId(Constants::C_PYTHONEDITOR_ID);
|
||||
setDisplayName(QCoreApplication::translate("OpenWith::Editors", Constants::C_EDITOR_DISPLAY_NAME));
|
||||
addMimeType(Constants::C_PY_MIMETYPE);
|
||||
|
||||
setEditorActionHandlers(TextEditorActionHandler::Format
|
||||
| TextEditorActionHandler::UnCommentSelection
|
||||
| TextEditorActionHandler::UnCollapseAll
|
||||
| TextEditorActionHandler::FollowSymbolUnderCursor);
|
||||
|
||||
setDocumentCreator([] { return new TextDocument(Constants::C_PYTHONEDITOR_ID); });
|
||||
setIndenterCreator([](QTextDocument *doc) { return new PythonIndenter(doc); });
|
||||
setSyntaxHighlighterCreator([] { return new PythonHighlighter; });
|
||||
setCommentDefinition(Utils::CommentDefinition::HashStyle);
|
||||
setParenthesesMatchingEnabled(true);
|
||||
setCodeFoldingSupported(true);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Python
|
||||
40
src/plugins/python/pythoneditor.h
Normal file
40
src/plugins/python/pythoneditor.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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/texteditor.h>
|
||||
|
||||
namespace Python {
|
||||
namespace Internal {
|
||||
|
||||
class PythonEditorFactory : public TextEditor::TextEditorFactory
|
||||
{
|
||||
public:
|
||||
PythonEditorFactory();
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Python
|
||||
71
src/plugins/python/pythonformattoken.h
Normal file
71
src/plugins/python/pythonformattoken.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Python {
|
||||
namespace Internal {
|
||||
|
||||
enum Format {
|
||||
Format_Number = 0,
|
||||
Format_String,
|
||||
Format_Keyword,
|
||||
Format_Type,
|
||||
Format_ClassField,
|
||||
Format_MagicAttr, // magic class attribute/method, like __name__, __init__
|
||||
Format_Operator,
|
||||
Format_Comment,
|
||||
Format_Doxygen,
|
||||
Format_Identifier,
|
||||
Format_Whitespace,
|
||||
Format_ImportedModule,
|
||||
|
||||
Format_FormatsAmount
|
||||
};
|
||||
|
||||
class FormatToken
|
||||
{
|
||||
public:
|
||||
FormatToken() = default;
|
||||
|
||||
FormatToken(Format format, int position, int length)
|
||||
: m_format(format), m_position(position), m_length(length)
|
||||
{}
|
||||
|
||||
bool isEndOfBlock() { return m_position == -1; }
|
||||
|
||||
Format format() const { return m_format; }
|
||||
int begin() const { return m_position; }
|
||||
int end() const { return m_position + m_length; }
|
||||
int length() const { return m_length; }
|
||||
|
||||
private:
|
||||
Format m_format = Format_FormatsAmount;
|
||||
int m_position = -1;
|
||||
int m_length = -1;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Python
|
||||
201
src/plugins/python/pythonhighlighter.cpp
Normal file
201
src/plugins/python/pythonhighlighter.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief The Highlighter class pre-highlights Python source using simple scanner.
|
||||
*
|
||||
* Highlighter doesn't highlight user types (classes and enumerations), syntax
|
||||
* and semantic errors, unnecessary code, etc. It's implements only
|
||||
* basic highlight mechanism.
|
||||
*
|
||||
* Main highlight procedure is highlightBlock().
|
||||
*/
|
||||
|
||||
#include "pythonhighlighter.h"
|
||||
#include "pythonscanner.h"
|
||||
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <texteditor/textdocumentlayout.h>
|
||||
#include <texteditor/texteditorconstants.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
namespace Python {
|
||||
namespace Internal {
|
||||
|
||||
/**
|
||||
* @class PythonEditor::Internal::PythonHighlighter
|
||||
* @brief Handles incremental lexical highlighting, but not semantic
|
||||
*
|
||||
* Incremental lexical highlighting works every time when any character typed
|
||||
* or some text inserted (i.e. copied & pasted).
|
||||
* Each line keeps associated scanner state - integer number. This state is the
|
||||
* scanner context for next line. For example, 3 quotes begin a multiline
|
||||
* string, and each line up to next 3 quotes has state 'MultiLineString'.
|
||||
*
|
||||
* @code
|
||||
* def __init__: # Normal
|
||||
* self.__doc__ = """ # MultiLineString (next line is inside)
|
||||
* banana # MultiLineString
|
||||
* """ # Normal
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
static TextEditor::TextStyle styleForFormat(int format)
|
||||
{
|
||||
using namespace TextEditor;
|
||||
const auto f = Format(format);
|
||||
switch (f) {
|
||||
case Format_Number: return C_NUMBER;
|
||||
case Format_String: return C_STRING;
|
||||
case Format_Keyword: return C_KEYWORD;
|
||||
case Format_Type: return C_TYPE;
|
||||
case Format_ClassField: return C_FIELD;
|
||||
case Format_MagicAttr: return C_JS_SCOPE_VAR;
|
||||
case Format_Operator: return C_OPERATOR;
|
||||
case Format_Comment: return C_COMMENT;
|
||||
case Format_Doxygen: return C_DOXYGEN_COMMENT;
|
||||
case Format_Identifier: return C_TEXT;
|
||||
case Format_Whitespace: return C_VISUAL_WHITESPACE;
|
||||
case Format_ImportedModule: return C_STRING;
|
||||
case Format_FormatsAmount:
|
||||
QTC_CHECK(false); // should never get here
|
||||
return C_TEXT;
|
||||
}
|
||||
QTC_CHECK(false); // should never get here
|
||||
return C_TEXT;
|
||||
}
|
||||
|
||||
PythonHighlighter::PythonHighlighter()
|
||||
{
|
||||
setTextFormatCategories(Format_FormatsAmount, styleForFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief PythonHighlighter::highlightBlock highlights single line of Python code
|
||||
* @param text is single line without EOLN symbol. Access to all block data
|
||||
* can be obtained through inherited currentBlock() function.
|
||||
*
|
||||
* This function receives state (int number) from previously highlighted block,
|
||||
* scans block using received state and sets initial highlighting for current
|
||||
* block. At the end, it saves internal state in current block.
|
||||
*/
|
||||
void PythonHighlighter::highlightBlock(const QString &text)
|
||||
{
|
||||
int initialState = previousBlockState();
|
||||
if (initialState == -1)
|
||||
initialState = 0;
|
||||
setCurrentBlockState(highlightLine(text, initialState));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if this keyword is acceptable at start of import line
|
||||
*/
|
||||
static bool isImportKeyword(const QString &keyword)
|
||||
{
|
||||
return keyword == "import" || keyword == "from";
|
||||
}
|
||||
|
||||
static int indent(const QString &line)
|
||||
{
|
||||
for (int i = 0, size = line.size(); i < size; ++i) {
|
||||
if (!line.at(i).isSpace())
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void setFoldingIndent(const QTextBlock &block, int indent)
|
||||
{
|
||||
if (TextEditor::TextBlockUserData *userData = TextEditor::TextDocumentLayout::userData(block)) {
|
||||
userData->setFoldingIndent(indent);
|
||||
userData->setFoldingStartIncluded(false);
|
||||
userData->setFoldingEndIncluded(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Highlight line of code, returns new block state
|
||||
* @param text Source code to highlight
|
||||
* @param initialState Initial state of scanner, retrieved from previous block
|
||||
* @return Final state of scanner, should be saved with current block
|
||||
*/
|
||||
int PythonHighlighter::highlightLine(const QString &text, int initialState)
|
||||
{
|
||||
Scanner scanner(text.constData(), text.size());
|
||||
scanner.setState(initialState);
|
||||
|
||||
const int pos = indent(text);
|
||||
if (pos < 0) {
|
||||
// Empty lines do not change folding indent
|
||||
setFoldingIndent(currentBlock(), m_lastIndent);
|
||||
} else {
|
||||
m_lastIndent = pos;
|
||||
if (pos == 0 && text.startsWith('#') && !text.startsWith("#!")) {
|
||||
// A comment block at indentation 0. Fold on first line.
|
||||
setFoldingIndent(currentBlock(), withinLicenseHeader ? 1 : 0);
|
||||
withinLicenseHeader = true;
|
||||
} else {
|
||||
// Normal Python code. Line indentation can be used as folding indent.
|
||||
setFoldingIndent(currentBlock(), m_lastIndent);
|
||||
withinLicenseHeader = false;
|
||||
}
|
||||
}
|
||||
|
||||
FormatToken tk;
|
||||
bool hasOnlyWhitespace = true;
|
||||
while (!(tk = scanner.read()).isEndOfBlock()) {
|
||||
Format format = tk.format();
|
||||
if (format == Format_Keyword && isImportKeyword(scanner.value(tk)) && hasOnlyWhitespace) {
|
||||
setFormat(tk.begin(), tk.length(), formatForCategory(format));
|
||||
highlightImport(scanner);
|
||||
} else if (format == Format_Comment
|
||||
|| format == Format_String
|
||||
|| format == Format_Doxygen) {
|
||||
setFormatWithSpaces(text, tk.begin(), tk.length(), formatForCategory(format));
|
||||
} else {
|
||||
setFormat(tk.begin(), tk.length(), formatForCategory(format));
|
||||
}
|
||||
if (format != Format_Whitespace)
|
||||
hasOnlyWhitespace = false;
|
||||
}
|
||||
return scanner.state();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Highlights rest of line as import directive
|
||||
*/
|
||||
void PythonHighlighter::highlightImport(Scanner &scanner)
|
||||
{
|
||||
FormatToken tk;
|
||||
while (!(tk = scanner.read()).isEndOfBlock()) {
|
||||
Format format = tk.format();
|
||||
if (tk.format() == Format_Identifier)
|
||||
format = Format_ImportedModule;
|
||||
setFormat(tk.begin(), tk.length(), formatForCategory(format));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Python
|
||||
50
src/plugins/python/pythonhighlighter.h
Normal file
50
src/plugins/python/pythonhighlighter.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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/syntaxhighlighter.h>
|
||||
|
||||
namespace Python {
|
||||
namespace Internal {
|
||||
|
||||
class Scanner;
|
||||
|
||||
class PythonHighlighter : public TextEditor::SyntaxHighlighter
|
||||
{
|
||||
public:
|
||||
PythonHighlighter();
|
||||
|
||||
private:
|
||||
void highlightBlock(const QString &text) override;
|
||||
int highlightLine(const QString &text, int initialState);
|
||||
void highlightImport(Internal::Scanner &scanner);
|
||||
|
||||
int m_lastIndent = 0;
|
||||
bool withinLicenseHeader = false;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Python
|
||||
127
src/plugins/python/pythonindenter.cpp
Normal file
127
src/plugins/python/pythonindenter.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 "pythonindenter.h"
|
||||
#include "pythonscanner.h"
|
||||
|
||||
#include <texteditor/tabsettings.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Python {
|
||||
|
||||
static bool isEmptyLine(const QString &t)
|
||||
{
|
||||
return std::all_of(t.cbegin(), t.cend(), [] (QChar c) { return c.isSpace(); });
|
||||
}
|
||||
|
||||
static inline bool isEmptyLine(const QTextBlock &block)
|
||||
{
|
||||
return isEmptyLine(block.text());
|
||||
}
|
||||
|
||||
static QTextBlock previousNonEmptyBlock(const QTextBlock &block)
|
||||
{
|
||||
QTextBlock result = block;
|
||||
while (result.isValid() && isEmptyLine(result))
|
||||
result = result.previous();
|
||||
return result;
|
||||
}
|
||||
|
||||
PythonIndenter::PythonIndenter(QTextDocument *doc)
|
||||
: TextEditor::TextIndenter(doc)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Does given character change indentation level?
|
||||
* @param ch Any value
|
||||
* @return True if character increases indentation level at the next line
|
||||
*/
|
||||
bool PythonIndenter::isElectricCharacter(const QChar &ch) const
|
||||
{
|
||||
return ch == ':';
|
||||
}
|
||||
|
||||
int PythonIndenter::indentFor(const QTextBlock &block,
|
||||
const TextEditor::TabSettings &tabSettings,
|
||||
int /*cursorPositionInEditor*/)
|
||||
{
|
||||
QTextBlock previousBlock = block.previous();
|
||||
if (!previousBlock.isValid())
|
||||
return 0;
|
||||
|
||||
// When pasting in actual code, try to skip back past empty lines to an
|
||||
// actual code line to find a suitable indentation. This prevents code from
|
||||
// not being indented when pasting below an empty line.
|
||||
if (!isEmptyLine(block)) {
|
||||
const QTextBlock previousNonEmpty = previousNonEmptyBlock(previousBlock);
|
||||
if (previousNonEmpty.isValid())
|
||||
previousBlock = previousNonEmpty;
|
||||
}
|
||||
|
||||
QString previousLine = previousBlock.text();
|
||||
int indentation = tabSettings.indentationColumn(previousLine);
|
||||
|
||||
if (isElectricLine(previousLine))
|
||||
indentation += tabSettings.m_indentSize;
|
||||
else
|
||||
indentation = qMax<int>(0, indentation + getIndentDiff(previousLine, tabSettings));
|
||||
|
||||
return indentation;
|
||||
}
|
||||
|
||||
/// @return True if electric character is last non-space character at given string
|
||||
bool PythonIndenter::isElectricLine(const QString &line) const
|
||||
{
|
||||
if (line.isEmpty())
|
||||
return false;
|
||||
|
||||
// trim spaces in 'if True: '
|
||||
int index = line.length() - 1;
|
||||
while (index > 0 && line[index].isSpace())
|
||||
--index;
|
||||
|
||||
return isElectricCharacter(line[index]);
|
||||
}
|
||||
|
||||
/// @return negative indent diff if previous line breaks control flow branch
|
||||
int PythonIndenter::getIndentDiff(const QString &previousLine,
|
||||
const TextEditor::TabSettings &tabSettings) const
|
||||
{
|
||||
static const QStringList jumpKeywords = {
|
||||
"return", "yield", "break", "continue", "raise", "pass" };
|
||||
|
||||
Internal::Scanner sc(previousLine.constData(), previousLine.length());
|
||||
forever {
|
||||
Internal::FormatToken tk = sc.read();
|
||||
if (tk.format() == Internal::Format_Keyword && jumpKeywords.contains(sc.value(tk)))
|
||||
return -tabSettings.m_indentSize;
|
||||
if (tk.format() != Internal::Format_Whitespace)
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Python
|
||||
47
src/plugins/python/pythonindenter.h
Normal file
47
src/plugins/python/pythonindenter.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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/textindenter.h>
|
||||
|
||||
namespace Python {
|
||||
|
||||
class PythonIndenter : public TextEditor::TextIndenter
|
||||
{
|
||||
public:
|
||||
explicit PythonIndenter(QTextDocument *doc);
|
||||
private:
|
||||
bool isElectricCharacter(const QChar &ch) const override;
|
||||
int indentFor(const QTextBlock &block,
|
||||
const TextEditor::TabSettings &tabSettings,
|
||||
int cursorPositionInEditor = -1) override;
|
||||
|
||||
bool isElectricLine(const QString &line) const;
|
||||
int getIndentDiff(const QString &previousLine,
|
||||
const TextEditor::TabSettings &tabSettings) const;
|
||||
};
|
||||
|
||||
} // namespace Python
|
||||
756
src/plugins/python/pythonplugin.cpp
Normal file
756
src/plugins/python/pythonplugin.cpp
Normal file
@@ -0,0 +1,756 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 "pythonplugin.h"
|
||||
#include "pythoneditor.h"
|
||||
#include "pythonconstants.h"
|
||||
#include "pythonhighlighter.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/coreconstants.h>
|
||||
#include <coreplugin/documentmanager.h>
|
||||
#include <coreplugin/fileiconprovider.h>
|
||||
#include <coreplugin/id.h>
|
||||
#include <coreplugin/messagemanager.h>
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
|
||||
#include <projectexplorer/buildtargetinfo.h>
|
||||
#include <projectexplorer/kitmanager.h>
|
||||
#include <projectexplorer/localenvironmentaspect.h>
|
||||
#include <projectexplorer/runcontrol.h>
|
||||
#include <projectexplorer/runconfiguration.h>
|
||||
#include <projectexplorer/runconfigurationaspects.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
#include <projectexplorer/projectnodes.h>
|
||||
#include <projectexplorer/target.h>
|
||||
#include <projectexplorer/task.h>
|
||||
#include <projectexplorer/taskhub.h>
|
||||
|
||||
#include <texteditor/texteditorconstants.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/outputformatter.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QRegExp>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
#include <QTextCursor>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonValue>
|
||||
#include <QJsonArray>
|
||||
|
||||
using namespace Core;
|
||||
using namespace ProjectExplorer;
|
||||
using namespace Python::Constants;
|
||||
using namespace Utils;
|
||||
|
||||
namespace Python {
|
||||
namespace Internal {
|
||||
|
||||
const char PythonMimeType[] = "text/x-python-project"; // ### FIXME
|
||||
const char PythonProjectId[] = "PythonProject";
|
||||
const char PythonErrorTaskCategory[] = "Task.Category.Python";
|
||||
|
||||
class PythonProject : public Project
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PythonProject(const Utils::FilePath &filename);
|
||||
|
||||
bool addFiles(const QStringList &filePaths);
|
||||
bool removeFiles(const QStringList &filePaths);
|
||||
bool setFiles(const QStringList &filePaths);
|
||||
bool renameFile(const QString &filePath, const QString &newFilePath);
|
||||
void refresh(Target *target = nullptr);
|
||||
|
||||
bool needsConfiguration() const final { return false; }
|
||||
bool needsBuildConfigurations() const final { return false; }
|
||||
|
||||
bool writePyProjectFile(const QString &fileName, QString &content,
|
||||
const QStringList &rawList, QString *errorMessage);
|
||||
|
||||
private:
|
||||
RestoreResult fromMap(const QVariantMap &map, QString *errorMessage) override;
|
||||
bool setupTarget(Target *t) override
|
||||
{
|
||||
refresh(t);
|
||||
return Project::setupTarget(t);
|
||||
}
|
||||
|
||||
bool saveRawFileList(const QStringList &rawFileList);
|
||||
bool saveRawList(const QStringList &rawList, const QString &fileName);
|
||||
void parseProject();
|
||||
QStringList processEntries(const QStringList &paths,
|
||||
QHash<QString, QString> *map = nullptr) const;
|
||||
|
||||
QStringList m_rawFileList;
|
||||
QStringList m_files;
|
||||
QHash<QString, QString> m_rawListEntries;
|
||||
};
|
||||
|
||||
class PythonProjectNode : public ProjectNode
|
||||
{
|
||||
public:
|
||||
PythonProjectNode(PythonProject *project);
|
||||
|
||||
bool supportsAction(ProjectAction action, const Node *node) const override;
|
||||
bool addFiles(const QStringList &filePaths, QStringList *) override;
|
||||
ProjectExplorer::RemovedFilesFromProject removeFiles(const QStringList &filePaths,
|
||||
QStringList *) override;
|
||||
bool deleteFiles(const QStringList &) override;
|
||||
bool renameFile(const QString &filePath, const QString &newFilePath) override;
|
||||
|
||||
private:
|
||||
PythonProject *m_project;
|
||||
};
|
||||
|
||||
static QTextCharFormat linkFormat(const QTextCharFormat &inputFormat, const QString &href)
|
||||
{
|
||||
QTextCharFormat result = inputFormat;
|
||||
result.setForeground(creatorTheme()->color(Theme::TextColorLink));
|
||||
result.setUnderlineStyle(QTextCharFormat::SingleUnderline);
|
||||
result.setAnchor(true);
|
||||
result.setAnchorHref(href);
|
||||
return result;
|
||||
}
|
||||
|
||||
class PythonOutputFormatter : public OutputFormatter
|
||||
{
|
||||
public:
|
||||
PythonOutputFormatter(Project *)
|
||||
// Note that moc dislikes raw string literals.
|
||||
: filePattern("^(\\s*)(File \"([^\"]+)\", line (\\d+), .*$)")
|
||||
{
|
||||
TaskHub::clearTasks(PythonErrorTaskCategory);
|
||||
}
|
||||
|
||||
private:
|
||||
void appendMessage(const QString &text, OutputFormat format) final
|
||||
{
|
||||
const bool isTrace = (format == StdErrFormat
|
||||
|| format == StdErrFormatSameLine)
|
||||
&& (text.startsWith("Traceback (most recent call last):")
|
||||
|| text.startsWith("\nTraceback (most recent call last):"));
|
||||
|
||||
if (!isTrace) {
|
||||
OutputFormatter::appendMessage(text, format);
|
||||
return;
|
||||
}
|
||||
|
||||
const QTextCharFormat frm = charFormat(format);
|
||||
const Core::Id id(PythonErrorTaskCategory);
|
||||
QVector<Task> tasks;
|
||||
const QStringList lines = text.split('\n');
|
||||
unsigned taskId = unsigned(lines.size());
|
||||
|
||||
for (const QString &line : lines) {
|
||||
const QRegularExpressionMatch match = filePattern.match(line);
|
||||
if (match.hasMatch()) {
|
||||
QTextCursor tc = plainTextEdit()->textCursor();
|
||||
tc.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
|
||||
tc.insertText('\n' + match.captured(1));
|
||||
tc.insertText(match.captured(2), linkFormat(frm, match.captured(2)));
|
||||
|
||||
const auto fileName = FilePath::fromString(match.captured(3));
|
||||
const int lineNumber = match.capturedRef(4).toInt();
|
||||
Task task(Task::Warning,
|
||||
QString(), fileName, lineNumber, id);
|
||||
task.taskId = --taskId;
|
||||
tasks.append(task);
|
||||
} else {
|
||||
if (!tasks.isEmpty()) {
|
||||
Task &task = tasks.back();
|
||||
if (!task.description.isEmpty())
|
||||
task.description += ' ';
|
||||
task.description += line.trimmed();
|
||||
}
|
||||
OutputFormatter::appendMessage('\n' + line, format);
|
||||
}
|
||||
}
|
||||
if (!tasks.isEmpty()) {
|
||||
tasks.back().type = Task::Error;
|
||||
for (auto rit = tasks.crbegin(), rend = tasks.crend(); rit != rend; ++rit)
|
||||
TaskHub::addTask(*rit);
|
||||
}
|
||||
}
|
||||
|
||||
void handleLink(const QString &href) final
|
||||
{
|
||||
const QRegularExpressionMatch match = filePattern.match(href);
|
||||
if (!match.hasMatch())
|
||||
return;
|
||||
const QString fileName = match.captured(3);
|
||||
const int lineNumber = match.capturedRef(4).toInt();
|
||||
Core::EditorManager::openEditorAt(fileName, lineNumber);
|
||||
}
|
||||
|
||||
const QRegularExpression filePattern;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
class InterpreterAspect : public BaseStringAspect
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
InterpreterAspect() = default;
|
||||
};
|
||||
|
||||
class MainScriptAspect : public BaseStringAspect
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MainScriptAspect() = default;
|
||||
};
|
||||
|
||||
class PythonRunConfiguration : public RunConfiguration
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(bool supportsDebugger READ supportsDebugger)
|
||||
Q_PROPERTY(QString interpreter READ interpreter)
|
||||
Q_PROPERTY(QString mainScript READ mainScript)
|
||||
Q_PROPERTY(QString arguments READ arguments)
|
||||
|
||||
public:
|
||||
PythonRunConfiguration(Target *target, Core::Id id);
|
||||
|
||||
private:
|
||||
void doAdditionalSetup(const RunConfigurationCreationInfo &) final { updateTargetInformation(); }
|
||||
|
||||
bool supportsDebugger() const { return true; }
|
||||
QString mainScript() const { return aspect<MainScriptAspect>()->value(); }
|
||||
QString arguments() const { return aspect<ArgumentsAspect>()->arguments(macroExpander()); }
|
||||
QString interpreter() const { return aspect<InterpreterAspect>()->value(); }
|
||||
|
||||
void updateTargetInformation();
|
||||
};
|
||||
|
||||
PythonRunConfiguration::PythonRunConfiguration(Target *target, Core::Id id)
|
||||
: RunConfiguration(target, id)
|
||||
{
|
||||
const Environment sysEnv = Environment::systemEnvironment();
|
||||
const QString exec = sysEnv.searchInPath("python").toString();
|
||||
|
||||
auto interpreterAspect = addAspect<InterpreterAspect>();
|
||||
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter");
|
||||
interpreterAspect->setLabelText(tr("Interpreter:"));
|
||||
interpreterAspect->setDisplayStyle(BaseStringAspect::PathChooserDisplay);
|
||||
interpreterAspect->setHistoryCompleter("PythonEditor.Interpreter.History");
|
||||
interpreterAspect->setValue(exec.isEmpty() ? "python" : exec);
|
||||
|
||||
auto scriptAspect = addAspect<MainScriptAspect>();
|
||||
scriptAspect->setSettingsKey("PythonEditor.RunConfiguation.Script");
|
||||
scriptAspect->setLabelText(tr("Script:"));
|
||||
scriptAspect->setDisplayStyle(BaseStringAspect::LabelDisplay);
|
||||
|
||||
addAspect<LocalEnvironmentAspect>(target);
|
||||
|
||||
auto argumentsAspect = addAspect<ArgumentsAspect>();
|
||||
|
||||
addAspect<TerminalAspect>();
|
||||
|
||||
setOutputFormatter<PythonOutputFormatter>();
|
||||
setCommandLineGetter([this, interpreterAspect, argumentsAspect] {
|
||||
CommandLine cmd{FilePath::fromString(interpreterAspect->value()), {mainScript()}};
|
||||
cmd.addArgs(argumentsAspect->arguments(macroExpander()), CommandLine::Raw);
|
||||
return cmd;
|
||||
});
|
||||
|
||||
connect(target, &Target::applicationTargetsChanged,
|
||||
this, &PythonRunConfiguration::updateTargetInformation);
|
||||
connect(target->project(), &Project::parsingFinished,
|
||||
this, &PythonRunConfiguration::updateTargetInformation);
|
||||
}
|
||||
|
||||
void PythonRunConfiguration::updateTargetInformation()
|
||||
{
|
||||
const BuildTargetInfo bti = buildTargetInfo();
|
||||
const QString script = bti.targetFilePath.toString();
|
||||
setDefaultDisplayName(tr("Run %1").arg(script));
|
||||
aspect<MainScriptAspect>()->setValue(script);
|
||||
}
|
||||
|
||||
class PythonRunConfigurationFactory : public RunConfigurationFactory
|
||||
{
|
||||
public:
|
||||
PythonRunConfigurationFactory()
|
||||
{
|
||||
registerRunConfiguration<PythonRunConfiguration>("PythonEditor.RunConfiguration.");
|
||||
addSupportedProjectType(PythonProjectId);
|
||||
}
|
||||
};
|
||||
|
||||
PythonProject::PythonProject(const FilePath &fileName) :
|
||||
Project(Constants::C_PY_MIMETYPE, fileName, [this]() { refresh(); })
|
||||
{
|
||||
setId(PythonProjectId);
|
||||
setProjectLanguages(Context(ProjectExplorer::Constants::CXX_LANGUAGE_ID));
|
||||
setDisplayName(fileName.toFileInfo().completeBaseName());
|
||||
}
|
||||
|
||||
static QStringList readLines(const Utils::FilePath &projectFile)
|
||||
{
|
||||
const QString projectFileName = projectFile.fileName();
|
||||
QSet<QString> visited = { projectFileName };
|
||||
QStringList lines = { projectFileName };
|
||||
|
||||
QFile file(projectFile.toString());
|
||||
if (file.open(QFile::ReadOnly)) {
|
||||
QTextStream stream(&file);
|
||||
|
||||
while (true) {
|
||||
const QString line = stream.readLine();
|
||||
if (line.isNull())
|
||||
break;
|
||||
if (visited.contains(line))
|
||||
continue;
|
||||
lines.append(line);
|
||||
visited.insert(line);
|
||||
}
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
static QStringList readLinesJson(const Utils::FilePath &projectFile,
|
||||
QString *errorMessage)
|
||||
{
|
||||
const QString projectFileName = projectFile.fileName();
|
||||
QStringList lines = { projectFileName };
|
||||
|
||||
QFile file(projectFile.toString());
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
*errorMessage = PythonProject::tr("Unable to open \"%1\" for reading: %2")
|
||||
.arg(projectFile.toUserOutput(), file.errorString());
|
||||
return lines;
|
||||
}
|
||||
|
||||
const QByteArray content = file.readAll();
|
||||
|
||||
// This assumes te project file is formed with only one field called
|
||||
// 'files' that has a list associated of the files to include in the project.
|
||||
if (content.isEmpty()) {
|
||||
*errorMessage = PythonProject::tr("Unable to read \"%1\": The file is empty.")
|
||||
.arg(projectFile.toUserOutput());
|
||||
return lines;
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(content, &error);
|
||||
if (doc.isNull()) {
|
||||
const int line = content.left(error.offset).count('\n') + 1;
|
||||
*errorMessage = PythonProject::tr("Unable to parse \"%1\":%2: %3")
|
||||
.arg(projectFile.toUserOutput()).arg(line)
|
||||
.arg(error.errorString());
|
||||
return lines;
|
||||
}
|
||||
|
||||
const QJsonObject obj = doc.object();
|
||||
if (obj.contains("files")) {
|
||||
const QJsonValue files = obj.value("files");
|
||||
const QJsonArray files_array = files.toArray();
|
||||
QSet<QString> visited;
|
||||
for (const auto &file : files_array)
|
||||
visited.insert(file.toString());
|
||||
|
||||
lines.append(Utils::toList(visited));
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
bool PythonProject::saveRawFileList(const QStringList &rawFileList)
|
||||
{
|
||||
const bool result = saveRawList(rawFileList, projectFilePath().toString());
|
||||
// refresh(PythonProject::Files);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool PythonProject::saveRawList(const QStringList &rawList, const QString &fileName)
|
||||
{
|
||||
FileChangeBlocker changeGuarg(fileName);
|
||||
bool result = false;
|
||||
|
||||
// New project file
|
||||
if (fileName.endsWith(".pyproject")) {
|
||||
FileSaver saver(fileName, QIODevice::ReadOnly | QIODevice::Text);
|
||||
if (!saver.hasError()) {
|
||||
QString content = QTextStream(saver.file()).readAll();
|
||||
if (saver.finalize(ICore::mainWindow())) {
|
||||
QString errorMessage;
|
||||
result = writePyProjectFile(fileName, content, rawList, &errorMessage);
|
||||
if (!errorMessage.isEmpty())
|
||||
Core::MessageManager::write(errorMessage);
|
||||
}
|
||||
}
|
||||
} else { // Old project file
|
||||
FileSaver saver(fileName, QIODevice::WriteOnly | QIODevice::Text);
|
||||
if (!saver.hasError()) {
|
||||
QTextStream stream(saver.file());
|
||||
for (const QString &filePath : rawList)
|
||||
stream << filePath << '\n';
|
||||
saver.setResult(&stream);
|
||||
result = saver.finalize(ICore::mainWindow());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool PythonProject::writePyProjectFile(const QString &fileName, QString &content,
|
||||
const QStringList &rawList, QString *errorMessage)
|
||||
{
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
*errorMessage = PythonProject::tr("Unable to open \"%1\" for reading: %2")
|
||||
.arg(fileName, file.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build list of files with the current rawList for the JSON file
|
||||
QString files("[");
|
||||
for (const QString &f : rawList)
|
||||
if (!f.endsWith(".pyproject"))
|
||||
files += QString("\"%1\",").arg(f);
|
||||
files = files.left(files.lastIndexOf(',')); // Removing leading comma
|
||||
files += ']';
|
||||
|
||||
// Removing everything inside square parenthesis
|
||||
// to replace it with the new list of files for the JSON file.
|
||||
QRegularExpression pattern(R"(\[.*\])");
|
||||
content.replace(pattern, files);
|
||||
file.write(content.toUtf8());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PythonProject::addFiles(const QStringList &filePaths)
|
||||
{
|
||||
QStringList newList = m_rawFileList;
|
||||
|
||||
const QDir baseDir(projectDirectory().toString());
|
||||
for (const QString &filePath : filePaths)
|
||||
newList.append(baseDir.relativeFilePath(filePath));
|
||||
|
||||
return saveRawFileList(newList);
|
||||
}
|
||||
|
||||
bool PythonProject::removeFiles(const QStringList &filePaths)
|
||||
{
|
||||
QStringList newList = m_rawFileList;
|
||||
|
||||
for (const QString &filePath : filePaths) {
|
||||
const QHash<QString, QString>::iterator i = m_rawListEntries.find(filePath);
|
||||
if (i != m_rawListEntries.end())
|
||||
newList.removeOne(i.value());
|
||||
}
|
||||
|
||||
return saveRawFileList(newList);
|
||||
}
|
||||
|
||||
bool PythonProject::setFiles(const QStringList &filePaths)
|
||||
{
|
||||
QStringList newList;
|
||||
const QDir baseDir(projectDirectory().toString());
|
||||
for (const QString &filePath : filePaths)
|
||||
newList.append(baseDir.relativeFilePath(filePath));
|
||||
|
||||
return saveRawFileList(newList);
|
||||
}
|
||||
|
||||
bool PythonProject::renameFile(const QString &filePath, const QString &newFilePath)
|
||||
{
|
||||
QStringList newList = m_rawFileList;
|
||||
|
||||
const QHash<QString, QString>::iterator i = m_rawListEntries.find(filePath);
|
||||
if (i != m_rawListEntries.end()) {
|
||||
const int index = newList.indexOf(i.value());
|
||||
if (index != -1) {
|
||||
const QDir baseDir(projectDirectory().toString());
|
||||
newList.replace(index, baseDir.relativeFilePath(newFilePath));
|
||||
}
|
||||
}
|
||||
|
||||
return saveRawFileList(newList);
|
||||
}
|
||||
|
||||
void PythonProject::parseProject()
|
||||
{
|
||||
m_rawListEntries.clear();
|
||||
const Utils::FilePath filePath = projectFilePath();
|
||||
// The PySide project file is JSON based
|
||||
if (filePath.endsWith(".pyproject")) {
|
||||
QString errorMessage;
|
||||
m_rawFileList = readLinesJson(filePath, &errorMessage);
|
||||
if (!errorMessage.isEmpty())
|
||||
Core::MessageManager::write(errorMessage);
|
||||
}
|
||||
// To keep compatibility with PyQt we keep the compatibility with plain
|
||||
// text files as project files.
|
||||
else if (filePath.endsWith(".pyqtc"))
|
||||
m_rawFileList = readLines(filePath);
|
||||
|
||||
m_files = processEntries(m_rawFileList, &m_rawListEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Provides displayName relative to project node
|
||||
*/
|
||||
class PythonFileNode : public FileNode
|
||||
{
|
||||
public:
|
||||
PythonFileNode(const Utils::FilePath &filePath, const QString &nodeDisplayName,
|
||||
FileType fileType = FileType::Source)
|
||||
: FileNode(filePath, fileType)
|
||||
, m_displayName(nodeDisplayName)
|
||||
{}
|
||||
|
||||
QString displayName() const override { return m_displayName; }
|
||||
private:
|
||||
QString m_displayName;
|
||||
};
|
||||
|
||||
void PythonProject::refresh(Target *target)
|
||||
{
|
||||
emitParsingStarted();
|
||||
parseProject();
|
||||
|
||||
const QDir baseDir(projectDirectory().toString());
|
||||
QList<BuildTargetInfo> appTargets;
|
||||
auto newRoot = std::make_unique<PythonProjectNode>(this);
|
||||
for (const QString &f : qAsConst(m_files)) {
|
||||
const QString displayName = baseDir.relativeFilePath(f);
|
||||
const FileType fileType = f.endsWith(".pyproject") || f.endsWith(".pyqtc") ? FileType::Project
|
||||
: FileType::Source;
|
||||
newRoot->addNestedNode(std::make_unique<PythonFileNode>(FilePath::fromString(f),
|
||||
displayName, fileType));
|
||||
if (fileType == FileType::Source) {
|
||||
BuildTargetInfo bti;
|
||||
bti.buildKey = f;
|
||||
bti.targetFilePath = FilePath::fromString(f);
|
||||
bti.projectFilePath = projectFilePath();
|
||||
appTargets.append(bti);
|
||||
}
|
||||
}
|
||||
setRootProjectNode(std::move(newRoot));
|
||||
|
||||
if (!target)
|
||||
target = activeTarget();
|
||||
if (target)
|
||||
target->setApplicationTargets(appTargets);
|
||||
|
||||
emitParsingFinished(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands environment variables in the given \a string when they are written
|
||||
* like $$(VARIABLE).
|
||||
*/
|
||||
static void expandEnvironmentVariables(const QProcessEnvironment &env, QString &string)
|
||||
{
|
||||
static QRegExp candidate(QLatin1String("\\$\\$\\((.+)\\)"));
|
||||
|
||||
int index = candidate.indexIn(string);
|
||||
while (index != -1) {
|
||||
const QString value = env.value(candidate.cap(1));
|
||||
|
||||
string.replace(index, candidate.matchedLength(), value);
|
||||
index += value.length();
|
||||
|
||||
index = candidate.indexIn(string, index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands environment variables and converts the path from relative to the
|
||||
* project to an absolute path.
|
||||
*
|
||||
* The \a map variable is an optional argument that will map the returned
|
||||
* absolute paths back to their original \a entries.
|
||||
*/
|
||||
QStringList PythonProject::processEntries(const QStringList &paths,
|
||||
QHash<QString, QString> *map) const
|
||||
{
|
||||
const QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
const QDir projectDir(projectDirectory().toString());
|
||||
|
||||
QFileInfo fileInfo;
|
||||
QStringList absolutePaths;
|
||||
for (const QString &path : paths) {
|
||||
QString trimmedPath = path.trimmed();
|
||||
if (trimmedPath.isEmpty())
|
||||
continue;
|
||||
|
||||
expandEnvironmentVariables(env, trimmedPath);
|
||||
|
||||
trimmedPath = FilePath::fromUserInput(trimmedPath).toString();
|
||||
|
||||
fileInfo.setFile(projectDir, trimmedPath);
|
||||
if (fileInfo.exists()) {
|
||||
const QString absPath = fileInfo.absoluteFilePath();
|
||||
absolutePaths.append(absPath);
|
||||
if (map)
|
||||
map->insert(absPath, trimmedPath);
|
||||
}
|
||||
}
|
||||
absolutePaths.removeDuplicates();
|
||||
return absolutePaths;
|
||||
}
|
||||
|
||||
Project::RestoreResult PythonProject::fromMap(const QVariantMap &map, QString *errorMessage)
|
||||
{
|
||||
Project::RestoreResult res = Project::fromMap(map, errorMessage);
|
||||
if (res == RestoreResult::Ok) {
|
||||
refresh();
|
||||
|
||||
Kit *defaultKit = KitManager::defaultKit();
|
||||
if (!activeTarget() && defaultKit)
|
||||
addTarget(createTarget(defaultKit));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
PythonProjectNode::PythonProjectNode(PythonProject *project)
|
||||
: ProjectNode(project->projectDirectory())
|
||||
, m_project(project)
|
||||
{
|
||||
setDisplayName(project->projectFilePath().toFileInfo().completeBaseName());
|
||||
setAddFileFilter("*.py");
|
||||
}
|
||||
|
||||
QHash<QString, QStringList> sortFilesIntoPaths(const QString &base, const QSet<QString> &files)
|
||||
{
|
||||
QHash<QString, QStringList> filesInPath;
|
||||
const QDir baseDir(base);
|
||||
|
||||
for (const QString &absoluteFileName : files) {
|
||||
const QFileInfo fileInfo(absoluteFileName);
|
||||
const FilePath absoluteFilePath = FilePath::fromString(fileInfo.path());
|
||||
QString relativeFilePath;
|
||||
|
||||
if (absoluteFilePath.isChildOf(baseDir)) {
|
||||
relativeFilePath = absoluteFilePath.relativeChildPath(FilePath::fromString(base)).toString();
|
||||
} else {
|
||||
// 'file' is not part of the project.
|
||||
relativeFilePath = baseDir.relativeFilePath(absoluteFilePath.toString());
|
||||
if (relativeFilePath.endsWith('/'))
|
||||
relativeFilePath.chop(1);
|
||||
}
|
||||
|
||||
filesInPath[relativeFilePath].append(absoluteFileName);
|
||||
}
|
||||
return filesInPath;
|
||||
}
|
||||
|
||||
bool PythonProjectNode::supportsAction(ProjectAction action, const Node *node) const
|
||||
{
|
||||
if (node->asFileNode()) {
|
||||
return action == ProjectAction::Rename
|
||||
|| action == ProjectAction::RemoveFile;
|
||||
}
|
||||
if (node->isFolderNodeType() || node->isProjectNodeType()) {
|
||||
return action == ProjectAction::AddNewFile
|
||||
|| action == ProjectAction::RemoveFile
|
||||
|| action == ProjectAction::AddExistingFile;
|
||||
}
|
||||
return ProjectNode::supportsAction(action, node);
|
||||
}
|
||||
|
||||
bool PythonProjectNode::addFiles(const QStringList &filePaths, QStringList *)
|
||||
{
|
||||
return m_project->addFiles(filePaths);
|
||||
}
|
||||
|
||||
RemovedFilesFromProject PythonProjectNode::removeFiles(const QStringList &filePaths, QStringList *)
|
||||
{
|
||||
return m_project->removeFiles(filePaths) ? RemovedFilesFromProject::Ok
|
||||
: RemovedFilesFromProject::Error;
|
||||
}
|
||||
|
||||
bool PythonProjectNode::deleteFiles(const QStringList &)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PythonProjectNode::renameFile(const QString &filePath, const QString &newFilePath)
|
||||
{
|
||||
return m_project->renameFile(filePath, newFilePath);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PythonPlugin
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class PythonPluginPrivate
|
||||
{
|
||||
public:
|
||||
PythonEditorFactory editorFactory;
|
||||
PythonRunConfigurationFactory runConfigFactory;
|
||||
SimpleRunWorkerFactory<SimpleTargetRunner, PythonRunConfiguration> runWorkerFactory;
|
||||
};
|
||||
|
||||
PythonPlugin::~PythonPlugin()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
bool PythonPlugin::initialize(const QStringList &arguments, QString *errorMessage)
|
||||
{
|
||||
Q_UNUSED(arguments)
|
||||
Q_UNUSED(errorMessage)
|
||||
|
||||
d = new PythonPluginPrivate;
|
||||
|
||||
ProjectManager::registerProjectType<PythonProject>(PythonMimeType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PythonPlugin::extensionsInitialized()
|
||||
{
|
||||
// Add MIME overlay icons (these icons displayed at Project dock panel)
|
||||
QString imageFile = creatorTheme()->imageFile(Theme::IconOverlayPro,
|
||||
ProjectExplorer::Constants::FILEOVERLAY_PY);
|
||||
FileIconProvider::registerIconOverlayForSuffix(imageFile, "py");
|
||||
|
||||
TaskHub::addCategory(PythonErrorTaskCategory, "Python", true);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Python
|
||||
|
||||
#include "pythonplugin.moc"
|
||||
50
src/plugins/python/pythonplugin.h
Normal file
50
src/plugins/python/pythonplugin.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 <extensionsystem/iplugin.h>
|
||||
|
||||
namespace Python {
|
||||
namespace Internal {
|
||||
|
||||
class PythonPlugin : public ExtensionSystem::IPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Python.json")
|
||||
|
||||
public:
|
||||
PythonPlugin() = default;
|
||||
~PythonPlugin() final;
|
||||
|
||||
private:
|
||||
bool initialize(const QStringList &arguments, QString *errorMessage) final;
|
||||
void extensionsInitialized() final;
|
||||
|
||||
class PythonPluginPrivate *d = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Python
|
||||
392
src/plugins/python/pythonscanner.cpp
Normal file
392
src/plugins/python/pythonscanner.cpp
Normal file
@@ -0,0 +1,392 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 "pythonscanner.h"
|
||||
|
||||
#include "pythonconstants.h"
|
||||
#include "pythonplugin.h"
|
||||
|
||||
namespace Python {
|
||||
namespace Internal {
|
||||
|
||||
Scanner::Scanner(const QChar *text, const int length)
|
||||
: m_text(text), m_textLength(length), m_state(0)
|
||||
{
|
||||
}
|
||||
|
||||
void Scanner::setState(int state)
|
||||
{
|
||||
m_state = state;
|
||||
}
|
||||
|
||||
int Scanner::state() const
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
|
||||
FormatToken Scanner::read()
|
||||
{
|
||||
setAnchor();
|
||||
if (isEnd())
|
||||
return FormatToken();
|
||||
|
||||
State state;
|
||||
QChar saved;
|
||||
parseState(state, saved);
|
||||
switch (state) {
|
||||
case State_String:
|
||||
return readStringLiteral(saved);
|
||||
case State_MultiLineString:
|
||||
return readMultiLineStringLiteral(saved);
|
||||
default:
|
||||
return onDefaultState();
|
||||
}
|
||||
}
|
||||
|
||||
QString Scanner::value(const FormatToken &tk) const
|
||||
{
|
||||
return QString(m_text + tk.begin(), tk.length());
|
||||
}
|
||||
|
||||
FormatToken Scanner::onDefaultState()
|
||||
{
|
||||
QChar first = peek();
|
||||
move();
|
||||
|
||||
if (first == '\\' && peek() == '\n') {
|
||||
move();
|
||||
return FormatToken(Format_Whitespace, anchor(), 2);
|
||||
}
|
||||
|
||||
if (first == '.' && peek().isDigit())
|
||||
return readFloatNumber();
|
||||
|
||||
if (first == '\'' || first == '\"')
|
||||
return readStringLiteral(first);
|
||||
|
||||
if (first.isLetter() || first == '_')
|
||||
return readIdentifier();
|
||||
|
||||
if (first.isDigit())
|
||||
return readNumber();
|
||||
|
||||
if (first == '#') {
|
||||
if (peek() == '#')
|
||||
return readDoxygenComment();
|
||||
return readComment();
|
||||
}
|
||||
|
||||
if (first.isSpace())
|
||||
return readWhiteSpace();
|
||||
|
||||
return readOperator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Lexer::passEscapeCharacter
|
||||
* @return returns true if escape sequence doesn't end with newline
|
||||
*/
|
||||
void Scanner::checkEscapeSequence(QChar quoteChar)
|
||||
{
|
||||
if (peek() == '\\') {
|
||||
move();
|
||||
QChar ch = peek();
|
||||
if (ch == '\n' || ch.isNull())
|
||||
saveState(State_String, quoteChar);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
reads single-line string literal, surrounded by ' or " quotes
|
||||
*/
|
||||
FormatToken Scanner::readStringLiteral(QChar quoteChar)
|
||||
{
|
||||
QChar ch = peek();
|
||||
if (ch == quoteChar && peek(1) == quoteChar) {
|
||||
saveState(State_MultiLineString, quoteChar);
|
||||
return readMultiLineStringLiteral(quoteChar);
|
||||
}
|
||||
|
||||
while (ch != quoteChar && !ch.isNull()) {
|
||||
checkEscapeSequence(quoteChar);
|
||||
move();
|
||||
ch = peek();
|
||||
}
|
||||
if (ch == quoteChar)
|
||||
clearState();
|
||||
move();
|
||||
return FormatToken(Format_String, anchor(), length());
|
||||
}
|
||||
|
||||
/**
|
||||
reads multi-line string literal, surrounded by ''' or """ sequences
|
||||
*/
|
||||
FormatToken Scanner::readMultiLineStringLiteral(QChar quoteChar)
|
||||
{
|
||||
for (;;) {
|
||||
QChar ch = peek();
|
||||
if (ch.isNull())
|
||||
break;
|
||||
if (ch == quoteChar && peek(1) == quoteChar && peek(2) == quoteChar) {
|
||||
clearState();
|
||||
move();
|
||||
move();
|
||||
move();
|
||||
break;
|
||||
}
|
||||
move();
|
||||
}
|
||||
|
||||
return FormatToken(Format_String, anchor(), length());
|
||||
}
|
||||
|
||||
/**
|
||||
reads identifier and classifies it
|
||||
*/
|
||||
FormatToken Scanner::readIdentifier()
|
||||
{
|
||||
static const QSet<QString> keywords = {
|
||||
"and", "as", "assert", "break", "class", "continue", "def", "del", "elif",
|
||||
"else", "except", "exec", "finally", "for", "from", "global", "if", "import",
|
||||
"in", "is", "lambda", "not", "or", "pass", "print", "raise", "return", "try",
|
||||
"while", "with", "yield"
|
||||
};
|
||||
|
||||
// List of Python magic methods and attributes
|
||||
static const QSet<QString> magics = {
|
||||
// ctor & dtor
|
||||
"__init__", "__del__",
|
||||
// string conversion functions
|
||||
"__str__", "__repr__", "__unicode__",
|
||||
// attribute access functions
|
||||
"__setattr__", "__getattr__", "__delattr__",
|
||||
// binary operators
|
||||
"__add__", "__sub__", "__mul__", "__truediv__", "__floordiv__", "__mod__",
|
||||
"__pow__", "__and__", "__or__", "__xor__", "__eq__", "__ne__", "__gt__",
|
||||
"__lt__", "__ge__", "__le__", "__lshift__", "__rshift__", "__contains__",
|
||||
// unary operators
|
||||
"__pos__", "__neg__", "__inv__", "__abs__", "__len__",
|
||||
// item operators like []
|
||||
"__getitem__", "__setitem__", "__delitem__", "__getslice__", "__setslice__",
|
||||
"__delslice__",
|
||||
// other functions
|
||||
"__cmp__", "__hash__", "__nonzero__", "__call__", "__iter__", "__reversed__",
|
||||
"__divmod__", "__int__", "__long__", "__float__", "__complex__", "__hex__",
|
||||
"__oct__", "__index__", "__copy__", "__deepcopy__", "__sizeof__", "__trunc__",
|
||||
"__format__",
|
||||
// magic attributes
|
||||
"__name__", "__module__", "__dict__", "__bases__", "__doc__"
|
||||
};
|
||||
|
||||
// List of python built-in functions and objects
|
||||
static const QSet<QString> builtins = {
|
||||
"range", "xrange", "int", "float", "long", "hex", "oct", "chr", "ord",
|
||||
"len", "abs", "None", "True", "False"
|
||||
};
|
||||
|
||||
QChar ch = peek();
|
||||
while (ch.isLetterOrNumber() || ch == '_') {
|
||||
move();
|
||||
ch = peek();
|
||||
}
|
||||
|
||||
const QString v = QString(m_text + m_markedPosition, length());
|
||||
Format tkFormat = Format_Identifier;
|
||||
if (v == "self")
|
||||
tkFormat = Format_ClassField;
|
||||
else if (builtins.contains(v))
|
||||
tkFormat = Format_Type;
|
||||
else if (magics.contains(v))
|
||||
tkFormat = Format_MagicAttr;
|
||||
else if (keywords.contains(v))
|
||||
tkFormat = Format_Keyword;
|
||||
|
||||
return FormatToken(tkFormat, anchor(), length());
|
||||
}
|
||||
|
||||
inline static bool isHexDigit(QChar ch)
|
||||
{
|
||||
return ch.isDigit()
|
||||
|| (ch >= 'a' && ch <= 'f')
|
||||
|| (ch >= 'A' && ch <= 'F');
|
||||
}
|
||||
|
||||
inline static bool isOctalDigit(QChar ch)
|
||||
{
|
||||
return ch.isDigit() && ch != '8' && ch != '9';
|
||||
}
|
||||
|
||||
inline static bool isBinaryDigit(QChar ch)
|
||||
{
|
||||
return ch == '0' || ch == '1';
|
||||
}
|
||||
|
||||
inline static bool isValidIntegerSuffix(QChar ch)
|
||||
{
|
||||
return ch == 'l' || ch == 'L';
|
||||
}
|
||||
|
||||
FormatToken Scanner::readNumber()
|
||||
{
|
||||
if (!isEnd()) {
|
||||
QChar ch = peek();
|
||||
if (ch.toLower() == 'b') {
|
||||
move();
|
||||
while (isBinaryDigit(peek()))
|
||||
move();
|
||||
} else if (ch.toLower() == 'o') {
|
||||
move();
|
||||
while (isOctalDigit(peek()))
|
||||
move();
|
||||
} else if (ch.toLower() == 'x') {
|
||||
move();
|
||||
while (isHexDigit(peek()))
|
||||
move();
|
||||
} else { // either integer or float number
|
||||
return readFloatNumber();
|
||||
}
|
||||
if (isValidIntegerSuffix(peek()))
|
||||
move();
|
||||
}
|
||||
return FormatToken(Format_Number, anchor(), length());
|
||||
}
|
||||
|
||||
FormatToken Scanner::readFloatNumber()
|
||||
{
|
||||
enum
|
||||
{
|
||||
State_INTEGER,
|
||||
State_FRACTION,
|
||||
State_EXPONENT
|
||||
} state;
|
||||
state = (peek(-1) == '.') ? State_FRACTION : State_INTEGER;
|
||||
|
||||
for (;;) {
|
||||
QChar ch = peek();
|
||||
if (ch.isNull())
|
||||
break;
|
||||
|
||||
if (state == State_INTEGER) {
|
||||
if (ch == '.')
|
||||
state = State_FRACTION;
|
||||
else if (!ch.isDigit())
|
||||
break;
|
||||
} else if (state == State_FRACTION) {
|
||||
if (ch == 'e' || ch == 'E') {
|
||||
QChar next = peek(1);
|
||||
QChar next2 = peek(2);
|
||||
bool isExp = next.isDigit()
|
||||
|| ((next == '-' || next == '+') && next2.isDigit());
|
||||
if (isExp) {
|
||||
move();
|
||||
state = State_EXPONENT;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else if (!ch.isDigit()) {
|
||||
break;
|
||||
}
|
||||
} else if (!ch.isDigit()) {
|
||||
break;
|
||||
}
|
||||
move();
|
||||
}
|
||||
|
||||
QChar ch = peek();
|
||||
if ((state == State_INTEGER && (ch == 'l' || ch == 'L'))
|
||||
|| (ch == 'j' || ch =='J'))
|
||||
move();
|
||||
|
||||
return FormatToken(Format_Number, anchor(), length());
|
||||
}
|
||||
|
||||
/**
|
||||
reads single-line python comment, started with "#"
|
||||
*/
|
||||
FormatToken Scanner::readComment()
|
||||
{
|
||||
QChar ch = peek();
|
||||
while (ch != '\n' && !ch.isNull()) {
|
||||
move();
|
||||
ch = peek();
|
||||
}
|
||||
return FormatToken(Format_Comment, anchor(), length());
|
||||
}
|
||||
|
||||
/**
|
||||
reads single-line python doxygen comment, started with "##"
|
||||
*/
|
||||
FormatToken Scanner::readDoxygenComment()
|
||||
{
|
||||
QChar ch = peek();
|
||||
while (ch != '\n' && !ch.isNull()) {
|
||||
move();
|
||||
ch = peek();
|
||||
}
|
||||
return FormatToken(Format_Doxygen, anchor(), length());
|
||||
}
|
||||
|
||||
/**
|
||||
reads whitespace
|
||||
*/
|
||||
FormatToken Scanner::readWhiteSpace()
|
||||
{
|
||||
while (peek().isSpace())
|
||||
move();
|
||||
return FormatToken(Format_Whitespace, anchor(), length());
|
||||
}
|
||||
|
||||
/**
|
||||
reads punctuation symbols, excluding some special
|
||||
*/
|
||||
FormatToken Scanner::readOperator()
|
||||
{
|
||||
static const QString EXCLUDED_CHARS = "\'\"_#";
|
||||
QChar ch = peek();
|
||||
while (ch.isPunct() && !EXCLUDED_CHARS.contains(ch)) {
|
||||
move();
|
||||
ch = peek();
|
||||
}
|
||||
return FormatToken(Format_Operator, anchor(), length());
|
||||
}
|
||||
|
||||
void Scanner::clearState()
|
||||
{
|
||||
m_state = 0;
|
||||
}
|
||||
|
||||
void Scanner::saveState(State state, QChar savedData)
|
||||
{
|
||||
m_state = (state << 16) | static_cast<int>(savedData.unicode());
|
||||
}
|
||||
|
||||
void Scanner::parseState(State &state, QChar &savedData) const
|
||||
{
|
||||
state = static_cast<State>(m_state >> 16);
|
||||
savedData = static_cast<ushort>(m_state);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Python
|
||||
98
src/plugins/python/pythonscanner.h
Normal file
98
src/plugins/python/pythonscanner.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 "pythonformattoken.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace Python {
|
||||
namespace Internal {
|
||||
|
||||
/**
|
||||
* @brief The Scanner class - scans source code for highlighting only
|
||||
*/
|
||||
class Scanner
|
||||
{
|
||||
public:
|
||||
Scanner(const Scanner &other) = delete;
|
||||
void operator=(const Scanner &other) = delete;
|
||||
|
||||
enum State {
|
||||
State_Default,
|
||||
State_String,
|
||||
State_MultiLineString
|
||||
};
|
||||
|
||||
Scanner(const QChar *text, const int length);
|
||||
|
||||
void setState(int state);
|
||||
int state() const;
|
||||
FormatToken read();
|
||||
QString value(const FormatToken& tk) const;
|
||||
|
||||
private:
|
||||
FormatToken onDefaultState();
|
||||
|
||||
void checkEscapeSequence(QChar quoteChar);
|
||||
FormatToken readStringLiteral(QChar quoteChar);
|
||||
FormatToken readMultiLineStringLiteral(QChar quoteChar);
|
||||
FormatToken readIdentifier();
|
||||
FormatToken readNumber();
|
||||
FormatToken readFloatNumber();
|
||||
FormatToken readComment();
|
||||
FormatToken readDoxygenComment();
|
||||
FormatToken readWhiteSpace();
|
||||
FormatToken readOperator();
|
||||
|
||||
void clearState();
|
||||
void saveState(State state, QChar savedData);
|
||||
void parseState(State &state, QChar &savedData) const;
|
||||
|
||||
void setAnchor() { m_markedPosition = m_position; }
|
||||
void move() { ++m_position; }
|
||||
int length() const { return m_position - m_markedPosition; }
|
||||
int anchor() const { return m_markedPosition; }
|
||||
bool isEnd() const { return m_position >= m_textLength; }
|
||||
|
||||
QChar peek(int offset = 0) const
|
||||
{
|
||||
int pos = m_position + offset;
|
||||
if (pos >= m_textLength)
|
||||
return QLatin1Char('\0');
|
||||
return m_text[pos];
|
||||
}
|
||||
|
||||
const QChar *m_text;
|
||||
const int m_textLength;
|
||||
int m_position = 0;
|
||||
int m_markedPosition = 0;
|
||||
|
||||
int m_state;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Python
|
||||
Reference in New Issue
Block a user