<commit_before>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (<EMAIL>)
**
**
** GNU Lesser General Public License Usage
**
** 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.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at <EMAIL>.
**
**************************************************************************/

#include "fakevimplugin.h"

#include "fakevimhandler.h"
#include "ui_fakevimoptions.h"

#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/actionmanager/commandmappings.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/openeditorsmodel.h>
#include <coreplugin/filemanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/ifile.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/id.h>
#include <coreplugin/statusbarwidget.h>
#include <coreplugin/statusbarmanager.h>

#include <projectexplorer/projectexplorerconstants.h>

#include <texteditor/basetextdocumentlayout.h>
#include <texteditor/basetexteditor.h>
#include <texteditor/basetextmark.h>
#include <texteditor/texteditorconstants.h>
#include <texteditor/tabsettings.h>
#include <texteditor/tabpreferences.h>
#include <texteditor/texteditorsettings.h>
#include <texteditor/indenter.h>
#include <texteditor/codeassist/basicproposalitem.h>
#include <texteditor/codeassist/basicproposalitemlistmodel.h>
#include <texteditor/codeassist/completionassistprovider.h>
#include <texteditor/codeassist/iassistprocessor.h>
#include <texteditor/codeassist/iassistinterface.h>
#include <texteditor/codeassist/genericproposal.h>

#include <find/findplugin.h>
#include <find/textfindconstants.h>

#include <utils/qtcassert.h>
#include <utils/savedaction.h>
#include <utils/treewidgetcolumnstretcher.h>
#include <utils/stylehelper.h>

#include <cppeditor/cppeditorconstants.h>

#include <cpptools/cpptoolsconstants.h>

#include <QtCore/QAbstractTableModel>
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <QtCore/QtPlugin>
#include <QtCore/QObject>
#include <QtCore/QSettings>
#include <QtCore/QTextStream>

#include <QtGui/QDesktopServices>
#include <QtGui/QItemDelegate>
#include <QtGui/QMessageBox>
#include <QtGui/QPlainTextEdit>
#include <QtGui/QShortcut>
#include <QtGui/QTextBlock>
#include <QtGui/QTextCursor>
#include <QtGui/QTextEdit>
#include <QtGui/QTreeWidgetItem>

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

namespace FakeVim {
namespace Constants {

const char * const INSTALL_HANDLER                = "TextEditor.FakeVimHandler";
const char * const MINI_BUFFER                    = "TextEditor.FakeVimMiniBuffer";
const char * const INSTALL_KEY                    = "Alt+V,Alt+V";
const char * const SETTINGS_CATEGORY              = "D.FakeVim";
const char * const SETTINGS_CATEGORY_FAKEVIM_ICON = ":/core/images/category_fakevim.png";
const char * const SETTINGS_ID                    = "A.General";
const char * const SETTINGS_EX_CMDS_ID            = "B.ExCommands";
const char * const SETTINGS_USER_CMDS_ID          = "C.UserCommands";

} // namespace Constants
} // namespace FakeVim


namespace FakeVim {
namespace Internal {

class MiniBuffer : public QLabel
{
    Q_OBJECT

public:
    void setContents(const QString &contents, int cursorPos)
    {
        QString msg = contents;
        if (cursorPos != -1)
            msg = contents.left(cursorPos) + QChar(10073) + contents.mid(cursorPos);
        setText("  " + msg);
    }
};

class MiniBuffer1 : public QLineEdit
{
    Q_OBJECT

public:
    MiniBuffer1()
    {
        setFrame(false);
    }
    void showEvent(QShowEvent *ev)
    {
        QLineEdit::showEvent(ev);
        QColor color = Qt::black;
        QPalette pal = parentWidget()->palette();
        pal.setBrush(QPalette::All, QPalette::WindowText, color);
        pal.setBrush(QPalette::All, QPalette::ButtonText, color);
        pal.setBrush(QPalette::All, QPalette::Foreground, color);
        pal.setBrush(QPalette::All, QPalette::Background, color);
        //color.setAlpha(100);
        //pal.setBrush(QPalette::Disabled, QPalette::WindowText, color);
        //pal.setBrush(QPalette::Disabled, QPalette::ButtonText, color);
        //pal.setBrush(QPalette::Disabled, QPalette::Foreground, color);
        setPalette(pal);
    }
    void setContents(const QString &contents, int cursorPos)
    {
        setText(contents);
        setCursorPosition(cursorPos);
    }
};

///////////////////////////////////////////////////////////////////////
//
// FakeVimOptionPage
//
///////////////////////////////////////////////////////////////////////

typedef QMap<QString, QRegExp> ExCommandMap;
typedef QMap<int, QString> UserCommandMap;
typedef QLatin1String _;

class FakeVimOptionPage : public Core::IOptionsPage
{
    Q_OBJECT

public:
    FakeVimOptionPage() {}

    // IOptionsPage
    QString id() const { return _(Constants::SETTINGS_ID); }
    QString displayName() const { return tr("General"); }
    QString category() const { return _(Constants::SETTINGS_CATEGORY); }
    QString displayCategory() const { return tr("FakeVim"); }
    QIcon categoryIcon() const
        { return QIcon(_(Constants::SETTINGS_CATEGORY_FAKEVIM_ICON)); }

    QWidget *createPage(QWidget *parent);
    void apply() { m_group.apply(ICore::instance()->settings()); }
    void finish() { m_group.finish(); }
    virtual bool matches(const QString &) const;

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

private:
    friend class DebuggerPlugin;
    Ui::FakeVimOptionPage m_ui;
    QString m_searchKeywords;
    Utils::SavedActionSet m_group;
};

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

    m_group.clear();
    m_group.insert(theFakeVimSetting(ConfigUseFakeVim),
        m_ui.checkBoxUseFakeVim);
    m_group.insert(theFakeVimSetting(ConfigReadVimRc),
        m_ui.checkBoxReadVimRc);

    m_group.insert(theFakeVimSetting(ConfigExpandTab),
        m_ui.checkBoxExpandTab);
    m_group.insert(theFakeVimSetting(ConfigHlSearch),
        m_ui.checkBoxHlSearch);
    m_group.insert(theFakeVimSetting(ConfigShiftWidth),
        m_ui.spinBoxShiftWidth);
    m_group.insert(theFakeVimSetting(ConfigShowMarks),
        m_ui.checkBoxShowMarks);

    m_group.insert(theFakeVimSetting(ConfigSmartTab),
        m_ui.checkBoxSmartTab);
    m_group.insert(theFakeVimSetting(ConfigStartOfLine),
        m_ui.checkBoxStartOfLine);
    m_group.insert(theFakeVimSetting(ConfigTabStop),
        m_ui.spinBoxTabStop);
    m_group.insert(theFakeVimSetting(ConfigBackspace),
        m_ui.lineEditBackspace);
    m_group.insert(theFakeVimSetting(ConfigIsKeyword),
        m_ui.lineEditIsKeyword);

    m_group.insert(theFakeVimSetting(ConfigPassControlKey),
        m_ui.checkBoxPassControlKey);
    m_group.insert(theFakeVimSetting(ConfigAutoIndent),
        m_ui.checkBoxAutoIndent);
    m_group.insert(theFakeVimSetting(ConfigSmartIndent),
        m_ui.checkBoxSmartIndent);
    m_group.insert(theFakeVimSetting(ConfigIncSearch),
        m_ui.checkBoxIncSearch);
    m_group.insert(theFakeVimSetting(ConfigUseCoreSearch),
        m_ui.checkBoxUseCoreSearch);

    connect(m_ui.pushButtonCopyTextEditorSettings, SIGNAL(clicked()),
        SLOT(copyTextEditorSettings()));
    connect(m_ui.pushButtonSetQtStyle, SIGNAL(clicked()),
        SLOT(setQtStyle()));
    connect(m_ui.pushButtonSetPlainStyle, SIGNAL(clicked()),
        SLOT(setPlainStyle()));

