Repository: spyder-ide/spyder-vim (language: python)

Issue:
Is Spyder-vim still under development?
… just wondering 😊


Discussion / hints:
Unfortunately, its mostly inactive right now due to lack of developer bandwidth; the primary blocker is #75 . Due to recent changes in the Spyder team and funding, it seems unlikely this will change too much in the immediate future at least with respect to the core team, but if someone was willing to step up to help, it certainly could...
I wish I could! :( 
I make small donations every once in a while but I don’t think I could support in coding. :( 
It seems that Spyder is entirely written in Python and I am a very newbie. 
Certainly, Spyder + vim would be killer though. Hopefully more skilled developers will start to support you guys! 

Fix patch:
diff --git a/requirements/conda.txt b/requirements/conda.txt
index 5ca7270..936b355 100644
--- a/requirements/conda.txt
+++ b/requirements/conda.txt
@@ -1,1 +1,4 @@
-spyder>=4.1.0
+qtawesome
+qtpy
+setuptools
+spyder>=5.3.3
diff --git a/setup.py b/setup.py
index 6a55633..a3e5b46 100644
--- a/setup.py
+++ b/setup.py
@@ -1,67 +1,52 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Copyright (c) Spyder Project Contributors
+# ----------------------------------------------------------------------------
+# Copyright © 2022, spyder-vim
 #
-# Licensed under the terms of the MIT License
-# (see LICENSE.txt for details)
-# -----------------------------------------------------------------------------
+# Licensed under the terms of the MIT license
+# ----------------------------------------------------------------------------
+"""
+spyder-vim setup.
+"""
+from setuptools import find_packages
+from setuptools import setup
 
-"""Setup script for spyder_vim"""
+from spyder_vim import __version__
 
-# Standard library imports
-import ast
-import os
-
-# Third party imports
-from setuptools import find_packages, setup
-
-HERE = os.path.abspath(os.path.dirname(__file__))
-
-
-def get_version(module='spyder_vim'):
-    """Get version."""
-    with open(os.path.join(HERE, module, '__init__.py'), 'r') as f:
-        data = f.read()
-    lines = data.split('\n')
-    for line in lines:
-        if line.startswith('VERSION_INFO'):
-            version_tuple = ast.literal_eval(line.split('=')[-1].strip())
-            version = '.'.join(map(str, version_tuple))
-            break
-    return version
-
-
-def get_description():
-    """Get long description."""
-    with open(os.path.join(HERE, 'README.rst'), 'r') as f:
-        data = f.read()
-    return data
-
-
-REQUIREMENTS = ['spyder>=3.2.0']
 
 setup(
-    name='spyder-vim',
-    version=get_version(),
-    packages=find_packages(exclude=['contrib', 'docs', 'tests*']),
-    keywords=['Spyder', 'Plugin', 'Vim'],
-    url='https://github.com/spyder-ide/spyder-vim',
-    license='MIT',
-    author='Joseph Martinot-Lagarde',
-    author_email='contrebasse@gmail.com',
-    description='A plugin to enable vim keybingins to the spyder editor',
-    long_description=get_description(),
-    install_requires=REQUIREMENTS,
-    include_package_data=True,
+    # See: https://setuptools.readthedocs.io/en/latest/setuptools.html
+    name="spyder-vim",
+    version=__version__,
+    author="Joseph Martinot-Lagarde and the spyder-vim contributors",
+    author_email="spyder.python@gmail.com",
+    description="A plugin to enable vim keybindings to the spyder editor",
+    license="MIT license",
+    url="https://github.com/spyder-ide/spyder-vim",
+    python_requires='>= 3.7',
+    install_requires=[
+        "qtpy",
+        "qtawesome",
+        "spyder>=5.3.3",
+    ],
+    packages=find_packages(),
+    entry_points={
+        "spyder.plugins": [
+            "spyder_vim = spyder_vim.spyder.plugin:SpyderVim"
+        ],
+    },
     classifiers=[
-        'Development Status :: 4 - Beta',
-        'Environment :: X11 Applications :: Qt',
-        'Environment :: Win32 (MS Windows)',
-        'Intended Audience :: Developers',
-        'License :: OSI Approved :: MIT License',
-        'Operating System :: OS Independent',
-        'Programming Language :: Python :: 2.7',
-        'Programming Language :: Python :: 3.5',
-        'Programming Language :: Python :: 3.6',
-        'Topic :: Software Development :: Widget Sets'])
+        "Operating System :: MacOS",
+        "Operating System :: Microsoft :: Windows",
+        "Operating System :: POSIX :: Linux",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+        "Development Status :: 4 - Beta",
+        "Intended Audience :: Education",
+        "Intended Audience :: Science/Research",
+        "Intended Audience :: Developers",
+        "Topic :: Scientific/Engineering",
+        "Topic :: Text Editors :: Integrated Development Environments (IDE)",
+    ],
+)
diff --git a/spyder_vim/__init__.py b/spyder_vim/__init__.py
index 16836bd..63f90cc 100644
--- a/spyder_vim/__init__.py
+++ b/spyder_vim/__init__.py
@@ -1,14 +1,11 @@
 # -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Copyright (c) Spyder Project Contributors
