fakevimplugin.cpp 37.2 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"

35
#include <coreplugin/actionmanager/actionmanager.h>
36 37 38
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/actionmanager/command.h>
hjk's avatar
hjk committed
39 40
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
41
#include <coreplugin/editormanager/openeditorsmodel.h>
42
#include <coreplugin/filemanager.h>
hjk's avatar
hjk committed
43
#include <coreplugin/icore.h>
44
#include <coreplugin/ifile.h>
hjk's avatar
hjk committed
45
#include <coreplugin/dialogs/ioptionspage.h>
46
#include <coreplugin/actionmanager/commandmappings.h>
hjk's avatar
hjk committed
47 48 49 50 51 52 53 54
#include <coreplugin/messagemanager.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
55
#include <texteditor/completionsupport.h>
hjk's avatar
hjk committed
56 57
#include <texteditor/itexteditor.h>
#include <texteditor/texteditorconstants.h>
58 59
#include <texteditor/tabsettings.h>
#include <texteditor/texteditorsettings.h>
60
#include <texteditor/textblockiterator.h>
hjk's avatar
hjk committed
61

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

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

69 70
#include <cppeditor/cppeditorconstants.h>

71 72
#include <cpptools/cpptoolsconstants.h>

73 74
#include <indenter.h>

hjk's avatar
hjk committed
75
#include <QtCore/QDebug>
76
#include <QtCore/QtPlugin>
hjk's avatar
hjk committed
77 78 79
#include <QtCore/QObject>
#include <QtCore/QPoint>
#include <QtCore/QSettings>
80
#include <QtCore/QTextStream>
hjk's avatar
hjk committed
81

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

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


namespace FakeVim {
namespace Constants {

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

} // namespace Constants
} // namespace FakeVim


hjk's avatar
hjk committed
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
///////////////////////////////////////////////////////////////////////
//
// FakeVimOptionPage
//
///////////////////////////////////////////////////////////////////////

namespace FakeVim {
namespace Internal {

class FakeVimOptionPage : public Core::IOptionsPage
{
    Q_OBJECT

public:
    FakeVimOptionPage() {}

    // IOptionsPage
129
    QString id() const { return QLatin1String(Constants::SETTINGS_ID); }
130
    QString displayName() const { return tr("General"); }
131
    QString category() const { return QLatin1String(Constants::SETTINGS_CATEGORY); }
132
    QString displayCategory() const { return tr("FakeVim"); }
133
    QIcon categoryIcon() const { return QIcon(); } // TODO: Add an icon or move into another category
hjk's avatar
hjk committed
134 135 136 137

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

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

private:
    friend class DebuggerPlugin;
    Ui::FakeVimOptionPage m_ui;
148
    QString m_searchKeywords;
149
    Utils::SavedActionSet m_group;
hjk's avatar
hjk committed
150 151 152 153 154 155 156 157
};

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

    m_group.clear();
158
    m_group.insert(theFakeVimSetting(ConfigUseFakeVim),
159
        m_ui.checkBoxUseFakeVim);
hjk's avatar
hjk committed
160

161
    m_group.insert(theFakeVimSetting(ConfigExpandTab),
hjk's avatar
hjk committed
162
        m_ui.checkBoxExpandTab);
163
    m_group.insert(theFakeVimSetting(ConfigHlSearch),
hjk's avatar
hjk committed
164
        m_ui.checkBoxHlSearch);
165
    m_group.insert(theFakeVimSetting(ConfigShiftWidth),
hjk's avatar
hjk committed
166 167
        m_ui.lineEditShiftWidth);

168
    m_group.insert(theFakeVimSetting(ConfigSmartTab),
hjk's avatar
hjk committed
169
        m_ui.checkBoxSmartTab);
170
    m_group.insert(theFakeVimSetting(ConfigStartOfLine),
hjk's avatar
hjk committed
171
        m_ui.checkBoxStartOfLine);
172
    m_group.insert(theFakeVimSetting(ConfigTabStop),
hjk's avatar
hjk committed
173
        m_ui.lineEditTabStop);
174
    m_group.insert(theFakeVimSetting(ConfigBackspace),
hjk's avatar
hjk committed
175 176
        m_ui.lineEditBackspace);

177
    m_group.insert(theFakeVimSetting(ConfigAutoIndent),
hjk's avatar
hjk committed
178
        m_ui.checkBoxAutoIndent);
179
    m_group.insert(theFakeVimSetting(ConfigSmartIndent),
180
        m_ui.checkBoxSmartIndent);
181
    m_group.insert(theFakeVimSetting(ConfigIncSearch),
182
        m_ui.checkBoxIncSearch);
hjk's avatar
hjk committed
183 184

