Пользователь
0,0
рейтинг
9 августа 2010 в 17:17

Разработка → TODO Plugin для QtCreator tutorial

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


Заключение


Ну вот и все, плагин готов, спасибо дочитавшим.
Скачать плагин можно отсюда.
Василий Сорокин @Vass
карма
76,8
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (30)

  • +1
    Спасибо!
    А так-то троли молодцы! Может быть скоро креатор заслужит моё почтение, а пока увы еще рано об этом говорить.
    • +2
      Он и сейчас достоит уважения. Нативная поддержка CMake, и отлично работающий code completion (точно лучше, чем в том же Eclipse). А ещё работает очень быстро, и занимает мало оперативной памяти.
      • +3
        Мне отладка там не нравится
        • +1
          Опередили :)
        • 0
          С хелпером более-менее можно жить. Плюс QtCreator в том, что можно скачать SDK, установить, и сразу программировать. С полной средой именно для Qt.
    • 0
      А что сейчас не устраивает? Просто, сейчас, как мне кажется у креатора есть два конкурента: MSVS под windows и KDevelop под Linux. MSVS бьет тем что, что у него отличный дебаггер, KDevelop же очень гибок.
      • 0
        KDevelop кладет креатора на лопатки своим продвинутым code completion, можно классы шлепать просто как семечки грызть.
        • 0
          Да, но новый KDevelop пока все-еще нестабилен. А Creator и Eclipse позволяют работать в одном окружении под Win и Linux. Я сейчас делю рабоче время примерно 85/15 между крейтором и эклипсом (последний использую из-за интеграции со средствами анализа кода / рефакторинга и т.п.).
      • 0
        Про себя скажу, что не устраивает:
        1. Неудобное, маленькое, недокируемое окно переменных
        2. Кривое отображение русского текста в результатах поиска
        3. Навигация по проекту / файлу. Очень не хватает эклипсовского «Outline»
        4. Слабый анализ кода (даже начал прикручивать cppcheck, но сейчас катастрофически не хватает времени)
  • +3
    ОО, молодец, прикольно. Я даже и не заметил что в креаторе нет такой фичи :-)
    А не пробовал это заулить в ветку основную?
    • 0
      Нет не пробовал, это накидано было за пару вечеров на даче, сырое все, может, если до ума довести, можно и попробовать будет.
  • 0
    А чёй-то у меня критор на Рысе только через минут пять открывается, в выходной поставил, думал что он вообще не открылся.Может кто подскажет, что не так?
    • 0
      Первый запуск отнимает обычно отнимает много времени. И некоторое время после первого запуска QtCreator будет порядочно грузить процессор. Это объясняется тем, что происходит индексация справочной информации. Просто подождите немного, последующий запуск происходит мгновенно.
  • +1
    Очень радует развитие этой замечательной среды. Очень радует что люди интересуются и
    пишут плагины. Мне кажется что сейчас слишком сложен сам процесс распространения плагинов,
    ведь чтобы мне например воспользоваться данным плагином, мне необходимо либо собрать
    qt creator из исходников, либо дождаться попадания его в основную ветку. Ни тот ни другой
    вариант не радует. Мне кажется что для приобретения популярности эту систему необходимо упростить. Именно по этой причине не решаюсь пока начать писать свои плагины. Надеюсь в ближайшее время
    все станет проще. Поправьте пожалуйста если не прав.
    • 0
      Вообще, если версия креатора и заявленная в плагине совпадают, то можно использовать уже скомпилированный кем-то бинарник, Но, учитывая, огромную скорость развития Qt и QtCreator, бинарные сборки будут очень быстро терять актуальность.
  • 0
    Забавно, что в каждом из «TODO, FIXME и т. д. комментариев» у вас как минимум по одной ошибке.
    • 0
      Английский — моя слабая сторона.
  • 0
    Просьба. Кто-нибудь может выложить плагин в скомпилированном виде?
    • 0
      Есть только для Linux для версии 2.0.0
      • 0
        Попытаюсь завтра с утра скомпилировать под Win, плагин must have однозначно.
        • 0
          Отлично, будут вопросы, пишите в личку или на почту/jabber контакты мои легко ищутся.
    • 0
      вот тут можно взять бинарник для винды (работает с qt sdk, qt creator 2.0), плагин собран с учётом багфиксов/импрувов, описанных мной ниже.
  • 0
    кто-либо под винду собирал? у меня mingw выдаёт «cannot find -lAggregationd»
    • 0
      вот что не указано в статье: чтобы собрать плагин надо сначала собрать qt creator (либо взять уже готовый из бинарных пакетов), иначе нужные либы не подцепятся
  • 0
    собрал, запустил. баг есть: при клике на тудушку не переходит на нужную строку.
    • 0
      под виндой баг в том, что двоеточие в хинте присутствует 2 раза. я бы предложил не использовать такой метод. для этих целей лучше подходят функции QListWidgetItem::data()/QListWidgetItem::setData(), их можно юзать для хранения пути к файлу и номера строки.

      тогда в функции TodoOutputPane::addItem() добавим две строки:

      1. newItem->setData(Qt::UserRole + 1, file);
      2. newItem->setData(Qt::UserRole + 2, rowNumber);
      * This source code was highlighted with Source Code Highlighter.


      а функция TodoPlugin::gotoToRowInFile() будет выглядеть теперь так:

      1. void TodoPlugin::gotoToRowInFile(QListWidgetItem *item)
      2. {
      3.   int row = item->data(Qt::UserRole + 2).toInt();
      4.   QString file = item->data(Qt::UserRole + 1).toString();
      5.  
      6.   if (QFileInfo(file).exists())
      7.   {
      8.     TextEditor::BaseTextEditor::openEditorAt(file, row);
      9.     Core::EditorManager::instance()->ensureEditorManagerVisible();
      10.   }
      11. }
      * This source code was highlighted with Source Code Highlighter.


      кроме того, предлагаю использовать сигнал itemClicked(QListWidgetItem*) вместо itemActivated(QListWidgetItem*), чтобы одинарный клик на итеме работал.

      для себя сделал указанные изменения и теперь спокойно юзаю, автору респект и спасибо за плагин =)

      идея на будущее: хорошо бы сделать поиск тудушек и прочего во всём проекте.
      • 0
        UPD: сигналы itemClicked и itemActivated лучше использовать совместно, чтобы и клик мышью и нажатие Enter на клаве при навигации с её помощью работали.
  • 0
    кстати, ещё совет: в todo.pro надо бы написать вместо «DESTDIR = $$IDE_PLUGIN_PATH/Nokia» что-то вроде «DESTDIR = $$IDE_PLUGIN_PATH/VSorokin» или даже просто «DESTDIR = $$IDE_PLUGIN_PATH» (что тоже хорошо работает)
    • 0
      Спасибо, мне так и не удалось ничего скомпилировать. Думаю, компиляция плагинов для QtCreatora под Windows тема для отдельного поста на Хабре.
      • 0
        угу, причём стоит уже тут отметить, что собирать надо с помощью Qt 4.7 beta2 для msvs2008 (если есть желание чтобы плагин заработал с криейтором из SDK)

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