├── .gitignore ├── docs ├── options.png └── preferences.png ├── .babelrc ├── prettier.config.mjs ├── styles └── vim.css ├── src ├── utils.js ├── scroll.js ├── ex.js ├── vim.js ├── clipboard.js └── preview.js ├── eslint.config.mjs ├── keymaps └── vim.json ├── package.json ├── LICENSE.md ├── README.md ├── .github └── workflows │ ├── claude.yml │ └── claude-code-review.yml └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /docs/options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inkdropapp/inkdrop-vim/HEAD/docs/options.png -------------------------------------------------------------------------------- /docs/preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inkdropapp/inkdrop-vim/HEAD/docs/preferences.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "targets": { "electron": "12.2.1" } 5 | }] 6 | ], 7 | "plugins": [ 8 | "@babel/plugin-proposal-class-properties" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | arrowParens: 'avoid', 3 | singleQuote: true, 4 | bracketSpacing: true, 5 | endOfLine: 'lf', 6 | semi: false, 7 | tabWidth: 2, 8 | trailingComma: 'none' 9 | } 10 | -------------------------------------------------------------------------------- /styles/vim.css: -------------------------------------------------------------------------------- 1 | .editor .mde .cm-editor { 2 | .cm-scroller.cm-vimMode .cm-fat-cursor { 3 | background: var(--editor-caret-color) !important; 4 | } 5 | &:not(.cm-focused) .cm-scroller.cm-vimMode .cm-fat-cursor { 6 | background: transparent !important; 7 | outline: solid 1px var(--editor-caret-color) !important; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const { Vim } = require('@replit/codemirror-vim') 2 | const { ViewPlugin } = require('@codemirror/view') 3 | 4 | const editorInitHandler = ViewPlugin.define(_view => { 5 | /* 6 | * NOTE: Reset the Vim global state when opening another note 7 | */ 8 | Vim.resetVimGlobalState_() 9 | return {} 10 | }) 11 | 12 | module.exports = { 13 | editorInitHandler 14 | } 15 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import prettier from 'eslint-config-prettier' 2 | 3 | export default [ 4 | { 5 | ignores: [] 6 | }, 7 | 8 | prettier, 9 | 10 | { 11 | languageOptions: { 12 | globals: { 13 | inkdrop: 'readonly' 14 | } 15 | }, 16 | rules: { 17 | 'no-useless-escape': 0, 18 | 'prefer-const': 2, 19 | 'no-unused-vars': 0 20 | } 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /keymaps/vim.json: -------------------------------------------------------------------------------- 1 | { 2 | ".note-list-bar": { 3 | "k": "core:open-prev-note", 4 | "j": "core:open-next-note" 5 | }, 6 | ".mde-preview-container": { 7 | "g g": "vim:move-to-start-of-file", 8 | "ctrl-u": "vim:scroll-half-screen-up", 9 | "ctrl-b": "vim:scroll-full-screen-up", 10 | "ctrl-d": "vim:scroll-half-screen-down", 11 | "ctrl-f": "vim:scroll-full-screen-down", 12 | "k": "vim:scroll-up", 13 | "j": "vim:scroll-down", 14 | "G": "vim:move-to-line", 15 | "H": "vim:move-to-start-of-file" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vim", 3 | "main": "./src/vim", 4 | "version": "3.0.1", 5 | "description": "vim keybindings", 6 | "keywords": [], 7 | "repository": "https://github.com/inkdropapp/inkdrop-vim", 8 | "license": "MIT", 9 | "scripts": { 10 | "lint": "eslint ." 11 | }, 12 | "engines": { 13 | "inkdrop": "^6.x" 14 | }, 15 | "devDependencies": { 16 | "eslint": "^9.28.0", 17 | "eslint-config-prettier": "^10.1.5", 18 | "prettier": "^3.5.3" 19 | }, 20 | "dependencies": { 21 | "@replit/codemirror-vim": "^6.3.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/scroll.js: -------------------------------------------------------------------------------- 1 | const { ViewPlugin } = require('@codemirror/view') 2 | const { vim, CodeMirror, getCM } = require('@replit/codemirror-vim') 3 | 4 | const disableScrollIntoView = ViewPlugin.fromClass( 5 | class { 6 | constructor(view) { 7 | const cm = getCM(view) 8 | cm.scrollIntoView = () => { 9 | // Do nothing to disable the vim's scrollIntoView behavior 10 | // because it conflicts with Inkdrop's scroll behavior with scroll margin support 11 | } 12 | } 13 | } 14 | ) 15 | 16 | module.exports = { 17 | disableScrollIntoView 18 | } 19 | -------------------------------------------------------------------------------- /src/ex.js: -------------------------------------------------------------------------------- 1 | const { Vim } = require('@replit/codemirror-vim') 2 | 3 | Vim.defineEx('write', 'w', () => { 4 | inkdrop.commands.dispatch(document.body, 'core:save-note') 5 | }) 6 | Vim.defineEx('next', 'n', () => { 7 | inkdrop.commands.dispatch(document.body, 'core:open-next-note') 8 | }) 9 | Vim.defineEx('prev', '', () => { 10 | inkdrop.commands.dispatch(document.body, 'core:open-prev-note') 11 | }) 12 | Vim.defineEx('preview', 'p', () => { 13 | inkdrop.commands.dispatch(document.body, 'view:toggle-preview') 14 | }) 15 | Vim.defineEx('side-by-side', 'side', () => { 16 | inkdrop.commands.dispatch(document.body, 'view:toggle-side-by-side') 17 | }) 18 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Takuya Matsuyama 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/vim.js: -------------------------------------------------------------------------------- 1 | const { vim, Vim } = require('@replit/codemirror-vim') 2 | const { actions } = require('inkdrop') 3 | const { 4 | registerClipboardTextOnFocus, 5 | registerClipboardText 6 | } = require('./clipboard') 7 | const { editorInitHandler } = require('./utils') 8 | const { bindPreviewVimCommands } = require('./preview') 9 | const { disableScrollIntoView } = require('./scroll') 10 | require('./ex') 11 | 12 | class Plugin { 13 | config = { 14 | seamlessJumpToTitle: { 15 | title: 'Seamlessly jump to note title', 16 | type: 'boolean', 17 | description: 18 | 'Focus jumps from the editor to the note title bar by `vim:move-up` command', 19 | default: false 20 | } 21 | } 22 | 23 | activate() { 24 | this.Vim = Vim 25 | this.extension = [ 26 | vim(), 27 | registerClipboardTextOnFocus(), 28 | disableScrollIntoView, 29 | editorInitHandler 30 | ] 31 | inkdrop.store.dispatch(actions.mde.addExtension(this.extension)) 32 | inkdrop.window.on('focus', this.handleAppFocus) 33 | this.unbindPreviewViewCommands = bindPreviewVimCommands() 34 | } 35 | 36 | deactivate() { 37 | inkdrop.store.dispatch(actions.mde.removeExtension(this.extension)) 38 | this.extension = null 39 | inkdrop.window.off('focus', this.handleAppFocus) 40 | 41 | this.unbindPreviewViewCommands() 42 | this.unbindPreviewViewCommands = null 43 | } 44 | 45 | handleAppFocus() { 46 | registerClipboardText() 47 | } 48 | } 49 | 50 | module.exports = new Plugin() 51 | -------------------------------------------------------------------------------- /src/clipboard.js: -------------------------------------------------------------------------------- 1 | const { vim, Vim } = require('@replit/codemirror-vim') 2 | const { clipboard } = require('electron') 3 | const { EditorView } = require('@codemirror/view') 4 | 5 | const origResetVimGlobalState = Vim.resetVimGlobalState_ 6 | Vim.resetVimGlobalState_ = () => { 7 | origResetVimGlobalState.call(Vim) 8 | const state = Vim.getVimGlobalState_() 9 | const origPushText = state.registerController.pushText 10 | state.registerController.pushText = ( 11 | registerName, 12 | operator, 13 | text, 14 | linewise, 15 | blockwise 16 | ) => { 17 | // console.log('pushText', registerName, operator, text, linewise, blockwise) 18 | if (!registerName) { 19 | const currentText = clipboard.readText() 20 | if (currentText !== text) { 21 | clipboard.writeText(text) 22 | } 23 | } 24 | origPushText.call( 25 | state.registerController, 26 | registerName, 27 | operator, 28 | text, 29 | linewise, 30 | blockwise 31 | ) 32 | } 33 | } 34 | 35 | Vim.resetVimGlobalState_() 36 | 37 | const registerClipboardText = () => { 38 | const text = clipboard.readText() 39 | if (text) { 40 | const isLinewise = text.indexOf('\n') >= 0 41 | Vim.getRegisterController().pushText( 42 | '', 43 | 'yank', 44 | text, 45 | isLinewise, 46 | vim.visualBlock 47 | ) 48 | } 49 | } 50 | const registerClipboardTextOnFocus = () => { 51 | return EditorView.updateListener.of(update => { 52 | if (update.focusChanged) { 53 | if (update.view.hasFocus) { 54 | registerClipboardText() 55 | } 56 | } 57 | }) 58 | } 59 | 60 | module.exports = { 61 | registerClipboardText, 62 | registerClipboardTextOnFocus 63 | } 64 | -------------------------------------------------------------------------------- /src/preview.js: -------------------------------------------------------------------------------- 1 | function bindPreviewVimCommands() { 2 | let sub = null 3 | 4 | function bindHandlers(target) { 5 | if (sub) sub.dispose() 6 | if (!target) return 7 | 8 | sub = inkdrop.commands.add(target, { 9 | 'vim:move-to-start-of-file': ({ target }) => { 10 | target.scrollTop = 0 11 | }, 12 | 'vim:scroll-up': ({ target }) => { 13 | console.log('vim:scroll-up', target) 14 | target.scrollTop -= 30 15 | }, 16 | 'vim:scroll-down': ({ target }) => { 17 | console.log('vim:scroll-down', target) 18 | target.scrollTop += 30 19 | }, 20 | 'vim:scroll-half-screen-up': ({ target }) => { 21 | target.scrollTop -= target.clientHeight / 2 22 | }, 23 | 'vim:scroll-half-screen-down': ({ target }) => { 24 | target.scrollTop += target.clientHeight / 2 25 | }, 26 | 'vim:scroll-full-screen-up': ({ target }) => { 27 | target.scrollTop -= target.clientHeight 28 | }, 29 | 'vim:scroll-full-screen-down': ({ target }) => { 30 | target.scrollTop += target.clientHeight 31 | }, 32 | 'vim:move-to-line': ({ target }) => { 33 | target.scrollTop = target.scrollHeight 34 | } 35 | }) 36 | } 37 | 38 | const selector = '.mde-preview-container' 39 | const el = document.querySelector(selector) 40 | bindHandlers(el) 41 | 42 | const observer = new MutationObserver(() => { 43 | const el = document.querySelector(selector) 44 | bindHandlers(el) 45 | }) 46 | observer.observe(document.body, { 47 | childList: true, 48 | subtree: true 49 | }) 50 | 51 | return () => { 52 | observer.disconnect() 53 | if (sub) sub.dispose() 54 | } 55 | } 56 | 57 | module.exports = { 58 | bindPreviewVimCommands 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim keybindings plugin for Inkdrop 2 | 3 | Provides Vim modal control for Inkdrop, blending the best of Vim and Inkdrop. 4 | 5 | ## Features 6 | 7 | - All common motions and operators, including text objects 8 | - Operator motion orthogonality 9 | - Visual mode - characterwise, linewise, blockwise 10 | - Incremental highlighted search (`/`, `?`, `#`, `*`, `g#`, `g*`) 11 | - Search/replace with confirm (:substitute, :%s) 12 | - Search history 13 | - Sort (`:sort`) 14 | - Marks (`,`) 15 | - Cross-buffer yank/paste 16 | - Select next/prev item in note list bar (`j` / `k`) 17 | - Scroll markdown preview pane 18 | 19 | ## Install 20 | 21 | ```sh 22 | ipm install vim 23 | ``` 24 | 25 | ## Key customizations 26 | 27 | You can customize keybindings in your [init.js](https://developers.inkdrop.app/guides/the-init-file). 28 | For example, 29 | 30 | ```js 31 | function configureKeyBindings() { 32 | const Vim = inkdrop.packages.getLoadedPackage('vim').mainModule.Vim 33 | 34 | // Map keys 35 | Vim.map('jj', '', 'insert') // in insert mode 36 | Vim.map('Y', 'y$') // in normal mode 37 | // Unmap keys 38 | Vim.unmap('jj', 'insert') 39 | } 40 | 41 | const editor = inkdrop.getActiveEditor() 42 | if (editor) configureKeyBindings() 43 | inkdrop.onEditorLoad(() => { 44 | configureKeyBindings() 45 | }) 46 | ``` 47 | 48 | ## Ex Commands 49 | 50 | ### `:w`, `:write` 51 | 52 | Saves current note immediately to the disk. 53 | 54 | ### `:next`, `:n` 55 | 56 | Opens next note on the note list. 57 | 58 | ### `:prev` 59 | 60 | Opens previous note on the note list. 61 | 62 | ### `:preview`, `:p` 63 | 64 | Toggles HMTL preview. 65 | 66 | ### `:side-by-side`, `:side` 67 | 68 | Toggles side-by-side mode. 69 | 70 | ### Define custom Ex commands 71 | 72 | You can extend Ex commands by writing [init.js](https://docs.inkdrop.app/manual/the-init-file). 73 | The following example defines `:find` command: 74 | 75 | ```js 76 | inkdrop.onEditorLoad(() => { 77 | const Vim = inkdrop.packages.getLoadedPackage('vim').mainModule.Vim 78 | Vim.defineEx('find', 'f', (cm, event) => { 79 | inkdrop.commands.dispatch(document.body, 'core:find-global') 80 | if (event.argString) 81 | inkdrop.commands.dispatch(document.body, 'core:search-notes', { 82 | keyword: event.argString.trim() 83 | }) 84 | }) 85 | }) 86 | ``` 87 | 88 | ## Changelog 89 | 90 | See the [GitHub releases](https://github.com/inkdropapp/inkdrop-vim/releases) for an overview of what changed in each update. 91 | -------------------------------------------------------------------------------- /.github/workflows/claude.yml: -------------------------------------------------------------------------------- 1 | name: Claude Code 2 | 3 | on: 4 | issue_comment: 5 | types: [created] 6 | pull_request_review_comment: 7 | types: [created] 8 | issues: 9 | types: [opened, assigned] 10 | pull_request_review: 11 | types: [submitted] 12 | 13 | jobs: 14 | claude: 15 | if: | 16 | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || 17 | (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || 18 | (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || 19 | (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: read 23 | pull-requests: read 24 | issues: read 25 | id-token: write 26 | actions: read # Required for Claude to read CI results on PRs 27 | steps: 28 | - name: Checkout repository 29 | uses: actions/checkout@v4 30 | with: 31 | fetch-depth: 1 32 | 33 | - name: Run Claude Code 34 | id: claude 35 | uses: anthropics/claude-code-action@beta 36 | with: 37 | claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} 38 | 39 | # This is an optional setting that allows Claude to read CI results on PRs 40 | additional_permissions: | 41 | actions: read 42 | 43 | # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4) 44 | # model: "claude-opus-4-20250514" 45 | 46 | # Optional: Customize the trigger phrase (default: @claude) 47 | # trigger_phrase: "/claude" 48 | 49 | # Optional: Trigger when specific user is assigned to an issue 50 | # assignee_trigger: "claude-bot" 51 | 52 | # Optional: Allow Claude to run specific commands 53 | # allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)" 54 | 55 | # Optional: Add custom instructions for Claude to customize its behavior for your project 56 | # custom_instructions: | 57 | # Follow our coding standards 58 | # Ensure all new code has tests 59 | # Use TypeScript for new files 60 | 61 | # Optional: Custom environment variables for Claude 62 | # claude_env: | 63 | # NODE_ENV: test 64 | 65 | -------------------------------------------------------------------------------- /.github/workflows/claude-code-review.yml: -------------------------------------------------------------------------------- 1 | name: Claude Code Review 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize] 6 | # Optional: Only run on specific file changes 7 | # paths: 8 | # - "src/**/*.ts" 9 | # - "src/**/*.tsx" 10 | # - "src/**/*.js" 11 | # - "src/**/*.jsx" 12 | 13 | jobs: 14 | claude-review: 15 | # Optional: Filter by PR author 16 | # if: | 17 | # github.event.pull_request.user.login == 'external-contributor' || 18 | # github.event.pull_request.user.login == 'new-developer' || 19 | # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' 20 | 21 | runs-on: ubuntu-latest 22 | permissions: 23 | contents: read 24 | pull-requests: read 25 | issues: read 26 | id-token: write 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v4 31 | with: 32 | fetch-depth: 1 33 | 34 | - name: Run Claude Code Review 35 | id: claude-review 36 | uses: anthropics/claude-code-action@beta 37 | with: 38 | claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} 39 | 40 | # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4) 41 | # model: "claude-opus-4-20250514" 42 | 43 | # Direct prompt for automated review (no @claude mention needed) 44 | direct_prompt: | 45 | Please review this pull request and provide feedback on: 46 | - Code quality and best practices 47 | - Potential bugs or issues 48 | - Performance considerations 49 | - Security concerns 50 | - Test coverage 51 | 52 | Be constructive and helpful in your feedback. 53 | 54 | # Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR 55 | # use_sticky_comment: true 56 | 57 | # Optional: Customize review based on file types 58 | # direct_prompt: | 59 | # Review this PR focusing on: 60 | # - For TypeScript files: Type safety and proper interface usage 61 | # - For API endpoints: Security, input validation, and error handling 62 | # - For React components: Performance, accessibility, and best practices 63 | # - For tests: Coverage, edge cases, and test quality 64 | 65 | # Optional: Different prompts for different authors 66 | # direct_prompt: | 67 | # ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' && 68 | # 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' || 69 | # 'Please provide a thorough code review focusing on our coding standards and best practices.' }} 70 | 71 | # Optional: Add specific tools for running tests or linting 72 | # allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)" 73 | 74 | # Optional: Skip review for certain conditions 75 | # if: | 76 | # !contains(github.event.pull_request.title, '[skip-review]') && 77 | # !contains(github.event.pull_request.title, '[WIP]') 78 | 79 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 2.2.2 2 | 3 | - fix(option): The seamless focus jump from the editor to the note title bar is now an option and disabled by default 4 | 5 | ### 2.2.1 6 | 7 | - feat(motion): Jump to note title on invoking `vim:move-up` at the first line and char 8 | 9 | ### 2.2.0 10 | 11 | - feat(motion): Support moitons like yG/ygg/dG/dgg (Thanks [jamalmoir](https://github.com/inkdropapp/inkdrop-vim/issues/39)) 12 | 13 | ### 2.1.10 14 | 15 | - fix(buffering): space key should be processed as a character input (Thanks [Chris](https://github.com/inkdropapp/inkdrop-vim/issues/29)) 16 | 17 | ### 2.1.9 18 | 19 | - fix(keymap): Commands should not work while buffering key in visual mode (Thanks [Kazuhiro](https://forum.inkdrop.app/t/minor-issue-in-vim-plugin/2702)) 20 | 21 | ### 2.1.7 22 | 23 | - fix(typo): Number keys not working as expected (Thanks [FORTRAN](https://forum.inkdrop.app/t/vim-plugin/2228/2)) 24 | 25 | ### 2.1.6 26 | 27 | - fix(motion): Ignore numeric keys when a modifier key is pressed (Thanks [Basyura](https://github.com/inkdropapp/inkdrop-vim/pull/25)) 28 | 29 | ### 2.1.5 30 | 31 | - fix(motion): enter/space/arrow keys not working as expected while key buffering (Thanks [rcashie](https://github.com/inkdropapp/inkdrop-vim/issues/24)) 32 | 33 | ### 2.1.4 34 | 35 | - fix(motion): text object manipulation not working for some tokens (Thanks [rcashie](https://github.com/inkdropapp/inkdrop-vim/issues/23)) 36 | 37 | ### 2.1.2 38 | 39 | - fix(keymap): remove keybindings of s h, s k, s l since those conflict with the default vim behavior of `s` (Thanks [oniatsu-san](https://github.com/inkdropapp/inkdrop-vim/issues/19)) 40 | 41 | ### 2.1.1 42 | 43 | - fix(keymap): change keybinding for `vim:move-to-mark` from " to ' (Thanks [oniatsu-san](https://github.com/inkdropapp/inkdrop-vim/issues/18)) 44 | 45 | ### 2.1.0 46 | 47 | - feat(motion): support moving cursor up/down by display lines (g k / g j) (Thanks [jolyon129](https://github.com/inkdropapp/inkdrop-vim/issues/16)) 48 | 49 | ### 2.0.1 50 | 51 | - Fix a bug where `vim:scroll-full-screen-up` and `vim:scroll-full-screen-down` not working (Thanks [@basyura](https://github.com/inkdropapp/inkdrop-vim/issues/13#issuecomment-612326857)) 52 | 53 | ### 2.0.0 54 | 55 | - (Breaking) The command prefix has been changed from `vim-mode:` to `vim:` so that the keybindings are correctly listed in the plugin settings 56 | - Moving focus between panes (sidebar, note list bar, editor, note title) with `s h` / `s j` / `s k` / `s l` 57 | - Select next/prev item in note list bar (`j` / `k`) 58 | - Scroll markdown preview pane ([Thanks @trietphm](https://github.com/inkdropapp/inkdrop-vim/issues/13)) 59 | 60 | ### 1.0.12 61 | 62 | - fix(key-buffering): replace character with "a" does not work ([Thanks seachicken](https://github.com/inkdropapp/inkdrop-vim/issues/11)) 63 | 64 | ### 1.0.11 65 | 66 | - fix(debug): typo in debug code that causes an error 67 | 68 | ### 1.0.10 69 | 70 | - fix(operatormotion): do not start key buffering for "D" and "C" operator motions (Thanks shimizu-san) 71 | - fix(buffering): avoid running command with 0 key while key buffering (Thanks volment) 72 | 73 | ### 1.0.8 74 | 75 | - fix(keymap): handle keystrokes as text input which failed to match binding [#8](https://github.com/inkdropapp/inkdrop-vim/issues/8) (Thanks @rtmoranorg) 76 | 77 | ### 1.0.7 78 | 79 | - fix(keymap): substitute keys not working [#4](https://github.com/inkdropapp/inkdrop-vim/issues/4) (Thanks @gregwebs and @giantsol) 80 | 81 | ### 1.0.6 82 | 83 | - fix(keymap): 'X' in visual mode does not work [#7](https://github.com/inkdropapp/inkdrop-vim/issues/7) (Thanks [@usou](https://github.com/usou)) 84 | 85 | ### 1.0.5 86 | 87 | - Copy deleted text to clipboard 88 | - Fix invalid selectors for `vim-mode:text-object-manipulation*` keymaps again 89 | 90 | ### 1.0.4 91 | 92 | - Fix invalid selectors for `vim-mode:text-object-manipulation*` keymaps 93 | 94 | ### 1.0.3 95 | 96 | - Support some actions for visual mode ([diff](https://github.com/inkdropapp/inkdrop-vim/commit/4536385f6d74c5e7c7247e7c65e593108925b056)) 97 | 98 | ### 1.0.2 99 | 100 | - feat(visual-mode): Support insert-at-start-of-target & insert-at-end-of-target (Thanks [Vikram](https://forum.inkdrop.app/t/vim-inserting-at-beginning-of-line-or-at-target-in-visual-block-mode-doesnt-work/1397/)) 101 | 102 | ### 1.0.1 103 | 104 | - fix(keybuffering): Avoid buffering key after processing command 105 | - fix(keybuffering): Avoid incorrect key buffering 106 | - fix(replace): Replacing with numeric character not working 107 | 108 | ### 1.0.0 109 | 110 | - feat(\*): Support inkdrop 4.x 111 | 112 | ### 0.3.2 113 | 114 | - fix(operator): Fix incorrect handling for operators 115 | 116 | ### 0.3.1 117 | 118 | - fix(keymaps): Support key buffering for keys like 'd' and 'c' 119 | 120 | ### 0.3.0 121 | 122 | - fix(keymaps): Support text object manipulations 123 | 124 | ### 0.2.4 125 | 126 | - Support Inkdrop v3.17.1 127 | 128 | ### 0.2.3 129 | 130 | - Support `ge` and `gE` (Thanks [@kiryph](https://github.com/kiryph)) 131 | 132 | ### 0.1.0 - First Release 133 | 134 | - Every feature added 135 | - Every bug fixed 136 | --------------------------------------------------------------------------------