    if (m_searchKeywords.isEmpty()) {
        QLatin1Char sep(' ');
        QTextStream(&m_searchKeywords)
                << sep << m_ui.checkBoxUseFakeVim->text()
                << sep << m_ui.checkBoxReadVimRc->text()
                << sep << m_ui.checkBoxAutoIndent->text()
                << sep << m_ui.checkBoxSmartIndent->text()
                << sep << m_ui.checkBoxExpandTab->text()
                << sep << m_ui.checkBoxSmartTab->text()
                << sep << m_ui.checkBoxHlSearch->text()
                << sep << m_ui.checkBoxIncSearch->text()
                << sep << m_ui.checkBoxStartOfLine->text()
                << sep << m_ui.checkBoxUseCoreSearch->text()
                << sep << m_ui.checkBoxShowMarks->text()
                << sep << m_ui.checkBoxPassControlKey->text()
                << sep << m_ui.labelShiftWidth->text()
                << sep << m_ui.labelTabulator->text()
                << sep << m_ui.labelBackspace->text()
                << sep << m_ui.labelIsKeyword->text();
        m_searchKeywords.remove(QLatin1Char('&'));
    }
    return w;
}

void FakeVimOptionPage::copyTextEditorSettings()
{
    TabSettings ts = TextEditorSettings::instance()->tabPreferences()->settings();
    m_ui.checkBoxExpandTab->setChecked(ts.m_spacesForTabs);
    m_ui.spinBoxTabStop->setValue(ts.m_tabSize);
    m_ui.spinBoxShiftWidth->setValue(ts.m_indentSize);
    m_ui.checkBoxSmartTab->setChecked(
        ts.m_smartBackspaceBehavior == TabSettings::BackspaceFollowsPreviousIndents);
    m_ui.checkBoxAutoIndent->setChecked(true);
    m_ui.checkBoxSmartIndent->setChecked(ts.m_autoIndent);
    m_ui.checkBoxIncSearch->setChecked(true);
}

void FakeVimOptionPage::setQtStyle()
{
    m_ui.checkBoxExpandTab->setChecked(true);
    m_ui.spinBoxTabStop->setValue(4);
    m_ui.spinBoxShiftWidth->setValue(4);
    m_ui.checkBoxSmartTab->setChecked(true);
    m_ui.checkBoxAutoIndent->setChecked(true);
    m_ui.checkBoxSmartIndent->setChecked(true);
    m_ui.checkBoxIncSearch->setChecked(true);
    m_ui.lineEditBackspace->setText(_("indent,eol,start"));
}

void FakeVimOptionPage::setPlainStyle()
{
    m_ui.checkBoxExpandTab->setChecked(false);
    m_ui.spinBoxTabStop->setValue(8);
    m_ui.spinBoxShiftWidth->setValue(8);
    m_ui.checkBoxSmartTab->setChecked(false);
    m_ui.checkBoxAutoIndent->setChecked(false);
    m_ui.checkBoxSmartIndent->setChecked(false);
    m_ui.checkBoxIncSearch->setChecked(false);
    m_ui.lineEditBackspace->setText(QString());
}

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

//const char *FAKEVIM_CONTEXT = "FakeVim";

///////////////////////////////////////////////////////////////////////
//
// FakeVimExCommandsPage
//
///////////////////////////////////////////////////////////////////////

enum { CommandRole = Qt::UserRole };

class FakeVimExCommandsPage : public Core::CommandMappings
{
    Q_OBJECT

public:
    FakeVimExCommandsPage(FakeVimPluginPrivate *q) : m_q(q) {}
    ~FakeVimExCommandsPage() {}

    // IOptionsPage
    QString id() const { return _(Constants::SETTINGS_EX_CMDS_ID); }
    QString displayName() const { return tr("Ex Command Mapping"); }
    QString category() const { return _(Constants::SETTINGS_CATEGORY); }
    QString displayCategory() const { return tr("FakeVim"); }
    QIcon categoryIcon() const { return QIcon(); } // TODO: Icon for FakeVim

    QWidget *createPage(QWidget *parent);
    void initialize();
    ExCommandMap &exCommandMap();
    ExCommandMap &defaultExCommandMap();

public slots:
    void commandChanged(QTreeWidgetItem *current);
    void targetIdentifierChanged();
    void resetTargetIdentifier();
    void removeTargetIdentifier();
    void defaultAction();

private:
    FakeVimPluginPrivate *m_q;
};

QWidget *FakeVimExCommandsPage::createPage(QWidget *parent)
{
    QWidget *w = CommandMappings::createPage(parent);
    setPageTitle(tr("Ex Command Mapping"));
    setTargetHeader(tr("Ex Trigger Expression"));
    setTargetLabelText(tr("Regular expression:"));
    setTargetEditTitle(tr("Ex Command"));
    setImportExportEnabled(false);
    return w;
}

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

    QMap<QString, QTreeWidgetItem *> sections;

    foreach (Command *c, am->commands()) {
        if (c->action() && c->action()->isSeparator())
            continue;

        QTreeWidgetItem *item = new QTreeWidgetItem;
        const QString name = Id::fromUniqueIdentifier(c->id()).toString();
        const int pos = name.indexOf(QLatin1Char('.'));
        const QString section = name.left(pos);
        const QString subId = name.mid(pos + 1);
        item->setData(0, CommandRole, name);

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

        item->setText(0, subId);

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

        QString regex;
        if (exCommandMap().contains(name))
            regex = exCommandMap()[name].pattern();
        item->setText(2, regex);

        if (regex != defaultExCommandMap()[name].pattern())
            setModified(item, true);
    }

    commandChanged(0);
}

void FakeVimExCommandsPage::commandChanged(QTreeWidgetItem *current)
{
    CommandMappings::commandChanged(current);
    if (current)
        targetEdit()->setText(current->text(2));
}

void FakeVimExCommandsPage::targetIdentifierChanged()
{
    QTreeWidgetItem *current = commandList()->currentItem();
    if (!current)
        return;

    const QString name =  current->data(0, CommandRole).toString();
    const QString regex = targetEdit()->text();

    if (current->data(0, Qt::UserRole).isValid()) {
        current->setText(2, regex);
        exCommandMap()[name] = QRegExp(regex);
    }

    setModified(current, regex != defaultExCommandMap()[name].pattern());
}

void FakeVimExCommandsPage::resetTargetIdentifier()
{
    QTreeWidgetItem *current = commandList()->currentItem();
    if (!current)
        return;
    const QString name = current->data(0, CommandRole).toString();
    QString regex;
    if (defaultExCommandMap().contains(name))
        regex = defaultExCommandMap()[name].pattern();
    targetEdit()->setText(regex);
}

void FakeVimExCommandsPage::removeTargetIdentifier()
{
    targetEdit()->clear();
}

void FakeVimExCommandsPage::defaultAction()
{
    int n = commandList()->topLevelItemCount();
    for (int i = 0; i != n; ++i) {
        QTreeWidgetItem *section = commandList()->topLevelItem(i);
        int m = section->childCount();
        for (int j = 0; j != m; ++j) {
            QTreeWidgetItem *item = section->child(j);
            const QString name = item->data(0, CommandRole).toString();
            QString regex;
            if (defaultExCommandMap().contains(name))
                regex = defaultExCommandMap()[name].pattern();
            setModified(item, false);
            item->setText(2, regex);
            if (item == commandList()->currentItem())
                commandChanged(item);
        }
    }
}

///////////////////////////////////////////////////////////////////////
//
// FakeVimUserCommandsPage
//
///////////////////////////////////////////////////////////////////////

class FakeVimUserCommandsModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    FakeVimUserCommandsModel(FakeVimPluginPrivate *q) : m_q(q) {}
    ~FakeVimUserCommandsModel() {}

    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;
    QVariant data(const QModelIndex &index, int role) const;
    bool setData(const QModelIndex &index, const QVariant &data, int role);
    QVariant headerData(int section, Qt::Orientation orientation, int role) const;
    Qt::ItemFlags flags(const QModelIndex &index) const;

