QtCreator: создание расширения редакторов на примере QmlJSEditor

Возникла потребность в активном использовании extension'ов к QtScript, причем в js-виде. Как известно, они хранятся в файлах __init__.js, раскиданных по разным папкам внутри папки qtscriptextension. Поменять название файла, содержащего extension, возможности нет (оно зашито в исходниках Qt). Следовательно, в QtCreator мы видим кучу файлов с одинаковым названием (__init__.js), что не очень удобно и совсем не продуктивно. В качестве решения был реализован небольшой плагин, являющийся по сути надстройкой над QmlJSEditor. На его примере и будет рассказано как отнаследоваться от QmlJSEditor, чтобы не потерять имеющийся функционал.Описание решенияИтак, какое решение нас устроит? Вполне достаточно чтобы в Open Documents и по Ctrl+Tab появлялись не просто __init__.js всей своей массой, а также было указано в какой директории лежит этот файл (например someext/__init__.js). Ничто не мешает конечно поменять количество папок в префиксе или приписать что-то свое.MIME-типИтак, первое что нам необходимо сделать это прицепиться к нужным нам файлам, предоставляя стандартный QmlJSEditor для всех остальных js-файлов. Ну тут все просто, нам надо всего лишь задать подтип обычного javascript с конкретным паттерном файла (в нашем случае это как раз будет __init__.js).<mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>    <mime-type type=«application/x-script-extension-javascript»>        <sub-class-of type=«application/javascript»/>        <comment>Qt Script Extension init file</comment>        <glob pattern="__init__.js"/>    </mime-type></mime-info>Наследник IPluginСначала приведу полный код метода initialize(), ну а потом уже разберем что в нем происходит.bool ScriptExtensionsEditorPluginImpl::initialize(const QStringList &arguments, QString *errorString){ Q_UNUSED(arguments) Core::ICore *core = Core::ICore::instance(); if (!core->mimeDatabase()->addMimeTypes( QLatin1String(":/seeditor/SEEditor.mimetypes.xml"), errorString)) return false; m_modelManager = QmlJS::ModelManagerInterface::instance(); m_editor = new ScriptExtensionsEditorFactory(this); addObject(m_editor); m_actionHandler = new TextEditor::TextEditorActionHandler( «BlackTass.SEEditor», TextEditor::TextEditorActionHandler::Format | TextEditor::TextEditorActionHandler::UnCommentSelection | TextEditor::TextEditorActionHandler::UnCollapseAll); m_actionHandler->initializeActions(); QmlJSEditor::Internal::CodeCompletion *completion = new QmlJSEditor::Internal::CodeCompletion(m_modelManager); addAutoReleasedObject(completion); // Set completion settings and keep them up to date TextEditor::TextEditorSettings *textEditorSettings = TextEditor::TextEditorSettings::instance(); completion->setCompletionSettings( textEditorSettings->completionSettings()); connect(textEditorSettings, SIGNAL(completionSettingsChanged(TextEditor::CompletionSettings)), completion, SLOT(setCompletionSettings(TextEditor::CompletionSettings))); return true;}Итак, задаем xml для MIME, создаем фабрику наших редакторов и инициализируем action'ы. А вот дальше начинается работа с самым приятным, что есть в QmlJSeditor — автокомплит. К сожалению, класс, отвечающий за автокомплит в QmlJSEditor является внутренним и поэтому приходится прилинковывать его к нашему плагину. Для этого в про-файле надо добавить следующие строки:SOURCES += \ $$QTCREATOR_SOURCES/src/plugins/qmljseditor/qmljscodecompletion.cppHEADERS +=\ $$QTCREATOR_SOURCES/src/plugins/qmljseditor/qmljscodecompletion.hНу и под конец настраиваем сигналы для отлова изменения настроек автокомплита.Также нам необходимо сделать метод, который будет непосредственно настраивать редактор, назовем его initializeEditor():void ScriptExtensionsEditorPluginImpl::initializeEditor( ScriptExtensionsEditor *editor){ QTC_ASSERT(m_instance, /**/); m_actionHandler->setupActions(editor); TextEditor::TextEditorSettings::instance()->initializeEditor(editor); // auto completion connect(editor, SIGNAL(requestAutoCompletion(TextEditor::ITextEditable*, bool)), TextEditor::CompletionSupport::instance(), SLOT(autoComplete(TextEditor::ITextEditable*, bool)));}Он не делает ничего сверхъестественного, только самое необходимое: настраивает внешний вид самого редактора и подключает сигнал для вызова автокомплита. В самом QmlJSEditor в этом методе также настраивается QuickFix, но, так как пока что они существуют только для Qml, было решено их не использовать.Непосредственно редакторДля реализации редактора в QtCreator, как известно нужны минимум три класса: фабрика, сам редактор и editable (который отслеживает различные операции с файлами и взаимодействия с редактором). На фабрике особо останавливаться не буду, напомню только что в createEditor() необходимо вызвать initializeEditor() из экземпляра класса рассмотренного выше.В editable необходимо не забыть возвращать правильный контекст (который должен содержать наш плагин и текстовый редактор), чтобы правильно работали все горячие клавиши:ScriptExtensionsEditorEditable::ScriptExtensionsEditorEditable( ScriptExtensionsEditor *editor): QmlJSEditor::QmlJSEditorEditable(editor){ m_context.add(«BlackTass.SEEditor»); m_context.add(TextEditor::Constants::C_TEXTEDITOR);}Core::Context ScriptExtensionsEditorEditable::context() const{ return m_context;}В самом же редакторе мы подключимся к сигналу, который испускается при смене имени файла (и следовательно по этому сигналу меняются все обозначения открытого файла в различных отображениях): Сам слот достаточно прост. Он проверяет является ли текущий файл extension'ом (а это очень легко проверить по пути файла, в нем должна быть папка qtscriptextension и сам файл должен называться __init__.js) и если подходит, то дописывает перед названием файла имя папки, в которой он лежит. После этого в очередь событий кладется изменение displayName (который и отвечает за надписи во всех нужных нам местах) для того чтобы наше изменение было гарантированно последним.void ScriptExtensionsEditor::onTitleChanged(const QString &title){ Q_UNUSED(title); QString fileName = baseTextDocument()->fileName(); if (fileName.contains(QRegExp(«qtscriptextension.*__init__\\.js$»))) { QFileInfo fi(fileName); QString realTitle = fi.dir().dirName()+"/"+fi.fileName(); QMetaObject::invokeMethod(this, «setDisplayName», Qt::QueuedConnection, Q_ARG(QString, realTitle)); }}ИтогВ итоге мы имеем небольшой плагин (суммарное количество кода в районе 350 строк), который не нарушает приятный функционал QmlJSEditor и дает возможность легче работать с extension'ами.Внимание! Подобные плагины не будут работать в 2.0 и скорее всего не будут в 2.1. В них editable у QmlJSEditor являлся также внутренним классом и только потом его заэкспортили в библиотеку.

(Источник)