Make Ctrl+R work as redo shortcut
This commit is contained in:
@@ -11,6 +11,11 @@ shortcuts. Most of them are also listed in the application's user interface.
|
||||
|
||||
On MacOS, replace :kbd:`Ctrl` with :kbd:`Cmd`.
|
||||
|
||||
.. note::
|
||||
|
||||
Shortcuts marked with (*) are remapped when Vim Mode is enabled and no longer work as listed in
|
||||
the tables below. See :ref:`docs_features_vim_mode` for more details.
|
||||
|
||||
|
||||
Main Window Shortcuts
|
||||
=====================
|
||||
@@ -53,7 +58,7 @@ Project Tree Shortcuts
|
||||
":kbd:`Ctrl+L`", "Open the **Quick Links** menu"
|
||||
":kbd:`Ctrl+N`", "Open the **Create New Item** menu"
|
||||
":kbd:`Ctrl+O`", "Open the selected document in the editor"
|
||||
":kbd:`Ctrl+R`", "Open the selected document in the viewer"
|
||||
":kbd:`Ctrl+R`", "Open the selected document in the viewer (*)"
|
||||
":kbd:`Ctrl+Up`", "Move selected item one step up in the tree"
|
||||
":kbd:`Ctrl+Down`", "Move selected item one step down in the tree"
|
||||
":kbd:`Ctrl+Shift+Del`", "Move the selected item to Trash"
|
||||
@@ -116,7 +121,7 @@ Other Editor Shortcuts
|
||||
":kbd:`Ctrl+A`", "Select all text in the document"
|
||||
":kbd:`Ctrl+C`", "Copy selected text to clipboard"
|
||||
":kbd:`Ctrl+K`", "Activate the insert commands (see list in :ref:`docs_features_shortcuts_insert`)"
|
||||
":kbd:`Ctrl+R`", "Open or reload the current document in the viewer"
|
||||
":kbd:`Ctrl+R`", "Open or reload the current document in the viewer (*)"
|
||||
":kbd:`Ctrl+S`", "Save the current document"
|
||||
":kbd:`Ctrl+V`", "Paste text from clipboard to cursor position"
|
||||
":kbd:`Ctrl+W`", "Close the current document"
|
||||
@@ -205,4 +210,4 @@ Document Viewer Shortcuts
|
||||
":kbd:`Ctrl+-`", "Zoom out"
|
||||
":kbd:`Ctrl+_`", "Zoom out"
|
||||
":kbd:`Ctrl+Shift+A`", "Select all text in the current paragraph"
|
||||
":kbd:`Ctrl+Shift+R`", "Close the document viewer"
|
||||
":kbd:`Ctrl+Shift+R`", "Close the document viewer (*)"
|
||||
|
||||
@@ -60,40 +60,47 @@ The table below shows the vim motions currently implemented in novelWriter.
|
||||
.. csv-table::
|
||||
:header: "Motion", "Mode(s)", "Description"
|
||||
|
||||
":kbd:`i`", "Normal → Insert", "Enter insert mode"
|
||||
":kbd:`I`", "Normal → Insert", "Jump to start of line and enter insert"
|
||||
":kbd:`v`", "Normal → Visual", "Enter character-wise visual mode"
|
||||
":kbd:`V`", "Normal → V-Line", "Enter line-wise visual mode"
|
||||
":kbd:`dd`", "Normal", "Delete (cut) current line"
|
||||
":kbd:`x`", "Normal", "Delete character under cursor"
|
||||
":kbd:`w`", "Normal / Visual", "Move to next word start"
|
||||
":kbd:`b`", "Normal / Visual", "Move to previous word start"
|
||||
":kbd:`e`", "Normal / Visual", "Move to next word end"
|
||||
":kbd:`dw`", "Normal", "Delete from cursor to next word start"
|
||||
":kbd:`de`", "Normal", "Delete from cursor to next word end"
|
||||
":kbd:`db`", "Normal", "Delete from cursor to previous word start"
|
||||
":kbd:`d$`", "Normal", "Delete from cursor to end of line"
|
||||
":kbd:`yw`", "Normal", "Yank (copy) from cursor to next word start"
|
||||
":kbd:`gg`", "Normal / Visual", "Jump to first line"
|
||||
":kbd:`G`", "Normal / Visual", "Jump to last line"
|
||||
":kbd:`yy`", "Normal", "Yank current line"
|
||||
":kbd:`p`", "Normal", "Paste after current line or cursor"
|
||||
":kbd:`P`", "Normal", "Paste before current line or at cursor"
|
||||
":kbd:`o`", "Normal → Insert", "Open new line below and enter insert"
|
||||
":kbd:`O`", "Normal → Insert", "Open new line above and enter insert"
|
||||
":kbd:`$`", "Normal / Visual", "Move to end of line"
|
||||
":kbd:`0`", "Normal / Visual", "Move to start of line"
|
||||
":kbd:`a`", "Normal → Insert", "Enter insert after cursor"
|
||||
":kbd:`A`", "Normal → Insert", "Enter insert at end of line"
|
||||
":kbd:`u`", "Normal", "Undo last change"
|
||||
":kbd:`zz`", "Normal", "Centre cursor vertically"
|
||||
":kbd:`h`", "Normal / Visual", "Move left"
|
||||
":kbd:`j`", "Normal / Visual", "Move down"
|
||||
":kbd:`k`", "Normal / Visual", "Move up"
|
||||
":kbd:`l`", "Normal / Visual", "Move right"
|
||||
":kbd:`d`", "Visual / V-Line", "Delete selection"
|
||||
":kbd:`x`", "Visual / V-Line", "Delete selection"
|
||||
":kbd:`y`", "Visual / V-Line", "Yank selection"
|
||||
":kbd:`i`", "Normal → Insert", "Enter insert mode"
|
||||
":kbd:`I`", "Normal → Insert", "Jump to start of line and enter insert"
|
||||
":kbd:`v`", "Normal → Visual", "Enter character-wise visual mode"
|
||||
":kbd:`V`", "Normal → V-Line", "Enter line-wise visual mode"
|
||||
":kbd:`dd`", "Normal", "Delete (cut) current line"
|
||||
":kbd:`x`", "Normal", "Delete character under cursor"
|
||||
":kbd:`w`", "Normal / Visual", "Move to next word start"
|
||||
":kbd:`b`", "Normal / Visual", "Move to previous word start"
|
||||
":kbd:`e`", "Normal / Visual", "Move to next word end"
|
||||
":kbd:`dw`", "Normal", "Delete from cursor to next word start"
|
||||
":kbd:`de`", "Normal", "Delete from cursor to next word end"
|
||||
":kbd:`db`", "Normal", "Delete from cursor to previous word start"
|
||||
":kbd:`d$`", "Normal", "Delete from cursor to end of line"
|
||||
":kbd:`yw`", "Normal", "Yank (copy) from cursor to next word start"
|
||||
":kbd:`gg`", "Normal / Visual", "Jump to first line"
|
||||
":kbd:`G`", "Normal / Visual", "Jump to last line"
|
||||
":kbd:`yy`", "Normal", "Yank current line"
|
||||
":kbd:`p`", "Normal", "Paste after current line or cursor"
|
||||
":kbd:`P`", "Normal", "Paste before current line or at cursor"
|
||||
":kbd:`o`", "Normal → Insert", "Open new line below and enter insert"
|
||||
":kbd:`O`", "Normal → Insert", "Open new line above and enter insert"
|
||||
":kbd:`$`", "Normal / Visual", "Move to end of line"
|
||||
":kbd:`0`", "Normal / Visual", "Move to start of line"
|
||||
":kbd:`a`", "Normal → Insert", "Enter insert after cursor"
|
||||
":kbd:`A`", "Normal → Insert", "Enter insert at end of line"
|
||||
":kbd:`u`", "Normal", "Undo last change"
|
||||
":kbd:`Ctrl+R`", "Normal", "Redo last undone change (*)"
|
||||
":kbd:`zz`", "Normal", "Centre cursor vertically"
|
||||
":kbd:`h`", "Normal / Visual", "Move left"
|
||||
":kbd:`j`", "Normal / Visual", "Move down"
|
||||
":kbd:`k`", "Normal / Visual", "Move up"
|
||||
":kbd:`l`", "Normal / Visual", "Move right"
|
||||
":kbd:`d`", "Visual / V-Line", "Delete selection"
|
||||
":kbd:`x`", "Visual / V-Line", "Delete selection"
|
||||
":kbd:`y`", "Visual / V-Line", "Yank selection"
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
Shortcuts marked with (*) are remapped when Vim Mode is enabled. These are shortcuts that
|
||||
usually do other things when Vim Mode is off.
|
||||
|
||||
|
||||
Known Issues
|
||||
|
||||
@@ -26,6 +26,8 @@ import logging
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
from novelwriter.enum import nwDocAction, nwVimMode
|
||||
from novelwriter.types import (
|
||||
QtKeepAnchor,
|
||||
@@ -37,6 +39,7 @@ from novelwriter.types import (
|
||||
QtKeyPageUp,
|
||||
QtKeyRight,
|
||||
QtKeyUp,
|
||||
QtModCtrl,
|
||||
QtModShift,
|
||||
QtMoveDown,
|
||||
QtMoveEnd,
|
||||
@@ -158,6 +161,8 @@ class EditorVimMode:
|
||||
"""Handle key events for Vim mode NORMAL mode motions."""
|
||||
key = event.text()
|
||||
cursor = self.textCursor()
|
||||
if event.key() == Qt.Key.Key_R and event.modifiers() == QtModCtrl:
|
||||
key = "<C-r>"
|
||||
|
||||
if key in self._vim.PREFIX_KEYS or key in self._vim.SUFFIX_KEYS:
|
||||
self._vim.pushCommandKey(key)
|
||||
@@ -319,6 +324,10 @@ class EditorVimMode:
|
||||
self._editor.docAction(nwDocAction.UNDO)
|
||||
self._vim.resetCommand()
|
||||
|
||||
elif command == "<C-r>":
|
||||
self._editor.docAction(nwDocAction.REDO)
|
||||
self._vim.resetCommand()
|
||||
|
||||
elif command == "zz":
|
||||
self._editor.ensureCursorVisible(centre=True)
|
||||
self._vim.resetCommand()
|
||||
|
||||
@@ -85,10 +85,24 @@ class GuiMainMenu(QMenuBar):
|
||||
self._buildToolsMenu()
|
||||
self._buildHelpMenu()
|
||||
|
||||
self.updateVimModeKeys()
|
||||
self.installEventFilter(StatusTipFilter(mainGui))
|
||||
|
||||
logger.debug("Ready: GuiMainMenu")
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def updateVimModeKeys(self) -> None:
|
||||
"""Update shortcuts that depend on vim mode state."""
|
||||
if CONFIG.vimMode:
|
||||
self.aViewDoc.setShortcuts([])
|
||||
self.aCloseView.setShortcuts([])
|
||||
else:
|
||||
self.aViewDoc.setShortcut("Ctrl+R")
|
||||
self.aCloseView.setShortcut("Ctrl+Shift+R")
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
##
|
||||
@@ -263,12 +277,10 @@ class GuiMainMenu(QMenuBar):
|
||||
|
||||
# Document > Preview
|
||||
self.aViewDoc = qtAddAction(self.docuMenu, self.tr("View Document"))
|
||||
self.aViewDoc.setShortcut("Ctrl+R")
|
||||
self.aViewDoc.triggered.connect(qtWeakLambda(self.mainGui.docPanes.viewDocument, None))
|
||||
|
||||
# Document > Close Preview
|
||||
self.aCloseView = qtAddAction(self.docuMenu, self.tr("Close Document View"))
|
||||
self.aCloseView.setShortcut("Ctrl+Shift+R")
|
||||
self.aCloseView.triggered.connect(self.mainGui.docPanes.closeDocViewer)
|
||||
|
||||
# Document > Separator
|
||||
|
||||
@@ -775,6 +775,8 @@ class GuiMain(QMainWindow):
|
||||
|
||||
if update.theme:
|
||||
self.refreshThemeColors(syntax=update.syntax, force=True)
|
||||
if update.vim:
|
||||
self.mainMenu.updateVimModeKeys()
|
||||
self.docPanes.applySettings(update)
|
||||
if update.viewport:
|
||||
self.projView.initViewport()
|
||||
|
||||
@@ -26,19 +26,32 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.dialogs.preferences import GuiNeedsUpdate
|
||||
from novelwriter.editor.editor import GuiDocEditor
|
||||
from novelwriter.editor.vim import VimState
|
||||
from novelwriter.enum import nwVimMode
|
||||
from novelwriter.guimain import GuiMain
|
||||
from novelwriter.types import QtKeyEscape, QtKeyRight, QtModShift
|
||||
from novelwriter.types import QtKeyEscape, QtKeyRight, QtModCtrl, QtModShift
|
||||
|
||||
from tests.helpers import C, MockRnd, buildTestProject
|
||||
|
||||
KEY_DELAY = 2
|
||||
VIM_CHANGED = GuiNeedsUpdate(
|
||||
restart=False,
|
||||
tree=False,
|
||||
theme=False,
|
||||
syntax=False,
|
||||
spelling=False,
|
||||
vim=True,
|
||||
editor=False,
|
||||
viewer=False,
|
||||
viewport=False,
|
||||
)
|
||||
|
||||
|
||||
def openVimEditor(nwGUI: GuiMain, projPath: Path) -> GuiDocEditor:
|
||||
@@ -47,7 +60,7 @@ def openVimEditor(nwGUI: GuiMain, projPath: Path) -> GuiDocEditor:
|
||||
nwGUI.openProject(projPath)
|
||||
assert nwGUI.docPanes.openDocument(C.hSceneDoc)
|
||||
CONFIG.vimMode = True
|
||||
nwGUI.docEditor.initSettings(updateVimMode=True)
|
||||
nwGUI._processConfigChanges(VIM_CHANGED)
|
||||
return nwGUI.docEditor
|
||||
|
||||
|
||||
@@ -80,19 +93,24 @@ def testEditorVimMode_Init(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockRnd
|
||||
assert nwGUI.docPanes.openDocument(C.hSceneDoc)
|
||||
docEditor = nwGUI.docEditor
|
||||
docFooter = docEditor.docFooter
|
||||
mainMenu = nwGUI.mainMenu
|
||||
|
||||
# Disabled by default
|
||||
assert docEditor._vimMode is None
|
||||
assert docFooter.vimStatus.isHidden() is True
|
||||
assert mainMenu.aViewDoc.shortcut().toString() == "Ctrl+R"
|
||||
assert mainMenu.aCloseView.shortcut().toString() == "Ctrl+Shift+R"
|
||||
|
||||
# Enable from settings
|
||||
# Enable from settings, which also releases conflicting shortcuts
|
||||
CONFIG.vimMode = True
|
||||
docEditor.initSettings(updateVimMode=True)
|
||||
nwGUI._processConfigChanges(VIM_CHANGED)
|
||||
vimMode = docEditor._vimMode
|
||||
assert vimMode is not None
|
||||
assert vimMode._vim.mode == nwVimMode.NORMAL
|
||||
assert docFooter._vimMode == nwVimMode.NORMAL
|
||||
assert docFooter.vimStatus.isHidden() is False
|
||||
assert mainMenu.aViewDoc.shortcut().isEmpty() is True
|
||||
assert mainMenu.aCloseView.shortcut().isEmpty() is True
|
||||
|
||||
# Mode changes are reported to the footer
|
||||
qtbot.keyClick(docEditor, "i", delay=KEY_DELAY)
|
||||
@@ -139,12 +157,14 @@ def testEditorVimMode_Init(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, mockRnd
|
||||
assert nwGUI.docPanes.openDocument(C.hChapterDoc)
|
||||
assert vimMode._vim.mode == nwVimMode.NORMAL
|
||||
|
||||
# Disable from settings
|
||||
# Disable from settings, which also restores the shortcuts
|
||||
CONFIG.vimMode = False
|
||||
docEditor.initSettings(updateVimMode=True)
|
||||
nwGUI._processConfigChanges(VIM_CHANGED)
|
||||
assert docEditor._vimMode is None
|
||||
assert docFooter._vimMode is None
|
||||
assert docFooter.vimStatus.isHidden() is True
|
||||
assert mainMenu.aViewDoc.shortcut().toString() == "Ctrl+R"
|
||||
assert mainMenu.aCloseView.shortcut().toString() == "Ctrl+Shift+R"
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
@@ -601,6 +621,12 @@ def testEditorVimMode_VisualMode(qtbot: QtBot, nwGUI: GuiMain, projPath: Path, m
|
||||
qtbot.keyClick(docEditor, "u", delay=KEY_DELAY)
|
||||
assert docEditor.getText() == originalText
|
||||
|
||||
# Ctrl+R: Redo and undo again
|
||||
qtbot.keyClick(docEditor, Qt.Key.Key_R, QtModCtrl, delay=KEY_DELAY)
|
||||
assert docEditor.getText().strip() == ""
|
||||
qtbot.keyClick(docEditor, "u", delay=KEY_DELAY)
|
||||
assert docEditor.getText() == originalText
|
||||
|
||||
qtbot.keyClicks(docEditor, "g", delay=KEY_DELAY)
|
||||
qtbot.keyClicks(docEditor, "g", delay=KEY_DELAY)
|
||||
assert docEditor.textCursor().position() == 0
|
||||
|
||||
Reference in New Issue
Block a user