    connect(m_ui.pushButtonCopyTextEditorSettings, SIGNAL(clicked()),
hjk's avatar
hjk committed
185
        this, SLOT(copyTextEditorSettings()));
hjk's avatar
hjk committed
186 187 188 189
    connect(m_ui.pushButtonSetQtStyle, SIGNAL(clicked()),
        this, SLOT(setQtStyle()));
    connect(m_ui.pushButtonSetPlainStyle, SIGNAL(clicked()),
        this, SLOT(setPlainStyle()));
190 191
    if (m_searchKeywords.isEmpty()) {
        QTextStream(&m_searchKeywords)
hjk's avatar
hjk committed
192 193 194 195 196 197 198 199 200 201 202
            << ' ' << 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();
203 204
        m_searchKeywords.remove(QLatin1Char('&'));
    }
hjk's avatar
hjk committed
205 206 207 208 209
    return w;
}

void FakeVimOptionPage::copyTextEditorSettings()
{
210
    TextEditor::TabSettings ts =
hjk's avatar
hjk committed
211
        TextEditor::TextEditorSettings::instance()->tabSettings();
212

hjk's avatar
hjk committed
213 214 215 216
    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);
217 218
    m_ui.checkBoxAutoIndent->setChecked(true);
    m_ui.checkBoxSmartIndent->setChecked(ts.m_autoIndent);
219 220
    // FIXME: Not present in core
    //m_ui.checkBoxIncSearch->setChecked(ts.m_incSearch);
hjk's avatar
hjk committed
221 222 223 224 225
}

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

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

249 250 251 252 253
bool FakeVimOptionPage::matches(const QString &s) const
{
    return m_searchKeywords.contains(s, Qt::CaseInsensitive);
}

hjk's avatar
hjk committed
254 255 256 257
} // namespace Internal
} // namespace FakeVim


258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
///////////////////////////////////////////////////////////////////////
//
// 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;


280
class FakeVimExCommandsPage : public Core::CommandMappings
281 282 283 284 285 286 287 288 289 290 291
{
    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"); }
292
    QIcon categoryIcon() const { return QIcon(); } // TODO: Icon for FakeVim
293 294 295 296 297 298

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

public slots:
    void commandChanged(QTreeWidgetItem *current);
299 300 301
    void targetIdentifierChanged();
    void resetTargetIdentifier();
    void removeTargetIdentifier();
302 303 304 305 306 307 308 309 310
    void defaultAction();

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

QWidget *FakeVimExCommandsPage::createPage(QWidget *parent)
{
311 312 313 314 315
    QWidget *w = CommandMappings::createPage(parent);
    setPageTitle(tr("Ex Command Mapping"));
    setTargetHeader(tr("Ex Trigger Expression"));
    setTargetLabelText(tr("Regular Expression:"));
    setTargetEditTitle(tr("Ex Command"));
316

317
    setImportExportEnabled(false);
318 319 320 321 322 323 324 325 326 327 328

    return w;
}

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

329 330
    QMap<QString, QTreeWidgetItem *> sections;

331 332 333 334 335
    foreach (Command *c, am->commands()) {
        if (c->action() && c->action()->isSeparator())
            continue;

        CommandItem *ci = new CommandItem;
336
        QTreeWidgetItem *item = new QTreeWidgetItem;
337 338 339 340 341
        ci->m_cmd = c;
        ci->m_item = item;
        m_citems << ci;

        const QString name = uidm->stringForUniqueIdentifier(c->id());
342 343 344 345 346
        const int pos = name.indexOf(QLatin1Char('.'));
        const QString section = name.left(pos);
        const QString subId = name.mid(pos+1);

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

        item->setText(0, subId);
357 358 359 360 361 362 363 364 365 366 367

        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
368
            ci->m_regex.clear();
369 370 371 372
        }

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

374 375
        if (ci->m_regex != s_defaultExCommandMap[name].pattern())
            setModified(item, true);
376 377 378 379 380 381 382
    }

    commandChanged(0);
}