private:
    FakeVimPluginPrivate *m_q;
};

int FakeVimUserCommandsModel::rowCount(const QModelIndex &parent) const
{
    return parent.isValid() ? 0 : 9;
}

int FakeVimUserCommandsModel::columnCount(const QModelIndex &parent) const
{
    return parent.isValid() ? 0 : 2;
}


QVariant FakeVimUserCommandsModel::headerData(int section,
    Qt::Orientation orient, int role) const
{
    if (orient == Qt::Horizontal && role == Qt::DisplayRole) {
        switch (section) {
            case 0: return tr("Action");
            case 1: return tr("Command");
        };
    }
    return QVariant();
}

Qt::ItemFlags FakeVimUserCommandsModel::flags(const QModelIndex &index) const
{
    if (index.column() == 1)
        return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
    return QAbstractTableModel::flags(index);
}

class FakeVimUserCommandsDelegate : public QItemDelegate
{
public:
    explicit FakeVimUserCommandsDelegate(QObject *parent)
        : QItemDelegate(parent)
    {}

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &,
        const QModelIndex &) const
    {
        QLineEdit *lineEdit = new QLineEdit(parent);
        lineEdit->setFrame(false);
        return lineEdit;
    }

    void setModelData(QWidget *editor, QAbstractItemModel *model,
                      const QModelIndex &index) const
    {
        QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor);
        QTC_ASSERT(lineEdit, return);
        model->setData(index, lineEdit->text(), Qt::EditRole);
    }
};

class FakeVimUserCommandsPage : public Core::IOptionsPage
{
    Q_OBJECT

public:
    FakeVimUserCommandsPage(FakeVimPluginPrivate *q) : m_q(q) {}
    ~FakeVimUserCommandsPage() {}

    // IOptionsPage
    QString id() const { return _(Constants::SETTINGS_USER_CMDS_ID); }
    QString displayName() const { return tr("User Command Mapping"); }
    QString category() const { return _(Constants::SETTINGS_CATEGORY); }
    QString displayCategory() const { return tr("FakeVim"); }
    QIcon categoryIcon() const { return QIcon(); }
    void apply();
    void finish() {}

    QWidget *createPage(QWidget *parent);
    void initialize() {}
    UserCommandMap &userCommandMap();
    UserCommandMap &defaultUserCommandMap();

private:
    FakeVimPluginPrivate *m_q;
};

QWidget *FakeVimUserCommandsPage::createPage(QWidget *parent)
{
    QGroupBox *box = new QGroupBox(parent);

    FakeVimUserCommandsModel *model = new FakeVimUserCommandsModel(m_q);
    QTreeView *widget = new QTreeView;
    widget->setModel(model);
    widget->resizeColumnToContents(0);

    FakeVimUserCommandsDelegate *delegate = new FakeVimUserCommandsDelegate(widget);
    widget->setItemDelegateForColumn(1, delegate);

    QGridLayout *layout = new QGridLayout(box);
    layout->addWidget(widget, 0, 0);
    box->setLayout(layout);

    return box;
}

void FakeVimUserCommandsPage::apply()
{
    //m_q->writeSettings();
}


///////////////////////////////////////////////////////////////////////
//
// WordCompletion
//
///////////////////////////////////////////////////////////////////////

class FakeVimCompletionAssistProvider : public TextEditor::CompletionAssistProvider
{
public:
    virtual bool supportsEditor(const QString &) const
    {
        return false;
    }

    virtual TextEditor::IAssistProcessor *createProcessor() const;

    void setActive(const QString &needle, bool forward, FakeVimHandler *handler)
    {
        Q_UNUSED(forward);
        m_handler = handler;
        if (!m_handler)
            return;

        BaseTextEditorWidget *editor = qobject_cast<BaseTextEditorWidget *>(handler->widget());
        if (!editor)
            return;

        //qDebug() << "ACTIVATE: " << needle << forward;
        m_needle = needle;
        editor->invokeAssist(Completion, this);
    }

    void setInactive()
    {
        m_needle.clear();
        m_handler = 0;
    }

    const QString &needle() const
    {
        return m_needle;
    }

    void appendNeedle(const QChar &c)
    {
        m_needle.append(c);
    }

    FakeVimHandler *handler() const
    {
        return m_handler;
    }

private:
    FakeVimHandler *m_handler;
    QString m_needle;
};

class FakeVimAssistProposalItem : public BasicProposalItem
{
public:
    FakeVimAssistProposalItem(const FakeVimCompletionAssistProvider *provider)
        : m_provider(const_cast<FakeVimCompletionAssistProvider *>(provider))
    {}

    virtual bool implicitlyApplies() const
    {
        return false;
    }

    virtual bool prematurelyApplies(const QChar &c) const
    {
        m_provider->appendNeedle(c);
        return text() == m_provider->needle();
    }

    virtual void applyContextualContent(BaseTextEditor *, int) const
    {
        QTC_ASSERT(m_provider->handler(), return);
        m_provider->handler()->handleReplay(text().mid(m_provider->needle().size()));
        const_cast<FakeVimCompletionAssistProvider *>(m_provider)->setInactive();
    }

private:
    FakeVimCompletionAssistProvider *m_provider;
};


class FakeVimAssistProposalModel : public BasicProposalItemListModel
{
public:
    FakeVimAssistProposalModel(const QList<BasicProposalItem *> &items)
        : BasicProposalItemListModel(items)
    {}

    virtual bool supportsPrefixExpansion() const
    {
        return false;
    }
};

class FakeVimCompletionAssistProcessor : public IAssistProcessor
{
public:
    FakeVimCompletionAssistProcessor(const TextEditor::IAssistProvider *provider)
        : m_provider(static_cast<const FakeVimCompletionAssistProvider *>(provider))
    {}

    virtual TextEditor::IAssistProposal *perform(const IAssistInterface *interface)
    {
        const QString &needle = m_provider->needle();

        const int basePosition = interface->position() - needle.size();

        QTextCursor tc(interface->document());
        tc.setPosition(interface->position());
        tc.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);

        QList<BasicProposalItem *> items;
        QSet<QString> seen;
        QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively;
        while (1) {
            tc = tc.document()->find(needle, tc.position(), flags);
            if (tc.isNull())
                break;
            QTextCursor sel = tc;
            sel.select(QTextCursor::WordUnderCursor);
            QString found = sel.selectedText();
            // Only add "real" completions.
            if (found.startsWith(needle)
                    && !seen.contains(found)
                    && sel.anchor() != basePosition) {
                seen.insert(found);
                BasicProposalItem *item = new FakeVimAssistProposalItem(m_provider);
                item->setText(found);
                items.append(item);
            }
            tc.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor);
        }
        //qDebug() << "COMPLETIONS" << completions->size();

        delete interface;
        return new GenericProposal(basePosition, new FakeVimAssistProposalModel(items));
    }

private:
    const FakeVimCompletionAssistProvider *m_provider;
};

IAssistProcessor *FakeVimCompletionAssistProvider::createProcessor() const
{
    return new FakeVimCompletionAssistProcessor(this);
}


///////////////////////////////////////////////////////////////////////
//
// FakeVimPluginPrivate
//
///////////////////////////////////////////////////////////////////////

class FakeVimPluginPrivate : public QObject
{
    Q_OBJECT

public:
    FakeVimPluginPrivate(FakeVimPlugin *);
    ~FakeVimPluginPrivate();
    friend class FakeVimPlugin;
    friend class FakeVimExCommandsPage;
    friend class FakeVimUserCommandsPage;
    friend class FakeVimUserCommandsModel;

    bool initialize();
    void aboutToShutdown();

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