+# ----------------------------------------------------------------------------
+# Copyright © 2022, spyder-vim
 #
-# Licensed under the terms of the MIT License
-# (see LICENSE.txt for details)
-# -----------------------------------------------------------------------------
-"""Spyder Vim Plugin."""
+# Licensed under the terms of the MIT license
+# ----------------------------------------------------------------------------
+"""
+Spyder Vim Plugin (spyder-vim).
+"""
 
-from .vim import Vim as PLUGIN_CLASS  # pylint: disable=C0103
-PLUGIN_CLASS
-
-VERSION_INFO = (0, 1, 0, 'dev0')
-__version__ = '.'.join(map(str, VERSION_INFO))
+__version__ = "0.1.0-dev0"
diff --git a/spyder_vim/spyder/__init__.py b/spyder_vim/spyder/__init__.py
new file mode 100644
index 0000000..6c2a6a3
--- /dev/null
+++ b/spyder_vim/spyder/__init__.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# ----------------------------------------------------------------------------
+# Copyright © 2022, spyder-vim
+#
+# Licensed under the terms of the MIT license
+# ----------------------------------------------------------------------------
+"""
+spyder-vim
+
+A plugin to enable vim keybindings to the spyder editor.
+"""
diff --git a/spyder_vim/spyder/api.py b/spyder_vim/spyder/api.py
new file mode 100644
index 0000000..c0f5d4a
--- /dev/null
+++ b/spyder_vim/spyder/api.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+# ----------------------------------------------------------------------------
+# Copyright © 2022, spyder-vim
+#
+# Licensed under the terms of the MIT license
+# ----------------------------------------------------------------------------
+"""
+spyder-vim API.
+"""
diff --git a/spyder_vim/spyder/confpage.py b/spyder_vim/spyder/confpage.py
new file mode 100644
index 0000000..be83bab
--- /dev/null
+++ b/spyder_vim/spyder/confpage.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# ----------------------------------------------------------------------------
+# Copyright © 2022, spyder-vim
+#
+# Licensed under the terms of the MIT license
+# ----------------------------------------------------------------------------
+"""
+spyder-vim Preferences Page.
+"""
+from spyder.api.preferences import PluginConfigPage
+from spyder.api.translations import get_translation
+
+_ = get_translation("spyder_vim.spyder")
+
+
+class SpyderVimConfigPage(PluginConfigPage):
+
+    # --- PluginConfigPage API
+    # ------------------------------------------------------------------------
+    def setup_page(self):
+        pass
diff --git a/spyder_vim/spyder/container.py b/spyder_vim/spyder/container.py
new file mode 100644
index 0000000..c58c26e
--- /dev/null
+++ b/spyder_vim/spyder/container.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# ----------------------------------------------------------------------------
+# Copyright © 2022, spyder-vim
+#
+# Licensed under the terms of the MIT license
+# ----------------------------------------------------------------------------
+"""
+spyder-vim Main Container.
+"""
+# Spyder imports
+from spyder.api.config.decorators import on_conf_change
+from spyder.api.translations import get_translation
+from spyder.api.widgets.main_container import PluginMainContainer
+
+_ = get_translation("spyder_vim.spyder")
+
+
+class SpyderVimContainer(PluginMainContainer):
+
+    # Signals
+
+    # --- PluginMainContainer API
+    # ------------------------------------------------------------------------
+    def setup(self):
+        pass
+
+    def update_actions(self):
+        pass
+
+    # --- Public API
+    # ------------------------------------------------------------------------
diff --git a/spyder_vim/spyder/plugin.py b/spyder_vim/spyder/plugin.py
new file mode 100644
index 0000000..4639591
--- /dev/null
+++ b/spyder_vim/spyder/plugin.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+# ----------------------------------------------------------------------------
+# Copyright © 2022, spyder-vim
+#
+# Licensed under the terms of the MIT license
+# ----------------------------------------------------------------------------
+"""
+spyder-vim Plugin.
+"""
+
+# Third-party imports
+from qtpy.QtCore import Qt, Signal
+from qtpy.QtGui import QIcon, QKeySequence
+from qtpy.QtWidgets import QShortcut
+
+# Spyder imports
+from spyder.api.plugins import Plugins, SpyderPluginV2
+from spyder.api.plugin_registration.decorators import on_plugin_available
+from spyder.api.translations import get_translation
+
+# Local imports
+from spyder_vim.spyder.confpage import SpyderVimConfigPage
+from spyder_vim.spyder.container import SpyderVimContainer
+from spyder_vim.spyder.widgets import VimWidget
+
+_ = get_translation("spyder_vim.spyder")
+
+
+class SpyderVim(SpyderPluginV2):
+    """
+    spyder-vim plugin.
+    """
+
+    NAME = "spyder_vim"
+    REQUIRES = [Plugins.Editor]
+    OPTIONAL = []
+    CONTAINER_CLASS = SpyderVimContainer
+    CONF_SECTION = NAME
+    CONF_WIDGET_CLASS = SpyderVimConfigPage
+
+    # --- Signals
+
+    # --- SpyderPluginV2 API
+    # ------------------------------------------------------------------------
+    @staticmethod
+    def get_name():
+        return _("spyder-vim")
+
+    def get_description(self):
+        return _("A plugin to enable vim keybindings to the spyder editor")
+
+    def get_icon(self):
+        return QIcon()
+
+    def on_initialize(self):
+        pass
+
+    @on_plugin_available(plugin=Plugins.Editor)
+    def on_editor_available(self):
+        """
+        Set up interactions when Editor plugin available.
+        """
+        editor = self.get_plugin(Plugins.Editor)
+        self.vim_cmd = VimWidget(editor, self.main)
+        editor.layout().addWidget(self.vim_cmd)
+        sc = QShortcut(
+            QKeySequence("Esc"),
+            editor.editorsplitter,
+            self.vim_cmd.commandline.setFocus)
+        sc.setContext(Qt.WidgetWithChildrenShortcut)
+
+    def check_compatibility(self):
+        valid = True
+        message = ""  # Note: Remember to use _("") to localize the string
+        return valid, message
+
+    def on_close(self, cancellable=True):
+        return True
+
+    # --- Public API
+    # ------------------------------------------------------------------------
diff --git a/spyder_vim/vim_widget.py b/spyder_vim/spyder/widgets.py
similarity index 98%
rename from spyder_vim/vim_widget.py
rename to spyder_vim/spyder/widgets.py
index 3655a55..72514d7 100644
--- a/spyder_vim/vim_widget.py
+++ b/spyder_vim/spyder/widgets.py
@@ -1,13 +1,12 @@
 # -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Copyright (c) Spyder Project Contributors