void FakeVimExCommandsPage::commandChanged(QTreeWidgetItem *current)
{
383 384 385
    CommandMappings::commandChanged(current);

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

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

392
void FakeVimExCommandsPage::targetIdentifierChanged()
393
{
394
    QTreeWidgetItem *current = commandList()->currentItem();
395 396 397 398 399 400 401 402
    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()) {
403
        citem->m_regex = targetEdit()->text();
404
        current->setText(2, citem->m_regex);
405
        s_exCommandMap[name] = QRegExp(citem->m_regex);
406
    }
407

408 409 410 411
    if (citem->m_regex != s_defaultExCommandMap[name].pattern())
        setModified(current, true);
    else
        setModified(current, false);
412

413 414 415 416
}

void FakeVimExCommandsPage::setRegex(const QString &regex)
{
417
    targetEdit()->setText(regex);
418 419
}

420
void FakeVimExCommandsPage::resetTargetIdentifier()
421 422
{
    UniqueIDManager *uidm = UniqueIDManager::instance();
423
    QTreeWidgetItem *current = commandList()->currentItem();
424 425 426 427 428 429
    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
430
            setRegex(QString());
431 432 433
    }
}

434
void FakeVimExCommandsPage::removeTargetIdentifier()
435
{
436
    targetEdit()->clear();
437 438 439 440 441 442 443 444 445 446
}

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
447
            item->m_regex.clear();
448
        }
449
        setModified(item->m_item, false);
450
        item->m_item->setText(2, item->m_regex);
451
        if (item->m_item == commandList()->currentItem())
452 453 454 455 456 457 458 459
            commandChanged(item->m_item);
    }
}

} // namespace Internal
} // namespace FakeVim


hjk's avatar
hjk committed
460 461
///////////////////////////////////////////////////////////////////////
//
462
// FakeVimPluginPrivate
hjk's avatar
hjk committed
463 464 465
//
///////////////////////////////////////////////////////////////////////

466 467 468 469
namespace FakeVim {
namespace Internal {

class FakeVimPluginPrivate : public QObject
hjk's avatar
hjk committed
470
{
471 472 473 474 475 476 477
    Q_OBJECT

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

478
    bool initialize();
479 480 481 482 483
    void shutdown();

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

485
    void setUseFakeVim(const QVariant &value);
486
    void quitFakeVim();
487
    void triggerCompletions();
hjk's avatar
hjk committed
488
    void windowCommand(int key);
489
    void find(bool reverse);
490
    void findNext(bool reverse);
hjk's avatar
hjk committed
491
    void showSettingsDialog();
492 493 494 495 496

    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);
497
    void moveToMatchingParenthesis(bool *moved, bool *forward, QTextCursor *cursor);
498
    void checkForElectricCharacter(bool *result, QChar c);
hjk's avatar
hjk committed
499
    void indentRegion(int *amount, int beginLine, int endLine,  QChar typedChar);
500
    void handleExCommand(const QString &cmd);
501
    void handleSetCommand(bool *handled, QString cmd);
502 503 504 505

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

506 507 508 509
    void switchFile(bool previous);
    void switchFileNext();
    void switchFilePrev();

510 511 512
signals:
    void delayedQuitRequested(bool forced, Core::IEditor *editor);
    void delayedQuitAllRequested(bool forced);
513 514 515

private:
    FakeVimPlugin *q;
hjk's avatar
hjk committed
516
    FakeVimOptionPage *m_fakeVimOptionsPage;
517
    FakeVimExCommandsPage *m_fakeVimExCommandsPage;
518
    QHash<Core::IEditor *, FakeVimHandler *> m_editorToHandler;
519 520

