Итак, уже давно уважаемый Евгений писал нам о хаках 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.


Заключение


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