    void setUseFakeVim(const QVariant &value);
    void setUseFakeVimInternal(bool on);
    void quitFakeVim();
    void triggerCompletions();
    void triggerSimpleCompletions(const QString &needle, bool forward);
    void windowCommand(int key);
    void find(bool reverse);
    void findNext(bool reverse);
    void showSettingsDialog();
    void maybeReadVimRc();
    void setBlockSelection(bool);
    void hasBlockSelection(bool*);

    void showCommandBuffer(const QString &contents, int cursorPos);
    void showExtraInformation(const QString &msg);
    void changeSelection(const QList<QTextEdit::ExtraSelection> &selections);
    void moveToMatchingParenthesis(bool *moved, bool *forward, QTextCursor *cursor);
    void checkForElectricCharacter(bool *result, QChar c);
    void indentRegion(int beginLine, int endLine, QChar typedChar);
    void handleExCommand(bool *handled, const ExCommand &cmd);

    void writeSettings();
    void readSettings();

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

    void switchToFile(int n);
    int currentFile() const;

signals:
    void delayedQuitRequested(bool forced, Core::IEditor *editor);
    void delayedQuitAllRequested(bool forced);

private:
    FakeVimPlugin *q;
    FakeVimOptionPage *m_fakeVimOptionsPage;
    FakeVimExCommandsPage *m_fakeVimExCommandsPage;
    FakeVimUserCommandsPage *m_fakeVimUserCommandsPage;
    QHash<Core::IEditor *, FakeVimHandler *> m_editorToHandler;

    void triggerAction(const QString &code);
    void setActionChecked(const QString &code, bool check);

    typedef int (*DistFunction)(const QRect &cursor, const QRect &other);
    void moveSomewhere(DistFunction f);

    ExCommandMap &exCommandMap() { return m_exCommandMap; }
    ExCommandMap &defaultExCommandMap() { return m_defaultExCommandMap; }
    ExCommandMap m_exCommandMap;
    ExCommandMap m_defaultExCommandMap;

    UserCommandMap &userCommandMap() { return m_userCommandMap; }
    UserCommandMap &defaultUserCommandMap() { return m_defaultUserCommandMap; }
    UserCommandMap m_userCommandMap;
    UserCommandMap m_defaultUserCommandMap;

    Core::StatusBarWidget *m_statusBar;
    // @TODO: Delete
    //WordCompletion *m_wordCompletion;
    FakeVimCompletionAssistProvider *m_wordProvider;
};

QVariant FakeVimUserCommandsModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (role == Qt::DisplayRole || role == Qt::EditRole) {
        switch (index.column()) {
        case 0: // Action
            return tr("User command #%1").arg(index.row() + 1);
        case 1: // Command
            return m_q->userCommandMap().value(index.row() + 1);
        }
    }

    return QVariant();
}

bool FakeVimUserCommandsModel::setData(const QModelIndex &index,
    const QVariant &data, int role)
{
    if (role == Qt::DisplayRole || role == Qt::EditRole)
        if (index.column() == 1)
            m_q->userCommandMap()[index.row() + 1] = data.toString();
    return true;
}

FakeVimPluginPrivate::FakeVimPluginPrivate(FakeVimPlugin *plugin)
{
    q = plugin;
    m_fakeVimOptionsPage = 0;
    m_fakeVimExCommandsPage = 0;
    m_fakeVimUserCommandsPage = 0;
    defaultExCommandMap()[CppTools::Constants::SWITCH_HEADER_SOURCE] =
        QRegExp("^A$");
    defaultExCommandMap()["Coreplugin.OutputPane.previtem"] =
        QRegExp("^(cN(ext)?|cp(revious)?)!?( (.*))?$");
    defaultExCommandMap()["Coreplugin.OutputPane.nextitem"] =
        QRegExp("^cn(ext)?!?( (.*))?$");
    defaultExCommandMap()[CppEditor::Constants::JUMP_TO_DEFINITION] =
        QRegExp("^tag?$");
    defaultExCommandMap()[Core::Constants::GO_BACK] =
        QRegExp("^pop?$");
    defaultExCommandMap()[_("QtCreator.Locate")] =
        QRegExp("^e$");

    for (int i = 1; i < 10; ++i) {
        QString cmd = QString::fromLatin1(":echo User command %1 executed.<CR>");
        defaultUserCommandMap().insert(i, cmd.arg(i));
    }

    m_statusBar = 0;
}

FakeVimPluginPrivate::~FakeVimPluginPrivate()
{
    q->removeObject(m_fakeVimOptionsPage);
    delete m_fakeVimOptionsPage;
    m_fakeVimOptionsPage = 0;
    delete theFakeVimSettings();

    q->removeObject(m_fakeVimExCommandsPage);
    delete m_fakeVimExCommandsPage;
    m_fakeVimExCommandsPage = 0;

    q->removeObject(m_fakeVimUserCommandsPage);
    delete m_fakeVimUserCommandsPage;
    m_fakeVimUserCommandsPage = 0;
}

void FakeVimPluginPrivate::onCoreAboutToClose()
{
    // Don't attach to editors anymore.
    disconnect(ICore::instance()->editorManager(), SIGNAL(editorOpened(Core::IEditor*)),
        this, SLOT(editorOpened(Core::IEditor*)));
}

void FakeVimPluginPrivate::aboutToShutdown()
{
}