    void triggerAction(const QString& code);
521
    void setActionChecked(const QString& code, bool check);
522 523 524

    void readSettings(QSettings *settings);
    void writeSettings(QSettings *settings);
525 526 527 528 529 530
};

} // namespace Internal
} // namespace FakeVim

FakeVimPluginPrivate::FakeVimPluginPrivate(FakeVimPlugin *plugin)
531
{
532
    q = plugin;
hjk's avatar
hjk committed
533
    m_fakeVimOptionsPage = 0;
534 535
    m_fakeVimExCommandsPage = 0;

536 537
    s_defaultExCommandMap[Constants::CMD_FILE_NEXT] = QRegExp("^n(ext)?!?( (.*))?$");
    s_defaultExCommandMap[Constants::CMD_FILE_PREV] = QRegExp("^(N(ext)?|prev(ious)?)!?( (.*))?$");
538 539 540 541
    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)?!?( (.*))?$");
542 543
    s_defaultExCommandMap[CppEditor::Constants::JUMP_TO_DEFINITION] = QRegExp("^tag?$");
    s_defaultExCommandMap[Core::Constants::GO_BACK] = QRegExp("^pop?$");
hjk's avatar
hjk committed
544 545
}

546 547 548
FakeVimPluginPrivate::~FakeVimPluginPrivate()
{
}
hjk's avatar
hjk committed
549

550
void FakeVimPluginPrivate::shutdown()
hjk's avatar
hjk committed
551
{
hjk's avatar
hjk committed
552 553 554 555
    q->removeObject(m_fakeVimOptionsPage);
    delete m_fakeVimOptionsPage;
    m_fakeVimOptionsPage = 0;
    theFakeVimSettings()->writeSettings(Core::ICore::instance()->settings());
dt's avatar
dt committed
556
    delete theFakeVimSettings();
557 558 559 560 561

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

564
bool FakeVimPluginPrivate::initialize()
hjk's avatar
hjk committed
565
{
hjk's avatar
hjk committed
566
    Core::ActionManager *actionManager = Core::ICore::instance()->actionManager();
hjk's avatar
hjk committed
567 568 569 570 571
    QTC_ASSERT(actionManager, return false);

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

hjk's avatar
hjk committed
572 573 574
    m_fakeVimOptionsPage = new FakeVimOptionPage;
    q->addObject(m_fakeVimOptionsPage);
    theFakeVimSettings()->readSettings(Core::ICore::instance()->settings());
575 576 577 578

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

con's avatar
con committed
580
    Core::Command *cmd = 0;
581
    cmd = actionManager->registerAction(theFakeVimSetting(ConfigUseFakeVim),
hjk's avatar
hjk committed
582 583 584
        Constants::INSTALL_HANDLER, globalcontext);
    cmd->setDefaultKeySequence(QKeySequence(Constants::INSTALL_KEY));

585
    ActionContainer *advancedMenu =
hjk's avatar
hjk committed
586
        actionManager->actionContainer(Core::Constants::M_EDIT_ADVANCED);
587
    advancedMenu->addAction(cmd, Core::Constants::G_EDIT_EDITOR);
hjk's avatar
hjk committed
588

589
    // EditorManager
hjk's avatar
hjk committed
590
    QObject *editorManager = Core::ICore::instance()->editorManager();
591 592 593 594 595
    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
596 597
    connect(theFakeVimSetting(SettingsDialog), SIGNAL(triggered()),
        this, SLOT(showSettingsDialog()));
598 599
    connect(theFakeVimSetting(ConfigUseFakeVim), SIGNAL(valueChanged(QVariant)),
        this, SLOT(setUseFakeVim(QVariant)));
hjk's avatar
hjk committed
600

601 602 603 604 605 606 607 608 609 610
    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()));

