Skip to content

Conversation

@idealemu
Copy link
Contributor

@idealemu idealemu commented Aug 16, 2025

Summary:

Adding an editor Vim-like mode to novelWriter. This will enable users who activate this feature to navigate files using basic vim motions, performing basic editing in a vim "Normal mode" and write text normally in the "Insert mode".

Related Issue(s):

Closes #2493

Reviewer's Checklist:

  • The header of all files contain a reference to the repository license
  • The overall test coverage is increased or remains the same as before
  • All tests are passing
  • All linting checks are passing and the style guide is followed
  • Documentation (as docstrings) is complete and understandable
  • Only files that have been actively changed are committed

@idealemu idealemu mentioned this pull request Aug 16, 2025
@vkbo vkbo added this to the Release 2.8 Beta 1 milestone Aug 16, 2025
@vkbo
Copy link
Owner

vkbo commented Aug 16, 2025

Just an FYI (I already fixed it) under "Related Issue(s)" you put "Closes" and the issue number (not the link). It then triggers the automatic linking to the ticket.

@vkbo
Copy link
Owner

vkbo commented Aug 29, 2025

This is looking really great. I'll see if I can fix my part of it this weekend.

Test coverage is still a little spotty. You can click forward to the test coverage report from the codecov check here on the pull request, or if you run tests locally with run_tests.py, setting the -r flag will generate a HTML report folder at the root of the repo. On Linux, the coverage report should show 26 missing lines when the code is covered 100%. The remaining lines are covered on Windows and MacOS.

@vkbo
Copy link
Owner

vkbo commented Aug 29, 2025

I'm also happy to write some of the tests. It helps to understand the code flow. I also find that writing tests is a good way to test the logic of the code. I often discover issues and corner cases when writing tests.

@idealemu
Copy link
Contributor Author

This is looking really great. I'll see if I can fix my part of it this weekend.

Test coverage is still a little spotty. You can click forward to the test coverage report from the codecov check here on the pull request, or if you run tests locally with run_tests.py, setting the -r flag will generate a HTML report folder at the root of the repo. On Linux, the coverage report should show 26 missing lines when the code is covered 100%. The remaining lines are covered on Windows and MacOS.

Super ! I did a trial usage of it yesterday evening and I have one or two features I realised I was missing. I'll bump the test coverage up once those are added. 😄

@vkbo
Copy link
Owner

vkbo commented Oct 4, 2025

I also added the colours from the discussion to the Vim mode labels on the editor footer. You can change them if you wish. I just picked the ones from the screenshot and added blue for insert so it stands out.

@vkbo
Copy link
Owner

vkbo commented Oct 27, 2025

Hi, I haven't heard from you in a while. I want to make the beta release very soon, but there are a couple of steps left before this can go in. I mainly just need you to do a functional test of the Vim mode after my changes to your PR. The tests pass, so I expect it works as it should.

I also want to do some optimisations in another PR, but I need an answer to the remaining comment for my optimisation rewrite.

I also need documentation for the Vim mode. If you don't know how to write reStructuredText docs, you can post it as Markdown and I'll adapt it (just create docs/vim_mode.md or something). It can be done in a separate PR, so no rush for this one to be merged, but I need it before the RC1 release. I think a short introduction and a table listing the different modes and what codes are available and what they do should be enough. I assume most people who know what Vim mode is already know what the commands mean, but a quick overview is anyway helpful and also documents which commands are implemented.

@idealemu
Copy link
Contributor Author

idealemu commented Nov 5, 2025

Hi, I haven't heard from you in a while. I want to make the beta release very soon, but there are a couple of steps left before this can go in. I mainly just need you to do a functional test of the Vim mode after my changes to your PR. The tests pass, so I expect it works as it should.

I also want to do some optimisations in another PR, but I need an answer to the remaining comment for my optimisation rewrite.

I also need documentation for the Vim mode. If you don't know how to write reStructuredText docs, you can post it as Markdown and I'll adapt it (just create docs/vim_mode.md or something). It can be done in a separate PR, so no rush for this one to be merged, but I need it before the RC1 release. I think a short introduction and a table listing the different modes and what codes are available and what they do should be enough. I assume most people who know what Vim mode is already know what the commands mean, but a quick overview is anyway helpful and also documents which commands are implemented.

Hi, sorry for the long gap. I will check up on the remaining comment later today.

I will add the content for the docs as a markdown in docs/vim_mode.md. It will have an introduction, list of implemented commands, and any particular quirks that diverge a little from normal vim behaviour.

I will also redo some functionality testing and check everything works as intended. Thank you again for the changes and taking the time to review !

@vkbo
Copy link
Owner

vkbo commented Nov 5, 2025

Great, thanks!

I have a performance optimisation branch as well where I only read the self._vim.command value once and cache it for the rest of the if-elif. As far as I can tell from the code, that shouldn't be a problem. The only exception from the pattern is the one elif that doesn't return True. If that's just something you forgot, my rewrite should be fine. (It passes your tests.)

@idealemu
Copy link
Contributor Author

idealemu commented Nov 5, 2025

The vimmode variable is also now present in the novelwriter.conf file

I can't seem to find a file with that exact name ? Could you point me towards how I can enable vim mode until it is in the preferences ?
I can see tests/reference/baseConfig_novelwriter.conf but that would only enable it for the test right ? Is there another path to enableing the config and just 'free using' novelWriter ?

I can still hardcode in novelwriter/config.py, so just wondering if there is a better way in place currently until the config hits the GUI.

@vkbo
Copy link
Owner

vkbo commented Nov 5, 2025

