fakevimplugin.cpp 38.4 KB
Newer Older
1
/**************************************************************************
hjk's avatar
hjk committed
2 3 4
**
** This file is part of Qt Creator
**
5
** Copyright (c) 2009 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
#include "ui_fakevimoptions.h"
34
#include "ui_fakevimexcommands.h"
hjk's avatar
hjk committed
35

hjk's avatar
hjk committed
36

37
#include <coreplugin/actionmanager/actionmanager.h>
hjk's avatar
hjk committed
38 39
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
40
#include <coreplugin/editormanager/openeditorsmodel.h>
41
#include <coreplugin/filemanager.h>
hjk's avatar
hjk committed
42
#include <coreplugin/icore.h>
43
#include <coreplugin/ifile.h>
hjk's avatar
hjk committed
44
#include <coreplugin/dialogs/ioptionspage.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 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
///////////////////////////////////////////////////////////////////////
//
// 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;


class FakeVimExCommandsPage : public Core::IOptionsPage
{
    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();
    void apply() {}
    void finish() {}
    virtual bool matches(const QString &) const;
    bool filter(const QString &f, const QTreeWidgetItem *item);

public slots:
    void filterChanged(const QString &f);
    void commandChanged(QTreeWidgetItem *current);
    void regexChanged();
    void resetRegex();
    void removeRegex();
    void defaultAction();

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

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

    connect(m_ui.resetButton, SIGNAL(clicked()),
        this, SLOT(resetRegex()));
    connect(m_ui.removeButton, SIGNAL(clicked()),
        this, SLOT(removeRegex()));
    connect(m_ui.defaultButton, SIGNAL(clicked()),
        this, SLOT(defaultAction()));

    initialize();

    m_ui.commandList->sortByColumn(0, Qt::AscendingOrder);

    connect(m_ui.filterEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString)));
    connect(m_ui.commandList, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)),
        this, SLOT(commandChanged(QTreeWidgetItem *)));
    connect(m_ui.regexEdit, SIGNAL(textChanged(QString)), this, SLOT(regexChanged()));

    if (m_searchKeywords.isEmpty()) {
        QTextStream(&m_searchKeywords)
            << ' ' << m_ui.groupBox->title();
        m_searchKeywords.remove(QLatin1Char('&'));
    }
337
    new Utils::TreeWidgetColumnStretcher(m_ui.commandList, 1);
338 339 340 341 342 343 344 345 346 347 348

    return w;
}

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

349 350
    QMap<QString, QTreeWidgetItem *> sections;

351 352 353 354 355
    foreach (Command *c, am->commands()) {
        if (c->action() && c->action()->isSeparator())
            continue;

        CommandItem *ci = new CommandItem;
356
        QTreeWidgetItem *item = new QTreeWidgetItem;
357 358 359 360 361
        ci->m_cmd = c;
        ci->m_item = item;
        m_citems << ci;

        const QString name = uidm->stringForUniqueIdentifier(c->id());
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
        const int pos = name.indexOf(QLatin1Char('.'));
        const QString section = name.left(pos);
        const QString subId = name.mid(pos+1);

        if (!sections.contains(section)) {
            QTreeWidgetItem *categoryItem = new QTreeWidgetItem(m_ui.commandList, QStringList() << section);
            QFont f = categoryItem->font(0);
            f.setBold(true);
            categoryItem->setFont(0, f);
            sections.insert(section, categoryItem);
            m_ui.commandList->expandItem(categoryItem);
        }
        sections[section]->addChild(item);

        item->setText(0, subId);
377 378 379 380 381 382 383 384 385 386 387

        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
388
            ci->m_regex.clear();
389 390 391 392 393 394 395 396 397 398 399 400
        }

        item->setText(2, ci->m_regex);
        item->setData(0, Qt::UserRole, qVariantFromValue(ci));
    }

    commandChanged(0);
}

void FakeVimExCommandsPage::commandChanged(QTreeWidgetItem *current)
{
    if (!current || !current->data(0, Qt::UserRole).isValid()) {
hjk's avatar
hjk committed
401
        m_ui.regexEdit->setText(QString());
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
        m_ui.seqGrp->setEnabled(false);
        return;
    }
    m_ui.seqGrp->setEnabled(true);
    CommandItem *citem = qVariantValue<CommandItem *>(current->data(0, Qt::UserRole));
    m_ui.regexEdit->setText(citem->m_regex);
}

void FakeVimExCommandsPage::filterChanged(const QString &f)
{
    for (int i=0; i<m_ui.commandList->topLevelItemCount(); ++i) {
        QTreeWidgetItem *item = m_ui.commandList->topLevelItem(i);
        item->setHidden(filter(f, item));
    }
}

void FakeVimExCommandsPage::regexChanged()
{
    UniqueIDManager *uidm = UniqueIDManager::instance();
    QTreeWidgetItem *current = m_ui.commandList->currentItem();
    if (current && current->data(0, Qt::UserRole).isValid()) {
        CommandItem *citem = qVariantValue<CommandItem *>(current->data(0, Qt::UserRole));
        citem->m_regex = m_ui.regexEdit->text();
        current->setText(2, citem->m_regex);
        s_exCommandMap[uidm->stringForUniqueIdentifier(citem->m_cmd->id())] = QRegExp(citem->m_regex);
    }
}

void FakeVimExCommandsPage::setRegex(const QString &regex)
{
    m_ui.regexEdit->setText(regex);
}

bool FakeVimExCommandsPage::filter(const QString &f, const QTreeWidgetItem *item)
{
437 438 439 440 441
    if (QTreeWidgetItem *parent = item->parent()) {
        if (parent->text(0).contains(f, Qt::CaseInsensitive))
            return false;
    }

442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
    if (item->childCount() == 0) {
        if (f.isEmpty())
            return false;
        for (int i = 0; i < item->columnCount(); ++i) {
            if (item->text(i).contains(f, Qt::CaseInsensitive))
                return false;
        }
        return true;
    }

    bool found = false;
    for (int i = 0; i < item->childCount(); ++i) {
        QTreeWidgetItem *citem = item->child(i);
        if (filter(f, citem)) {
            citem->setHidden(true);
        } else {
            citem->setHidden(false);
            found = true;
        }
    }
    return !found;
}

void FakeVimExCommandsPage::resetRegex()
{
    UniqueIDManager *uidm = UniqueIDManager::instance();
    QTreeWidgetItem *current = m_ui.commandList->currentItem();
    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
475
            setRegex(QString());
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
    }
}

void FakeVimExCommandsPage::removeRegex()
{
    m_ui.regexEdit->clear();
}

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
492
            item->m_regex.clear();
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
        }
        item->m_item->setText(2, item->m_regex);
        if (item->m_item == m_ui.commandList->currentItem())
            commandChanged(item->m_item);
    }
}

bool FakeVimExCommandsPage::matches(const QString &s) const
{
    return m_searchKeywords.contains(s, Qt::CaseInsensitive);
}

} // namespace Internal
} // namespace FakeVim


hjk's avatar
hjk committed
509 510
///////////////////////////////////////////////////////////////////////
//
511
// FakeVimPluginPrivate
hjk's avatar
hjk committed
512 513 514
//
///////////////////////////////////////////////////////////////////////

515 516 517 518
namespace FakeVim {
namespace Internal {

class FakeVimPluginPrivate : public QObject
hjk's avatar
hjk committed
519
{
520 521 522 523 524 525 526
    Q_OBJECT

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

527
    bool initialize();
528 529 530 531 532
    void shutdown();

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

534
    void setUseFakeVim(const QVariant &value);
535
    void quitFakeVim();
536
    void triggerCompletions();
hjk's avatar
hjk committed
537
    void windowCommand(int key);
538
    void find(bool reverse);
539
    void findNext(bool reverse);
hjk's avatar
hjk committed
540
    void showSettingsDialog();
541 542 543 544 545

    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);
546
    void moveToMatchingParenthesis(bool *moved, bool *forward, QTextCursor *cursor);
547
    void checkForElectricCharacter(bool *result, QChar c);
hjk's avatar
hjk committed
548
    void indentRegion(int *amount, int beginLine, int endLine,  QChar typedChar);
549
    void handleExCommand(const QString &cmd);
550
    void handleSetCommand(bool *handled, QString cmd);
551 552 553 554

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

555 556 557 558
    void switchFile(bool previous);
    void switchFileNext();
    void switchFilePrev();

559 560 561
signals:
    void delayedQuitRequested(bool forced, Core::IEditor *editor);
    void delayedQuitAllRequested(bool forced);
562 563 564

private:
    FakeVimPlugin *q;
hjk's avatar
hjk committed
565
    FakeVimOptionPage *m_fakeVimOptionsPage;
566
    FakeVimExCommandsPage *m_fakeVimExCommandsPage;
567
    QHash<Core::IEditor *, FakeVimHandler *> m_editorToHandler;
568 569

    void triggerAction(const QString& code);
570
    void setActionChecked(const QString& code, bool check);
571 572 573

    void readSettings(QSettings *settings);
    void writeSettings(QSettings *settings);
574 575 576 577 578 579
};

} // namespace Internal
} // namespace FakeVim

FakeVimPluginPrivate::FakeVimPluginPrivate(FakeVimPlugin *plugin)
580
{
581
    q = plugin;
hjk's avatar
hjk committed
582
    m_fakeVimOptionsPage = 0;
583 584
    m_fakeVimExCommandsPage = 0;

585 586
    s_defaultExCommandMap[Constants::CMD_FILE_NEXT] = QRegExp("^n(ext)?!?( (.*))?$");
    s_defaultExCommandMap[Constants::CMD_FILE_PREV] = QRegExp("^(N(ext)?|prev(ious)?)!?( (.*))?$");
587 588 589 590
    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
591 592
}

593 594 595
FakeVimPluginPrivate::~FakeVimPluginPrivate()
{
}
hjk's avatar
hjk committed
596

597
void FakeVimPluginPrivate::shutdown()
hjk's avatar
hjk committed
598
{
hjk's avatar
hjk committed
599 600 601 602
    q->removeObject(m_fakeVimOptionsPage);
    delete m_fakeVimOptionsPage;
    m_fakeVimOptionsPage = 0;
    theFakeVimSettings()->writeSettings(Core::ICore::instance()->settings());
dt's avatar
dt committed
603
    delete theFakeVimSettings();
604 605 606 607 608

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

611
bool FakeVimPluginPrivate::initialize()
hjk's avatar
hjk committed
612
{
hjk's avatar
hjk committed
613
    Core::ActionManager *actionManager = Core::ICore::instance()->actionManager();
hjk's avatar
hjk committed
614 615 616 617 618
    QTC_ASSERT(actionManager, return false);

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

hjk's avatar
hjk committed
619 620 621
    m_fakeVimOptionsPage = new FakeVimOptionPage;
    q->addObject(m_fakeVimOptionsPage);
    theFakeVimSettings()->readSettings(Core::ICore::instance()->settings());
622 623 624 625

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

con's avatar
con committed
627
    Core::Command *cmd = 0;
628
    cmd = actionManager->registerAction(theFakeVimSetting(ConfigUseFakeVim),
hjk's avatar
hjk committed
629 630 631
        Constants::INSTALL_HANDLER, globalcontext);
    cmd->setDefaultKeySequence(QKeySequence(Constants::INSTALL_KEY));

632
    ActionContainer *advancedMenu =
hjk's avatar
hjk committed
633
        actionManager->actionContainer(Core::Constants::M_EDIT_ADVANCED);
634
    advancedMenu->addAction(cmd, Core::Constants::G_EDIT_EDITOR);
hjk's avatar
hjk committed
635

636
    // EditorManager
hjk's avatar
hjk committed
637
    QObject *editorManager = Core::ICore::instance()->editorManager();
638 639 640 641 642
    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
643 644
    connect(theFakeVimSetting(SettingsDialog), SIGNAL(triggered()),
        this, SLOT(showSettingsDialog()));
645 646
    connect(theFakeVimSetting(ConfigUseFakeVim), SIGNAL(valueChanged(QVariant)),
        this, SLOT(setUseFakeVim(QVariant)));
hjk's avatar
hjk committed
647

648 649 650 651 652 653 654 655 656 657
    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()));

658 659 660 661 662 663
    // 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
664 665 666
    return true;
}

667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706
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
707 708
void FakeVimPluginPrivate::showSettingsDialog()
{
709 710
    Core::ICore::instance()->showOptionsDialog(QLatin1String(Constants::SETTINGS_CATEGORY),
                                               QLatin1String(Constants::SETTINGS_ID));
hjk's avatar
hjk committed
711 712
}

713 714 715 716 717 718 719 720 721 722 723
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();
}

724 725 726 727 728 729 730 731 732 733 734 735 736
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
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767
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;
    }
768 769 770 771 772
    triggerAction(code);
}

void FakeVimPluginPrivate::find(bool reverse)
{
773 774 775 776 777 778
    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
779 780
}

781 782 783 784 785 786 787 788
void FakeVimPluginPrivate::findNext(bool reverse)
{
    if (reverse)
        triggerAction(Find::Constants::FIND_PREVIOUS);
    else
        triggerAction(Find::Constants::FIND_NEXT);
}

789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812
// 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;
        }
    }
};

813
void FakeVimPluginPrivate::editorOpened(Core::IEditor *editor)
814
{
815 816 817
    if (!editor)
        return;

818
    QWidget *widget = editor->widget();
819 820
    if (!widget)
        return;
821 822 823 824

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

826 827 828
    //qDebug() << "OPENING: " << editor << editor->widget()
    //    << "MODE: " << theFakeVimSetting(ConfigUseFakeVim)->value();

829 830 831 832
    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);
833
    m_editorToHandler[editor] = handler;
834 835 836 837

    connect(handler, SIGNAL(extraInformationChanged(QString)),
        this, SLOT(showExtraInformation(QString)));
    connect(handler, SIGNAL(commandBufferChanged(QString)),
hjk's avatar
hjk committed
838
        this, SLOT(showCommandBuffer(QString)));
839 840 841 842
    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>)));
843 844
    connect(handler, SIGNAL(moveToMatchingParenthesis(bool*,bool*,QTextCursor*)),
        this, SLOT(moveToMatchingParenthesis(bool*,bool*,QTextCursor*)));
hjk's avatar
hjk committed
845 846
    connect(handler, SIGNAL(indentRegion(int*,int,int,QChar)),
        this, SLOT(indentRegion(int*,int,int,QChar)));
847 848
    connect(handler, SIGNAL(checkForElectricCharacter(bool*,QChar)),
        this, SLOT(checkForElectricCharacter(bool*,QChar)));
849 850
    connect(handler, SIGNAL(completionRequested()),
        this, SLOT(triggerCompletions()));
hjk's avatar
hjk committed
851 852
    connect(handler, SIGNAL(windowCommandRequested(int)),
        this, SLOT(windowCommand(int)));
853 854
    connect(handler, SIGNAL(findRequested(bool)),
        this, SLOT(find(bool)));
855 856
    connect(handler, SIGNAL(findNextRequested(bool)),
        this, SLOT(findNext(bool)));
857

858 859
    connect(handler, SIGNAL(handleExCommandRequested(QString)),
        this, SLOT(handleExCommand(QString)));
860 861
    connect(handler, SIGNAL(handleSetCommandRequested(bool *,QString)),
        this, SLOT(handleSetCommand(bool *,QString)));
862

hjk's avatar
hjk committed
863
    handler->setCurrentFileName(editor->file()->fileName());
864
    handler->installEventFilter();
865

866 867
    // pop up the bar
    if (theFakeVimSetting(ConfigUseFakeVim)->value().toBool())
hjk's avatar
hjk committed
868
       showCommandBuffer(QString());
hjk's avatar
hjk committed
869 870
}

871
void FakeVimPluginPrivate::editorAboutToClose(Core::IEditor *editor)
hjk's avatar
hjk committed
872
{
873 874 875 876 877 878
    //qDebug() << "CLOSING: " << editor << editor->widget();
    m_editorToHandler.remove(editor);
}

void FakeVimPluginPrivate::setUseFakeVim(const QVariant &value)
{
879
    //qDebug() << "SET USE FAKEVIM" << value;
880
    bool on = value.toBool();
881 882
    if (Find::Internal::FindPlugin::instance())
        Find::Internal::FindPlugin::instance()->setUseFakeVim(on);
883
    if (on) {
884 885
        Core::EditorManager::instance()->showEditorStatusBar(
            QLatin1String(Constants::MINI_BUFFER),
886
            "vi emulation mode. Type :q to leave. Use , Ctrl-R to trigger run.",
887
            tr("Quit FakeVim"), this, SLOT(quitFakeVim()));
888 889 890 891 892 893 894 895
        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
896 897
}

898 899 900 901 902 903
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
904 905 906
        TextEditor::Internal::CompletionSupport::instance()->
            autoComplete(bt->editableInterface(), false);
   //     bt->triggerCompletions();
907 908
}

909 910 911 912 913 914 915 916 917
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);
}

918 919
void FakeVimPluginPrivate::writeFile(bool *handled,
    const QString &fileName, const QString &contents)
920
{
921
    Q_UNUSED(contents)
922

923 924 925 926
    FakeVimHandler *handler = qobject_cast<FakeVimHandler *>(sender());
    if (!handler)
        return;

927
    Core::IEditor *editor = m_editorToHandler.key(handler);
928
    if (editor && editor->file()->fileName() == fileName) {
hjk's avatar
hjk committed
929
        // Handle that as a special case for nicer interaction with core
930
        Core::IFile *file = editor->file();
hjk's avatar
hjk committed
931
        Core::ICore::instance()->fileManager()->blockFileChange(file);
932
        file->save(fileName);
hjk's avatar
hjk committed
933
        Core::ICore::instance()->fileManager()->unblockFileChange(file);
934
        *handled = true;
935
    }
936 937
}

938 939 940 941 942 943 944 945 946 947 948 949 950 951 952
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);

953
    if (reWriteAll.indexIn(cmd) != -1) {
954 955 956 957 958 959 960
        // :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
961
            handler->showRedMessage(tr("%n files not saved", 0, failed.size()));
962 963 964 965 966 967 968 969 970
    } 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 {
971 972 973 974 975 976 977 978 979 980 981
        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;
            }
        }

982
        handler->showRedMessage(tr("Not an editor command: %1").arg(cmd));
983 984 985
    }
}

986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000
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;
    }
}

1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
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);
}

1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
void FakeVimPluginPrivate::moveToMatchingParenthesis(bool *moved, bool *forward,
        QTextCursor *cursor)
{
    *moved = false;

    bool undoFakeEOL = false;
    if (cursor->atBlockEnd() && cursor->block().length() > 1) {
        cursor->movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, 1);
        undoFakeEOL = true;
    }
    TextEditor::TextBlockUserData::MatchType match
        = TextEditor::TextBlockUserData::matchCursorForward(cursor);
    if (match == TextEditor::TextBlockUserData::Match) {
        *moved = true;
        *forward = true;
1028
    } else {
1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
        if (undoFakeEOL)
            cursor->movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 1);
        if (match == TextEditor::TextBlockUserData::NoMatch) {
            // backward matching is according to the character before the cursor
            bool undoMove = false;
            if (!cursor->atBlockEnd()) {
                cursor->movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 1);
                undoMove = true;
            }
            match = TextEditor::TextBlockUserData::matchCursorBackward(cursor);
            if (match == TextEditor::TextBlockUserData::Match) {
                *moved = true;
                *forward = false;
            } else if (undoMove) {
                cursor->movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, 1);
            }
        }
    }
}

hjk's avatar
hjk committed
1049
void FakeVimPluginPrivate::indentRegion(int *amount, int beginLine, int endLine,
1050 1051 1052 1053 1054 1055 1056 1057 1058 1059
      QChar typedChar)
{
    FakeVimHandler *handler = qobject_cast<FakeVimHandler *>(sender());
    if (!handler)
        return;

    BaseTextEditor *bt = qobject_cast<BaseTextEditor *>(handler->widget());
    if (!bt)
        return;

1060 1061 1062 1063
    TextEditor::TabSettings tabSettings;
    tabSettings.m_indentSize = theFakeVimSetting(ConfigShiftWidth)->value().toInt();
    tabSettings.m_tabSize = theFakeVimSetting(ConfigTabStop)->value().toInt();
    tabSettings.m_spacesForTabs = theFakeVimSetting(