Pull to refresh

TODO Plugin для QtCreator

Reading time9 min
Views8.9K
Итак, уже давно уважаемый Евгений писал нам о хаках QtCreator, а также указывал в своем блоге документ с подробной инструкцией по созданию плагина. И вот на этих выходных, будучи на даче, оторванным от цивилизации и интернета, я решил попробовать написать свой плагин. Плагин достаточно прост, он выводит список все TODO, FIXME и т. д. комментариев в текущем открытом документе. Ниже я расскажу про то, как написан этот плагин, и вообще про написание плагинов для QtCreator.
TODO Plugin


Начало


Первое что потребуется для создания плагина, это скачать исходный код QtCreator, это можно сделать здесь. Теперь, нужно распаковать полученный архив и создать новый пустой проект в папке /src/plugins/.

Создаем плагин который ничего не делает


Для этого в проект нужно добавить новый класс, который будет основным классом плагина, он должен наследоваться от интерфейса ExtensionSystem::IPlugin.
Для этого необходимо в .pro файл добавить следующие строки:
include(../../qtcreatorplugin.pri)
include(../../plugins/coreplugin/coreplugin.pri)

Coreplugin нужен, потому что все пользовательские плагины зависят от него, он предоставляет основные интерфейсы для создания других плагинов.

Также потребуется создать xml-файл <имяплагина>.pluginspec следующего формата
<plugin name=«todo» version=«0.0.1» compatVersion=«2.0.0»>
  <vendor>vsorokin</vendor>
  <copyright>Your copyright</copyright>
  <license>Your license</license>
  <description>Plugin description</description>
  <url>Project homepage</url>
  <dependencyList>
    <dependency name=«Core» version=«2.0.0»/>
  </dependencyList>
</plugin>

Смысл полей понятен из их названия.

Итак, сам класс ничего не делающего плагина должен иметь такую структуру:
#include <extensionsystem/iplugin.h>

class TodoPlugin: public ExtensionSystem::IPlugin
{
    Q_OBJECT
public:
    TodoPlugin();
    ~TodoPlugin();
    void extensionsInitialized();
    bool initialize(const QStringList & arguments, QString * errorString);
    void shutdown();
};


* This source code was highlighted with Source Code Highlighter.

Главным здесь является метод initialize, который выполняет инициализацию плагина. Впрочем, в плагине, который ничего не делает, все методы пусты.

Создаем новый Output Pane


Для моего плагина потребовалась новая область вывода, по соседству с уже имеющимися, делается она очень просто, для этого достаточно создать новый класс наследуемый от интерфейса Core::IOutputPane, вот заголовок этого класса:
#include <coreplugin/ioutputpane.h>
#include <QObject>
#include <QListWidget>

class TodoOutputPane: public Core::IOutputPane
{
public:
    TodoOutputPane(QObject *parent);
    ~TodoOutputPane();

    QWidget *outputWidget(QWidget *parent); // Возвращает основной виджет класса, здесь это QListWidget
    QList<QWidget*> toolBarWidgets() const; // Возвращает список виджетов тулбара, в этом плагине их нет
    QString name() const; // Просто текстовое название панели

    int priorityInStatusBar() const; // Возвращает число от одного до ста, чем больше число тем более верхнее положение займет наш pane

    void clearContents(); // Выполняет очистку основного виджета
    void visibilityChanged(bool visible);

    void setFocus();
    bool hasFocus();
    bool canFocus();

    bool canNavigate();
    bool canNext();
    bool canPrevious();
    void goToNext();
    void goToPrev();

    void addItem(QString text, QString file, int rowNumber);// Метод, который вставляет новую запись в список
    QListWidget *getTodoList() const; // Указатель на список

private:
    QListWidget *todoList;
};


* This source code was highlighted with Source Code Highlighter.


Теперь надо как-то зарегистрировать наш свежесозданный todoPane в системе, для этого в методе initialize пишем такой код:
    outPane = new TodoOutputPane(this);
    addAutoReleasedObject(outPane);


* This source code was highlighted with Source Code Highlighter.

Все очень просто, теперь наш outPane появится среди остальных, но пока он ничего не может.

Получаем информацию об открытом файле и парсим его


Для того чтобы узнать какой файл сейчас открыт в редакторе, надо как-то достучаться до редактора…
Для этого необходимо добавить следующие заголовки:
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>


* This source code was highlighted with Source Code Highlighter.

А в метод initialize добавить следующую строку:
connect(Core::EditorManager::instance(), SIGNAL(currentEditorChanged(Core::IEditor*)), this, SLOT(currentEditorChanged(Core::IEditor*)));

* This source code was highlighted with Source Code Highlighter.

Ну и конечно же объявить слот currentEditorChanged(Core::IEditor*)
Этот слот будет вызываться каждый раз когда изменился текущий редактор, т. е. пользователь открыл/закрыл файл, или перешел к другому уже открытому.
Его реализация такова:
void TodoPlugin::currentEditorChanged(Core::IEditor *editor)
{
    outPane->clearContents(); // Очищаем
    if (!editor) // Проверяем, а есть ли редактор? вообще
    {
        return;
    }
/* Тонкий момент, соединяем сигнал изменения класса файла привязанного к текущему редактору, чтобы последствии можно было перечитывать файл, после того как пользователь нажал Ctrl+S */
    connect(editor->file(), SIGNAL(changed()), this, SLOT(fileChanged()));
    QString fileName = editor->file()->fileName(); // Получаем имя файла
    readFile(fileName); // читаем и парсим файл.
}


