Complete the connection between editor and vim handler
This commit is contained in:
@@ -89,7 +89,7 @@ from novelwriter.editor.highlighter import BLOCK_META, BLOCK_TITLE
|
||||
from novelwriter.editor.hovercard import GuiDocHoverCard
|
||||
from novelwriter.editor.runnables import T_TextCheckPayload, TextCheckDispatcher, WordCounterDispatcher
|
||||
from novelwriter.editor.textblock import T_TextCheckList, TextBlockData
|
||||
from novelwriter.editor.vim import DocumentVimMode
|
||||
from novelwriter.editor.vim import EditorVimMode
|
||||
from novelwriter.enum import (
|
||||
nwChange,
|
||||
nwComment,
|
||||
@@ -462,8 +462,8 @@ class GuiDocEditor(QTextEdit):
|
||||
self.searchVisible = self.docSearch.isVisible
|
||||
self.changeFocusState = self.docHeader.changeFocusState
|
||||
|
||||
# Vim State for vimMode
|
||||
self._vimMode = DocumentVimMode(self)
|
||||
# Vim Mode
|
||||
self._vimMode: EditorVimMode | None = None
|
||||
|
||||
# Finalise
|
||||
self.updateSyntaxColors()
|
||||
@@ -652,8 +652,7 @@ class GuiDocEditor(QTextEdit):
|
||||
else:
|
||||
self.clearEditor()
|
||||
|
||||
# Refresh Vim Mode
|
||||
self._vimMode.setVimMode(nwVimMode.NORMAL)
|
||||
self._initVimMode()
|
||||
|
||||
def initViewport(self) -> None:
|
||||
"""Initialise the settings of the editor viewport."""
|
||||
@@ -683,7 +682,7 @@ class GuiDocEditor(QTextEdit):
|
||||
self.docHeader.setHandle(self._docHandle)
|
||||
self.docSearch.setAutoReplaceEnabled(CONFIG.doReplace)
|
||||
if updateVimMode:
|
||||
self._vimMode.setVimMode(nwVimMode.NORMAL)
|
||||
self._initVimMode()
|
||||
|
||||
def loadText(self, tHandle: str, tLine: int | None = None) -> bool:
|
||||
"""Load text from a document into the editor. If we have an I/O
|
||||
@@ -743,6 +742,8 @@ class GuiDocEditor(QTextEdit):
|
||||
self.setDocumentChanged(False)
|
||||
self._qDocument.clearUndoRedoStacks()
|
||||
self.docToolBar.setVisible(CONFIG.showEditToolBar)
|
||||
if self._vimMode:
|
||||
self._vimMode.setVimMode(nwVimMode.NORMAL)
|
||||
|
||||
# Process State Changes
|
||||
SHARED.project.data.setLastHandle(tHandle, "editor")
|
||||
@@ -1248,14 +1249,14 @@ class GuiDocEditor(QTextEdit):
|
||||
if key == QtKeyEscape:
|
||||
if self.searchVisible():
|
||||
self.closeSearch()
|
||||
elif CONFIG.vimMode:
|
||||
elif self._vimMode:
|
||||
self._vimMode.setVimMode(nwVimMode.NORMAL)
|
||||
elif SHARED.focusMode:
|
||||
SHARED.setFocusMode(False)
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
if CONFIG.vimMode and self._vimMode.processVimKeyEvent(event):
|
||||
if self._vimMode and self._vimMode.processVimKeyEvent(event):
|
||||
return
|
||||
|
||||
if self.docSearch.anyFocus() and key in ENTER_KEYS:
|
||||
@@ -2732,6 +2733,16 @@ class GuiDocEditor(QTextEdit):
|
||||
options.setFlags(options.flags() | QTextOption.Flag.ShowLineAndParagraphSeparators)
|
||||
self._qDocument.setDefaultTextOption(options)
|
||||
|
||||
def _initVimMode(self) -> None:
|
||||
"""Enable or disable vim mode from the user settings."""
|
||||
if CONFIG.vimMode:
|
||||
if self._vimMode is None:
|
||||
self._vimMode = EditorVimMode(self, self.docFooter.updateVimModeStatusBar)
|
||||
self._vimMode.setVimMode(nwVimMode.NORMAL)
|
||||
else:
|
||||
self._vimMode = None
|
||||
self.docFooter.updateVimModeStatusBar(None)
|
||||
|
||||
def _dispatchKeyPress(self, event: QKeyEvent) -> None:
|
||||
"""Send a key event to the base class, except plain Return.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
novelWriter - GUI Document Vim Mode
|
||||
===================================
|
||||
novelWriter - GUI Editor Vim Mode
|
||||
=================================
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright (C) 2026 Veronica Berglyd Olsen and novelWriter contributors
|
||||
@@ -25,7 +25,6 @@ import logging
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.enum import nwDocAction, nwVimMode
|
||||
from novelwriter.types import (
|
||||
QtKeepAnchor,
|
||||
@@ -45,6 +44,8 @@ from novelwriter.types import (
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
from PyQt6.QtGui import QKeyEvent, QTextCursor
|
||||
|
||||
from novelwriter.editor.editor import GuiDocEditor
|
||||
@@ -52,32 +53,38 @@ if TYPE_CHECKING:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DocumentVimMode:
|
||||
"""Gui Widget: Editor Vim Mode."""
|
||||
class EditorVimMode:
|
||||
"""Editor Component: Vim Mode."""
|
||||
|
||||
__slots__ = ("_editor", "_vim")
|
||||
__slots__ = ("_callback", "_editor", "_vim")
|
||||
|
||||
def __init__(self, editor: GuiDocEditor) -> None:
|
||||
def __init__(self, editor: GuiDocEditor, callback: Callable[[nwVimMode], None]) -> None:
|
||||
|
||||
self._editor = editor
|
||||
self._vim = VimState()
|
||||
self._callback = callback
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setVimMode(self, mode: nwVimMode) -> None:
|
||||
"""Change the vim mode."""
|
||||
if CONFIG.vimMode:
|
||||
if mode == nwVimMode.NORMAL:
|
||||
cursor = self.textCursor()
|
||||
cursor.clearSelection()
|
||||
self.setTextCursor(cursor)
|
||||
self._vim.setMode(mode)
|
||||
self._editor.docFooter.updateVimModeStatusBar(mode)
|
||||
else:
|
||||
self._editor.docFooter.updateVimModeStatusBar(None)
|
||||
if mode == nwVimMode.NORMAL:
|
||||
cursor = self.textCursor()
|
||||
cursor.clearSelection()
|
||||
self.setTextCursor(cursor)
|
||||
self._vim.setMode(mode)
|
||||
self._callback(mode)
|
||||
|
||||
def setTextCursor(self, cursor: QTextCursor) -> None:
|
||||
"""Set the text cursor."""
|
||||
self._editor.setTextCursor(cursor)
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def textCursor(self) -> QTextCursor:
|
||||
"""Return a new text cursor."""
|
||||
return self._editor.textCursor()
|
||||
@@ -207,6 +214,7 @@ class DocumentVimMode:
|
||||
if origPos < textLen:
|
||||
cursor.movePosition(QtMoveNextChar, QtKeepAnchor)
|
||||
cursor.movePosition(QtMoveEndOfWord, QtKeepAnchor)
|
||||
|
||||
self._vim.yankToInternal(cursor.selectedText())
|
||||
cursor.removeSelectedText()
|
||||
cursor.endEditBlock()
|
||||
@@ -418,7 +426,7 @@ class DocumentVimMode:
|
||||
class VimState:
|
||||
"""Minimal Vim state machine."""
|
||||
|
||||
__slots__ = ("_internalClipboard", "_mode", "_normalCommand", "_visualCommand")
|
||||
__slots__ = ("_buffer", "_mode", "_normalCmd", "_visualCmd")
|
||||
|
||||
PREFIX_KEYS = "dygz"
|
||||
VISUAL_PREFIX_KEYS = "g"
|
||||
@@ -426,9 +434,9 @@ class VimState:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._mode: nwVimMode = nwVimMode.NORMAL
|
||||
self._normalCommand = ""
|
||||
self._visualCommand = ""
|
||||
self._internalClipboard = ""
|
||||
self._buffer = ""
|
||||
self._normalCmd = ""
|
||||
self._visualCmd = ""
|
||||
self.setMode(nwVimMode.NORMAL)
|
||||
|
||||
@property
|
||||
@@ -440,9 +448,9 @@ class VimState:
|
||||
def command(self) -> str:
|
||||
"""Return the current vim command."""
|
||||
if self._mode in (nwVimMode.VISUAL, nwVimMode.V_LINE):
|
||||
return self._visualCommand
|
||||
return self._visualCmd
|
||||
else:
|
||||
return self._normalCommand
|
||||
return self._normalCmd
|
||||
|
||||
def setMode(self, mode: nwVimMode) -> None:
|
||||
"""Switch vim mode."""
|
||||
@@ -453,27 +461,27 @@ class VimState:
|
||||
|
||||
def resetCommand(self) -> None:
|
||||
"""Reset internal vim command."""
|
||||
self._normalCommand = ""
|
||||
self._visualCommand = ""
|
||||
self._normalCmd = ""
|
||||
self._visualCmd = ""
|
||||
|
||||
def pushCommandKey(self, key: str) -> None:
|
||||
"""Push key to the current command building stack."""
|
||||
if self._mode is nwVimMode.NORMAL:
|
||||
self._normalCommand += key
|
||||
self._normalCmd += key
|
||||
elif self._mode in (nwVimMode.VISUAL, nwVimMode.V_LINE):
|
||||
self._visualCommand += key
|
||||
self._visualCmd += key
|
||||
|
||||
def setCommand(self, key: str) -> None:
|
||||
"""Set the state of the current vim command."""
|
||||
if self._mode is nwVimMode.NORMAL:
|
||||
self._normalCommand = key
|
||||
self._normalCmd = key
|
||||
elif self._mode in (nwVimMode.VISUAL, nwVimMode.V_LINE):
|
||||
self._visualCommand = key
|
||||
self._visualCmd = key
|
||||
|
||||
def yankToInternal(self, text: str) -> None:
|
||||
"""Put text into internal vim buffer."""
|
||||
self._internalClipboard = text
|
||||
self._buffer = text
|
||||
|
||||
def pasteFromInternal(self) -> str:
|
||||
"""Paste from the internal vim clipboard."""
|
||||
return self._internalClipboard
|
||||
"""Paste from the internal vim buffer."""
|
||||
return self._buffer
|
||||
|
||||
Reference in New Issue
Block a user