611 612 613 614 615 616
    // 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
617 618 619
    return true;
}

620 621 622 623 624 625 626 627 628 629 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
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
660 661
void FakeVimPluginPrivate::showSettingsDialog()
{
662 663
    Core::ICore::instance()->showOptionsDialog(QLatin1String(Constants::SETTINGS_CATEGORY),
                                               QLatin1String(Constants::SETTINGS_ID));
hjk's avatar
hjk committed
664 665
}

666 667 668 669 670 671 672 673 674 675 676
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();
}

677 678 679 680 681 682 683 684 685 686 687 688 689
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
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
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
hjk's avatar
hjk committed
716
    //qDebug() << "RUNNING WINDOW COMMAND: " << key << code;
hjk's avatar
hjk committed
717
    if (code.isEmpty()) {
hjk's avatar
hjk committed
718
        //qDebug() << "UNKNOWN WINDOWS COMMAND: " << key;
hjk's avatar
hjk committed
719 720
        return;
    }
721 722 723 724 725
    triggerAction(code);
}

void FakeVimPluginPrivate::find(bool reverse)
{
726
    if (Find::FindPlugin *plugin = Find::FindPlugin::instance()) {
727 728
        plugin->setUseFakeVim(true);
        plugin->openFindToolBar(reverse
729 730
                ? Find::FindPlugin::FindBackward
                : Find::FindPlugin::FindForward);
731
    }
hjk's avatar
hjk committed
732 733
}

734 735 736 737 738 739 740 741
void FakeVimPluginPrivate::findNext(bool reverse)
{
    if (reverse)
        triggerAction(Find::Constants::FIND_PREVIOUS);
    else
        triggerAction(Find::Constants::FIND_NEXT);
}

742
// This class defers deletion of a child FakeVimHandler using deleteLater().
743 744 745 746 747 748 749 750
class DeferredDeleter : public QObject
{
    Q_OBJECT

    FakeVimHandler *m_handler;

public:
    DeferredDeleter(QObject *parent, FakeVimHandler *handler)
751
        : QObject(parent), m_handler(handler)
752 753 754 755 756 757 758 759 760 761 762 763
    {}

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

764
void FakeVimPluginPrivate::editorOpened(Core::IEditor *editor)
765
{
766 767 768
    if (!editor)
        return;

769
    QWidget *widget = editor->widget();
770 771
    if (!widget)
        return;
772 773 774 775

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

777 778 779
    //qDebug() << "OPENING: " << editor << editor->widget()
    //    << "MODE: " << theFakeVimSetting(ConfigUseFakeVim)->value();

780 781 782 783
    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);
784
    m_editorToHandler[editor] = handler;
785 786 787 788

    connect(handler, SIGNAL(extraInformationChanged(QString)),
        this, SLOT(showExtraInformation(QString)));
    connect(handler, SIGNAL(commandBufferChanged(QString)),
hjk's avatar
hjk committed
789
        this, SLOT(showCommandBuffer(QString)));
790 791 792 793
    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>)));
794 795
    connect(handler, SIGNAL(moveToMatchingParenthesis(bool*,bool*,QTextCursor*)),
        this, SLOT(moveToMatchingParenthesis(bool*,bool*,QTextCursor*)));
hjk's avatar
hjk committed
796 797
    connect(handler, SIGNAL(indentRegion(int*,int,int,QChar)),
        this, SLOT(indentRegion(int*,int,int,QChar)));
798 799
    connect(handler, SIGNAL(checkForElectricCharacter(bool*,QChar)),
        this, SLOT(checkForElectricCharacter(bool*,QChar)));
800 801
    connect(handler, SIGNAL(completionRequested()),
        this, SLOT(triggerCompletions()));
