Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for vim mode #1270

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@
[submodule "third_party/syntax-highlighting"]
path = third_party/syntax-highlighting
url = https://github.com/KDE/syntax-highlighting.git
[submodule "third_party/FakeVim"]
path = third_party/FakeVim
url = https://github.com/cpeditor/FakeVim
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- Use [Kate's Syntax highlighter](https://api.kde.org/frameworks/syntax-highlighting/html/) engine for Code Highlighting. (#1101)
- Add options to select error/warning messages colors for message logger. (#521 and #1247)
- Add vim emulation with [custom commands](to be updated after docs PR is ready). (#220 and #1270)
coder3101 marked this conversation as resolved.
Show resolved Hide resolved

### Fixied

Expand Down
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ add_subdirectory(third_party/lsp-cpp)

add_subdirectory(third_party/qhttp)

add_subdirectory(third_party/FakeVim)

add_subdirectory(third_party/diff_match_patch)

option(PORTABLE_VERSION "Build the portable version" Off)
Expand Down Expand Up @@ -162,6 +164,10 @@ add_executable(cpeditor
src/Editor/CodeEditor.hpp
src/Editor/CodeEditorSideBar.cpp
src/Editor/CodeEditorSideBar.hpp
src/Editor/FakeVimProxy.cpp
src/Editor/FakeVimProxy.hpp
src/Editor/FakeVimCommands.cpp
src/Editor/FakeVimCommands.hpp
src/Editor/HighLighter.cpp
src/Editor/HighLighter.hpp
src/Editor/KSHRepository.cpp
Expand Down Expand Up @@ -279,6 +285,7 @@ target_link_libraries(cpeditor PRIVATE Qt5::Network)
target_link_libraries(cpeditor PRIVATE Qt5::Widgets)
target_link_libraries(cpeditor PRIVATE QtFindReplaceDialog)
target_link_libraries(cpeditor PRIVATE SingleApplication)
target_link_libraries(cpeditor PRIVATE fakevim)
target_link_libraries(cpeditor PRIVATE QHttp)
target_link_libraries(cpeditor PRIVATE diff_match_patch)
target_link_libraries(cpeditor PRIVATE KF5::SyntaxHighlighting)
Expand Down
4 changes: 3 additions & 1 deletion dist/aur/git/PKGBUILD
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ conflicts=("cpeditor")

source=('git+https://github.com/cpeditor/cpeditor.git'
'git+https://github.com/cpeditor/QtFindReplaceDialog.git'
'git+https://github.com/cpeditor/FakeVim.git'
'git+https://github.com/cpeditor/lsp-cpp.git'
'git+https://github.com/itay-grudev/singleapplication.git'
'git+https://github.com/MikeMirzayanov/testlib.git'
'git+https://github.com/cpeditor/qhttp.git')

md5sums=('SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP')
md5sums=('SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP')

pkgver() {
cd "$_pkgname"
Expand All @@ -46,6 +47,7 @@ prepare() {
git submodule init

git config submodule.third_party/QtFindReplaceDialog.url "$srcdir/QtFindReplaceDialog"
git config submodule.third_party/FakeVim.url "$srcdir/FakeVim"
git config submodule.third_party/lsp-cpp.url "$srcdir/lsp-cpp"
git config submodule.third_party/singleapplication.url "$srcdir/singleapplication"
git config submodule.third_party/testlib.url "$srcdir/testlib"
Expand Down
234 changes: 234 additions & 0 deletions src/Editor/FakeVimCommands.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
/*
* Copyright (C) 2019-2023 Ashar Khan <ashar786khan@gmail.com>
*
* This file is part of CP Editor.
*
* CP Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* I will not be responsible if CP Editor behaves in unexpected way and
* causes your ratings to go down and or lose any important contest.
*
* Believe Software is "Software" and it isn't immune to bugs.
*
*/

#include "Editor/FakeVimCommands.hpp"
#include "Util/FileUtil.hpp"
#include "Widgets/TestCases.hpp"
#include "appwindow.hpp"
#include "generated/SettingsHelper.hpp"
#include "third_party/FakeVim/fakevim/fakevimhandler.h"
#include <QApplication>
#include <QDir>
#include <QFileInfo>
#include <QStatusBar>

namespace Editor
{

FakeVimCommands::FakeVimCommands(AppWindow *window, QObject *parent) : QObject(parent), appwin(window)
{
}

FakeVimCommands::CommandTypes FakeVimCommands::customCommandType(FakeVim::Internal::ExCommand const &ex)
{
if (ex.cmd == "new")
return CommandTypes::New;

if (ex.cmd == "open" || ex.cmd == "opn")
return CommandTypes::Open;

if (ex.cmd == "compile" || ex.cmd == "cmp")
return CommandTypes::Compile;

if (ex.cmd == "crun" || ex.cmd == "crn")
return CommandTypes::CompileRun;

if (ex.cmd == "run")
return CommandTypes::Run;

if (ex.cmd == "drun" || ex.cmd == "drn")
return CommandTypes::DetachedRun;

if (ex.cmd == "killall" || ex.cmd == "kap")
return CommandTypes::KillProcess;

if (ex.cmd == "format" || ex.cmd == "fmt")
return CommandTypes::FormatCode;

if (ex.cmd == "snippet" || ex.cmd == "snp")
return CommandTypes::Snippets;

if (ex.cmd == "vmode" || ex.cmd == "vmd")
return CommandTypes::Vmode;

if (ex.cmd == "preference" || ex.cmd == "prf")
return CommandTypes::Preference;

if (ex.cmd == "chlang" || ex.cmd == "chl")
return CommandTypes::Chlang;

if (ex.cmd == "clear" || ex.cmd == "clr")
return CommandTypes::Clear;

if (ex.cmd == "exit" || ex.cmd == "ext")
return CommandTypes::Exit;

return CommandTypes::Unknown;
}

bool FakeVimCommands::handleCustomCommand(CommandTypes type, QString const &args, bool hasbang)
{
if (type == CommandTypes::Unknown)
return false;

switch (type)
{
case CommandTypes::New: {
const QString lang = args.isEmpty() ? SettingsHelper::getDefaultLanguage() : language(args);

if (!lang.isEmpty())
appwin->openTab("", lang);
else
showError(tr("`new` requires no argument or one of 'cpp', 'java' and 'python', got [%1]").arg(args));
break;
}
case CommandTypes::Open: {
QString path = args;

if (path.isEmpty())
{
appwin->on_actionOpen_triggered();
break;
}

if (path.startsWith('~'))
path = QDir::home().filePath(args.mid(1));

QFileInfo file(path);

if (!Util::cppSuffix.contains(file.suffix()) && !Util::javaSuffix.contains(file.suffix()) &&
!Util::javaSuffix.contains(file.suffix()))
{
showError(tr("[%1] is not C++, Python or Java source file").arg(file.absoluteFilePath()));
break;
}

if (!file.exists() && !hasbang)
{
showError(tr("[%1] does not exist. To open a tab with a non-existing file, use `open!` instead").arg(path));
break;
}

appwin->openTab(file.absoluteFilePath());
break;
}
case CommandTypes::Compile: {
appwin->on_actionCompile_triggered();
break;
}
case CommandTypes::CompileRun: {
appwin->on_actionCompileRun_triggered();
break;
}
case CommandTypes::Run: {

if (args.isEmpty())
appwin->on_actionRun_triggered();
else // args is not empty
{
bool ok = false;
int caseNum = args.toInt(&ok);

if (!ok)
showError(tr("[%1] is not a number").arg(args));
else // args is a number
{
if (appwin->currentWindow() && caseNum > 0 &&
caseNum <= appwin->currentWindow()->testcases->count()) // args is valid
appwin->currentWindow()->runTestCase(caseNum - 1);
else
showError(
tr("%1 is out of range [1, %2]").arg(args).arg(appwin->currentWindow()->testcases->count()));
}
}

break;
}
case CommandTypes::DetachedRun: {
appwin->on_actionRunDetached_triggered();
}
break;
case CommandTypes::KillProcess: {
appwin->on_actionKillProcesses_triggered();
break;
}
case CommandTypes::FormatCode: {
appwin->on_actionFormatCode_triggered();
break;
}
case CommandTypes::Snippets: {
appwin->on_actionUseSnippets_triggered();
break;
}
case CommandTypes::Vmode: {
if (args == "edit")
appwin->on_actionEditorMode_triggered();
else if (args == "split")
appwin->on_actionSplitMode_triggered();
else
showError(tr("[%1] is not a valid view mode. It should be one of 'split' and 'edit'").arg(args));
break;
}
case CommandTypes::Preference: {
appwin->on_actionSettings_triggered();
break;
}
case CommandTypes::Chlang: {
const QString lang = language(args);

if (!lang.isEmpty() && appwin->currentWindow())
appwin->currentWindow()->setLanguage(lang);
else
showError(tr("%1 is not a valid language name. It should be one of 'cpp', 'java' and 'python'").arg(args));
break;
}
case CommandTypes::Clear: {
appwin->currentWindow()->on_clearMessagesButton_clicked();
break;
}
case CommandTypes::Exit: {
appwin->on_actionQuit_triggered();
break;
}
case CommandTypes::Unknown: {
Q_UNREACHABLE();
}
};
return true;
}

void FakeVimCommands::showError(QString const &message)
{
if (auto *handler = qobject_cast<FakeVim::Internal::FakeVimHandler *>(parent()))
{
handler->showMessage(FakeVim::Internal::MessageInfo, message);
}
}

QString FakeVimCommands::language(const QString &langCode)
{
const auto code = langCode.toLower().trimmed();
if (code == "cpp" || code == "c++")
return "C++";
if (code == "java")
return "Java";
if (code == "py" || code == "python")
return "Python";
return QString();
}

} // namespace Editor
92 changes: 92 additions & 0 deletions src/Editor/FakeVimCommands.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2019-2023 Ashar Khan <ashar786khan@gmail.com>
*
* This file is part of CP Editor.
*
* CP Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* I will not be responsible if CP Editor behaves in unexpected way and
* causes your ratings to go down and or lose any important contest.
*
* Believe Software is "Software" and it isn't immune to bugs.
*
*/

#ifndef FAKE_VIM_COMMAND_HPP
#define FAKE_VIM_COMMAND_HPP

#include <QObject>

namespace FakeVim
{
namespace Internal
{
class ExCommand;
}
} // namespace FakeVim

class AppWindow;
template <class A, class B> struct QPair;

namespace Editor
{
class FakeVimCommands : public QObject
{
Q_OBJECT

public:
enum class CommandTypes
{
New,
Open,
Compile,
CompileRun,
Run,
DetachedRun,
KillProcess,
FormatCode,
Snippets,
Vmode,
Preference,
Chlang,
Clear,
Exit,

Unknown
};

/**
* @brief Constructs a new handler which performs actions using/on the provided appwindow
*/
explicit FakeVimCommands(AppWindow *window, QObject *parent = nullptr);

/**
* @brief retrieve the type of custom command.
* @returns enum CommandTypes that represents the command type.
* @note if not a custom command or invalid command, the CommandTypes::UNKNOWN is returned
*/
static CommandTypes customCommandType(FakeVim::Internal::ExCommand const &ex);

/**
* @brief Tries to handle passed command type with commandArgs and bang.
* @returns true if the command was handled otherwise false
* @note Custom commands are handled before Vim Commands
*/
bool handleCustomCommand(CommandTypes type, QString const &commandArgs, bool hasBang = false);

private:
void showError(QString const &message);

/**
* @brief get the language of a language code
*/
static QString language(const QString &langCode);

AppWindow *appwin;
};
} // namespace Editor

#endif // FAKE_VIM_COMMAND_HPP