+# ----------------------------------------------------------------------------
+# Copyright © 2022, spyder-vim
 #
-# Licensed under the terms of the MIT License
-# (see LICENSE.txt for details)
-# -----------------------------------------------------------------------------
-"""Vim Widget."""
-
-
+# Licensed under the terms of the MIT license
+# ----------------------------------------------------------------------------
+"""
+spyder-vim Main Widget.
+"""
 import re
 import bisect
 from time import time
@@ -17,7 +16,12 @@
 from qtpy.QtGui import QTextCursor, QTextDocument
 from qtpy.QtCore import Qt, QObject, QRegularExpression, Signal, QPoint
 
-from spyder.config.gui import get_color_scheme, is_dark_interface
+# Spyder imports
+from spyder.config.gui import is_dark_interface
+from spyder.api.translations import get_translation
+
+# Localization
+_ = get_translation("spyder_vim.spyder")
 
 
 VIM_COMMAND_PREFIX = ":!/?"
@@ -81,7 +85,7 @@ def __call__(self, key, repeat):
         elif key[0] in "ia" and self.visual_mode == "char":
             leftover = key[1]
             key = key[0]
-        elif key[0] is "\"":
+        elif key[0] == "\"":
             leftover = key[1]
             key = key[0]
         for symbol, text in SYMBOLS_REPLACEMENT.items():
