fakevimplugin.cpp 39.2 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
        }

        item->setText(2, ci->m_regex);
        item->setData(0, Qt::UserRole, qVariantFromValue(ci));
393 394 395 396 397 398 399 400 401 402

        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);
        }

403 404 405 406 407 408 409 410
    }

    commandChanged(0);
}

void FakeVimExCommandsPage::commandChanged(QTreeWidgetItem *current)
{
    if (!current || !current->data(0, Qt::UserRole).isValid()) {
hjk's avatar
hjk committed
411
        m_ui.regexEdit->setText(QString());
412 413 414
        m_ui.seqGrp->setEnabled(false);
        return;
    }
415

416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
    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()
{
    QTreeWidgetItem *current = m_ui.commandList->currentItem();
432 433 434 435 436 437 438 439
    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()) {
440 441
        citem->m_regex = m_ui.regexEdit->text();
        current->setText(2, citem->m_regex);
442
        s_exCommandMap[name] = QRegExp(citem->m_regex);
443
    }
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460

    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);
    }

461 462 463 464 465 466 467 468 469
}

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

bool FakeVimExCommandsPage::filter(const QString &f, const QTreeWidgetItem *item)
{
470 471 472 473 474
    if (QTreeWidgetItem *parent = item->parent()) {
        if (parent->text(0).contains(f, Qt::CaseInsensitive))
            return false;
    }

475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
    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
508
            setRegex(QString());
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
    }
}

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
525
            item->m_regex.clear();
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
        }
        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
542 543
///////////////////////////////////////////////////////////////////////
//
544
// FakeVimPluginPrivate
hjk's avatar
hjk committed
545 546 547
//
///////////////////////////////////////////////////////////////////////

548 549 550 551
namespace FakeVim {
namespace Internal {

class FakeVimPluginPrivate : public QObject
hjk's avatar
hjk committed
552
{
553 554 555 556 557 558 559
    Q_OBJECT

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

560
    bool initialize();
561 562 563 564 565
    void shutdown();

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

567
    void setUseFakeVim(const QVariant &value);
568
    void quitFakeVim();
569
    void triggerCompletions();
hjk's avatar
hjk committed
570
    void windowCommand(int key);
571
    void find(bool reverse);
572
    void findNext(bool reverse);
hjk's avatar
hjk committed
573
    void showSettingsDialog();
574 575 576 577 578

    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);
579
    void moveToMatchingParenthesis(bool *moved, bool *forward, QTextCursor *cursor);
580
    void checkForElectricCharacter(bool *result, QChar c);
hjk's avatar
hjk committed
581
    void indentRegion(int *amount, int beginLine, int endLine,  QChar typedChar);
582
    void handleExCommand(const QString &cmd);
583
    void handleSetCommand(bool *handled, QString cmd);
584 585 586 587

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

588 589 590 591
    void switchFile(bool previous);
    void switchFileNext();
    void switchFilePrev();

592 593 594
signals:
    void delayedQuitRequested(bool forced, Core::IEditor *editor);
    void delayedQuitAllRequested(bool forced);
595 596 597

private:
    FakeVimPlugin *q;
hjk's avatar
hjk committed
598
    FakeVimOptionPage *m_fakeVimOptionsPage;
599
    FakeVimExCommandsPage *m_fakeVimExCommandsPage;
600
    QHash<Core::IEditor *, FakeVimHandler *> m_editorToHandler;
601 602

    void triggerAction(const QString& code);
603
    void setActionChecked(const QString& code, bool check);
604 605 606

    void readSettings(QSettings *settings);
    void writeSettings(QSettings *settings);
607 608 609 610 611 612
};

} // namespace Internal
} // namespace FakeVim

FakeVimPluginPrivate::FakeVimPluginPrivate(FakeVimPlugin *plugin)
613
{
614
    q = plugin;
hjk's avatar
hjk committed
615
    m_fakeVimOptionsPage = 0;
616 617
    m_fakeVimExCommandsPage = 0;

618 619
    s_defaultExCommandMap[Constants::CMD_FILE_NEXT] = QRegExp("^n(ext)?!?( (.*))?$");
    s_defaultExCommandMap[Constants::CMD_FILE_PREV] = QRegExp("^(N(ext)?|prev(ious)?)!?( (.*))?$");
620 621 622 623
    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
624 625
}

626 627 628
FakeVimPluginPrivate::~FakeVimPluginPrivate()
{
}
hjk's avatar
hjk committed
629

