fakevimplugin.cpp 37.1 KB
Newer Older
1
/**************************************************************************
hjk's avatar
hjk committed
2 3 4
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
hjk's avatar
hjk committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
hjk's avatar
hjk committed
8
**
9
** Commercial Usage
hjk's avatar
hjk committed
10
**
11 12 13 14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
hjk's avatar
hjk committed
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
**
18 19 20 21 22 23
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
**
25
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.com/contact.
hjk's avatar
hjk committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29 30 31

#include "fakevimplugin.h"

32
#include "fakevimhandler.h"
hjk's avatar
hjk committed
33 34
#include "ui_fakevimoptions.h"

hjk's avatar
hjk committed
35

36
#include <coreplugin/actionmanager/actionmanager.h>
hjk's avatar
hjk committed
37 38
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
39
#include <coreplugin/editormanager/openeditorsmodel.h>
40
#include <coreplugin/filemanager.h>
hjk's avatar
hjk committed
41
#include <coreplugin/icore.h>
42
#include <coreplugin/ifile.h>
hjk's avatar
hjk committed
43
#include <coreplugin/dialogs/ioptionspage.h>
44
#include <coreplugin/actionmanager/commandmappings.h>
hjk's avatar
hjk committed
45 46 47 48 49 50 51 52 53
#include <coreplugin/messagemanager.h>
#include <coreplugin/modemanager.h>
#include <coreplugin/uniqueidmanager.h>

#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/session.h>

#include <texteditor/basetexteditor.h>
#include <texteditor/basetextmark.h>
hjk's avatar
hjk committed
54
#include <texteditor/completionsupport.h>
hjk's avatar
hjk committed
55 56
#include <texteditor/itexteditor.h>
#include <texteditor/texteditorconstants.h>
57 58
#include <texteditor/tabsettings.h>
#include <texteditor/texteditorsettings.h>
59
#include <texteditor/textblockiterator.h>
hjk's avatar
hjk committed
60

61
#include <find/findplugin.h>
62 63
#include <find/textfindconstants.h>

hjk's avatar
hjk committed
64
#include <utils/qtcassert.h>
hjk's avatar
hjk committed
65
#include <utils/savedaction.h>
66
#include <utils/treewidgetcolumnstretcher.h>
hjk's avatar
hjk committed
67

68 69
#include <cpptools/cpptoolsconstants.h>

70 71
#include <indenter.h>

hjk's avatar
hjk committed
72
#include <QtCore/QDebug>
73
#include <QtCore/QtPlugin>
hjk's avatar
hjk committed
74 75 76
#include <QtCore/QObject>
#include <QtCore/QPoint>
#include <QtCore/QSettings>
77
#include <QtCore/QTextStream>
hjk's avatar
hjk committed
78

79
#include <QtGui/QMessageBox>
hjk's avatar
hjk committed
80
#include <QtGui/QPlainTextEdit>
81
#include <QtGui/QShortcut>
hjk's avatar
hjk committed
82 83
#include <QtGui/QTextBlock>
#include <QtGui/QTextCursor>
84
#include <QtGui/QTextEdit>
hjk's avatar
hjk committed
85 86 87 88 89 90 91 92 93 94 95


using namespace FakeVim::Internal;
using namespace TextEditor;
using namespace Core;
using namespace ProjectExplorer;


namespace FakeVim {
namespace Constants {

96 97
const char * const INSTALL_HANDLER        = "TextEditor.FakeVimHandler";
const char * const MINI_BUFFER            = "TextEditor.FakeVimMiniBuffer";
hjk's avatar
hjk committed
98
const char * const INSTALL_KEY            = "Alt+V,Alt+V";
99 100
const char * const SETTINGS_CATEGORY      = "D.FakeVim";
const char * const SETTINGS_ID            = "General";
101
const char * const SETTINGS_EX_CMDS_ID    = "ExCommands";
102 103
const char * const CMD_FILE_NEXT          = "FakeVim.SwitchFileNext";
const char * const CMD_FILE_PREV          = "FakeVim.SwitchFilePrev";
hjk's avatar
hjk committed
104 105 106 107 108

} // namespace Constants
} // namespace FakeVim


hjk's avatar
hjk committed
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
///////////////////////////////////////////////////////////////////////
//
// FakeVimOptionPage
//
///////////////////////////////////////////////////////////////////////

namespace FakeVim {
namespace Internal {

class FakeVimOptionPage : public Core::IOptionsPage
{
    Q_OBJECT

public:
    FakeVimOptionPage() {}

    // IOptionsPage
126
    QString id() const { return QLatin1String(Constants::SETTINGS_ID); }
127
    QString displayName() const { return tr("General"); }
128
    QString category() const { return QLatin1String(Constants::SETTINGS_CATEGORY); }
129
    QString displayCategory() const { return tr("FakeVim"); }
hjk's avatar
hjk committed
130 131 132 133

    QWidget *createPage(QWidget *parent);
    void apply() { m_group.apply(ICore::instance()->settings()); }
    void finish() { m_group.finish(); }
134
    virtual bool matches(const QString &) const;
hjk's avatar
hjk committed
135 136 137 138 139 140 141 142 143

private slots:
    void copyTextEditorSettings();
    void setQtStyle();
    void setPlainStyle();

private:
    friend class DebuggerPlugin;
    Ui::FakeVimOptionPage m_ui;
144
    QString m_searchKeywords;
145
    Utils::SavedActionSet m_group;
hjk's avatar
hjk committed
146 147 148 149 150 151 152 153
};

QWidget *FakeVimOptionPage::createPage(QWidget *parent)
{
    QWidget *w = new QWidget(parent);
    m_ui.setupUi(w);

    m_group.clear();
154
    m_group.insert(theFakeVimSetting(ConfigUseFakeVim),
155
        m_ui.groupBox);
hjk's avatar
hjk committed
156

157
    m_group.insert(theFakeVimSetting(ConfigExpandTab),
hjk's avatar
hjk committed
158
        m_ui.checkBoxExpandTab);
159
    m_group.insert(theFakeVimSetting(ConfigHlSearch),
hjk's avatar
hjk committed
160
        m_ui.checkBoxHlSearch);
161
    m_group.insert(theFakeVimSetting(ConfigShiftWidth),
hjk's avatar
hjk committed
162 163
        m_ui.lineEditShiftWidth);

164
    m_group.insert(theFakeVimSetting(ConfigSmartTab),
hjk's avatar
hjk committed
165
        m_ui.checkBoxSmartTab);
166
    m_group.insert(theFakeVimSetting(ConfigStartOfLine),
hjk's avatar
hjk committed
167
        m_ui.checkBoxStartOfLine);
168
    m_group.insert(theFakeVimSetting(ConfigTabStop),
hjk's avatar
hjk committed
169
        m_ui.lineEditTabStop);
170
    m_group.insert(theFakeVimSetting(ConfigBackspace),
hjk's avatar
hjk committed
171 172
        m_ui.lineEditBackspace);

173
    m_group.insert(theFakeVimSetting(ConfigAutoIndent),
hjk's avatar
hjk committed
174
        m_ui.checkBoxAutoIndent);
175
    m_group.insert(theFakeVimSetting(ConfigSmartIndent),
176
        m_ui.checkBoxSmartIndent);
177
    m_group.insert(theFakeVimSetting(ConfigIncSearch),
178
        m_ui.checkBoxIncSearch);
hjk's avatar
hjk committed
179 180

    connect(m_ui.pushButtonCopyTextEditorSettings, SIGNAL(clicked()),
hjk's avatar
hjk committed
181
        this, SLOT(copyTextEditorSettings()));
hjk's avatar
hjk committed
182 183 184 185
    connect(m_ui.pushButtonSetQtStyle, SIGNAL(clicked()),
        this, SLOT(setQtStyle()));
    connect(m_ui.pushButtonSetPlainStyle, SIGNAL(clicked()),
        this, SLOT(setPlainStyle()));
186 187
    if (m_searchKeywords.isEmpty()) {
        QTextStream(&m_searchKeywords)
hjk's avatar
hjk committed
188 189 190 191 192 193 194 195 196 197 198
            << ' ' << m_ui.labelAutoIndent->text()
            << ' ' << m_ui.labelExpandTab->text()
            << ' ' << m_ui.labelSmartIndent->text()
            << ' ' << m_ui.labelExpandTab->text()
            << ' ' << m_ui.labelHlSearch->text()
            << ' ' << m_ui.labelIncSearch->text()
            << ' ' << m_ui.labelShiftWidth->text()
            << ' ' << m_ui.labelSmartTab->text()
            << ' ' << m_ui.labelStartOfLine->text()
            << ' ' << m_ui.tabulatorLabel->text()
            << ' ' << m_ui.labelBackspace->text();
199 200
        m_searchKeywords.remove(QLatin1Char('&'));
    }
hjk's avatar
hjk committed
201 202 203 204 205
    return w;
}

void FakeVimOptionPage::copyTextEditorSettings()
{
206
    TextEditor::TabSettings ts =
hjk's avatar
hjk committed
207
        TextEditor::TextEditorSettings::instance()->tabSettings();
208

hjk's avatar
hjk committed
209 210 211 212
    m_ui.checkBoxExpandTab->setChecked(ts.m_spacesForTabs);
    m_ui.lineEditTabStop->setText(QString::number(ts.m_tabSize));
    m_ui.lineEditShiftWidth->setText(QString::number(ts.m_indentSize));
    m_ui.checkBoxSmartTab->setChecked(ts.m_smartBackspace);
213 214
    m_ui.checkBoxAutoIndent->setChecked(true);
    m_ui.checkBoxSmartIndent->setChecked(ts.m_autoIndent);
215 216
    // FIXME: Not present in core
    //m_ui.checkBoxIncSearch->setChecked(ts.m_incSearch);
hjk's avatar
hjk committed
217 218 219 220 221
}

void FakeVimOptionPage::setQtStyle()
{
    m_ui.checkBoxExpandTab->setChecked(true);
222 223 224
    const QString four = QString(QLatin1Char('4'));
    m_ui.lineEditTabStop->setText(four);
    m_ui.lineEditShiftWidth->setText(four);
hjk's avatar
hjk committed
225 226
    m_ui.checkBoxSmartTab->setChecked(true);
    m_ui.checkBoxAutoIndent->setChecked(true);
227
    m_ui.checkBoxSmartIndent->setChecked(true);
228
    m_ui.checkBoxIncSearch->setChecked(true);
229
    m_ui.lineEditBackspace->setText(QLatin1String("indent,eol,start"));
hjk's avatar
hjk committed
230 231 232 233 234
}

void FakeVimOptionPage::setPlainStyle()
{
    m_ui.checkBoxExpandTab->setChecked(false);
235 236 237
    const QString eight = QString(QLatin1Char('4'));
    m_ui.lineEditTabStop->setText(eight);
    m_ui.lineEditShiftWidth->setText(eight);
hjk's avatar
hjk committed
238 239
    m_ui.checkBoxSmartTab->setChecked(false);
    m_ui.checkBoxAutoIndent->setChecked(false);
240
    m_ui.checkBoxSmartIndent->setChecked(false);
241
    m_ui.checkBoxIncSearch->setChecked(false);
hjk's avatar
hjk committed
242
    m_ui.lineEditBackspace->setText(QString());
hjk's avatar
hjk committed
243 244
}

245 246 247 248 249
bool FakeVimOptionPage::matches(const QString &s) const
{
    return m_searchKeywords.contains(s, Qt::CaseInsensitive);
}

hjk's avatar
hjk committed
250 251 252 253
} // namespace Internal
} // namespace FakeVim


254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
///////////////////////////////////////////////////////////////////////
//
// FakeVimExCommandsPage
//
///////////////////////////////////////////////////////////////////////

struct CommandItem
{
    Command *m_cmd;
    QString m_regex;
    QTreeWidgetItem *m_item;
};

Q_DECLARE_METATYPE(CommandItem*);

namespace FakeVim {
namespace Internal {

static QMap<QString, QRegExp> s_exCommandMap;
static QMap<QString, QRegExp> s_defaultExCommandMap;


276
class FakeVimExCommandsPage : public Core::CommandMappings
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
{
    Q_OBJECT

public:
    FakeVimExCommandsPage() {}

    // IOptionsPage
    QString id() const { return QLatin1String(Constants::SETTINGS_EX_CMDS_ID); }
    QString displayName() const { return tr("Ex Command Mapping"); }
    QString category() const { return QLatin1String(Constants::SETTINGS_CATEGORY); }
    QString displayCategory() const { return tr("FakeVim"); }

    QWidget *createPage(QWidget *parent);
    void initialize();

public slots:
    void commandChanged(QTreeWidgetItem *current);
294 295 296
    void targetIdentifierChanged();
    void resetTargetIdentifier();
    void removeTargetIdentifier();
297 298 299 300 301 302 303 304 305
    void defaultAction();

private:
    void setRegex(const QString &regex);
    QList<CommandItem *> m_citems;
};

QWidget *FakeVimExCommandsPage::createPage(QWidget *parent)
{
306 307 308 309 310
    QWidget *w = CommandMappings::createPage(parent);
    setPageTitle(tr("Ex Command Mapping"));
    setTargetHeader(tr("Ex Trigger Expression"));
    setTargetLabelText(tr("Regular Expression:"));
    setTargetEditTitle(tr("Ex Command"));
311

312
    setImportExportEnabled(false);
313 314 315 316 317 318 319 320 321 322 323

    return w;
}

void FakeVimExCommandsPage::initialize()
{
    Core::ActionManager *am = Core::ICore::instance()->actionManager();
    QTC_ASSERT(am, return);
    UniqueIDManager *uidm = UniqueIDManager::instance();
    QTC_ASSERT(uidm, return);

324 325
    QMap<QString, QTreeWidgetItem *> sections;

326 327 328 329 330
    foreach (Command *c, am->commands()) {
        if (c->action() && c->action()->isSeparator())
            continue;

        CommandItem *ci = new CommandItem;
331
        QTreeWidgetItem *item = new QTreeWidgetItem;
332 333 334 335 336
        ci->m_cmd = c;
        ci->m_item = item;
        m_citems << ci;

        const QString name = uidm->stringForUniqueIdentifier(c->id());
337 338 339 340 341
        const int pos = name.indexOf(QLatin1Char('.'));
        const QString section = name.left(pos);
        const QString subId = name.mid(pos+1);

        if (!sections.contains(section)) {
342
            QTreeWidgetItem *categoryItem = new QTreeWidgetItem(commandList(), QStringList() << section);
343 344 345 346
            QFont f = categoryItem->font(0);
            f.setBold(true);
            categoryItem->setFont(0, f);
            sections.insert(section, categoryItem);
347
            commandList()->expandItem(categoryItem);
348 349 350 351
        }
        sections[section]->addChild(item);

        item->setText(0, subId);
352 353 354 355 356 357 358 359 360 361 362

        if (c->action()) {
            QString text = c->hasAttribute(Command::CA_UpdateText) && !c->defaultText().isNull() ? c->defaultText() : c->action()->text();
            text.remove(QRegExp("&(?!&)"));
            item->setText(1, text);
        } else {
            item->setText(1, c->shortcut()->whatsThis());
        }
        if (s_exCommandMap.contains(name)) {
            ci->m_regex = s_exCommandMap[name].pattern();
        } else {
hjk's avatar
hjk committed
363
            ci->m_regex.clear();
364 365 366 367
        }

        item->setText(2, ci->m_regex);
        item->setData(0, Qt::UserRole, qVariantFromValue(ci));
368 369 370 371 372 373 374 375 376 377

        if (ci->m_regex != s_defaultExCommandMap[name].pattern()) {
            QFont f = item->font(0);
            f.setItalic(true);
            item->setFont(0, f);
            item->setFont(1, f);
            f.setBold(true);
            item->setFont(2, f);
        }

378 379 380 381 382 383 384
    }

    commandChanged(0);
}

void FakeVimExCommandsPage::commandChanged(QTreeWidgetItem *current)
{
385 386 387
    CommandMappings::commandChanged(current);

    if (!current || !current->data(0, Qt::UserRole).isValid())
388
        return;
389

390
    CommandItem *citem = qVariantValue<CommandItem *>(current->data(0, Qt::UserRole));
391
    targetEdit()->setText(citem->m_regex);
392 393
}

394
void FakeVimExCommandsPage::targetIdentifierChanged()
395
{
396
    QTreeWidgetItem *current = commandList()->currentItem();
397 398 399 400 401 402 403 404
    if (!current)
        return;

    UniqueIDManager *uidm = UniqueIDManager::instance();
    CommandItem *citem = qVariantValue<CommandItem *>(current->data(0, Qt::UserRole));
    const QString name = uidm->stringForUniqueIdentifier(citem->m_cmd->id());

    if (current->data(0, Qt::UserRole).isValid()) {
405
        citem->m_regex = targetEdit()->text();
406
        current->setText(2, citem->m_regex);
407
        s_exCommandMap[name] = QRegExp(citem->m_regex);
408
    }
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425

    if (citem->m_regex != s_defaultExCommandMap[name].pattern()) {
        QFont f = current->font(0);
        f.setItalic(true);
        current->setFont(0, f);
        current->setFont(1, f);
        f.setBold(true);
        current->setFont(2, f);
    } else {
        QFont f = current->font(0);
        f.setItalic(false);
        f.setBold(false);
        current->setFont(0, f);
        current->setFont(1, f);
        current->setFont(2, f);
    }

426 427 428 429
}

void FakeVimExCommandsPage::setRegex(const QString &regex)
{
430
    targetEdit()->setText(regex);
431 432
}

433
void FakeVimExCommandsPage::resetTargetIdentifier()
434 435
{
    UniqueIDManager *uidm = UniqueIDManager::instance();
436
    QTreeWidgetItem *current = commandList()->currentItem();
437 438 439 440 441 442
    if (current && current->data(0, Qt::UserRole).isValid()) {
        CommandItem *citem = qVariantValue<CommandItem *>(current->data(0, Qt::UserRole));
        const QString &name = uidm->stringForUniqueIdentifier(citem->m_cmd->id());
        if (s_defaultExCommandMap.contains(name))
            setRegex(s_defaultExCommandMap[name].pattern());
        else
hjk's avatar
hjk committed
443
            setRegex(QString());
444 445 446
    }
}

447
void FakeVimExCommandsPage::removeTargetIdentifier()
448
{
449
    targetEdit()->clear();
450 451 452 453 454 455 456 457 458 459
}

void FakeVimExCommandsPage::defaultAction()
{
    UniqueIDManager *uidm = UniqueIDManager::instance();
    foreach (CommandItem *item, m_citems) {
        const QString &name = uidm->stringForUniqueIdentifier(item->m_cmd->id());
        if (s_defaultExCommandMap.contains(name)) {
            item->m_regex = s_defaultExCommandMap[name].pattern();
        } else {
hjk's avatar
hjk committed
460
            item->m_regex.clear();
461 462
        }
        item->m_item->setText(2, item->m_regex);
463
        if (item->m_item == commandList()->currentItem())
464 465 466 467 468 469 470 471
            commandChanged(item->m_item);
    }
}

} // namespace Internal
} // namespace FakeVim


hjk's avatar
hjk committed
472 473
///////////////////////////////////////////////////////////////////////
//
474
// FakeVimPluginPrivate
hjk's avatar
hjk committed
475 476 477
//
///////////////////////////////////////////////////////////////////////

478 479 480 481
namespace FakeVim {
namespace Internal {

class FakeVimPluginPrivate : public QObject
hjk's avatar
hjk committed
482
{
483 484 485 486 487 488 489
    Q_OBJECT

public:
    FakeVimPluginPrivate(FakeVimPlugin *);
    ~FakeVimPluginPrivate();
    friend class FakeVimPlugin;

490
    bool initialize();
491 492 493 494 495
    void shutdown();

private slots:
    void editorOpened(Core::IEditor *);
    void editorAboutToClose(Core::IEditor *);
496

497
    void setUseFakeVim(const QVariant &value);
498
    void quitFakeVim();
499
    void triggerCompletions();
hjk's avatar
hjk committed
500
    void windowCommand(int key);
501
    void find(bool reverse);
502
    void findNext(bool reverse);
hjk's avatar
hjk committed
503
    void showSettingsDialog();
504 505 506 507 508

    void showCommandBuffer(const QString &contents);
    void showExtraInformation(const QString &msg);
    void changeSelection(const QList<QTextEdit::ExtraSelection> &selections);
    void writeFile(bool *handled, const QString &fileName, const QString &contents);
509
    void moveToMatchingParenthesis(bool *moved, bool *forward, QTextCursor *cursor);
510
    void checkForElectricCharacter(bool *result, QChar c);
hjk's avatar
hjk committed
511
    void indentRegion(int *amount, int beginLine, int endLine,  QChar typedChar);
512
    void handleExCommand(const QString &cmd);
513
    void handleSetCommand(bool *handled, QString cmd);
514 515 516 517

    void handleDelayedQuitAll(bool forced);
    void handleDelayedQuit(bool forced, Core::IEditor *editor);

518 519 520 521
    void switchFile(bool previous);
    void switchFileNext();
    void switchFilePrev();

522 523 524
signals:
    void delayedQuitRequested(bool forced, Core::IEditor *editor);
    void delayedQuitAllRequested(bool forced);
525 526 527

private:
    FakeVimPlugin *q;
hjk's avatar
hjk committed
528
    FakeVimOptionPage *m_fakeVimOptionsPage;
529
    FakeVimExCommandsPage *m_fakeVimExCommandsPage;
530
    QHash<Core::IEditor *, FakeVimHandler *> m_editorToHandler;
531 532

    void triggerAction(const QString& code);
533
    void setActionChecked(const QString& code, bool check);
534 535 536

    void readSettings(QSettings *settings);
    void writeSettings(QSettings *settings);
537 538 539 540 541 542
};

} // namespace Internal
} // namespace FakeVim

FakeVimPluginPrivate::FakeVimPluginPrivate(FakeVimPlugin *plugin)
543
{
544
    q = plugin;
hjk's avatar
hjk committed
545
    m_fakeVimOptionsPage = 0;
546 547
    m_fakeVimExCommandsPage = 0;

548 549
    s_defaultExCommandMap[Constants::CMD_FILE_NEXT] = QRegExp("^n(ext)?!?( (.*))?$");
    s_defaultExCommandMap[Constants::CMD_FILE_PREV] = QRegExp("^(N(ext)?|prev(ious)?)!?( (.*))?$");
550 551 552 553
    s_defaultExCommandMap[CppTools::Constants::SWITCH_HEADER_SOURCE] = QRegExp("^A$");
    s_defaultExCommandMap[ProjectExplorer::Constants::BUILD] = QRegExp("^make$");
    s_defaultExCommandMap["Coreplugin.OutputPane.previtem"] = QRegExp("^(cN(ext)?|cp(revious)?)!?( (.*))?$");
    s_defaultExCommandMap["Coreplugin.OutputPane.nextitem"] = QRegExp("^cn(ext)?!?( (.*))?$");
hjk's avatar
hjk committed
554 555
}

556 557 558
FakeVimPluginPrivate::~FakeVimPluginPrivate()
{
}
hjk's avatar
hjk committed
559

560
void FakeVimPluginPrivate::shutdown()
hjk's avatar
hjk committed
561
{
hjk's avatar
hjk committed
562 563 564 565
    q->removeObject(m_fakeVimOptionsPage);
    delete m_fakeVimOptionsPage;
    m_fakeVimOptionsPage = 0;
    theFakeVimSettings()->writeSettings(Core::ICore::instance()->settings());
dt's avatar
dt committed
566
    delete theFakeVimSettings();
567 568 569 570 571

    q->removeObject(m_fakeVimExCommandsPage);
    delete m_fakeVimExCommandsPage;
    m_fakeVimExCommandsPage = 0;
    writeSettings(Core::ICore::instance()->settings());
hjk's avatar
hjk committed
572 573
}

574
bool FakeVimPluginPrivate::initialize()
hjk's avatar
hjk committed
575
{
hjk's avatar
hjk committed
576
    Core::ActionManager *actionManager = Core::ICore::instance()->actionManager();
hjk's avatar
hjk committed
577 578 579 580 581
    QTC_ASSERT(actionManager, return false);

    QList<int> globalcontext;
    globalcontext << Core::Constants::C_GLOBAL_ID;

hjk's avatar
hjk committed
582 583 584
    m_fakeVimOptionsPage = new FakeVimOptionPage;
    q->addObject(m_fakeVimOptionsPage);
    theFakeVimSettings()->readSettings(Core::ICore::instance()->settings());
585 586 587 588

    m_fakeVimExCommandsPage = new FakeVimExCommandsPage;
    q->addObject(m_fakeVimExCommandsPage);
    readSettings(Core::ICore::instance()->settings());
589

con's avatar
con committed
590
    Core::Command *cmd = 0;
591
    cmd = actionManager->registerAction(theFakeVimSetting(ConfigUseFakeVim),
hjk's avatar
hjk committed
592 593 594
        Constants::INSTALL_HANDLER, globalcontext);
    cmd->setDefaultKeySequence(QKeySequence(Constants::INSTALL_KEY));

595
    ActionContainer *advancedMenu =
hjk's avatar
hjk committed
596
        actionManager->actionContainer(Core::Constants::M_EDIT_ADVANCED);
597
    advancedMenu->addAction(cmd, Core::Constants::G_EDIT_EDITOR);
hjk's avatar
hjk committed
598

599
    // EditorManager
hjk's avatar
hjk committed
600
    QObject *editorManager = Core::ICore::instance()->editorManager();
601 602 603 604 605
    connect(editorManager, SIGNAL(editorAboutToClose(Core::IEditor*)),
        this, SLOT(editorAboutToClose(Core::IEditor*)));
    connect(editorManager, SIGNAL(editorOpened(Core::IEditor*)),
        this, SLOT(editorOpened(Core::IEditor*)));

hjk's avatar
hjk committed
606 607
    connect(theFakeVimSetting(SettingsDialog), SIGNAL(triggered()),
        this, SLOT(showSettingsDialog()));
608 609
    connect(theFakeVimSetting(ConfigUseFakeVim), SIGNAL(valueChanged(QVariant)),
        this, SLOT(setUseFakeVim(QVariant)));
hjk's avatar
hjk committed
610

611 612 613 614 615 616 617 618 619 620
    QAction *switchFileNextAction = new QAction(tr("Switch to next file"), this);
    cmd = actionManager->registerAction(switchFileNextAction, Constants::CMD_FILE_NEXT, globalcontext);
    cmd->setAttribute(Command::CA_Hide);
    connect(switchFileNextAction, SIGNAL(triggered()), this, SLOT(switchFileNext()));

    QAction *switchFilePrevAction = new QAction(tr("Switch to previous file"), this);
    cmd = actionManager->registerAction(switchFilePrevAction, Constants::CMD_FILE_PREV, globalcontext);
    cmd->setAttribute(Command::CA_Hide);
    connect(switchFilePrevAction, SIGNAL(triggered()), this, SLOT(switchFilePrev()));

621 622 623 624 625 626
    // Delayed operatiosn
    connect(this, SIGNAL(delayedQuitRequested(bool,Core::IEditor*)),
        this, SLOT(handleDelayedQuit(bool,Core::IEditor*)), Qt::QueuedConnection);
    connect(this, SIGNAL(delayedQuitAllRequested(bool)),
        this, SLOT(handleDelayedQuitAll(bool)), Qt::QueuedConnection);

hjk's avatar
hjk committed
627 628 629
    return true;
}

630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669
static const char *exCommandMapGroup = "FakeVimExCommand";
static const char *reKey = "RegEx";
static const char *idKey = "Command";

void FakeVimPluginPrivate::writeSettings(QSettings *settings)
{
    settings->beginWriteArray(QLatin1String(exCommandMapGroup));

    int count = 0;
    const QMap<QString, QRegExp>::const_iterator end = s_exCommandMap.constEnd();
    for (QMap<QString, QRegExp>::const_iterator it = s_exCommandMap.constBegin(); it != end; ++it) {
        const QString &id = it.key();
        const QRegExp &re = it.value();

        if ((s_defaultExCommandMap.contains(id) && s_defaultExCommandMap[id] != re)
            || (!s_defaultExCommandMap.contains(id) && !re.pattern().isEmpty())) {
            settings->setArrayIndex(count);
            settings->setValue(QLatin1String(idKey), id);
            settings->setValue(QLatin1String(reKey), re.pattern());
            ++count;
        }
    }

    settings->endArray();
}

void FakeVimPluginPrivate::readSettings(QSettings *settings)
{
    s_exCommandMap = s_defaultExCommandMap;

    int size = settings->beginReadArray(QLatin1String(exCommandMapGroup));
    for (int i=0; i<size; ++i) {
        settings->setArrayIndex(i);
        const QString id = settings->value(QLatin1String(idKey)).toString();
        const QString re = settings->value(QLatin1String(reKey)).toString();
        s_exCommandMap[id] = QRegExp(re);
    }
    settings->endArray();
}

hjk's avatar
hjk committed
670 671
void FakeVimPluginPrivate::showSettingsDialog()
{
672 673
    Core::ICore::instance()->showOptionsDialog(QLatin1String(Constants::SETTINGS_CATEGORY),
                                               QLatin1String(Constants::SETTINGS_ID));
hjk's avatar
hjk committed
674 675
}

676 677 678 679 680 681 682 683 684 685 686
void FakeVimPluginPrivate::triggerAction(const QString& code)
{
    Core::ActionManager *am = Core::ICore::instance()->actionManager();
    QTC_ASSERT(am, return);
    Core::Command *cmd = am->command(code);
    QTC_ASSERT(cmd, return);
    QAction *action = cmd->action();
    QTC_ASSERT(action, return);
    action->trigger();
}

687 688 689 690 691 692 693 694 695 696 697 698 699
void FakeVimPluginPrivate::setActionChecked(const QString& code, bool check)
{
    Core::ActionManager *am = Core::ICore::instance()->actionManager();
    QTC_ASSERT(am, return);
    Core::Command *cmd = am->command(code);
    QTC_ASSERT(cmd, return);
    QAction *action = cmd->action();
    QTC_ASSERT(action, return);
    QTC_ASSERT(action->isCheckable(), return);
    action->setChecked(check);
    action->trigger();
}

hjk's avatar
hjk committed
700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730
void FakeVimPluginPrivate::windowCommand(int key)
{
    #define control(n) (256 + n)
    QString code;
    switch (key) {
        case 'c': case 'C': case control('c'):
            code = Core::Constants::CLOSE;
            break;
        case 'n': case 'N': case control('n'):
            code = Core::Constants::GOTONEXT;
            break;
        case 'o': case 'O': case control('o'):
            code = Core::Constants::REMOVE_ALL_SPLITS;
            code = Core::Constants::REMOVE_CURRENT_SPLIT;
            break;
        case 'p': case 'P': case control('p'):
            code = Core::Constants::GOTOPREV;
            break;
        case 's': case 'S': case control('s'):
            code = Core::Constants::SPLIT;
            break;
        case 'w': case 'W': case control('w'):
            code = Core::Constants::GOTO_OTHER_SPLIT;
            break;
    }
    #undef control
    qDebug() << "RUNNING WINDOW COMMAND: " << key << code;
    if (code.isEmpty()) {
        qDebug() << "UNKNOWN WINDOWS COMMAND: " << key;
        return;
    }
731 732 733 734 735
    triggerAction(code);
}

void FakeVimPluginPrivate::find(bool reverse)
{
736 737 738 739 740 741
    if (Find::Internal::FindPlugin *plugin = Find::Internal::FindPlugin::instance()) {
        plugin->setUseFakeVim(true);
        plugin->openFindToolBar(reverse
                ? Find::Internal::FindPlugin::FindBackward
                : Find::Internal::FindPlugin::FindForward);
    }
hjk's avatar
hjk committed
742 743
}

744 745 746 747 748 749 750 751
void FakeVimPluginPrivate::findNext(bool reverse)
{
    if (reverse)
        triggerAction(Find::Constants::FIND_PREVIOUS);
    else
        triggerAction(Find::Constants::FIND_NEXT);
}

752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775
// this class defers deletion of a child FakeVimHandler using 'deleteLater'
// - direct children QObject's would be 'delete'ed immediately before their parents
class DeferredDeleter : public QObject
{
    Q_OBJECT

    FakeVimHandler *m_handler;

public:
    DeferredDeleter(QObject *parent, FakeVimHandler *handler)
        : QObject(parent)
          , m_handler(handler)
    {}

    virtual ~DeferredDeleter()
    {
        if (m_handler) {
            m_handler->disconnectFromEditor();
            m_handler->deleteLater();
            m_handler = 0;
        }
    }
};

776
void FakeVimPluginPrivate::editorOpened(Core::IEditor *editor)
777
{
778 779 780
    if (!editor)
        return;

781
    QWidget *widget = editor->widget();
782 783
    if (!widget)
        return;
784 785 786 787

    // we can only handle QTextEdit and QPlainTextEdit
    if (!qobject_cast<QTextEdit *>(widget) && !qobject_cast<QPlainTextEdit *>(widget))
        return;
788

789 790 791
    //qDebug() << "OPENING: " << editor << editor->widget()
    //    << "MODE: " << theFakeVimSetting(ConfigUseFakeVim)->value();

792 793 794 795
    FakeVimHandler *handler = new FakeVimHandler(widget, 0);
    // the handler might have triggered the deletion of the editor:
    // make sure that it can return before being deleted itself
    new DeferredDeleter(widget, handler);
796
    m_editorToHandler[editor] = handler;
797 798 799 800

    connect(handler, SIGNAL(extraInformationChanged(QString)),
        this, SLOT(showExtraInformation(QString)));
    connect(handler, SIGNAL(commandBufferChanged(QString)),
hjk's avatar
hjk committed
801
        this, SLOT(showCommandBuffer(QString)));
802 803 804 805
    connect(handler, SIGNAL(writeFileRequested(bool*,QString,QString)),
        this, SLOT(writeFile(bool*,QString,QString)));
    connect(handler, SIGNAL(selectionChanged(QList<QTextEdit::ExtraSelection>)),
        this, SLOT(changeSelection(QList<QTextEdit::ExtraSelection>)));
806 807
    connect(handler, SIGNAL(moveToMatchingParenthesis(bool*,bool*,QTextCursor*)),
        this, SLOT(moveToMatchingParenthesis(bool*,bool*,QTextCursor*)));
hjk's avatar
hjk committed
808 809
    connect(handler, SIGNAL(indentRegion(int*,int,int,QChar)),
        this, SLOT(indentRegion(int*,int,int,QChar)));
810 811
    connect(handler, SIGNAL(checkForElectricCharacter(bool*,QChar)),
        this, SLOT(checkForElectricCharacter(bool*,QChar)));
812 813
    connect(handler, SIGNAL(completionRequested()),
        this, SLOT(triggerCompletions()));
hjk's avatar
hjk committed
814 815
    connect(handler, SIGNAL(windowCommandRequested(int)),
        this, SLOT(windowCommand(int)));
816 817
    connect(handler, SIGNAL(findRequested(bool)),
        this, SLOT(find(bool)));
818 819
    connect(handler, SIGNAL(findNextRequested(bool)),
        this, SLOT(findNext(bool)));
820

821 822
    connect(handler, SIGNAL(handleExCommandRequested(QString)),
        this, SLOT(handleExCommand(QString)));
823 824
    connect(handler, SIGNAL(handleSetCommandRequested(bool *,QString)),
        this, SLOT(handleSetCommand(bool *,QString)));
825

hjk's avatar
hjk committed
826
    handler->setCurrentFileName(editor->file()->fileName());
827
    handler->installEventFilter();
828

829 830
    // pop up the bar
    if (theFakeVimSetting(ConfigUseFakeVim)->value().toBool())
hjk's avatar
hjk committed
831
       showCommandBuffer(QString());
hjk's avatar
hjk committed
832 833
}

834
void FakeVimPluginPrivate::editorAboutToClose(Core::IEditor *editor)
hjk's avatar
hjk committed
835
{
836 837 838 839 840 841
    //qDebug() << "CLOSING: " << editor << editor->widget();
    m_editorToHandler.remove(editor);
}

void FakeVimPluginPrivate::setUseFakeVim(const QVariant &value)
{
842
    //qDebug() << "SET USE FAKEVIM" << value;
843
    bool on = value.toBool();
844 845
    if (Find::Internal::FindPlugin::instance())
        Find::Internal::FindPlugin::instance()->setUseFakeVim(on);
846
    if (on) {
847 848
        Core::EditorManager::instance()->showEditorStatusBar(
            QLatin1String(Constants::MINI_BUFFER),
849
            "vi emulation mode. Type :q to leave. Use , Ctrl-R to trigger run.",
850
            tr("Quit FakeVim"), this, SLOT(quitFakeVim()));
851 852 853 854 855 856 857 858
        foreach (Core::IEditor *editor, m_editorToHandler.keys())
            m_editorToHandler[editor]->setupWidget();
    } else {
        Core::EditorManager::instance()->hideEditorStatusBar(
            QLatin1String(Constants::MINI_BUFFER));
        foreach (Core::IEditor *editor, m_editorToHandler.keys())
            m_editorToHandler[editor]->restoreWidget();
    }
hjk's avatar
hjk committed
859 860
}

861 862 863 864 865 866
void FakeVimPluginPrivate::triggerCompletions()
{
    FakeVimHandler *handler = qobject_cast<FakeVimHandler *>(sender());
    if (!handler)
        return;
    if (BaseTextEditor *bt = qobject_cast<BaseTextEditor *>(handler->widget()))
hjk's avatar
hjk committed
867 868 869
        TextEditor::Internal::CompletionSupport::instance()->
            autoComplete(bt->editableInterface(), false);
   //     bt->triggerCompletions();
870 871
}

872 873 874 875 876 877 878 879 880
void FakeVimPluginPrivate::checkForElectricCharacter(bool *result, QChar c)
{
    FakeVimHandler *handler = qobject_cast<FakeVimHandler *>(sender());
    if (!handler)
        return;
    if (BaseTextEditor *bt = qobject_cast<BaseTextEditor *>(handler->widget()))
        *result = bt->isElectricCharacter(c);
}

881 882
void FakeVimPluginPrivate::writeFile(bool *handled,
    const QString &fileName, const QString &contents)
883
{
884
    Q_UNUSED(contents)
885

886 887 888 889
    FakeVimHandler *handler = qobject_cast<FakeVimHandler *>(sender());
    if (!handler)
        return;

890
    Core::IEditor *editor = m_editorToHandler.key(handler);
891
    if (editor && editor->file()->fileName() == fileName) {
hjk's avatar
hjk committed
892
        // Handle that as a special case for nicer interaction with core
893
        Core::IFile *file = editor->file();
hjk's avatar
hjk committed
894
        Core::ICore::instance()->fileManager()->blockFileChange(file);
895
        file->save(fileName);
hjk's avatar
hjk committed
896
        Core::ICore::instance()->fileManager()->unblockFileChange(file);
897
        *handled = true;
898
    }
899 900
}

901 902 903 904 905 906 907 908 909 910 911 912 913 914 915
void FakeVimPluginPrivate::handleExCommand(const QString &cmd)
{
    static QRegExp reWriteAll("^wa(ll)?!?$");
    static QRegExp reQuit("^q!?$");
    static QRegExp reQuitAll("^qa!?$");

    using namespace Core;

    FakeVimHandler *handler = qobject_cast<FakeVimHandler *>(sender());
    if (!handler)
        return;

    EditorManager *editorManager = EditorManager::instance();
    QTC_ASSERT(editorManager, return);

916
    if (reWriteAll.indexIn(cmd) != -1) {
917 918 919 920 921 922 923
        // :wa
        FileManager *fm = ICore::instance()->fileManager();
        QList<IFile *> toSave = fm->modifiedFiles();
        QList<IFile *> failed = fm->saveModifiedFilesSilently(toSave);
        if (failed.isEmpty())
            handler->showBlackMessage(tr("Saving succeeded"));
        else
924
            handler->showRedMessage(tr("%n files not saved", 0, failed.size()));
925 926 927 928 929 930 931 932 933
    } else if (reQuit.indexIn(cmd) != -1) {
        // :q
        bool forced = cmd.contains(QChar('!'));
        emit delayedQuitRequested(forced, m_editorToHandler.key(handler));
    } else if (reQuitAll.indexIn(cmd) != -1) {
        // :qa
        bool forced = cmd.contains(QChar('!'));
        emit delayedQuitAllRequested(forced);
    } else {
934 935 936 937 938 939 940 941 942 943 944
        const QMap<QString, QRegExp>::const_iterator end = s_exCommandMap.constEnd();
        for (QMap<QString, QRegExp>::const_iterator it = s_exCommandMap.constBegin(); it != end; ++it) {
            const QString &id = it.key();
            const QRegExp &re = it.value();

            if (re.indexIn(cmd) != -1) {
                triggerAction(id);
                return;
            }
        }

945
        handler->showRedMessage(tr("Not an editor command: %1").arg(cmd));
946 947 948
    }
}

949 950 951 952 953 954 955 956 957 958 959 960 961 962 963
void FakeVimPluginPrivate::handleSetCommand(bool *handled, QString cmd)
{
    *handled = false;
    bool value = true;
    if (cmd.startsWith("no")) {
        value = false;
        cmd = cmd.mid(2);
    }

    if (cmd == "ic" || cmd == "ignorecase") {
        setActionChecked(Find::Constants::CASE_SENSITIVE, value);
        *handled = true;
    }
}

964 965 966 967 968 969 970 971 972 973 974 975
void FakeVimPluginPrivate::handleDelayedQuit(bool forced, Core::IEditor *editor)
{
    QList<Core::IEditor *> editors;
    editors.append(editor);
    Core::EditorManager::instance()->closeEditors(editors, !forced);
}

void FakeVimPluginPrivate::handleDelayedQuitAll(bool forced)
{
    Core::EditorManager::instance()->closeAllEditors(!forced);
}