bool FakeVimPluginPrivate::initialize()
{
    Core::ICore *core = Core::ICore::instance();
    EditorManager *editorManager = core->editorManager();
    ActionManager *actionManager = core->actionManager();
    QTC_ASSERT(actionManager, return false);

    //m_wordCompletion = new WordCompletion;
    //q->addAutoReleasedObject(m_wordCompletion);
    m_wordProvider = new FakeVimCompletionAssistProvider;

/*
    // Set completion settings and keep them up to date.
    TextEditorSettings *textEditorSettings = TextEditorSettings::instance();
    completion->setCompletionSettings(textEditorSettings->completionSettings());
    connect(textEditorSettings,
        SIGNAL(completionSettingsChanged(TextEditor::CompletionSettings)),
        completion,
        SLOT(setCompletionSettings(TextEditor::CompletionSettings)));
*/

    Context globalcontext(Core::Constants::C_GLOBAL);

    m_fakeVimOptionsPage = new FakeVimOptionPage;
    q->addObject(m_fakeVimOptionsPage);

    m_fakeVimExCommandsPage = new FakeVimExCommandsPage(this);
    q->addObject(m_fakeVimExCommandsPage);

    m_fakeVimUserCommandsPage = new FakeVimUserCommandsPage(this);
    q->addObject(m_fakeVimUserCommandsPage);

    readSettings();

    Core::Command *cmd = 0;
    cmd = actionManager->registerAction(theFakeVimSetting(ConfigUseFakeVim),
        Constants::INSTALL_HANDLER, globalcontext);
    cmd->setDefaultKeySequence(QKeySequence(Constants::INSTALL_KEY));

    ActionContainer *advancedMenu =
        actionManager->actionContainer(Core::Constants::M_EDIT_ADVANCED);
    advancedMenu->addAction(cmd, Core::Constants::G_EDIT_EDITOR);

    for (int i = 1; i < 10; ++i) {
        QAction *act = new QAction(this);
        act->setText(QString("Execute User Action #%1").arg(i));
        act->setData(i);
        QString id = QString("FakeVim.UserAction%1").arg(i);
        QString keys = QString("<KEY>").arg(i);
        cmd = actionManager->registerAction(act, id, globalcontext);
        cmd->setDefaultKeySequence(QKeySequence(keys));
        connect(act, SIGNAL(triggered()), SLOT(userActionTriggered()));
    }<commit_msg>fakevim: make toggle action scriptable<commit_after>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (<EMAIL>)
**
**
** GNU Lesser General Public License Usage
**
** 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.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at <EMAIL>.
**
**************************************************************************/

#include "fakevimplugin.h"

#include "fakevimhandler.h"
#include "ui_fakevimoptions.h"

#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/actionmanager/commandmappings.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/openeditorsmodel.h>
#include <coreplugin/filemanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/ifile.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/id.h>
#include <coreplugin/statusbarwidget.h>
#include <coreplugin/statusbarmanager.h>

#include <projectexplorer/projectexplorerconstants.h>

#include <texteditor/basetextdocumentlayout.h>
#include <texteditor/basetexteditor.h>
#include <texteditor/basetextmark.h>
#include <texteditor/texteditorconstants.h>
#include <texteditor/tabsettings.h>
#include <texteditor/tabpreferences.h>
#include <texteditor/texteditorsettings.h>
#include <texteditor/indenter.h>
#include <texteditor/codeassist/basicproposalitem.h>
#include <texteditor/codeassist/basicproposalitemlistmodel.h>
#include <texteditor/codeassist/completionassistprovider.h>
#include <texteditor/codeassist/iassistprocessor.h>
#include <texteditor/codeassist/iassistinterface.h>
#include <texteditor/codeassist/genericproposal.h>

#include <find/findplugin.h>
#include <find/textfindconstants.h>

#include <utils/qtcassert.h>
#include <utils/savedaction.h>
#include <utils/treewidgetcolumnstretcher.h>
#include <utils/stylehelper.h>

#include <cppeditor/cppeditorconstants.h>

#include <cpptools/cpptoolsconstants.h>

#include <QtCore/QAbstractTableModel>
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <QtCore/QtPlugin>
#include <QtCore/QObject>
#include <QtCore/QSettings>
#include <QtCore/QTextStream>

#include <QtGui/QDesktopServices>
#include <QtGui/QItemDelegate>
#include <QtGui/QMessageBox>
#include <QtGui/QPlainTextEdit>
#include <QtGui/QShortcut>
#include <QtGui/QTextBlock>
#include <QtGui/QTextCursor>
#include <QtGui/QTextEdit>
#include <QtGui/QTreeWidgetItem>

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

namespace FakeVim {
namespace Constants {

const char * const INSTALL_HANDLER                = "TextEditor.FakeVimHandler";
const char * const MINI_BUFFER                    = "TextEditor.FakeVimMiniBuffer";
const char * const INSTALL_KEY                    = "Alt+V,Alt+V";
const char * const SETTINGS_CATEGORY              = "D.FakeVim";
const char * const SETTINGS_CATEGORY_FAKEVIM_ICON = ":/core/images/category_fakevim.png";
const char * const SETTINGS_ID                    = "A.General";
const char * const SETTINGS_EX_CMDS_ID            = "B.ExCommands";
const char * const SETTINGS_USER_CMDS_ID          = "C.UserCommands";

} // namespace Constants
} // namespace FakeVim


namespace FakeVim {
namespace Internal {

class MiniBuffer : public QLabel
{
    Q_OBJECT

public:
    void setContents(const QString &contents, int cursorPos)
    {
        QString msg = contents;
        if (cursorPos != -1)
            msg = contents.left(cursorPos) + QChar(10073) + contents.mid(cursorPos);
        setText("  " + msg);
    }
};

class MiniBuffer1 : public QLineEdit
{
    Q_OBJECT

public:
    MiniBuffer1()
    {
        setFrame(false);
    }
    void showEvent(QShowEvent *ev)
    {
        QLineEdit::showEvent(ev);
        QColor color = Qt::black;
        QPalette pal = parentWidget()->palette();
        pal.setBrush(QPalette::All, QPalette::WindowText, color);
        pal.setBrush(QPalette::All, QPalette::ButtonText, color);
        pal.setBrush(QPalette::All, QPalette::Foreground, color);
        pal.setBrush(QPalette::All, QPalette::Background, color);
        //color.setAlpha(100);
        //pal.setBrush(QPalette::Disabled, QPalette::WindowText, color);
        //pal.setBrush(QPalette::Disabled, QPalette::ButtonText, color);
        //pal.setBrush(QPalette::Disabled, QPalette::Foreground, color);
        setPalette(pal);
    }
    void setContents(const QString &contents, int cursorPos)
    {
        setText(contents);
        setCursorPosition(cursorPos);
    }
};

///////////////////////////////////////////////////////////////////////
//
// FakeVimOptionPage
//
///////////////////////////////////////////////////////////////////////

typedef QMap<QString, QRegExp> ExCommandMap;
typedef QMap<int, QString> UserCommandMap;
typedef QLatin1String _;

class FakeVimOptionPage : public Core::IOptionsPage
{
    Q_OBJECT

public:
    FakeVimOptionPage() {}

    // IOptionsPage
    QString id() const { return _(Constants::SETTINGS_ID); }
    QString displayName() const { return tr("General"); }
    QString category() const { return _(Constants::SETTINGS_CATEGORY); }
    QString displayCategory() const { return tr("FakeVim"); }
    QIcon categoryIcon() const
        { return QIcon(_(Constants::SETTINGS_CATEGORY_FAKEVIM_ICON)); }

    QWidget *createPage(QWidget *parent);
    void apply() { m_group.apply(ICore::instance()->settings()); }
    void finish() { m_group.finish(); }
    virtual bool matches(const QString &) const;

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

private:
    friend class DebuggerPlugin;
    Ui::FakeVimOptionPage m_ui;
    QString m_searchKeywords;
    Utils::SavedActionSet m_group;
};

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

    m_group.clear();
    m_group.insert(theFakeVimSetting(ConfigUseFakeVim),
        m_ui.checkBoxUseFakeVim);
    m_group.insert(theFakeVimSetting(ConfigReadVimRc),
        m_ui.checkBoxReadVimRc);

    m_group.insert(theFakeVimSetting(ConfigExpandTab),
        m_ui.checkBoxExpandTab);
    m_group.insert(theFakeVimSetting(ConfigHlSearch),
        m_ui.checkBoxHlSearch);
    m_group.insert(theFakeVimSetting(ConfigShiftWidth),
        m_ui.spinBoxShiftWidth);
    m_group.insert(theFakeVimSetting(ConfigShowMarks),
        m_ui.checkBoxShowMarks);

    m_group.insert(theFakeVimSetting(ConfigSmartTab),
        m_ui.checkBoxSmartTab);
    m_group.insert(theFakeVimSetting(ConfigStartOfLine),
        m_ui.checkBoxStartOfLine);
    m_group.insert(theFakeVimSetting(ConfigTabStop),
        m_ui.spinBoxTabStop);
    m_group.insert(theFakeVimSetting(ConfigBackspace),
        m_ui.lineEditBackspace);
    m_group.insert(theFakeVimSetting(ConfigIsKeyword),
        m_ui.lineEditIsKeyword);

    m_group.insert(theFakeVimSetting(ConfigPassControlKey),
        m_ui.checkBoxPassControlKey);
    m_group.insert(theFakeVimSetting(ConfigAutoIndent),
        m_ui.checkBoxAutoIndent);
    m_group.insert(theFakeVimSetting(ConfigSmartIndent),
        m_ui.checkBoxSmartIndent);
    m_group.insert(theFakeVimSetting(ConfigIncSearch),
        m_ui.checkBoxIncSearch);
    m_group.insert(theFakeVimSetting(ConfigUseCoreSearch),
        m_ui.checkBoxUseCoreSearch);

    connect(m_ui.pushButtonCopyTextEditorSettings, SIGNAL(clicked()),
        SLOT(copyTextEditorSettings()));
    connect(m_ui.pushButtonSetQtStyle, SIGNAL(clicked()),
        SLOT(setQtStyle()));
    connect(m_ui.pushButtonSetPlainStyle, SIGNAL(clicked()),
        SLOT(setPlainStyle()));

    if (m_searchKeywords.isEmpty()) {
        QLatin1Char sep(' ');
        QTextStream(&m_searchKeywords)
                << sep << m_ui.checkBoxUseFakeVim->text()
                << sep << m_ui.checkBoxReadVimRc->text()
                << sep << m_ui.checkBoxAutoIndent->text()
                << sep << m_ui.checkBoxSmartIndent->text()
                << sep << m_ui.checkBoxExpandTab->text()
                << sep << m_ui.checkBoxSmartTab->text()
                << sep << m_ui.checkBoxHlSearch->text()
                << sep << m_ui.checkBoxIncSearch->text()
                << sep << m_ui.checkBoxStartOfLine->text()
                << sep << m_ui.checkBoxUseCoreSearch->text()
                << sep << m_ui.checkBoxShowMarks->text()
                << sep << m_ui.checkBoxPassControlKey->text()
                << sep << m_ui.labelShiftWidth->text()
                << sep << m_ui.labelTabulator->text()
                << sep << m_ui.labelBackspace->text()
                << sep << m_ui.labelIsKeyword->text();
        m_searchKeywords.remove(QLatin1Char('&'));
    }
    return w;
}

void FakeVimOptionPage::copyTextEditorSettings()
{
    TabSettings ts = TextEditorSettings::instance()->tabPreferences()->settings();
    m_ui.checkBoxExpandTab->setChecked(ts.m_spacesForTabs);
    m_ui.spinBoxTabStop->setValue(ts.m_tabSize);
    m_ui.spinBoxShiftWidth->setValue(ts.m_indentSize);
    m_ui.checkBoxSmartTab->setChecked(
        ts.m_smartBackspaceBehavior == TabSettings::BackspaceFollowsPreviousIndents);
    m_ui.checkBoxAutoIndent->setChecked(true);
    m_ui.checkBoxSmartIndent->setChecked(ts.m_autoIndent);
    m_ui.checkBoxIncSearch->setChecked(true);
}

void FakeVimOptionPage::setQtStyle()
{
    m_ui.checkBoxExpandTab->setChecked(true);
    m_ui.spinBoxTabStop->setValue(4);
    m_ui.spinBoxShiftWidth->setValue(4);
    m_ui.checkBoxSmartTab->setChecked(true);
    m_ui.checkBoxAutoIndent->setChecked(true);
    m_ui.checkBoxSmartIndent->setChecked(true);
    m_ui.checkBoxIncSearch->setChecked(true);
    m_ui.lineEditBackspace->setText(_("indent,eol,start"));
}

void FakeVimOptionPage::setPlainStyle()
{
    m_ui.checkBoxExpandTab->setChecked(false);
    m_ui.spinBoxTabStop->setValue(8);
    m_ui.spinBoxShiftWidth->setValue(8);
    m_ui.checkBoxSmartTab->setChecked(false);
    m_ui.checkBoxAutoIndent->setChecked(false);
    m_ui.checkBoxSmartIndent->setChecked(false);
    m_ui.checkBoxIncSearch->setChecked(false);
    m_ui.lineEditBackspace->setText(QString());
}

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

//const char *FAKEVIM_CONTEXT = "FakeVim";

///////////////////////////////////////////////////////////////////////
//
// FakeVimExCommandsPage
//
///////////////////////////////////////////////////////////////////////

enum { CommandRole = Qt::UserRole };

class FakeVimExCommandsPage : public Core::CommandMappings
{
    Q_OBJECT

public:
    FakeVimExCommandsPage(FakeVimPluginPrivate *q) : m_q(q) {}
    ~FakeVimExCommandsPage() {}

    // IOptionsPage
    QString id() const { return _(Constants::SETTINGS_EX_CMDS_ID); }
    QString displayName() const { return tr("Ex Command Mapping"); }
    QString category() const { return _(Constants::SETTINGS_CATEGORY); }
    QString displayCategory() const { return tr("FakeVim"); }
    QIcon categoryIcon() const { return QIcon(); } // TODO: Icon for FakeVim

    QWidget *createPage(QWidget *parent);
    void initialize();
    ExCommandMap &exCommandMap();
    ExCommandMap &defaultExCommandMap();

public slots:
    void commandChanged(QTreeWidgetItem *current);
    void targetIdentifierChanged();
    void resetTargetIdentifier();
    void removeTargetIdentifier();
    void defaultAction();

private:
    FakeVimPluginPrivate *m_q;
};

QWidget *FakeVimExCommandsPage::createPage(QWidget *parent)
{
    QWidget *w = CommandMappings::createPage(parent);
    setPageTitle(tr("Ex Command Mapping"));
    setTargetHeader(tr("Ex Trigger Expression"));
    setTargetLabelText(tr("Regular expression:"));
    setTargetEditTitle(tr("Ex Command"));
    setImportExportEnabled(false);
    return w;
}

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

    QMap<QString, QTreeWidgetItem *> sections;

    foreach (Command *c, am->commands()) {
        if (c->action() && c->action()->isSeparator())
            continue;

        QTreeWidgetItem *item = new QTreeWidgetItem;
        const QString name = Id::fromUniqueIdentifier(c->id()).toString();
        const int pos = name.indexOf(QLatin1Char('.'));
        const QString section = name.left(pos);
        const QString subId = name.mid(pos + 1);
        item->setData(0, CommandRole, name);

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

        item->setText(0, subId);

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

        QString regex;
        if (exCommandMap().contains(name))
            regex = exCommandMap()[name].pattern();
        item->setText(2, regex);

        if (regex != defaultExCommandMap()[name].pattern())
            setModified(item, true);
    }

    commandChanged(0);
}

void FakeVimExCommandsPage::commandChanged(QTreeWidgetItem *current)
{
    CommandMappings::commandChanged(current);
    if (current)
        targetEdit()->setText(current->text(2));
}

void FakeVimExCommandsPage::targetIdentifierChanged()
{
    QTreeWidgetItem *current = commandList()->currentItem();
    if (!current)
        return;

    const QString name =  current->data(0, CommandRole).toString();
    const QString regex = targetEdit()->text();

    if (current->data(0, Qt::UserRole).isValid()) {
        current->setText(2, regex);
        exCommandMap()[name] = QRegExp(regex);
    }

    setModified(current, regex != defaultExCommandMap()[name].pattern());
}

void FakeVimExCommandsPage::resetTargetIdentifier()
{
    QTreeWidgetItem *current = commandList()->currentItem();
    if (!current)
        return;
    const QString name = current->data(0, CommandRole).toString();
    QString regex;
    if (defaultExCommandMap().contains(name))
        regex = defaultExCommandMap()[name].pattern();
    targetEdit()->setText(regex);
}

void FakeVimExCommandsPage::removeTargetIdentifier()
{
    targetEdit()->clear();
}

void FakeVimExCommandsPage::defaultAction()
{
    int n = commandList()->topLevelItemCount();
    for (int i = 0; i != n; ++i) {
        QTreeWidgetItem *section = commandList()->topLevelItem(i);
        int m = section->childCount();
        for (int j = 0; j != m; ++j) {
            QTreeWidgetItem *item = section->child(j);
            const QString name = item->data(0, CommandRole).toString();
            QString regex;
            if (defaultExCommandMap().contains(name))
                regex = defaultExCommandMap()[name].pattern();
            setModified(item, false);
            item->setText(2, regex);
            if (item == commandList()->currentItem())
                commandChanged(item);
        }
    }
}

///////////////////////////////////////////////////////////////////////
//
// FakeVimUserCommandsPage
//
///////////////////////////////////////////////////////////////////////

class FakeVimUserCommandsModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    FakeVimUserCommandsModel(FakeVimPluginPrivate *q) : m_q(q) {}
    ~FakeVimUserCommandsModel() {}

    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;
    QVariant data(const QModelIndex &index, int role) const;
    bool setData(const QModelIndex &index, const QVariant &data, int role);
    QVariant headerData(int section, Qt::Orientation orientation, int role) const;
    Qt::ItemFlags flags(const QModelIndex &index) const;

private:
    FakeVimPluginPrivate *m_q;
};

