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

Первое что потребуется для создания плагина, это скачать исходный код QtCreator, это можно сделать здесь. Теперь, нужно распаковать полученный архив и создать новый пустой проект в папке /src/plugins/.
Для этого в проект нужно добавить новый класс, который будет основным классом плагина, он должен наследоваться от интерфейса ExtensionSystem::IPlugin.
Для этого необходимо в .pro файл добавить следующие строки:
Coreplugin нужен, потому что все пользовательские плагины зависят от него, он предоставляет основные интерфейсы для создания других плагинов.
Также потребуется создать xml-файл <имяплагина>.pluginspec следующего формата
Смысл полей понятен из их названия.
Итак, сам класс ничего не делающего плагина должен иметь такую структуру:
Главным здесь является метод initialize, который выполняет инициализацию плагина. Впрочем, в плагине, который ничего не делает, все методы пусты.
Для моего плагина потребовалась новая область вывода, по соседству с уже имеющимися, делается она очень просто, для этого достаточно создать новый класс наследуемый от интерфейса Core::IOutputPane, вот заголовок этого класса:
Теперь надо как-то зарегистрировать наш свежесозданный todoPane в системе, для этого в методе initialize пишем такой код:
Все очень просто, теперь наш outPane появится среди остальных, но пока он ничего не может.
Для того чтобы узнать какой файл сейчас открыт в редакторе, надо как-то достучаться до редактора…
Для этого необходимо добавить следующие заголовки:
А в метод initialize добавить следующую строку:
Ну и конечно же объявить слот currentEditorChanged(Core::IEditor*)
Этот слот будет вызываться каждый раз когда изменился текущий редактор, т. е. пользователь открыл/закрыл файл, или перешел к другому уже открытому.
Его реализация такова:
Здесь вызывается метод парсинга, он прост:
Также нужно описать новый слот, который будет вызываться, если файл сохранили, вот он:
Теперь необходимо описать добавление строк, то есть метод:
Единственное, что нам осталось сделать, это реализовать переход на нужную строку.
Для этого потребуется еще один слот, который будет выполняться тогда, когда выделили элемент списка:
Ну вот и все, плагин готов, спасибо дочитавшим.
Скачать плагин можно отсюда.

Начало
Первое что потребуется для создания плагина, это скачать исходный код 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.
Заключение
Ну вот и все, плагин готов, спасибо дочитавшим.
Скачать плагин можно отсюда.