@@ -176,7 +180,6 @@ def _move_selection(self, pos, move_start=False):
                 selection.cursor.setPosition(prev_cursor_block.position())
                 selection.cursor.setPosition(pos, QTextCursor.KeepAnchor)
         editor.set_extra_selections('vim_visual', [selection])
-        editor.update_extra_selections()
 
     def _get_selection_positions(self):
         editor = self._widget.editor()
@@ -248,14 +251,13 @@ def search(self, key, reverse=False):
             cursor = editor.document().find(QRegularExpression(key), cursor,
                                         QTextDocument.FindCaseSensitively)
         editor.set_extra_selections('search', [i for i in search_stack])
-        editor.update_extra_selections()
         search_dict = {"stack": search_stack, "reverse": reverse}
         return search_dict
 
     def n(self, repeat=1, reverse=False):
         """Move cursor to the next searched key"""
         cursor = self._editor_cursor()
-        search_stack = self.search_dict["stack"]
+        search_stack = self.search_dict.get("stack", None)
         if not search_stack:
             return
         if not self.search_dict["reverse"]^reverse:
@@ -674,7 +676,6 @@ def PERCENT(self, repeat=1):
                 selection.cursor.setPosition(end_position,
                                              QTextCursor.KeepAnchor)
             editor.set_extra_selections('vim_visual', [selection])
-            editor.update_extra_selections()
             self._set_cursor(end_position)
         else:
             self._set_cursor(end_position, mode=QTextCursor.MoveAnchor)
@@ -758,7 +759,6 @@ def i(self, leftover=None, repeat=1):
             selection.cursor.setPosition(end_position,
                                              QTextCursor.KeepAnchor)
             editor.set_extra_selections('vim_visual', [selection])
-            editor.update_extra_selections()
             self._set_cursor(end_position)
 
 
@@ -851,7 +851,6 @@ def a(self, leftover=None, repeat=1):
             selection.cursor.setPosition(end_position+1,
                                              QTextCursor.KeepAnchor)
             editor.set_extra_selections('vim_visual', [selection])
-            editor.update_extra_selections()
             self._set_cursor(end_position+1)
 
     def A(self, repeat):
@@ -1203,7 +1202,6 @@ def v(self, repeat):
         selection.format.setForeground(back)
         selection.cursor = editor.textCursor()
         editor.set_extra_selections('vim_visual', [selection])
-        editor.update_extra_selections()
 
     def V(self, repeat):
         """Start Visual mode per line."""
@@ -1222,7 +1220,6 @@ def V(self, repeat):
         selection.cursor.movePosition(QTextCursor.Down,
                                       QTextCursor.KeepAnchor)
         editor.set_extra_selections('vim_visual', [selection])
-        editor.update_extra_selections()
 
     # TODO: CTRL + V sets visual mode to 'block'
     def gt(self, repeat):
@@ -1509,9 +1506,9 @@ def focusInEvent(self, event):
 
     def focusOutEvent(self, event):
         """Enter editor mode."""