int FakeVimUserCommandsModel::rowCount(const QModelIndex &parent) const
{
    return parent.isValid() ? 0 : 9;
}

int FakeVimUserCommandsModel::columnCount(const QModelIndex &parent) const
{
    return parent.isValid() ? 0 : 2;
}


QVariant FakeVimUserCommandsModel::headerData(int section,
    Qt::Orientation orient, int role) const
{
    if (orient == Qt::Horizontal && role == Qt::DisplayRole) {
        switch (section) {
            case 0: return tr("Action");
            case 1: return tr("Command");
        };
    }
    return QVariant();
}

Qt::ItemFlags FakeVimUserCommandsModel::flags(const QModelIndex &index) const
{
    if (index.column() == 1)
        return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
    return QAbstractTableModel::flags(index);
}

class FakeVimUserCommandsDelegate : public QItemDelegate
{
public:
    explicit FakeVimUserCommandsDelegate(QObject *parent)
        : QItemDelegate(parent)
    {}

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &,
        const QModelIndex &) const
    {
        QLineEdit *lineEdit = new QLineEdit(parent);
        lineEdit->setFrame(false);
        return lineEdit;
    }

    void setModelData(QWidget *editor, QAbstractItemModel *model,
                      const QModelIndex &index) const
    {
        QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor);
        QTC_ASSERT(lineEdit, return);
        model->setData(index, lineEdit->text(), Qt::EditRole);
    }
};