hjk's avatar
hjk committed
802 803
    connect(handler, SIGNAL(windowCommandRequested(int)),
        this, SLOT(windowCommand(int)));
804 805
    connect(handler, SIGNAL(findRequested(bool)),
        this, SLOT(find(bool)));
806 807
    connect(handler, SIGNAL(findNextRequested(bool)),
        this, SLOT(findNext(bool)));
808

809 810
    connect(handler, SIGNAL(handleExCommandRequested(QString)),
        this, SLOT(handleExCommand(QString)));
811 812
    connect(handler, SIGNAL(handleSetCommandRequested(bool *,QString)),
        this, SLOT(handleSetCommand(bool *,QString)));
813

hjk's avatar
hjk committed
814
    handler->setCurrentFileName(editor->file()->fileName());
815
    handler->installEventFilter();
816

817
    // pop up the bar
818
    if (theFakeVimSetting(ConfigUseFakeVim)->value().toBool()) {
hjk's avatar
hjk committed
819
       showCommandBuffer(QString());
820 821
       handler->setupWidget();
    }
hjk's avatar
hjk committed
822 823
}

824
void FakeVimPluginPrivate::editorAboutToClose(Core::IEditor *editor)
hjk's avatar
hjk committed
825
{
826 827 828 829 830 831
    //qDebug() << "CLOSING: " << editor << editor->widget();
    m_editorToHandler.remove(editor);
}

void FakeVimPluginPrivate::setUseFakeVim(const QVariant &value)
{
832
    //qDebug() << "SET USE FAKEVIM" << value;
833
    bool on = value.toBool();
834 835
    if (Find::FindPlugin::instance())
        Find::FindPlugin::instance()->setUseFakeVim(on);
836
    if (on) {
837 838
        Core::EditorManager::instance()->showEditorStatusBar(
            QLatin1String(Constants::MINI_BUFFER),
839
            "vi emulation mode. Type :q to leave. Use , Ctrl-R to trigger run.",
840
            tr("Quit FakeVim"), this, SLOT(quitFakeVim()));
841 842 843 844 845
        foreach (Core::IEditor *editor, m_editorToHandler.keys())
            m_editorToHandler[editor]->setupWidget();
    } else {
        Core::EditorManager::instance()->hideEditorStatusBar(
            QLatin1String(Constants::MINI_BUFFER));
846 847
        TextEditor::TabSettings ts =
            TextEditor::TextEditorSettings::instance()->tabSettings();
848
        foreach (Core::IEditor *editor, m_editorToHandler.keys())
849
            m_editorToHandler[editor]->restoreWidget(ts.m_tabSize);
850
    }
hjk's avatar
hjk committed
851 852
}

853 854 855 856 857 858
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
859 860 861
        TextEditor::Internal::CompletionSupport::instance()->
            autoComplete(bt->editableInterface(), false);
   //     bt->triggerCompletions();
862 863
}

864 865 866 867 868 869 870 871 872
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);
}

873 874
void FakeVimPluginPrivate::writeFile(bool *handled,
    const QString &fileName, const QString &contents)
875
{
876
    Q_UNUSED(contents)
877

878 879 880 881
    FakeVimHandler *handler = qobject_cast<FakeVimHandler *>(sender());
    if (!handler)
        return;

882
    Core::IEditor *editor = m_editorToHandler.key(handler);
883
    if (editor && editor->file()->fileName() == fileName) {
hjk's avatar
hjk committed
884
        // Handle that as a special case for nicer interaction with core
885
        Core::IFile *file = editor->file();
hjk's avatar
hjk committed
886
        Core::ICore::instance()->fileManager()->blockFileChange(file);
887
        file->save(fileName);
hjk's avatar
hjk committed
888
        Core::ICore::instance()->fileManager()->unblockFileChange(file);
889
        *handled = true;
890
    }
891 892
}

893 894 895 896 897 898 899 900 901 902 903 904 905 906 907
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);

908
    if (reWriteAll.indexIn(cmd) != -1) {
909 910 911 912 913 914 915
        // :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"));