-        QLineEdit.focusOutEvent(self, event)
-        self.parent().editor().set_extra_selections('vim_cursor', [QTextEdit.ExtraSelection()])
-        self.parent().editor().update_extra_selections()
+        super().focusOutEvent(event)
+        self.parent().editor().clear_extra_selections('vim_cursor')
+        self.parent().editor().clear_extra_selections('search')
         self.parent().on_mode_changed("insert")
         if self.parent().vim_keys.visual_mode:
             self.parent().vim_keys.exit_visual_mode()
@@ -1638,9 +1635,7 @@ def on_copy(self):
     def editor(self):
         """Retrieve text of current opened file."""
         editorstack = self.editor_widget.get_current_editorstack()
-        index = editorstack.get_stack_index()
-        finfo = editorstack.data[index]
-        return finfo.editor
+        return editorstack.get_current_editor()
 
     def update_vim_cursor(self):
         """Update Vim cursor position."""
@@ -1657,4 +1652,3 @@ def update_vim_cursor(self):
         selection.cursor.movePosition(QTextCursor.Right,
                                       QTextCursor.KeepAnchor)
         self.editor().set_extra_selections('vim_cursor', [selection])
-        self.editor().update_extra_selections()
diff --git a/spyder_vim/vim.py b/spyder_vim/vim.py
deleted file mode 100644
index 253fe50..0000000
--- a/spyder_vim/vim.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Copyright (c) Spyder Project Contributors
-#
-# Licensed under the terms of the MIT License
-# (see LICENSE.txt for details)
-# -----------------------------------------------------------------------------
-"""Vim Plugin."""
-
-from spyder_vim.vim_widget import VimWidget
-from qtpy.QtCore import Signal
-
-# Local imports
-from spyder.config.base import _
-try:
-    # Spyder 4
-    from spyder.api.plugins import SpyderPluginWidget
-except ImportError:
-    # Spyder 3
-    from spyder.plugins import SpyderPluginWidget
-from qtpy.QtCore import Qt
-from qtpy.QtWidgets import QShortcut, QVBoxLayout
-from qtpy.QtGui import QKeySequence
-
-
-# %%
-class Vim(SpyderPluginWidget):  # pylint: disable=R0904
-    """Implements a Vim-like command mode."""
-
-    focus_changed = Signal()
-    CONF_SECTION = "Vim"
-    CONFIGWIDGET_CLASS = None
-
-    def __init__(self, parent):
-        """Main plugin constructor."""
-        SpyderPluginWidget.__init__(self, parent)
-        self.main = parent
-        self.vim_cmd = VimWidget(self.main.editor, self.main)
-        layout = QVBoxLayout()
-        layout.addWidget(self.vim_cmd)
-        self.setLayout(layout)
-
-    # %% SpyderPlugin API
-    def get_plugin_title(self):
-        """Return widget title."""
-        return _("Vim")
-
-    def get_plugin_icon(self):
-        """Return widget icon."""
-        return  # self.get_icon('vim.png')
-
-    def register_plugin(self):
-        """Register plugin in Spyder's main window."""
-        try:
-            # Spyder 3 compatibility
-            super(Vim, self).register_plugin()
-        except NotImplementedError:
-            pass
-        self.focus_changed.connect(self.main.plugin_focus_changed)
-        self.vim_cmd.editor_widget.layout().addWidget(self.vim_cmd)
-        sc = QShortcut(QKeySequence("Esc"), self.vim_cmd.editor_widget.editorsplitter, self.vim_cmd.commandline.setFocus)
-        sc.setContext(Qt.WidgetWithChildrenShortcut)
-
-    def get_focus_widget(self):
-        """Return vim command line and give it focus."""
-        return self.vim_cmd.commandline
-
-    def get_plugin_actions(self):
-        """Return plugin actions."""
-        return []


Test patch:
diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml
index a1ed5a5..c1bde77 100644
--- a/.github/workflows/linux-tests.yml
+++ b/.github/workflows/linux-tests.yml
@@ -19,7 +19,7 @@ jobs:
     strategy:
       fail-fast: false 
       matrix:
-        PYTHON_VERSION: ['3.6', '3.7', '3.8']
+        PYTHON_VERSION: ['3.7', '3.8', '3.9']
     steps:
       - name: Checkout branch
         uses: actions/checkout@v2
@@ -28,15 +28,19 @@ jobs:
           sudo apt-get update
           sudo apt-get install libegl1-mesa
       - name: Install Conda
-        uses: goanpeca/setup-miniconda@v1
+        uses: conda-incubator/setup-miniconda@v2
         with:
            activate-environment: test
-           auto-update-conda: true
+           auto-update-conda: false
            auto-activate-base: false
            python-version: ${{ matrix.PYTHON_VERSION }} 
+           use-only-tar-bz2: true
+           channels: conda-forge,defaults
+           channel-priority: strict
+           miniforge-variant: Mambaforge
       - name: Install package dependencies
         shell: bash -l {0}
-        run: conda install --file requirements/conda.txt -y -q
+        run: conda install --file requirements/conda.txt -y -q -c conda-forge
       - name: Install test dependencies
         shell: bash -l {0}
         run: |
diff --git a/.github/workflows/macos-tests.yml b/.github/workflows/macos-tests.yml
index fa99f51..4837ba8 100644
--- a/.github/workflows/macos-tests.yml
+++ b/.github/workflows/macos-tests.yml
@@ -19,20 +19,24 @@ jobs:
     strategy:
       fail-fast: false 
       matrix:
-        PYTHON_VERSION: ['3.6', '3.7', '3.8']
+        PYTHON_VERSION: ['3.7', '3.8', '3.9']
     steps:
       - name: Checkout branch
         uses: actions/checkout@v2
       - name: Install Conda
-        uses: goanpeca/setup-miniconda@v1
+        uses: conda-incubator/setup-miniconda@v2
         with:
            activate-environment: test
-           auto-update-conda: true
+           auto-update-conda: false
            auto-activate-base: false
            python-version: ${{ matrix.PYTHON_VERSION }} 
+           use-only-tar-bz2: true
+           channels: conda-forge,defaults
+           channel-priority: strict
+           miniforge-variant: Mambaforge
       - name: Install package dependencies
         shell: bash -l {0}
-        run: conda install --file requirements/conda.txt -y -q
+        run: conda install --file requirements/conda.txt -y -q -c conda-forge
       - name: Install test dependencies
         shell: bash -l {0}
         run: |
diff --git a/.github/workflows/windows-tests.yml b/.github/workflows/windows-tests.yml
index 995df19..04764be 100644
--- a/.github/workflows/windows-tests.yml
+++ b/.github/workflows/windows-tests.yml
@@ -19,20 +19,24 @@ jobs:
     strategy:
       fail-fast: false 
       matrix:
-        PYTHON_VERSION: ['3.6', '3.7', '3.8']
+        PYTHON_VERSION: ['3.7', '3.8', '3.9']
     steps:
       - name: Checkout branch
         uses: actions/checkout@v2
       - name: Install Conda
-        uses: goanpeca/setup-miniconda@v1
+        uses: conda-incubator/setup-miniconda@v2
         with:
            activate-environment: test
            auto-update-conda: true
            auto-activate-base: false
            python-version: ${{ matrix.PYTHON_VERSION }}
+           use-only-tar-bz2: true
+           channels: conda-forge,defaults
+           channel-priority: strict
+           miniforge-variant: Mambaforge
       - name: Install package dependencies
         shell: bash -l {0}
-        run: conda install --file requirements/conda.txt -y -q
+        run: conda install --file requirements/conda.txt -y -q -c conda-forge
       - name: Install test dependencies
         shell: bash -l {0}
         run: conda install -c spyder-ide --file requirements/tests.txt -y -q
diff --git a/runtests.py b/runtests.py
index 0f038c2..90d2121 100644
--- a/runtests.py
+++ b/runtests.py
@@ -16,7 +16,7 @@ def main():
     """
     Run pytest tests.
     """