class FakeVimUserCommandsPage : public Core::IOptionsPage
{
    Q_OBJECT

public:
    FakeVimUserCommandsPage(FakeVimPluginPrivate *q) : m_q(q) {}
    ~FakeVimUserCommandsPage() {}

    // IOptionsPage
    QString id() const { return _(Constants::SETTINGS_USER_CMDS_ID); }
    QString displayName() const { return tr("User Command Mapping"); }
    QString category() const { return _(Constants::SETTINGS_CATEGORY); }
    QString displayCategory() const { return tr("FakeVim"); }
    QIcon categoryIcon() const { return QIcon(); }
    void apply();
    void finish() {}

    QWidget *createPage(QWidget *parent);
    void initialize() {}
    UserCommandMap &userCommandMap();
    UserCommandMap &defaultUserCommandMap();

private:
    FakeVimPluginPrivate *m_q;
};

QWidget *FakeVimUserCommandsPage::createPage(QWidget *parent)
{
    QGroupBox *box = new QGroupBox(parent);

    FakeVimUserCommandsModel *model = new FakeVimUserCommandsModel(m_q);
    QTreeView *widget = new QTreeView;
    widget->setModel(model);
    widget->resizeColumnToContents(0);

    FakeVimUserCommandsDelegate *delegate = new FakeVimUserCommandsDelegate(widget);
    widget->setItemDelegateForColumn(1, delegate);

    QGridLayout *layout = new QGridLayout(box);
    layout->addWidget(widget, 0, 0);
    box->setLayout(layout);

    return box;
}

void FakeVimUserCommandsPage::apply()
{
    //m_q->writeSettings();
}


///////////////////////////////////////////////////////////////////////
//
// WordCompletion
//
///////////////////////////////////////////////////////////////////////

class FakeVimCompletionAssistProvider : public TextEditor::CompletionAssistProvider
{
public:
    virtual bool supportsEditor(const QString &) const
    {
        return false;
    }

    virtual TextEditor::IAssistProcessor *createProcessor() const;

    void setActive(const QString &needle, bool forward, FakeVimHandler *handler)
    {
        Q_UNUSED(forward);
        m_handler = handler;
        if (!m_handler)
            return;

        BaseTextEditorWidget *editor = qobject_cast<BaseTextEditorWidget *>(handler->widget());
        if (!editor)
            return;

        //qDebug() << "ACTIVATE: " << needle << forward;
        m_needle = needle;
        editor->invokeAssist(Completion, this);
    }

    void setInactive()
    {
        m_needle.clear();
        m_handler = 0;
    }

    const QString &needle() const
    {
        return m_needle;
    }

    void appendNeedle(const QChar &c)
    {
        m_needle.append(c);
    }

    FakeVimHandler *handler() const
    {
        return m_handler;
    }

private:
    FakeVimHandler *m_handler;
    QString m_needle;
};

class FakeVimAssistProposalItem : public BasicProposalItem
{
public:
    FakeVimAssistProposalItem(const FakeVimCompletionAssistProvider *provider)
        : m_provider(const_cast<FakeVimCompletionAssistProvider *>(provider))
    {}

    virtual bool implicitlyApplies() const
    {
        return false;
    }

    virtual bool prematurelyApplies(const QChar &c) const
    {
        m_provider->appendNeedle(c);
        return text() == m_provider->needle();
    }

    virtual void applyContextualContent(BaseTextEditor *, int) const
    {
        QTC_ASSERT(m_provider->handler(), return);
        m_provider->handler()->handleReplay(text().mid(m_provider->needle().size()));
        const_cast<FakeVimCompletionAssistProvider *>(m_provider)->setInactive();
    }

private:
    FakeVimCompletionAssistProvider *m_provider;
};


class FakeVimAssistProposalModel : public BasicProposalItemListModel
{
public:
    FakeVimAssistProposalModel(const QList<BasicProposalItem *> &items)
        : BasicProposalItemListModel(items)
    {}

    virtual bool supportsPrefixExpansion() const
    {
        return false;
    }
};

class FakeVimCompletionAssistProcessor : public IAssistProcessor
{
public:
    FakeVimCompletionAssistProcessor(const TextEditor::IAssistProvider *provider)
        : m_provider(static_cast<const FakeVimCompletionAssistProvider *>(provider))
    {}

    virtual TextEditor::IAssistProposal *perform(const IAssistInterface *interface)
    {
        const QString &needle = m_provider->needle();

        const int basePosition = interface->position() - needle.size();

        QTextCursor tc(interface->document());
        tc.setPosition(interface->position());
        tc.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);

        QList<BasicProposalItem *> items;
        QSet<QString> seen;
        QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively;
        while (1) {
            tc = tc.document()->find(needle, tc.position(), flags);
            if (tc.isNull())
                break;
            QTextCursor sel = tc;
            sel.select(QTextCursor::WordUnderCursor);
            QString found = sel.selectedText();
            // Only add "real" completions.
            if (found.startsWith(needle)
                    && !seen.contains(found)
                    && sel.anchor() != basePosition) {
                seen.insert(found);
                BasicProposalItem *item = new FakeVimAssistProposalItem(m_provider);
                item->setText(found);
                items.append(item);
            }
            tc.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor);
        }
        //qDebug() << "COMPLETIONS" << completions->size();

        delete interface;
        return new GenericProposal(basePosition, new FakeVimAssistProposalModel(items));
    }

private:
    const FakeVimCompletionAssistProvider *m_provider;
};

IAssistProcessor *FakeVimCompletionAssistProvider::createProcessor() const
{
    return new FakeVimCompletionAssistProcessor(this);
}


///////////////////////////////////////////////////////////////////////
//
// FakeVimPluginPrivate
//
///////////////////////////////////////////////////////////////////////

class FakeVimPluginPrivate : public QObject
{
    Q_OBJECT

public:
    FakeVimPluginPrivate(FakeVimPlugin *);
    ~FakeVimPluginPrivate();
    friend class FakeVimPlugin;
    friend class FakeVimExCommandsPage;
    friend class FakeVimUserCommandsPage;
    friend class FakeVimUserCommandsModel;

    bool initialize();
    void aboutToShutdown();

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