630
void FakeVimPluginPrivate::shutdown()
hjk's avatar
hjk committed
631
{
hjk's avatar
hjk committed
632 633 634 635
    q->removeObject(m_fakeVimOptionsPage);
    delete m_fakeVimOptionsPage;
    m_fakeVimOptionsPage = 0;
    theFakeVimSettings()->writeSettings(Core::ICore::instance()->settings());
dt's avatar
dt committed
636
    delete theFakeVimSettings();
637 638 639 640 641

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

644
bool FakeVimPluginPrivate::initialize()
hjk's avatar
hjk committed
645
{
hjk's avatar
hjk committed
646
    Core::ActionManager *actionManager = Core::ICore::instance()->actionManager();
hjk's avatar
hjk committed
647 648 649 650 651
    QTC_ASSERT(actionManager, return false);

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

hjk's avatar
hjk committed
652 653 654
    m_fakeVimOptionsPage = new FakeVimOptionPage;
    q->addObject(m_fakeVimOptionsPage);
    theFakeVimSettings()->readSettings(Core::ICore::instance()->settings());
655 656 657 658

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

con's avatar
con committed
660
    Core::Command *cmd = 0;
661
    cmd = actionManager->registerAction(theFakeVimSetting(ConfigUseFakeVim),
hjk's avatar
hjk committed
662 663 664
        Constants::INSTALL_HANDLER, globalcontext);
    cmd->setDefaultKeySequence(QKeySequence(Constants::INSTALL_KEY));

665
    ActionContainer *advancedMenu =
hjk's avatar
hjk committed
666
        actionManager->actionContainer(Core::Constants::M_EDIT_ADVANCED);
667
    advancedMenu->addAction(cmd, Core::Constants::G_EDIT_EDITOR);
hjk's avatar
hjk committed
668

669
    // EditorManager
hjk's avatar
hjk committed
670
    QObject *editorManager = Core::ICore::instance()->editorManager();
671 672 673 674 675
    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
676 677
    connect(theFakeVimSetting(SettingsDialog), SIGNAL(triggered()),
        this, SLOT(showSettingsDialog()));
678 679
    connect(theFakeVimSetting(ConfigUseFakeVim), SIGNAL(valueChanged(QVariant)),
        this, SLOT(setUseFakeVim(QVariant)));
hjk's avatar
hjk committed
680

681 682 683 684 685 686 687 688 689 690
    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()));

691 692 693 694 695 696
    // 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
697 698 699
    return true;
}

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 731 732 733 734 735 736 737 738 739
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
740 741
void FakeVimPluginPrivate::showSettingsDialog()
{
742 743
    Core::ICore::instance()->showOptionsDialog(QLatin1String(Constants::SETTINGS_CATEGORY),
                                               QLatin1String(Constants::SETTINGS_ID));
hjk's avatar
hjk committed
744 745
}

746 747 748 749 750 751 752 753 754 755 756
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();
}

757 758 759 760 761 762 763 764 765 766 767 768 769
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
770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800
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;
    }
801 802 803 804 805
    triggerAction(code);
}

void FakeVimPluginPrivate::find(bool reverse)
{
806 807 808 809 810 811
    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
812 813
}

814 815 816 817 818 819 820 821
void FakeVimPluginPrivate::findNext(bool reverse)
{
    if (reverse)
        triggerAction(Find::Constants::FIND_PREVIOUS);
    else
        triggerAction(Find::Constants::FIND_NEXT);
}

822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845
// 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;
        }
    }
};

846
void FakeVimPluginPrivate::editorOpened(Core::IEditor *editor)
847
{
848 849 850
    if (!editor)
        return;

851
    QWidget *widget = editor->widget();
852 853
    if (!widget)
        return;
854 855 856 857

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

859 860 861
    //qDebug() << "OPENING: " << editor << editor->widget()
    //    << "MODE: " << theFakeVimSetting(ConfigUseFakeVim)->value();

862 863 864 865
    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);
866
    m_editorToHandler[editor] = handler;
867 868 869 870

    connect(handler, SIGNAL(extraInformationChanged(QString)),
        this, SLOT(showExtraInformation(QString)));
    connect(handler, SIGNAL(commandBufferChanged(QString)),
hjk's avatar
hjk committed
871
        this, SLOT(showCommandBuffer(QString)));
872 873 874 875
    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>)));