-    errno = pytest.main(['-x', 'spyder_vim', '-v',
+    errno = pytest.main(['-x', 'tests', '-v',
                          '-rw', '--durations=10',
                          '--cov=spyder_vim', '--cov-report=term-missing'])
 
diff --git a/tests/spyder-vim/__init__.py b/tests/spyder-vim/__init__.py
new file mode 100644
index 0000000..6785899
--- /dev/null
+++ b/tests/spyder-vim/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+# ----------------------------------------------------------------------------
+# Copyright © 2022, spyder-vim
+#
+# Licensed under the terms of the MIT license
+# ----------------------------------------------------------------------------
+"""
+spyder-vim tests.
+"""
diff --git a/spyder_vim/tests/foo.txt b/tests/spyder-vim/foo.txt
similarity index 100%
rename from spyder_vim/tests/foo.txt
rename to tests/spyder-vim/foo.txt
diff --git a/spyder_vim/tests/test_vim.py b/tests/spyder-vim/test_widgets.py
similarity index 99%
rename from spyder_vim/tests/test_vim.py
rename to tests/spyder-vim/test_widgets.py
index 9b94a31..ddb0293 100644
--- a/spyder_vim/tests/test_vim.py
+++ b/tests/spyder-vim/test_widgets.py
@@ -1,21 +1,19 @@
 # -*- coding: utf-8 -*-
+# ----------------------------------------------------------------------------
+# Copyright © 2022, spyder-vim
 #
-# Copyright © Spyder Project Contributors
-# Licensed under the terms of the MIT License
-#
-
-"""Tests for the plugin."""
-
+# Licensed under the terms of the MIT license
+# ----------------------------------------------------------------------------
+"""
+spyder-vim widget tests.
+"""
 # Standard library imports
 import os
 import os.path as osp
 
 # Test library imports
 import pytest
-try:
-    from unittest.mock import Mock
-except ImportError:
-    from mock import Mock  # Python 2
+from unittest.mock import Mock
 
 # Qt imports
 from qtpy.QtCore import Qt, QPoint
@@ -26,19 +24,22 @@
 from spyder.plugins.editor.widgets.editor import EditorStack
 
 # Local imports
-from spyder_vim.vim import Vim
-from spyder_vim.vim_widget import RE_VIM_PREFIX
+from spyder_vim.spyder.plugin import SpyderVim
+from spyder_vim.spyder.widgets import RE_VIM_PREFIX
 
 
 LOCATION = osp.realpath(osp.join(
     os.getcwd(), osp.dirname(__file__)))
 
 
-class VimTesting(Vim):
-    CONF_FILE = False
+class VimTesting(SpyderVim):
 
-    def __init(self, parent):
-        Vim.__init__(self, parent)
+    def __init__(self, parent):
+        super().__init__(parent)
+        self.on_editor_available()
+
+    def get_focus_widget(self):
+        return self.vim_cmd.commandline
 
 
 class EditorMock(QWidget):
@@ -75,6 +76,9 @@ def __init__(self, editor_stack):
         layout.addWidget(self.editor)
         self.setLayout(layout)
 
+    def get_plugin(self, dummy, error=True):
+        return self.editor
+
     add_dockwidget = Mock()
 
 
@@ -102,7 +106,7 @@ def vim_bot(editor_bot):
     """Create an spyder-vim plugin instance."""
     main, editor_stack, editor, qtbot = editor_bot
     vim = VimTesting(main)
-    vim.register_plugin()
+    vim.on_initialize()
     return main, editor_stack, editor, vim, qtbot
 
 
@@ -239,7 +243,6 @@ def test_cursor_position(vim_bot):
     cmd_line = vim.get_focus_widget()
     qtbot.keyClicks(cmd_line, '$a')
     qtbot.keyClicks(editor, 'test')
-    editor.stdkey_escape()
     text = editor.toPlainText()
     expected_text = ('   123\n'
                      'line 1\n'
@@ -2098,3 +2101,4 @@ def test_lessless_command(vim_bot, text, command_list, result, cursor_pos):
 
     assert editor.toPlainText() == result
     assert editor.textCursor().position() == cursor_pos
+