    void setUseFakeVim(const QVariant &value);
    void setUseFakeVimInternal(bool on);
    void quitFakeVim();
    void triggerCompletions();
    void triggerSimpleCompletions(const QString &needle, bool forward);
    void windowCommand(int key);
    void find(bool reverse);
    void findNext(bool reverse);
    void showSettingsDialog();
    void maybeReadVimRc();
    void setBlockSelection(bool);
    void hasBlockSelection(bool*);

    void showCommandBuffer(const QString &contents, int cursorPos);
    void showExtraInformation(const QString &msg);
    void changeSelection(const QList<QTextEdit::ExtraSelection> &selections);
    void moveToMatchingParenthesis(bool *moved, bool *forward, QTextCursor *cursor);
    void checkForElectricCharacter(bool *result, QChar c);
    void indentRegion(int beginLine, int endLine, QChar typedChar);
    void handleExCommand(bool *handled, const ExCommand &cmd);

    void writeSettings();
    void readSettings();

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

    void switchToFile(int n);
    int currentFile() const;

signals:
    void delayedQuitRequested(bool forced, Core::IEditor *editor);
    void delayedQuitAllRequested(bool forced);

private:
    FakeVimPlugin *q;
    FakeVimOptionPage *m_fakeVimOptionsPage;
    FakeVimExCommandsPage *m_fakeVimExCommandsPage;
    FakeVimUserCommandsPage *m_fakeVimUserCommandsPage;
    QHash<Core::IEditor *, FakeVimHandler *> m_editorToHandler;

    void triggerAction(const QString &code);
    void setActionChecked(const QString &code, bool check);

    typedef int (*DistFunction)(const QRect &cursor, const QRect &other);
    void moveSomewhere(DistFunction f);

    ExCommandMap &exCommandMap() { return m_exCommandMap; }
    ExCommandMap &defaultExCommandMap() { return m_defaultExCommandMap; }
    ExCommandMap m_exCommandMap;
    ExCommandMap m_defaultExCommandMap;

    UserCommandMap &userCommandMap() { return m_userCommandMap; }
    UserCommandMap &defaultUserCommandMap() { return m_defaultUserCommandMap; }
    UserCommandMap m_userCommandMap;
    UserCommandMap m_defaultUserCommandMap;

    Core::StatusBarWidget *m_statusBar;
    // @TODO: Delete
    //WordCompletion *m_wordCompletion;
    FakeVimCompletionAssistProvider *m_wordProvider;
};

QVariant FakeVimUserCommandsModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (role == Qt::DisplayRole || role == Qt::EditRole) {
        switch (index.column()) {
        case 0: // Action
            return tr("User command #%1").arg(index.row() + 1);
        case 1: // Command
            return m_q->userCommandMap().value(index.row() + 1);
        }
    }

    return QVariant();
}

bool FakeVimUserCommandsModel::setData(const QModelIndex &index,
    const QVariant &data, int role)
{
    if (role == Qt::DisplayRole || role == Qt::EditRole)
        if (index.column() == 1)
            m_q->userCommandMap()[index.row() + 1] = data.toString();
    return true;
}

FakeVimPluginPrivate::FakeVimPluginPrivate(FakeVimPlugin *plugin)
{
    q = plugin;
    m_fakeVimOptionsPage = 0;
    m_fakeVimExCommandsPage = 0;
    m_fakeVimUserCommandsPage = 0;
    defaultExCommandMap()[CppTools::Constants::SWITCH_HEADER_SOURCE] =
        QRegExp("^A$");
    defaultExCommandMap()["Coreplugin.OutputPane.previtem"] =
        QRegExp("^(cN(ext)?|cp(revious)?)!?( (.*))?$");
    defaultExCommandMap()["Coreplugin.OutputPane.nextitem"] =
        QRegExp("^cn(ext)?!?( (.*))?$");
    defaultExCommandMap()[CppEditor::Constants::JUMP_TO_DEFINITION] =
        QRegExp("^tag?$");
    defaultExCommandMap()[Core::Constants::GO_BACK] =
        QRegExp("^pop?$");
    defaultExCommandMap()[_("QtCreator.Locate")] =
        QRegExp("^e$");

    for (int i = 1; i < 10; ++i) {
        QString cmd = QString::fromLatin1(":echo User command %1 executed.<CR>");
        defaultUserCommandMap().insert(i, cmd.arg(i));
    }

    m_statusBar = 0;
}

FakeVimPluginPrivate::~FakeVimPluginPrivate()
{
    q->removeObject(m_fakeVimOptionsPage);
    delete m_fakeVimOptionsPage;
    m_fakeVimOptionsPage = 0;
    delete theFakeVimSettings();

    q->removeObject(m_fakeVimExCommandsPage);
    delete m_fakeVimExCommandsPage;
    m_fakeVimExCommandsPage = 0;

    q->removeObject(m_fakeVimUserCommandsPage);
    delete m_fakeVimUserCommandsPage;
    m_fakeVimUserCommandsPage = 0;
}

void FakeVimPluginPrivate::onCoreAboutToClose()
{
    // Don't attach to editors anymore.
    disconnect(ICore::instance()->editorManager(), SIGNAL(editorOpened(Core::IEditor*)),
        this, SLOT(editorOpened(Core::IEditor*)));
}

void FakeVimPluginPrivate::aboutToShutdown()
{
}

bool FakeVimPluginPrivate::initialize()
{
    Core::ICore *core = Core::ICore::instance();
    EditorManager *editorManager = core->editorManager();
    ActionManager *actionManager = core->actionManager();
    QTC_ASSERT(actionManager, return false);

    //m_wordCompletion = new WordCompletion;
    //q->addAutoReleasedObject(m_wordCompletion);
    m_wordProvider = new FakeVimCompletionAssistProvider;

/*
    // Set completion settings and keep them up to date.
    TextEditorSettings *textEditorSettings = TextEditorSettings::instance();
    completion->setCompletionSettings(textEditorSettings->completionSettings());
    connect(textEditorSettings,
        SIGNAL(completionSettingsChanged(TextEditor::CompletionSettings)),
        completion,
        SLOT(setCompletionSettings(TextEditor::CompletionSettings)));
*/

    Context globalcontext(Core::Constants::C_GLOBAL);

    m_fakeVimOptionsPage = new FakeVimOptionPage;
    q->addObject(m_fakeVimOptionsPage);

    m_fakeVimExCommandsPage = new FakeVimExCommandsPage(this);
    q->addObject(m_fakeVimExCommandsPage);

    m_fakeVimUserCommandsPage = new FakeVimUserCommandsPage(this);
    q->addObject(m_fakeVimUserCommandsPage);

    readSettings();

    Core::Command *cmd = 0;
    cmd = actionManager->registerAction(theFakeVimSetting(ConfigUseFakeVim),
        Constants::INSTALL_HANDLER, globalcontext, true);
    cmd->setDefaultKeySequence(QKeySequence(Constants::INSTALL_KEY));

    ActionContainer *advancedMenu =
        actionManager->actionContainer(Core::Constants::M_EDIT_ADVANCED);
    advancedMenu->addAction(cmd, Core::Constants::G_EDIT_EDITOR);

    for (int i = 1; i < 10; ++i) {
        QAction *act = new QAction(this);
        act->setText(QString("Execute User Action #%1").arg(i));
        act->setData(i);
        QString id = QString("FakeVim.UserAction%1").arg(i);
        QString keys = QString("Alt+V,%1").arg(i);
        cmd = actionManager->registerAction(act, id, globalcontext);
        cmd->setDefaultKeySequence(QKeySequence(keys));
        connect(act, SIGNAL(triggered()), SLOT(userActionTriggered()));
    }
{'fraction_non_alphanumeric': 0.14370854711550493, 'fraction_numerical': 0.0027374546364660243, 'mean_word_length': 3.3913312268168703, 'pattern_counts': {'":': 4, '<': 302, '<?xml version=': 0, '>': 408, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5699301', 'n_tokens_mistral': 19489, 'n_tokens_neox': 19141, 'n_words': 3569}