* This source code was highlighted with Source Code Highlighter.

Здесь вызывается метод парсинга, он прост:
void TodoPlugin::readFile(QString fileName)
{
    QFile file(fileName);
    if (!file.open(QFile::ReadOnly | QFile::Text))
        return;
    int i = 1;
    while (!file.atEnd())
    {
        QString currentLine = file.readLine();
        if (currentLine.contains(QRegExp(patternString, Qt::CaseInsensitive)))
        {
            outPane->addItem(currentLine, fileName, i);
        }
        ++i;
    }
}


* This source code was highlighted with Source Code Highlighter.


Также нужно описать новый слот, который будет вызываться, если файл сохранили, вот он:
void TodoPlugin::fileChanged()
{
    outPane->clearContents();
    Core::IFile *file = (Core::IFile *)sender(); // Это несколько не православный способ, но он будет вполне безопасно работать, т. к. этому слоту не подключено больше никаких сигналов.
    if (file)
    {
        readFile(file->fileName());
    }
}


* This source code was highlighted with Source Code Highlighter.


Добавление строк


Теперь необходимо описать добавление строк, то есть метод:
void TodoOutputPane::addItem(QString text, QString file, int rowNumber)
{
    QListWidgetItem *newItem = new QListWidgetItem(); // Создаем новую запись
    QRegExp todoExp("//\\s*TODO(:|\\s)", Qt::CaseInsensitive); // Подготавливаем регекспы
    QRegExp noteExp("//\\s*NOTE(:|\\s)", Qt::CaseInsensitive);
    QRegExp fixmeExp("//\\s*FIXME(:|\\s)", Qt::CaseInsensitive);
    QRegExp bugExp("//\\s*BUG(:|\\s)", Qt::CaseInsensitive);
    QRegExp hackExp("//\\s*HACK(:|\\s)", Qt::CaseInsensitive);

    text = text.replace("\n", "");
    text = text.replace("\r", "");

    newItem->setTextColor(QColor("#2F2F2F"));
    if (text.contains(todoExp)) // Ищем регекспы
    {
        newItem->setBackgroundColor(QColor("#BFFFC8")); // Устанавливаем цвет записи
        text = text.replace(todoExp, «TODO: „); // Заменяем “возможно кривое» написание метки на правильное
        newItem->setIcon(QIcon(":/warning")); // Устанавливаем иконку
    }
    else if (text.contains(noteExp))
    {
        newItem->setBackgroundColor(QColor("#E2DFFF"));
        text = text.replace(noteExp, «NOTE: „);
        newItem->setIcon(QIcon(“:/info»));
    }
    else if (text.contains(bugExp))
    {
        newItem->setBackgroundColor(QColor("#FFBFBF"));
        text = text.replace(bugExp, «BUG: „);
        newItem->setIcon(QIcon(“:/error»));
    }
    else if (text.contains(fixmeExp))
    {
        newItem->setBackgroundColor(QColor("#FFDFDF"));
        text = text.replace(fixmeExp, «FIXME: „);
        newItem->setIcon(QIcon(“:/error»));
    }
    else if (text.contains(hackExp))
    {
        newItem->setBackgroundColor(QColor("#FFFFAA"));
        text = text.replace(hackExp, «HACK: „);
        newItem->setIcon(QIcon(“:/info»));
    }

    newItem->setText(text.trimmed() + " (" + tr(«line „) + QString::number(rowNumber) + “)»); // Устанавливаем текст записи
    newItem->setToolTip(file + ":" + QString::number(rowNumber)); // В подсказке будет храниться имя файла и номер строки, это не просто так, ниже расскажу зачем.
    todoList->addItem(newItem); // Добавляем запись в список
}


* This source code was highlighted with Source Code Highlighter.


Переход на нужную строку по клику на записи


Единственное, что нам осталось сделать, это реализовать переход на нужную строку.
Для этого потребуется еще один слот, который будет выполняться тогда, когда выделили элемент списка:
void TodoPlugin::gotoToRowInFile(QListWidgetItem *item)
{
    int row = 0; // Номер строки
    QString file = ""; //Имя файла

/* Получаем информацию из подсказки */
    QStringList tmpList = item->toolTip().split(":");
    if (tmpList.size() == 2)
    {
        file = tmpList.at(0);
        bool ok;
        row = tmpList.at(1).toInt(&ok);
        if (!ok)
        {
            row = 0;
        }
    }
    if (QFileInfo(file).exists()) // Проверяем, а существует ли файл.
    {
        TextEditor::BaseTextEditor::openEditorAt(file, row); // Принудительно показываем файл, установив курсор в строку с номером row. Для того чтобы эта строка работала, необходимо в зависимостях указать TextEditor plugin, и добавить соответсвующий заголовок
        Core::EditorManager::instance()->ensureEditorManagerVisible();
    }
}


* This source code was highlighted with Source Code Highlighter.


Заключение


Ну вот и все, плагин готов, спасибо дочитавшим.
Скачать плагин можно отсюда.
Tags:
Hubs:
+37
Comments30

Articles