876 877
    connect(handler, SIGNAL(moveToMatchingParenthesis(bool*,bool*,QTextCursor*)),
        this, SLOT(moveToMatchingParenthesis(bool*,bool*,QTextCursor*)));
hjk's avatar
hjk committed
878 879
    connect(handler, SIGNAL(indentRegion(int*,int,int,QChar)),
        this, SLOT(indentRegion(int*,int,int,QChar)));
880 881
    connect(handler, SIGNAL(checkForElectricCharacter(bool*,QChar)),
        this, SLOT(checkForElectricCharacter(bool*,QChar)));
882 883
    connect(handler, SIGNAL(completionRequested()),
        this, SLOT(triggerCompletions()));
hjk's avatar
hjk committed
884 885
    connect(handler, SIGNAL(windowCommandRequested(int)),
        this, SLOT(windowCommand(int)));
886 887
    connect(handler, SIGNAL(findRequested(bool)),
        this, SLOT(find(bool)));
888 889
    connect(handler, SIGNAL(findNextRequested(bool)),
        this, SLOT(findNext(bool)));
890

891 892
    connect(handler, SIGNAL(handleExCommandRequested(QString)),
        this, SLOT(handleExCommand(QString)));
893 894
    connect(handler, SIGNAL(handleSetCommandRequested(bool *,QString)),
        this, SLOT(handleSetCommand(bool *,QString)));
895

hjk's avatar
hjk committed
896
    handler->setCurrentFileName(editor->file()->fileName());
897
    handler->installEventFilter();
898

899 900
    // pop up the bar
    if (theFakeVimSetting(ConfigUseFakeVim)->value().toBool())
hjk's avatar
hjk committed
901
       showCommandBuffer(QString());
hjk's avatar
hjk committed
902 903
}

904
void FakeVimPluginPrivate::editorAboutToClose(Core::IEditor *editor)
hjk's avatar
hjk committed
905
{
906 907 908 909 910 911
    //qDebug() << "CLOSING: " << editor << editor->widget();
    m_editorToHandler.remove(editor);
}

void FakeVimPluginPrivate::setUseFakeVim(const QVariant &value)
{
912
    //qDebug() << "SET USE FAKEVIM" << value;
913
    bool on = value.toBool();
914 915
    if (Find::Internal::FindPlugin::instance())
        Find::Internal::FindPlugin::instance()->setUseFakeVim(on);
916
    if (on) {
917 918
        Core::EditorManager::instance()->showEditorStatusBar(
            QLatin1String(Constants::MINI_BUFFER),
919
            "vi emulation mode. Type :q to leave. Use , Ctrl-R to trigger run.",
920
            tr("Quit FakeVim"), this, SLOT(quitFakeVim()));
921 922 923 924 925 926 927 928
        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
929 930
}

931 932 933 934 935 936
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
937 938 939
        TextEditor::Internal::CompletionSupport::instance()->
            autoComplete(bt->editableInterface(), false);
   //     bt->triggerCompletions();
940 941
}

942 943 944 945 946 947 948 949 950
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);
}

951 952
void FakeVimPluginPrivate::writeFile(bool *handled,
    const QString &fileName, const QString &contents)
953
{
954
    Q_UNUSED(contents)
955

956 957 958 959
    FakeVimHandler *handler = qobject_cast<FakeVimHandler *>(sender());
    if (!handler)
        return;

960
    Core::IEditor *editor = m_editorToHandler.key(handler);
961
    if (editor && editor->file()->fileName() == fileName) {
hjk's avatar
hjk committed
962
        // Handle that as a special case for nicer interaction with core
963
        Core::IFile *file = editor->file();
hjk's avatar
hjk committed
964
        Core::ICore::instance()->fileManager()->blockFileChange(file);
965
        file->save(fileName);
hjk's avatar
hjk committed
966
        Core::ICore::instance()->fileManager()->unblockFileChange(file);
967
        *handled = true;
968
    }
969 970
}

971 972 973 974 975 976 977 978 979 980 981 982 983 984 985
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);

986
    if (reWriteAll.indexIn(cmd) != -1) {
987 988 989 990 991 992 993
        // :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
994
            handler->showRedMessage(tr("%n files not saved", 0, failed.size()));
995 996 997 998 999 1000 1001 1002 1003
    } 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 {
1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
        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;
            }
        }

1015
        handler->showRedMessage(tr("Not an editor command: %1").arg(cmd));
1016 1017 1018
    }
}

1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
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;
    }
}

1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
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);
}

1046 1047 1048 1049 1050 1051 1052 1053 1054