Commit 8560036d authored by nsf's avatar nsf Committed by hjk

Add EmacsKeys plugin.

A very simple non-intrusive plugin. Adds a bunch of actions typical emacs user
would expect. Doesn't contain any default key bindings.

Change-Id: Ib30fb46c577e2fdfd4c704e7f4eee7ee27a53c29
Reviewed-by: default avatarChristian Kandeler <christian.kandeler@digia.com>
Reviewed-by: default avatarhjk <hjk121@nokiamail.com>
Reviewed-by: default avatarEike Ziller <eike.ziller@digia.com>
parent faca3333
<plugin name=\"EmacsKeys\" version=\"$$QTCREATOR_VERSION\" compatVersion=\"$$QTCREATOR_COMPAT_VERSION\" experimental=\"true\">
<vendor>nsf</vendor>
<copyright>(C) nsf &lt;no.smile.face@gmail.com&gt;</copyright>
<license>
Commercial Usage
Licensees holding valid Qt Commercial licenses may use this plugin 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 Digia.
GNU Lesser General Public License Usage
Alternatively, this plugin may be used under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation. 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.
</license>
<description>
The main idea behind this plugin is to provide additional actions a typical emacs user would expect. It doesn&#39;t claim to provide full emacs emulation. The following actions are available:
- Movement [C-f, C-b, C-n, C-p, M-f, M-b, C-a, C-e, M-&lt;, M-&gt;]
- Mark-based selection [C-SPC, C-x C-x]
- Cut/copy/yank (doesn&#39;t provide kill ring feature) [M-w, C-w, C-y]
- Kill actions, which interact properly with clipboard [C-k, M-d, C-d]
- Scrolling (half of the screen, keeps cursor visible) [C-v, M-v]
- Insert new line and indent [C-j]
IMPORTANT: Actions are not bound to any key combinations by default. You can find them under &#39;EmacsKeys&#39; section in keyboard shortcuts settings.
Also it&#39;s worth mentioning that EmacsKeys plugin forces disabling of menu mnemonics by calling Qt&#39;s qt_set_sequence_auto_mnemonic function with false argument. Many of the english menu mnemonics get into the way of typical emacs keys, this includes: Alt+F (File), Alt+B (Build), Alt+W (Window). It&#39;s a temporary solution, it remains until there is a better one.
</description>
<url>http://nosmileface.ru</url>
$$dependencyList
</plugin>
include(../../qtcreatorplugin.pri)
SOURCES += \
emacskeysplugin.cpp \
emacskeysstate.cpp
HEADERS += \
emacskeysplugin.h \
emacskeysconstants.h \
emacskeysstate.h
import qbs.base 1.0
import QtcPlugin
QtcPlugin {
name: "EmacsKeys"
Depends { name: "Qt.widgets" }
Depends { name: "Core" }
Depends { name: "TextEditor" }
files: [
"emacskeysplugin.cpp",
"emacskeysplugin.h",
"emacskeysstate.cpp",
"emacskeysstate.h",
"emacskeysconstants.h",
]
}
QTC_PLUGIN_NAME = EmacsKeys
QTC_PLUGIN_DEPENDS += \
coreplugin \
texteditor
/**************************************************************************
**
** Copyright (c) nsf <no.smile.face@gmail.com>
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** 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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#ifndef EMACSKEYSCONSTANTS_H
#define EMACSKEYSCONSTANTS_H
namespace EmacsKeys {
namespace Constants {
const char DELETE_CHARACTER[] = "EmacsKeys.DeleteCharacter";
const char KILL_WORD[] = "EmacsKeys.KillWord";
const char KILL_LINE[] = "EmacsKeys.KillLine";
const char INSERT_LINE_AND_INDENT[] = "EmacsKeys.InsertLineAndIndent";
const char GOTO_FILE_START[] = "EmacsKeys.GotoFileStart";
const char GOTO_FILE_END[] = "EmacsKeys.GotoFileEnd";
const char GOTO_LINE_START[] = "EmacsKeys.GotoLineStart";
const char GOTO_LINE_END[] = "EmacsKeys.GotoLineEnd";
const char GOTO_NEXT_LINE[] = "EmacsKeys.GotoNextLine";
const char GOTO_PREVIOUS_LINE[] = "EmacsKeys.GotoPreviousLine";
const char GOTO_NEXT_CHARACTER[] = "EmacsKeys.GotoNextCharacter";
const char GOTO_PREVIOUS_CHARACTER[] = "EmacsKeys.GotoPreviousCharacter";
const char GOTO_NEXT_WORD[] = "EmacsKeys.GotoNextWord";
const char GOTO_PREVIOUS_WORD[] = "EmacsKeys.GotoPreviousWord";
const char MARK[] = "EmacsKeys.Mark";
const char EXCHANGE_CURSOR_AND_MARK[] = "EmacsKeys.ExchangeCursorAndMark";
const char COPY[] = "EmacsKeys.Copy";
const char CUT[] = "EmacsKeys.Cut";
const char YANK[] = "EmacsKeys.Yank";
const char SCROLL_HALF_DOWN[] = "EmacsKeys.ScrollHalfDown";
const char SCROLL_HALF_UP[] = "EmacsKeys.ScrollHalfUp";
} // namespace EmacsKeys
} // namespace Constants
#endif // EMACSKEYSCONSTANTS_H
/**************************************************************************
**
** Copyright (c) nsf <no.smile.face@gmail.com>
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** 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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "emacskeysplugin.h"
#include "emacskeysconstants.h"
#include "emacskeysstate.h"
#include <coreplugin/icore.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/icontext.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/coreconstants.h>
#include <utils/qtcassert.h>
#include <texteditor/basetexteditor.h>
#include <QAction>
#include <QPlainTextEdit>
#include <QApplication>
#include <QClipboard>
#include <QScrollBar>
#include <QtPlugin>
QT_BEGIN_NAMESPACE
extern void qt_set_sequence_auto_mnemonic(bool enable);
QT_END_NAMESPACE
using namespace EmacsKeys::Internal;
//---------------------------------------------------------------------------
// EmacsKeysPlugin
//---------------------------------------------------------------------------
EmacsKeysPlugin::EmacsKeysPlugin(): m_currentEditorWidget(0)
{
}
EmacsKeysPlugin::~EmacsKeysPlugin()
{
}
bool EmacsKeysPlugin::initialize(const QStringList &arguments, QString *errorString)
{
Q_UNUSED(arguments)
Q_UNUSED(errorString)
// We have to use this hack here at the moment, because it's the only way to
// disable Qt Creator menu accelerators aka mnemonics. Many of them get into
// the way of typical emacs keys, such as: Alt+F (File), Alt+B (Build),
// Alt+W (Window).
qt_set_sequence_auto_mnemonic(false);
connect(Core::EditorManager::instance(),
SIGNAL(editorAboutToClose(Core::IEditor*)),
this,
SLOT(editorAboutToClose(Core::IEditor*)));
connect(Core::EditorManager::instance(),
SIGNAL(currentEditorChanged(Core::IEditor*)),
this,
SLOT(currentEditorChanged(Core::IEditor*)));
registerAction(Constants::DELETE_CHARACTER,
SLOT(deleteCharacter()), tr("Delete Character"));
registerAction(Constants::KILL_WORD,
SLOT(killWord()), tr("Kill Word"));
registerAction(Constants::KILL_LINE,
SLOT(killLine()), tr("Kill Line"));
registerAction(Constants::INSERT_LINE_AND_INDENT,
SLOT(insertLineAndIndent()), tr("Insert New Line and Indent"));
registerAction(Constants::GOTO_FILE_START,
SLOT(gotoFileStart()), tr("Go to File Start"));
registerAction(Constants::GOTO_FILE_END,
SLOT(gotoFileEnd()), tr("Go to File End"));
registerAction(Constants::GOTO_LINE_START,
SLOT(gotoLineStart()), tr("Go to Line Start"));
registerAction(Constants::GOTO_LINE_END,
SLOT(gotoLineEnd()), tr("Go to Line End"));
registerAction(Constants::GOTO_NEXT_LINE,
SLOT(gotoNextLine()), tr("Go to Next Line"));
registerAction(Constants::GOTO_PREVIOUS_LINE,
SLOT(gotoPreviousLine()), tr("Go to Previous Line"));
registerAction(Constants::GOTO_NEXT_CHARACTER,
SLOT(gotoNextCharacter()), tr("Go to Next Character"));
registerAction(Constants::GOTO_PREVIOUS_CHARACTER,
SLOT(gotoPreviousCharacter()), tr("Go to Previous Character"));
registerAction(Constants::GOTO_NEXT_WORD,
SLOT(gotoNextWord()), tr("Go to Next Word"));
registerAction(Constants::GOTO_PREVIOUS_WORD,
SLOT(gotoPreviousWord()), tr("Go to Previous Word"));
registerAction(Constants::MARK,
SLOT(mark()), tr("Mark"));
registerAction(Constants::EXCHANGE_CURSOR_AND_MARK,
SLOT(exchangeCursorAndMark()), tr("Exchange Cursor and Mark"));
registerAction(Constants::COPY,
SLOT(copy()), tr("Copy"));
registerAction(Constants::CUT,
SLOT(cut()), tr("Cut"));
registerAction(Constants::YANK,
SLOT(yank()), tr("Yank"));
registerAction(Constants::SCROLL_HALF_DOWN,
SLOT(scrollHalfDown()), tr("Scroll Half Screen Down"));
registerAction(Constants::SCROLL_HALF_UP,
SLOT(scrollHalfUp()), tr("Scroll Half Screen Up"));
return true;
}
void EmacsKeysPlugin::extensionsInitialized()
{
}
ExtensionSystem::IPlugin::ShutdownFlag EmacsKeysPlugin::aboutToShutdown()
{
return SynchronousShutdown;
}
void EmacsKeysPlugin::editorAboutToClose(Core::IEditor *editor)
{
QPlainTextEdit *w = qobject_cast<QPlainTextEdit*>(editor->widget());
if (!w)
return;
if (m_stateMap.contains(w)) {
delete m_stateMap[w];
m_stateMap.remove(w);
}
}
void EmacsKeysPlugin::currentEditorChanged(Core::IEditor *editor)
{
if (!editor) {
m_currentEditorWidget = 0;
return;
}
m_currentEditorWidget = qobject_cast<QPlainTextEdit*>(editor->widget());
if (!m_currentEditorWidget)
return;
if (!m_stateMap.contains(m_currentEditorWidget)) {
m_stateMap[m_currentEditorWidget] = new EmacsKeysState(m_currentEditorWidget);
}
m_currentState = m_stateMap[m_currentEditorWidget];
m_currentBaseTextEditorWidget =
qobject_cast<TextEditor::BaseTextEditorWidget*>(editor->widget());
}
void EmacsKeysPlugin::gotoFileStart() { genericGoto(QTextCursor::Start); }
void EmacsKeysPlugin::gotoFileEnd() { genericGoto(QTextCursor::End); }
void EmacsKeysPlugin::gotoLineStart() { genericGoto(QTextCursor::StartOfLine); }
void EmacsKeysPlugin::gotoLineEnd() { genericGoto(QTextCursor::EndOfLine); }
void EmacsKeysPlugin::gotoNextLine() { genericGoto(QTextCursor::Down, false); }
void EmacsKeysPlugin::gotoPreviousLine() { genericGoto(QTextCursor::Up, false); }
void EmacsKeysPlugin::gotoNextCharacter() { genericGoto(QTextCursor::Right); }
void EmacsKeysPlugin::gotoPreviousCharacter() { genericGoto(QTextCursor::Left); }
void EmacsKeysPlugin::gotoNextWord() { genericGoto(QTextCursor::NextWord); }
void EmacsKeysPlugin::gotoPreviousWord() { genericGoto(QTextCursor::PreviousWord); }
void EmacsKeysPlugin::mark()
{
if (!m_currentEditorWidget)
return;
m_currentState->beginOwnAction();
QTextCursor cursor = m_currentEditorWidget->textCursor();
if (m_currentState->mark() == cursor.position()) {
m_currentState->setMark(-1);
} else {
cursor.clearSelection();
m_currentState->setMark(cursor.position());
m_currentEditorWidget->setTextCursor(cursor);
}
m_currentState->endOwnAction(KeysActionOther);
}
void EmacsKeysPlugin::exchangeCursorAndMark()
{
if (!m_currentEditorWidget)
return;
QTextCursor cursor = m_currentEditorWidget->textCursor();
if (m_currentState->mark() == -1 || m_currentState->mark() == cursor.position())
return;
m_currentState->beginOwnAction();
int position = cursor.position();
cursor.clearSelection();
cursor.setPosition(m_currentState->mark(), QTextCursor::KeepAnchor);
m_currentState->setMark(position);
m_currentEditorWidget->setTextCursor(cursor);
m_currentState->endOwnAction(KeysActionOther);
}
void EmacsKeysPlugin::copy()
{
if (!m_currentEditorWidget)
return;
m_currentState->beginOwnAction();
QTextCursor cursor = m_currentEditorWidget->textCursor();
QApplication::clipboard()->setText(cursor.selectedText());
cursor.clearSelection();
m_currentEditorWidget->setTextCursor(cursor);
m_currentState->setMark(-1);
m_currentState->endOwnAction(KeysActionOther);
}
void EmacsKeysPlugin::cut()
{
if (!m_currentEditorWidget)
return;
m_currentState->beginOwnAction();
QTextCursor cursor = m_currentEditorWidget->textCursor();
QApplication::clipboard()->setText(cursor.selectedText());
cursor.removeSelectedText();
m_currentState->setMark(-1);
m_currentState->endOwnAction(KeysActionOther);
}
void EmacsKeysPlugin::yank()
{
if (!m_currentEditorWidget)
return;
m_currentState->beginOwnAction();
m_currentEditorWidget->paste();
m_currentState->setMark(-1);
m_currentState->endOwnAction(KeysActionOther);
}
void EmacsKeysPlugin::scrollHalfDown() { genericVScroll(1); }
void EmacsKeysPlugin::scrollHalfUp() { genericVScroll(-1); }
void EmacsKeysPlugin::deleteCharacter()
{
if (!m_currentEditorWidget)
return;
m_currentState->beginOwnAction();
m_currentEditorWidget->textCursor().deleteChar();
m_currentState->endOwnAction(KeysActionOther);
}
void EmacsKeysPlugin::killWord()
{
if (!m_currentEditorWidget)
return;
m_currentState->beginOwnAction();
QTextCursor cursor = m_currentEditorWidget->textCursor();
cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor);
if (m_currentState->lastAction() == KeysActionKillWord) {
QApplication::clipboard()->setText(
QApplication::clipboard()->text() + cursor.selectedText());
} else {
QApplication::clipboard()->setText(cursor.selectedText());
}
cursor.removeSelectedText();
m_currentState->endOwnAction(KeysActionKillWord);
}
void EmacsKeysPlugin::killLine()
{
if (!m_currentEditorWidget)
return;
m_currentState->beginOwnAction();
QTextCursor cursor = m_currentEditorWidget->textCursor();
int position = cursor.position();
cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
if (cursor.position() == position) {
// empty line
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
}
if (m_currentState->lastAction() == KeysActionKillLine) {
QApplication::clipboard()->setText(
QApplication::clipboard()->text() + cursor.selectedText());
} else {
QApplication::clipboard()->setText(cursor.selectedText());
}
cursor.removeSelectedText();
m_currentState->endOwnAction(KeysActionKillLine);
}
void EmacsKeysPlugin::insertLineAndIndent()
{
if (!m_currentEditorWidget)
return;
m_currentState->beginOwnAction();
QTextCursor cursor = m_currentEditorWidget->textCursor();
cursor.beginEditBlock();
cursor.insertBlock();
if (m_currentBaseTextEditorWidget != 0)
m_currentBaseTextEditorWidget->baseTextDocument()->autoIndent(cursor);
cursor.endEditBlock();
m_currentEditorWidget->setTextCursor(cursor);
m_currentState->endOwnAction(KeysActionOther);
}
QAction *EmacsKeysPlugin::registerAction(const Core::Id &id, const char *slot,
const QString &title)
{
QAction *result = new QAction(title, this);
Core::ActionManager::registerAction(result, id,
Core::Context(Core::Constants::C_GLOBAL), true);
connect(result, SIGNAL(triggered(bool)), this, slot);
return result;
}
void EmacsKeysPlugin::genericGoto(QTextCursor::MoveOperation op, bool abortAssist)
{
if (!m_currentEditorWidget)
return;
m_currentState->beginOwnAction();
QTextCursor cursor = m_currentEditorWidget->textCursor();
cursor.movePosition(op, m_currentState->mark() != -1 ?
QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
m_currentEditorWidget->setTextCursor(cursor);
if (abortAssist && m_currentBaseTextEditorWidget != 0)
m_currentBaseTextEditorWidget->abortAssist();
m_currentState->endOwnAction(KeysActionOther);
}
void EmacsKeysPlugin::genericVScroll(int direction)
{
if (!m_currentEditorWidget)
return;
m_currentState->beginOwnAction();
QScrollBar *verticalScrollBar = m_currentEditorWidget->verticalScrollBar();
const int value = verticalScrollBar->value();
const int halfPageStep = verticalScrollBar->pageStep() / 2;
const int newValue = value + (direction > 0 ? halfPageStep : -halfPageStep);
verticalScrollBar->setValue(newValue);
// adjust cursor if it's out of screen
const QRect viewportRect = m_currentEditorWidget->viewport()->rect();
const QTextCursor::MoveMode mode =
m_currentState->mark() != -1 ?
QTextCursor::KeepAnchor :
QTextCursor::MoveAnchor ;
const QTextCursor::MoveOperation op =
m_currentEditorWidget->cursorRect().y() < 0 ?
QTextCursor::Down :
QTextCursor::Up ;
QTextCursor cursor = m_currentEditorWidget->textCursor();
while (!m_currentEditorWidget->cursorRect(cursor).intersects(viewportRect)) {
const int previousPosition = cursor.position();
cursor.movePosition(op, mode);
if (previousPosition == cursor.position())
break;
}
m_currentEditorWidget->setTextCursor(cursor);
m_currentState->endOwnAction(KeysActionOther);
}
Q_EXPORT_PLUGIN2(EmacsKeys, EmacsKeysPlugin)
/**************************************************************************
**
** Copyright (c) nsf <no.smile.face@gmail.com>
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** 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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#ifndef EMACSKEYS_H
#define EMACSKEYS_H
#include <extensionsystem/iplugin.h>
#include <QTextCursor>
// forward declarations
QT_FORWARD_DECLARE_CLASS(QAction)
QT_FORWARD_DECLARE_CLASS(QPlainTextEdit)
namespace Core {
class Id;
class IEditor;
}
namespace TextEditor {
class BaseTextEditorWidget;
}
namespace EmacsKeys {
namespace Internal {
class EmacsKeysState;
class EmacsKeysPlugin : public ExtensionSystem::IPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "EmacsKeys.json")
public:
EmacsKeysPlugin();
~EmacsKeysPlugin();
bool initialize(const QStringList &arguments, QString *errorString);
void extensionsInitialized();
ShutdownFlag aboutToShutdown();
private slots:
void editorAboutToClose(Core::IEditor *editor);
void currentEditorChanged(Core::IEditor *editor);
void deleteCharacter(); // C-d
void killWord(); // M-d
void killLine(); // C-k
void insertLineAndIndent(); // C-j
void gotoFileStart(); // M-<
void gotoFileEnd(); // M->
void gotoLineStart(); // C-a
void gotoLineEnd(); // C-e