I can't seem to find a file with that exact name ?

See https://novelwriter.io/docs/technical/locations.html#configuration

@idealemu
Copy link
Contributor Author

idealemu commented Nov 8, 2025

I have added docs/vim-mode.md with basic documentation.

I tested while doing some writing and everything seems to work fine.
However, I also noticed that there are two commands "db" and "de" are missing (delete back, and delete end, which are both analogous to delete word).

Based on existing code I would suggest:

if self._vim.command == "db":
    cursor.beginEditBlock()
    cursor.movePosition(QtMovePreviousWord, QtKeepAnchor)
    self._vim.yankToInternal(cursor.selectedText())
    cursor.removeSelectedText()
    cursor.endEditBlock()
    self.setTextCursor(cursor)
    self._vim.resetCommand()
    return True

if self._vim.command == "de":
    cursor.beginEditBlock()
    # Extend selection to end of current/next word
    origPos = cursor.position()
    cursor.movePosition(QtMoveEndOfWord, QtKeepAnchor)
    if cursor.position() == origPos:               # already at end-of-word
        textLen = len(self.toPlainText())
        if origPos < textLen:
            cursor.movePosition(QtMoveNextChar, QtKeepAnchor)
            cursor.movePosition(QtMoveEndOfWord, QtKeepAnchor)
    self._vim.yankToInternal(cursor.selectedText())
    cursor.removeSelectedText()
    cursor.endEditBlock()
    self.setTextCursor(cursor)
    self._vim.resetCommand()
    return True

But I need to seem if this matches the performances changes you made 😃
Otherwise I think I am decently satisfied with what this PR accomplishes.

@vkbo
Copy link
Owner

vkbo commented Nov 8, 2025

Don't worry about the performance changes. I can adapt those to fit.

I see there are merge conflict issues with the current main branch. I can fix those if you wish, once you're done with your changes.

@idealemu
Copy link
Contributor Author

idealemu commented Nov 9, 2025

Super ! I just added these two, and corresponding test coverage and doc update then :) If you could take care of the conflicts that would be great ! Docs are currently markdown as you suggested, I wanted to learn the format you use but I frankly kind of just want this PR to be ready 😄

@vkbo
Copy link
Owner

vkbo commented Nov 9, 2025

Updated your branch and fixed linting errors.

It looks good. One minor thing is that the "Already at end-of-word" condition here is not covered by tests.

        if self._vim.command == "de":
            cursor.beginEditBlock()
            # Extend selection to end of current/next word
            origPos = cursor.position()
            cursor.movePosition(QtMoveEndOfWord, QtKeepAnchor)
            if cursor.position() == origPos:  # Already at end-of-word
                textLen = len(self.toPlainText())
                if origPos < textLen:
                    cursor.movePosition(QtMoveNextChar, QtKeepAnchor)
                    cursor.movePosition(QtMoveEndOfWord, QtKeepAnchor)
            self._vim.yankToInternal(cursor.selectedText())
            cursor.removeSelectedText()
            cursor.endEditBlock()
            self.setTextCursor(cursor)
            self._vim.resetCommand()
            return True

@idealemu
Copy link
Contributor Author

Updated your branch and fixed linting errors.

It looks good. One minor thing is that the "Already at end-of-word" condition here is not covered by tests.

        if self._vim.command == "de":
            cursor.beginEditBlock()
            # Extend selection to end of current/next word
            origPos = cursor.position()
            cursor.movePosition(QtMoveEndOfWord, QtKeepAnchor)
            if cursor.position() == origPos:  # Already at end-of-word
                textLen = len(self.toPlainText())
                if origPos < textLen:
                    cursor.movePosition(QtMoveNextChar, QtKeepAnchor)
                    cursor.movePosition(QtMoveEndOfWord, QtKeepAnchor)
            self._vim.yankToInternal(cursor.selectedText())
            cursor.removeSelectedText()
            cursor.endEditBlock()
            self.setTextCursor(cursor)
            self._vim.resetCommand()
            return True

I just added the test coverage for that 👍 And expanded the doc on a slight difference in my implementation of the word end command "e" compared to how vim handles it.

@idealemu
Copy link
Contributor Author

For me this is now at a state where I am moving my personal notes and world-building from markdown documents I used to edit in vim into novelWriter to have everything in one place.
It might need more features (read more vim motions/commands) in the future, but it good enough for me to dog-food it for my personal novel writing 😄

Unless you have other points that I should address I'm willing to consider this PR ready.

(Now if novelWriter had the ability to have two editor windows open in a split I would be one happy novel writer !)

@vkbo
Copy link
Owner

vkbo commented Nov 13, 2025

For me this is now at a state where I am moving my personal notes and world-building from markdown documents I used to edit in vim into novelWriter to have everything in one place. It might need more features (read more vim motions/commands) in the future, but it good enough for me to dog-food it for my personal novel writing 😄

😄

Unless you have other points that I should address I'm willing to consider this PR ready.

Nope. All good! I'm planning to do an RC1 release not this weekend, but next. So I'll merge it before this so it's included in RC1. After that it's usually two weeks of testing before I do the full 2.8 release.

(Now if novelWriter had the ability to have two editor windows open in a split I would be one happy novel writer !)

Yeah, I'm missing that too sometimes when I'm writing but need to update something in my notes on the right side. I've thought about merging the editor and viewer classes into one and instead make it a edit/view mode in one panel. That way, you can switch either of them between edit and view as needed.

It's non-trivial but it may be worth the effort.

@vkbo vkbo merged commit 746ebb7 into vkbo:main Nov 14, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Vim mode

2 participants