From 0e8f692af3bc830ecdcd6cc2689ec7bf992ead4f Mon Sep 17 00:00:00 2001 From: NULL0B Date: Fri, 1 May 2026 18:46:00 -0400 Subject: [PATCH 1/3] feat(plugin): add configurable reference dialog and VS Code cursor support Replace the old toggle and summary flow with a single Config/Reference dialog that embeds the generated VS Code binding catalog directly in the plugin DLL. Add a searchable list-view with per-binding checkboxes so handled shortcuts can be enabled or disabled individually, while keeping a single top-level plugin mode toggle for strict handling versus pass-through behavior. Persist both the global enabled state and per-binding selections in the plugin INI file, and gate shortcut/chord interception on those saved selections so the runtime behavior matches the dialog configuration. Refactor shortcut execution into dedicated handler modules and add support for VS Code-style insert-cursor-above/below and column-selection up/down actions, including shared vertical movement logic and virtual-space handling. Update the keybinding sync pipeline to: - generate embedded binding reference metadata - preserve manifest order - support manifest-only bindings - stop emitting the intermediate JSON snapshot Refresh generated coverage data and docs to match the new flow, and simplify release packaging to ship only VSCodeKeymapNpp.dll now that the plugin no longer depends on external generated reference files. Also: - register a single `Config/Reference` plugin command - improve the dialog layout with grouped mode/search/status sections - link `comctl32` for the list-view UI - ignore `.idea` --- .gitignore | 1 + CMakeLists.txt | 9 +- CONTRIBUTING.md | 5 +- README.MD | 23 +- data/keybinding-mappings.json | 28 +- data/vscode-default-keybindings.windows.json | 1946 ------------------ docs/VSCodeKeybindingCoverage.MD | 12 +- scripts/package-release.ps1 | 2 - scripts/sync-vscode-keybindings.ps1 | 124 +- src/GeneratedBindings.inc | 396 +++- src/VSCodeKeymapNpp.rc.in | 25 + src/VSCodeKeymapPlugin.cpp | 1202 ++++++----- src/handlers/ActionHandlers.cpp | 72 + src/handlers/ActionHandlers.h | 77 + src/handlers/CommentHandlers.cpp | 82 + src/handlers/CursorHandlers.cpp | 368 ++++ src/handlers/CustomHandlers.h | 25 + src/handlers/FoldHandlers.cpp | 15 + src/handlers/LineHandlers.cpp | 50 + src/handlers/ScrollHandlers.cpp | 22 + 20 files changed, 1868 insertions(+), 2616 deletions(-) delete mode 100644 data/vscode-default-keybindings.windows.json create mode 100644 src/handlers/ActionHandlers.cpp create mode 100644 src/handlers/ActionHandlers.h create mode 100644 src/handlers/CommentHandlers.cpp create mode 100644 src/handlers/CursorHandlers.cpp create mode 100644 src/handlers/CustomHandlers.h create mode 100644 src/handlers/FoldHandlers.cpp create mode 100644 src/handlers/LineHandlers.cpp create mode 100644 src/handlers/ScrollHandlers.cpp diff --git a/.gitignore b/.gitignore index 11d0b51..b67bdf6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build/ build-*/ dist/ +.idea \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index ac38046..9b21666 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,13 +9,20 @@ configure_file( add_library(VSCodeKeymapNpp SHARED src/VSCodeKeymapPlugin.cpp + src/handlers/ActionHandlers.cpp + src/handlers/CommentHandlers.cpp + src/handlers/CursorHandlers.cpp + src/handlers/FoldHandlers.cpp + src/handlers/LineHandlers.cpp + src/handlers/ScrollHandlers.cpp src/GeneratedBindings.inc ${CMAKE_CURRENT_BINARY_DIR}/VSCodeKeymapNpp.rc ) -target_include_directories(VSCodeKeymapNpp PRIVATE include) +target_include_directories(VSCodeKeymapNpp PRIVATE include src) target_compile_features(VSCodeKeymapNpp PRIVATE cxx_std_20) target_compile_definitions(VSCodeKeymapNpp PRIVATE UNICODE _UNICODE WIN32_LEAN_AND_MEAN NOMINMAX) +target_link_libraries(VSCodeKeymapNpp PRIVATE comctl32) set_target_properties(VSCodeKeymapNpp PROPERTIES OUTPUT_NAME "VSCodeKeymapNpp" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9806080..de592db 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -86,9 +86,8 @@ notepad++.exe -noPlugin -multiInst -nosession -settingsDir= These files are generated and should stay in sync: -1. `data/vscode-default-keybindings.windows.json` -2. `docs/VSCodeKeybindingCoverage.MD` -3. `src/GeneratedBindings.inc` +1. `docs/VSCodeKeybindingCoverage.MD` +2. `src/GeneratedBindings.inc` If you change `data/keybinding-mappings.json` or the generator script, regenerate those files in the same change. diff --git a/README.MD b/README.MD index 0b7a617..d025947 100644 --- a/README.MD +++ b/README.MD @@ -14,10 +14,10 @@ documented as intentionally unported. - Runtime bindings come from generated tables in `src/GeneratedBindings.inc` - Release flow now supports `x86`, `x64`, and `arm64` -The plugin still registers two menu commands: +The plugin currently registers two menu commands: -- `Plugins > VSCode Keymap NPP > Toggle VSCode Strict Keymap` -- `Plugins > VSCode Keymap NPP > Show VSCode Coverage Summary` +- `Plugins > VSCode Keymap NPP > Config/Reference` +- `Plugins > VSCode Keymap NPP > About` ## Coverage model @@ -31,16 +31,15 @@ Bindings land in one of three states: - `documented-unported`: listed, but intentionally not emulated Current generated totals live in the coverage report and are also shown in the -plugin summary dialog. +plugin About dialog. ## Repository layout - `src/VSCodeKeymapPlugin.cpp`: plugin runtime and custom editor actions - `src/GeneratedBindings.inc`: generated runtime binding tables - `data/keybinding-mappings.json`: mapping manifest for portable shortcuts -- `data/vscode-default-keybindings.windows.json`: generated normalized source snapshot - `docs/VSCodeKeybindingCoverage.MD`: generated coverage report -- `scripts/sync-vscode-keybindings.ps1`: refresh source snapshot, report, and runtime tables +- `scripts/sync-vscode-keybindings.ps1`: refresh the source scrape, report, and embedded runtime/reference tables - `scripts/package-release.ps1`: build and package release zips ## Refresh generated shortcut data @@ -55,9 +54,8 @@ powershell -ExecutionPolicy Bypass -File .\scripts\sync-vscode-keybindings.ps1 That script will: - fetch the current VS Code default keybindings page -- regenerate the normalized Windows shortcut snapshot - validate manifest coverage and runtime collisions -- regenerate the runtime include file +- regenerate the embedded runtime/reference include file - regenerate the Markdown coverage report ## Build @@ -117,8 +115,9 @@ Artifacts: Each zip is Plugin Admin-friendly: - `VSCodeKeymapNpp.dll` at archive root -- `doc/README.MD` -- `doc/VSCodeKeybindingCoverage.MD` + +The DLL carries the reference data used by the config dialog, so deployment no +longer requires shipping the generated docs beside it. ## Install @@ -138,6 +137,10 @@ Or under a machine-wide Notepad++ install: Use the DLL whose architecture matches the installed Notepad++ build exactly. Restart Notepad++ after copying the plugin. +Inside `Plugins > VSCode Keymap NPP > Config/Reference`, checked rows are the +shortcuts currently handled by the plugin. Clearing a row passes that shortcut +back through to Notepad++. + ## Feedback and contributions If a shortcut still behaves differently from VS Code, opens the wrong diff --git a/data/keybinding-mappings.json b/data/keybinding-mappings.json index 6c671fe..5584d70 100644 --- a/data/keybinding-mappings.json +++ b/data/keybinding-mappings.json @@ -132,14 +132,34 @@ { "command": "editor.action.insertCursorBelow", "winKey": "Ctrl+Alt+Down", - "handler": "reservedNoOp", - "notes": "Reserved so Notepad++ defaults do not hijack VS Code multi-cursor shortcut." + "handler": "custom", + "target": "InsertCursorBelow", + "notes": "Move with the shared vertical-cursor rules; add/select one caret below. Virtual-space alignment is configurable." }, { "command": "editor.action.insertCursorAbove", "winKey": "Ctrl+Alt+Up", - "handler": "reservedNoOp", - "notes": "Reserved so Notepad++ defaults do not hijack VS Code multi-cursor shortcut." + "handler": "custom", + "target": "InsertCursorAbove", + "notes": "Move with the shared vertical-cursor rules; add/select one caret above." + }, + { + "command": "editor.action.cursorColumnSelectDown", + "winKey": "Ctrl+Alt+Shift+Down", + "section": "Basic Editing", + "label": "Column Select Down", + "handler": "custom", + "target": "CursorColumnSelectDown", + "notes": "Move with the shared vertical-cursor rules; rebuild the anchored column-selection range downward. Undocumented on the VS Code default keybindings page, but available in the VS Code Keyboard Shortcuts editor." + }, + { + "command": "editor.action.cursorColumnSelectUp", + "winKey": "Ctrl+Alt+Shift+Up", + "section": "Basic Editing", + "label": "Column Select Up", + "handler": "custom", + "target": "CursorColumnSelectUp", + "notes": "Move with the shared vertical-cursor rules; rebuild the anchored column-selection range upward. Undocumented on the VS Code default keybindings page, but available in the VS Code Keyboard Shortcuts editor." }, { "command": "cursorTop", diff --git a/data/vscode-default-keybindings.windows.json b/data/vscode-default-keybindings.windows.json deleted file mode 100644 index 611cd03..0000000 --- a/data/vscode-default-keybindings.windows.json +++ /dev/null @@ -1,1946 +0,0 @@ -{ - "source": { - "url": "https://code.visualstudio.com/docs/reference/default-keybindings", - "msDate": "4/22/2026" - }, - "counts": { - "unported": 40, - "reserved": 39, - "total": 161, - "mapped": 82 - }, - "bindings": [ - { - "section": "Basic Editing", - "subsection": "", - "label": "Cut line (empty selection)", - "command": "editor.action.clipboardCutAction", - "winKey": "Ctrl+X", - "status": "mapped", - "handler": "custom", - "target": "CutAllowLine", - "notes": "Cut whole line when selection is empty, like VS Code.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Copy line (empty selection)", - "command": "editor.action.clipboardCopyAction", - "winKey": "Ctrl+C", - "status": "mapped", - "handler": "custom", - "target": "CopyAllowLine", - "notes": "Copy whole line when selection is empty, like VS Code.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Paste", - "command": "editor.action.clipboardPasteAction", - "winKey": "Ctrl+V", - "status": "mapped", - "handler": "nativePassThrough", - "target": null, - "notes": "Notepad++ already matches VS Code for paste.", - "runtime": false - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Delete Line", - "command": "editor.action.deleteLines", - "winKey": "Ctrl+Shift+K", - "status": "mapped", - "handler": "sciCommand", - "target": "SCI_LINEDELETE", - "notes": "Delete current line when selection is empty, matching VS Code behavior closely.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Insert Line Below", - "command": "editor.action.insertLineAfter", - "winKey": "Ctrl+Enter", - "status": "mapped", - "handler": "custom", - "target": "InsertLineBelow", - "notes": "Insert blank line below caret.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Insert Line Above", - "command": "editor.action.insertLineBefore", - "winKey": "Ctrl+Shift+Enter", - "status": "mapped", - "handler": "custom", - "target": "InsertLineAbove", - "notes": "Insert blank line above caret.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Move Line Down", - "command": "editor.action.moveLinesDownAction", - "winKey": "Alt+Down", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_LINE_DOWN", - "notes": "Move current line or selection down.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Move Line Up", - "command": "editor.action.moveLinesUpAction", - "winKey": "Alt+Up", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_LINE_UP", - "notes": "Move current line or selection up.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Copy Line Down", - "command": "editor.action.copyLinesDownAction", - "winKey": "Shift+Alt+Down", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_DUP_LINE", - "notes": "Duplicate line downward.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Copy Line Up", - "command": "editor.action.copyLinesUpAction", - "winKey": "Shift+Alt+Up", - "status": "mapped", - "handler": "custom", - "target": "DuplicateLineUp", - "notes": "Duplicate line, then move duplicate upward.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Undo", - "command": "undo", - "winKey": "Ctrl+Z", - "status": "mapped", - "handler": "nativePassThrough", - "target": null, - "notes": "Notepad++ already matches VS Code for undo.", - "runtime": false - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Redo", - "command": "redo", - "winKey": "Ctrl+Y", - "status": "mapped", - "handler": "nativePassThrough", - "target": null, - "notes": "Notepad++ already matches VS Code for redo.", - "runtime": false - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Add Selection To Next Find Match", - "command": "editor.action.addSelectionToNextFindMatch", - "winKey": "Ctrl+D", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_MULTISELECTNEXT", - "notes": "Add next match to multiselection.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Move Last Selection To Next Find Match", - "command": "editor.action.moveSelectionToNextFindMatch", - "winKey": "Ctrl+K Ctrl+D", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_MULTISELECTSSKIP", - "notes": "Skip current occurrence in multi-select flow.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Undo last cursor operation", - "command": "cursorUndo", - "winKey": "Ctrl+U", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_MULTISELECTUNDO", - "notes": "Undo last multi-cursor selection step.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Insert cursor at end of each line selected", - "command": "editor.action.insertCursorAtEndOfEachLineSelected", - "winKey": "Shift+Alt+I", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Multiple-caret insertion per line is not implemented safely in Notepad++.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Select all occurrences of current selection", - "command": "editor.action.selectHighlights", - "winKey": "Ctrl+Shift+L", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_MULTISELECTALL", - "notes": "Select all current selection matches.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Select all occurrences of current word", - "command": "editor.action.changeAll", - "winKey": "Ctrl+F2", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_MULTISELECTALLWHOLEWORD", - "notes": "Approximate current-word multi-select.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Select current line", - "command": "expandLineSelection", - "winKey": "Ctrl+L", - "status": "mapped", - "handler": "custom", - "target": "SelectCurrentLine", - "notes": "Select current line without trailing newline.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Insert Cursor Below", - "command": "editor.action.insertCursorBelow", - "winKey": "Ctrl+Alt+Down", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so Notepad++ defaults do not hijack VS Code multi-cursor shortcut.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Insert Cursor Above", - "command": "editor.action.insertCursorAbove", - "winKey": "Ctrl+Alt+Up", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so Notepad++ defaults do not hijack VS Code multi-cursor shortcut.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Jump to matching bracket", - "command": "editor.action.jumpToBracket", - "winKey": "Ctrl+Shift+\\", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_SEARCH_GOTOMATCHINGBRACE", - "notes": "Jump to matching brace.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Indent Line", - "command": "editor.action.indentLines", - "winKey": "Ctrl+]", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_INS_TAB", - "notes": "Approximate VS Code indent with Notepad++ indent command.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Outdent Line", - "command": "editor.action.outdentLines", - "winKey": "Ctrl+[", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_RMV_TAB", - "notes": "Approximate VS Code outdent with Notepad++ unindent command.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Go to Beginning of Line", - "command": "cursorHome", - "winKey": "Home", - "status": "mapped", - "handler": "nativePassThrough", - "target": null, - "notes": "Notepad++ already matches VS Code for line-home movement.", - "runtime": false - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Go to End of Line", - "command": "cursorEnd", - "winKey": "End", - "status": "mapped", - "handler": "nativePassThrough", - "target": null, - "notes": "Notepad++ already matches VS Code for line-end movement.", - "runtime": false - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Go to End of File", - "command": "cursorBottom", - "winKey": "Ctrl+End", - "status": "mapped", - "handler": "sciCommand", - "target": "SCI_DOCUMENTEND", - "notes": "Jump to document end.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Go to Beginning of File", - "command": "cursorTop", - "winKey": "Ctrl+Home", - "status": "mapped", - "handler": "sciCommand", - "target": "SCI_DOCUMENTSTART", - "notes": "Jump to document start.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Scroll Line Down", - "command": "scrollLineDown", - "winKey": "Ctrl+Down", - "status": "mapped", - "handler": "sciCommand", - "target": "SCI_LINESCROLLDOWN", - "notes": "Scroll without moving caret.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Scroll Line Up", - "command": "scrollLineUp", - "winKey": "Ctrl+Up", - "status": "mapped", - "handler": "sciCommand", - "target": "SCI_LINESCROLLUP", - "notes": "Scroll without moving caret.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Scroll Page Down", - "command": "scrollPageDown", - "winKey": "Alt+PageDown", - "status": "mapped", - "handler": "custom", - "target": "ScrollPageDownNoCaret", - "notes": "Scroll a page without moving caret.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Scroll Page Up", - "command": "scrollPageUp", - "winKey": "Alt+PageUp", - "status": "mapped", - "handler": "custom", - "target": "ScrollPageUpNoCaret", - "notes": "Scroll a page without moving caret.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Fold (collapse) region", - "command": "editor.fold", - "winKey": "Ctrl+Shift+[", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_FOLD_CURRENT", - "notes": "Fold current region.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Unfold (uncollapse) region", - "command": "editor.unfold", - "winKey": "Ctrl+Shift+]", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_UNFOLD_CURRENT", - "notes": "Unfold current region.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Toggle Fold region", - "command": "editor.toggleFold", - "winKey": "Ctrl+K Ctrl+L", - "status": "mapped", - "handler": "custom", - "target": "ToggleFoldAtCaret", - "notes": "Toggle fold header at caret, or nearest fold parent.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Fold (collapse) all subregions", - "command": "editor.foldRecursively", - "winKey": "Ctrl+K Ctrl+[", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved chord; exact recursive fold not implemented.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Unfold (uncollapse) all subregions", - "command": "editor.unfoldRecursively", - "winKey": "Ctrl+K Ctrl+]", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved chord; exact recursive unfold not implemented.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Fold (collapse) all regions", - "command": "editor.foldAll", - "winKey": "Ctrl+K Ctrl+0", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_FOLDALL", - "notes": "Fold all regions.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Unfold (uncollapse) all regions", - "command": "editor.unfoldAll", - "winKey": "Ctrl+K Ctrl+J", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_UNFOLDALL", - "notes": "Unfold all regions.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Add Line Comment", - "command": "editor.action.addCommentLine", - "winKey": "Ctrl+K Ctrl+C", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_STREAM_COMMENT", - "notes": "Force comment selected lines.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Remove Line Comment", - "command": "editor.action.removeCommentLine", - "winKey": "Ctrl+K Ctrl+U", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_STREAM_UNCOMMENT", - "notes": "Force uncomment selected lines.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Toggle Line Comment", - "command": "editor.action.commentLine", - "winKey": "Ctrl+/", - "status": "mapped", - "handler": "custom", - "target": "ToggleStreamComment", - "notes": "Toggle line-style stream comment based on current selection state.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Toggle Block Comment", - "command": "editor.action.blockComment", - "winKey": "Shift+Alt+A", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_BLOCK_COMMENT", - "notes": "Use Notepad++ block comment command.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Find", - "command": "actions.find", - "winKey": "Ctrl+F", - "status": "mapped", - "handler": "nativePassThrough", - "target": null, - "notes": "Notepad++ already matches VS Code for find.", - "runtime": false - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Replace", - "command": "editor.action.startFindReplaceAction", - "winKey": "Ctrl+H", - "status": "mapped", - "handler": "nativePassThrough", - "target": null, - "notes": "Notepad++ already matches VS Code for replace.", - "runtime": false - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Find Next", - "command": "editor.action.nextMatchFindAction", - "winKey": "Enter", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Find Previous", - "command": "editor.action.previousMatchFindAction", - "winKey": "Shift+Enter", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Select All Occurrences of Find Match", - "command": "editor.action.selectAllMatches", - "winKey": "Alt+Enter", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_MULTISELECTALL", - "notes": "Select all find matches.", - "runtime": true - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Toggle Find Case Sensitive", - "command": "toggleFindCaseSensitive", - "winKey": "Alt+C", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Toggle Find Regex", - "command": "toggleFindRegex", - "winKey": "Alt+R", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Toggle Find Whole Word", - "command": "toggleFindWholeWord", - "winKey": "Alt+W", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Toggle Use of Tab Key for Setting Focus", - "command": "editor.action.toggleTabFocusMode", - "winKey": "Ctrl+M", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Basic Editing", - "subsection": "", - "label": "Toggle Word Wrap", - "command": "editor.action.toggleWordWrap", - "winKey": "Alt+Z", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_WRAP", - "notes": "Toggle word wrap.", - "runtime": true - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Trigger Suggest", - "command": "editor.action.triggerSuggest", - "winKey": "Ctrl+Space", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_AUTOCOMPLETE", - "notes": "Open autocomplete.", - "runtime": true - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Trigger Parameter Hints", - "command": "editor.action.triggerParameterHints", - "winKey": "Ctrl+Shift+Space", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_FUNCCALLTIP", - "notes": "Open function call tip.", - "runtime": true - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Format Document", - "command": "editor.action.formatDocument", - "winKey": "Shift+Alt+F", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Formatting depends on plugins/language support, so strict mode reserves it.", - "runtime": true - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Format Selection", - "command": "editor.action.formatSelection", - "winKey": "Ctrl+K Ctrl+F", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code format-selection chord.", - "runtime": true - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Go to Definition", - "command": "editor.action.revealDefinition", - "winKey": "F12", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "No built-in go-to-definition equivalent.", - "runtime": true - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Show Hover", - "command": "editor.action.showHover", - "winKey": "Ctrl+K Ctrl+I", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code hover chord.", - "runtime": true - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Peek Definition", - "command": "editor.action.peekDefinition", - "winKey": "Alt+F12", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "No peek-definition equivalent.", - "runtime": true - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Open Definition to the Side", - "command": "editor.action.revealDefinitionAside", - "winKey": "Ctrl+K F12", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "No open-definition-side equivalent.", - "runtime": true - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Quick Fix", - "command": "editor.action.quickFix", - "winKey": "Ctrl+.", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "No quick-fix provider equivalent.", - "runtime": true - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Go to References", - "command": "editor.action.goToReferences", - "winKey": "Shift+F12", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "No find-references equivalent.", - "runtime": true - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Rename Symbol", - "command": "editor.action.rename", - "winKey": "F2", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "No language-aware rename equivalent.", - "runtime": true - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Replace with Next Value", - "command": "editor.action.inPlaceReplace.down", - "winKey": "Ctrl+Shift+.", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Replace with Previous Value", - "command": "editor.action.inPlaceReplace.up", - "winKey": "Ctrl+Shift+,", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Expand AST Selection", - "command": "editor.action.smartSelect.expand", - "winKey": "Shift+Alt+Right", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Shrink AST Selection", - "command": "editor.action.smartSelect.shrink", - "winKey": "Shift+Alt+Left", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Trim Trailing Whitespace", - "command": "editor.action.trimTrailingWhitespace", - "winKey": "Ctrl+K Ctrl+X", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_TRIMTRAILING", - "notes": "Trim trailing whitespace.", - "runtime": true - }, - { - "section": "Rich Languages Editing", - "subsection": "", - "label": "Change Language Mode", - "command": "workbench.action.editor.changeLanguageMode", - "winKey": "Ctrl+K M", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code language-mode chord.", - "runtime": true - }, - { - "section": "Navigation", - "subsection": "", - "label": "Show All Symbols", - "command": "workbench.action.showAllSymbols", - "winKey": "Ctrl+T", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_SWITCHTO_FUNC_LIST", - "notes": "Approximate workspace symbol search with function list.", - "runtime": true - }, - { - "section": "Navigation", - "subsection": "", - "label": "Go to Line...", - "command": "workbench.action.gotoLine", - "winKey": "Ctrl+G", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_SEARCH_GOTOLINE", - "notes": "Go to line.", - "runtime": true - }, - { - "section": "Navigation", - "subsection": "", - "label": "Go to File..., Quick Open", - "command": "workbench.action.quickOpen", - "winKey": "Ctrl+P", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_FILE_OPEN", - "notes": "Approximate Quick Open with file-open dialog.", - "runtime": true - }, - { - "section": "Navigation", - "subsection": "", - "label": "Go to Symbol...", - "command": "workbench.action.gotoSymbol", - "winKey": "Ctrl+Shift+O", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_SWITCHTO_FUNC_LIST", - "notes": "Approximate symbol picker with function list panel.", - "runtime": true - }, - { - "section": "Navigation", - "subsection": "", - "label": "Show Problems", - "command": "workbench.actions.view.problems", - "winKey": "Ctrl+Shift+M", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code problems shortcut.", - "runtime": true - }, - { - "section": "Navigation", - "subsection": "", - "label": "Go to Next Error or Warning", - "command": "editor.action.marker.nextInFiles", - "winKey": "F8", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "No Problems panel equivalent.", - "runtime": true - }, - { - "section": "Navigation", - "subsection": "", - "label": "Go to Previous Error or Warning", - "command": "editor.action.marker.prevInFiles", - "winKey": "Shift+F8", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "No Problems panel equivalent.", - "runtime": true - }, - { - "section": "Navigation", - "subsection": "", - "label": "Show All Commands", - "command": "workbench.action.showCommands", - "winKey": "Ctrl+Shift+P", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "No Command Palette equivalent.", - "runtime": true - }, - { - "section": "Navigation", - "subsection": "", - "label": "Navigate Editor Group History", - "command": "workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup", - "winKey": "Ctrl+Tab", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_TAB_NEXT", - "notes": "Cycle tabs forward.", - "runtime": true - }, - { - "section": "Navigation", - "subsection": "", - "label": "Go Back", - "command": "workbench.action.navigateBack", - "winKey": "Alt+Left", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "No stable back-stack equivalent.", - "runtime": true - }, - { - "section": "Navigation", - "subsection": "", - "label": "Go back in Quick Input", - "command": "workbench.action.quickInputBack", - "winKey": "Alt+Left", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Navigation", - "subsection": "", - "label": "Go Forward", - "command": "workbench.action.navigateForward", - "winKey": "Alt+Right", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "No stable forward-stack equivalent.", - "runtime": true - }, - { - "section": "Navigation", - "subsection": "", - "label": "Focus Breadcrumbs", - "command": "breadcrumbs.focus", - "winKey": "Ctrl+Shift+;", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Navigation", - "subsection": "", - "label": "Focus and Select Breadcrumbs", - "command": "breadcrumbs.focusAndSelect", - "winKey": "Ctrl+Shift+.", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "New Window", - "command": "workbench.action.newWindow", - "winKey": "Ctrl+Shift+N", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_GOTO_NEW_INSTANCE", - "notes": "Approximate new window with new Notepad++ instance.", - "runtime": true - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "Close Window", - "command": "workbench.action.closeWindow", - "winKey": "Alt+F4", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "Close Editor", - "command": "workbench.action.closeActiveEditor", - "winKey": "Ctrl+F4", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_FILE_CLOSE", - "notes": "Close active file.", - "runtime": true - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "Close Folder", - "command": "workbench.action.closeFolder", - "winKey": "Ctrl+K F", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code close-folder chord.", - "runtime": true - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "Split Editor", - "command": "workbench.action.splitEditor", - "winKey": "Ctrl+\\", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_GOTO_ANOTHER_VIEW", - "notes": "Approximate split editor by moving document to the other view.", - "runtime": true - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "Focus into First Editor Group", - "command": "workbench.action.focusFirstEditorGroup", - "winKey": "Ctrl+1", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_TAB1", - "notes": "Approximate first editor group focus with first tab selection.", - "runtime": true - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "Focus into Second Editor Group", - "command": "workbench.action.focusSecondEditorGroup", - "winKey": "Ctrl+2", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_TAB2", - "notes": "Approximate second editor group focus with second tab selection.", - "runtime": true - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "Focus into Third Editor Group", - "command": "workbench.action.focusThirdEditorGroup", - "winKey": "Ctrl+3", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_TAB3", - "notes": "Approximate third editor group focus with third tab selection.", - "runtime": true - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "Focus into Editor Group on the Left", - "command": "workbench.action.focusLeftGroup", - "winKey": "Ctrl+K Ctrl+Left", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code focus-group chord.", - "runtime": true - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "Focus into Editor Group on the Right", - "command": "workbench.action.focusRightGroup", - "winKey": "Ctrl+K Ctrl+Right", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code focus-group chord.", - "runtime": true - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "Move Editor Left", - "command": "workbench.action.moveEditorLeftInGroup", - "winKey": "Ctrl+Shift+PageUp", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_TAB_MOVEBACKWARD", - "notes": "Approximate move editor left with move tab left.", - "runtime": true - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "Move Editor Right", - "command": "workbench.action.moveEditorRightInGroup", - "winKey": "Ctrl+Shift+PageDown", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_TAB_MOVEFORWARD", - "notes": "Approximate move editor right with move tab right.", - "runtime": true - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "Move Active Editor Group Left", - "command": "workbench.action.moveActiveEditorGroupLeft", - "winKey": "Ctrl+K Left", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code move-group chord.", - "runtime": true - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "Move Active Editor Group Right", - "command": "workbench.action.moveActiveEditorGroupRight", - "winKey": "Ctrl+K Right", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code move-group chord.", - "runtime": true - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "Move Editor into Next Group", - "command": "workbench.action.moveEditorToNextGroup", - "winKey": "Ctrl+Alt+Right", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_GOTO_ANOTHER_VIEW", - "notes": "Approximate move editor to next group by moving document to the other view.", - "runtime": true - }, - { - "section": "Editor/Window Management", - "subsection": "", - "label": "Move Editor into Previous Group", - "command": "workbench.action.moveEditorToPreviousGroup", - "winKey": "Ctrl+Alt+Left", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_GOTO_ANOTHER_VIEW", - "notes": "Approximate move editor to previous group by moving document to the other view.", - "runtime": true - }, - { - "section": "File Management", - "subsection": "", - "label": "New File", - "command": "workbench.action.files.newUntitledFile", - "winKey": "Ctrl+N", - "status": "mapped", - "handler": "nativePassThrough", - "target": null, - "notes": "Notepad++ already matches VS Code for new file.", - "runtime": false - }, - { - "section": "File Management", - "subsection": "", - "label": "Open File...", - "command": "workbench.action.files.openFile", - "winKey": "Ctrl+O", - "status": "mapped", - "handler": "nativePassThrough", - "target": null, - "notes": "Notepad++ already matches VS Code for open file.", - "runtime": false - }, - { - "section": "File Management", - "subsection": "", - "label": "Save", - "command": "workbench.action.files.save", - "winKey": "Ctrl+S", - "status": "mapped", - "handler": "nativePassThrough", - "target": null, - "notes": "Notepad++ already matches VS Code for save.", - "runtime": false - }, - { - "section": "File Management", - "subsection": "", - "label": "Save All", - "command": "saveAll", - "winKey": "Ctrl+K S", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_FILE_SAVEALL", - "notes": "Save all open files.", - "runtime": true - }, - { - "section": "File Management", - "subsection": "", - "label": "Save As...", - "command": "workbench.action.files.saveAs", - "winKey": "Ctrl+Shift+S", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_FILE_SAVEAS", - "notes": "Save current file as.", - "runtime": true - }, - { - "section": "File Management", - "subsection": "", - "label": "Close Group", - "command": "workbench.action.closeEditorsInGroup", - "winKey": "Ctrl+K W", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code close-group chord.", - "runtime": true - }, - { - "section": "File Management", - "subsection": "", - "label": "Close All", - "command": "workbench.action.closeAllEditors", - "winKey": "Ctrl+K Ctrl+W", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_FILE_CLOSEALL", - "notes": "Close all files.", - "runtime": true - }, - { - "section": "File Management", - "subsection": "", - "label": "Reopen Closed Editor", - "command": "workbench.action.reopenClosedEditor", - "winKey": "Ctrl+Shift+T", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_FILE_RESTORELASTCLOSEDFILE", - "notes": "Restore last closed file.", - "runtime": true - }, - { - "section": "File Management", - "subsection": "", - "label": "Keep Open", - "command": "workbench.action.keepEditor", - "winKey": "Ctrl+K Enter", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code keep-open chord.", - "runtime": true - }, - { - "section": "File Management", - "subsection": "", - "label": "Copy Path of Active File", - "command": "workbench.action.files.copyPathOfActiveFile", - "winKey": "Ctrl+K P", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_FULLPATHTOCLIP", - "notes": "Copy active file path.", - "runtime": true - }, - { - "section": "File Management", - "subsection": "", - "label": "Reveal Active File in Windows", - "command": "workbench.action.files.revealActiveFileInWindows", - "winKey": "Ctrl+K R", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_EDIT_OPENINFOLDER", - "notes": "Reveal active file in Explorer.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Toggle Full Screen", - "command": "workbench.action.toggleFullScreen", - "winKey": "F11", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_FULLSCREENTOGGLE", - "notes": "Toggle fullscreen.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Toggle Zen Mode", - "command": "workbench.action.toggleZenMode", - "winKey": "Ctrl+K Z", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_DISTRACTIONFREE", - "notes": "Approximate VS Code Zen Mode with Notepad++ distraction-free mode.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Leave Zen Mode", - "command": "workbench.action.exitZenMode", - "winKey": "Escape Escape", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Display", - "subsection": "", - "label": "Zoom in", - "command": "workbench.action.zoomIn", - "winKey": "Ctrl+=", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_ZOOMIN", - "notes": "Zoom in.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Zoom out", - "command": "workbench.action.zoomOut", - "winKey": "Ctrl+-", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_ZOOMOUT", - "notes": "Zoom out.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Reset Zoom", - "command": "workbench.action.zoomReset", - "winKey": "Ctrl+Numpad0", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_ZOOMRESTORE", - "notes": "Reset zoom.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Toggle Sidebar Visibility", - "command": "workbench.action.toggleSidebarVisibility", - "winKey": "Ctrl+B", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_FILEBROWSER", - "notes": "Approximate sidebar toggle with file browser panel.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Show Explorer / Toggle Focus", - "command": "workbench.view.explorer", - "winKey": "Ctrl+Shift+E", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_VIEW_SWITCHTO_FILEBROWSER", - "notes": "Focus file browser panel.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Show Search", - "command": "workbench.view.search", - "winKey": "Ctrl+Shift+F", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_SEARCH_FINDINFILES", - "notes": "Find in files.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Show Source Control", - "command": "workbench.view.scm", - "winKey": "Ctrl+Shift+G", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "No source-control sidebar equivalent.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Show Run", - "command": "workbench.view.debug", - "winKey": "Ctrl+Shift+D", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "No Run/Debug sidebar equivalent.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Show Extensions", - "command": "workbench.view.extensions", - "winKey": "Ctrl+Shift+X", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "No extensions marketplace sidebar equivalent.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Show Output", - "command": "workbench.action.output.toggleOutput", - "winKey": "Ctrl+Shift+U", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "No Output panel equivalent.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Quick Open View", - "command": "workbench.action.quickOpenView", - "winKey": "Ctrl+Q", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code quick-open-view shortcut.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Open New Command Prompt", - "command": "workbench.action.terminal.openNativeConsole", - "winKey": "Ctrl+Shift+C", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_FILE_OPEN_CMD", - "notes": "Approximate with Open Command Prompt Here.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Toggle Markdown Preview", - "command": "markdown.showPreview", - "winKey": "Ctrl+Shift+V", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code markdown-preview shortcut.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Open Preview to the Side", - "command": "markdown.showPreviewToSide", - "winKey": "Ctrl+K V", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code markdown-preview chord.", - "runtime": true - }, - { - "section": "Display", - "subsection": "", - "label": "Toggle Integrated Terminal", - "command": "workbench.action.terminal.toggleTerminal", - "winKey": "Ctrl+`", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_FILE_OPEN_CMD", - "notes": "Approximate integrated terminal with Open Command Prompt Here.", - "runtime": true - }, - { - "section": "Search", - "subsection": "", - "label": "Replace in Files", - "command": "workbench.action.replaceInFiles", - "winKey": "Ctrl+Shift+H", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_SEARCH_FINDINFILES", - "notes": "Approximate replace-in-files with Notepad++ find-in-files panel.", - "runtime": true - }, - { - "section": "Search", - "subsection": "", - "label": "Toggle Match Case", - "command": "toggleSearchCaseSensitive", - "winKey": "Alt+C", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Search", - "subsection": "", - "label": "Toggle Match Whole Word", - "command": "toggleSearchWholeWord", - "winKey": "Alt+W", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Search", - "subsection": "", - "label": "Toggle Use Regular Expression", - "command": "toggleSearchRegex", - "winKey": "Alt+R", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Search", - "subsection": "", - "label": "Toggle Search Details", - "command": "workbench.action.search.toggleQueryDetails", - "winKey": "Ctrl+Shift+J", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code search-details shortcut.", - "runtime": true - }, - { - "section": "Search", - "subsection": "", - "label": "Focus Next Search Result", - "command": "search.action.focusNextSearchResult", - "winKey": "F4", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Search", - "subsection": "", - "label": "Focus Previous Search Result", - "command": "search.action.focusPreviousSearchResult", - "winKey": "Shift+F4", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Search", - "subsection": "", - "label": "Show Next Search Term", - "command": "history.showNext", - "winKey": "Down", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Search", - "subsection": "", - "label": "Show Previous Search Term", - "command": "history.showPrevious", - "winKey": "Up", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Search Editor", - "subsection": "", - "label": "Open Results In Editor", - "command": "search.action.openInEditor", - "winKey": "Alt+Enter", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Search Editor", - "subsection": "", - "label": "Focus Search Editor Input", - "command": "search.action.focusQueryEditorWidget", - "winKey": "Escape", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Search Editor", - "subsection": "", - "label": "Search Again", - "command": "rerunSearchEditorSearch", - "winKey": "Ctrl+Shift+R", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Search Editor", - "subsection": "", - "label": "Delete File Results", - "command": "search.searchEditor.action.deleteFileResults", - "winKey": "Ctrl+Shift+Backspace", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Preferences", - "subsection": "", - "label": "Open Settings", - "command": "workbench.action.openSettings", - "winKey": "Ctrl+,", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_SETTING_PREFERENCE", - "notes": "Open Notepad++ preferences.", - "runtime": true - }, - { - "section": "Preferences", - "subsection": "", - "label": "Open Keyboard Shortcuts", - "command": "workbench.action.openGlobalKeybindings", - "winKey": "Ctrl+K Ctrl+S", - "status": "mapped", - "handler": "nppCommand", - "target": "IDM_SETTING_SHORTCUT_MAPPER", - "notes": "Open Shortcut Mapper.", - "runtime": true - }, - { - "section": "Preferences", - "subsection": "", - "label": "Select Color Theme", - "command": "workbench.action.selectTheme", - "winKey": "Ctrl+K Ctrl+T", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code theme chord.", - "runtime": true - }, - { - "section": "Chat", - "subsection": "", - "label": "Open Chat view", - "command": "workbench.action.chat.open", - "winKey": "Ctrl+Alt+I", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Chat", - "subsection": "", - "label": "Open chat in agent mode", - "command": "workbench.action.chat.openagent", - "winKey": "Ctrl+Shift+I", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Chat", - "subsection": "", - "label": "Open editor inline chat", - "command": "inlineChat.start", - "winKey": "Ctrl+I", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Chat", - "subsection": "", - "label": "Open terminal inline chat", - "command": "workbench.action.terminal.chat.start", - "winKey": "Ctrl+I", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Chat", - "subsection": "", - "label": "Open quick chat", - "command": "workbench.action.quickchat.toggle", - "winKey": "Ctrl+Shift+Alt+L", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Chat", - "subsection": "", - "label": "Open agent picker", - "command": "workbench.action.chat.openModePicker", - "winKey": "Ctrl+.", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Chat", - "subsection": "", - "label": "Open language model picker", - "command": "workbench.action.chat.openModelPicker", - "winKey": "Ctrl+Alt+.", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Chat", - "subsection": "", - "label": "New chat session", - "command": "workbench.action.chat.newChat", - "winKey": "Ctrl+N", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Chat", - "subsection": "", - "label": "Accept inline suggestion", - "command": "editor.action.inlineSuggest.commit", - "winKey": "Tab", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Debug", - "subsection": "", - "label": "Toggle Breakpoint", - "command": "editor.debug.action.toggleBreakpoint", - "winKey": "F9", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code breakpoint shortcut.", - "runtime": true - }, - { - "section": "Debug", - "subsection": "", - "label": "Start", - "command": "workbench.action.debug.start", - "winKey": "F5", - "status": "reserved-noop", - "handler": "reservedNoOp", - "target": null, - "notes": "Reserved so conflicting Notepad++ behavior does not fire for VS Code debug-start shortcut.", - "runtime": true - }, - { - "section": "Debug", - "subsection": "", - "label": "Continue", - "command": "workbench.action.debug.continue", - "winKey": "F5", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Debug", - "subsection": "", - "label": "Start (without debugging)", - "command": "workbench.action.debug.run", - "winKey": "Ctrl+F5", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Debug", - "subsection": "", - "label": "Pause", - "command": "workbench.action.debug.pause", - "winKey": "F6", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Debug", - "subsection": "", - "label": "Step Into", - "command": "workbench.action.debug.stepInto", - "winKey": "F11", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - }, - { - "section": "Tasks", - "subsection": "", - "label": "Run Build Task", - "command": "workbench.action.tasks.build", - "winKey": "Ctrl+Shift+B", - "status": "documented-unported", - "handler": "documentedUnported", - "target": null, - "notes": "", - "runtime": false - } - ] -} diff --git a/docs/VSCodeKeybindingCoverage.MD b/docs/VSCodeKeybindingCoverage.MD index ab8cb8f..88d002a 100644 --- a/docs/VSCodeKeybindingCoverage.MD +++ b/docs/VSCodeKeybindingCoverage.MD @@ -4,9 +4,9 @@ Generated from https://code.visualstudio.com/docs/reference/default-keybindings. | Metric | Count | | --- | ---: | -| Total Windows-default bindings | 161 | -| Mapped | 82 | -| Reserved no-op | 39 | +| Total Windows-default bindings | 163 | +| Mapped | 86 | +| Reserved no-op | 37 | | Documented unported | 40 | ## Basic Editing @@ -32,8 +32,8 @@ Generated from https://code.visualstudio.com/docs/reference/default-keybindings. | `Ctrl+Shift+L` | Select all occurrences of current selection | `editor.action.selectHighlights` | mapped | nppCommand | Select all current selection matches. | | `Ctrl+F2` | Select all occurrences of current word | `editor.action.changeAll` | mapped | nppCommand | Approximate current-word multi-select. | | `Ctrl+L` | Select current line | `expandLineSelection` | mapped | custom | Select current line without trailing newline. | -| `Ctrl+Alt+Down` | Insert Cursor Below | `editor.action.insertCursorBelow` | reserved-noop | reservedNoOp | Reserved so Notepad++ defaults do not hijack VS Code multi-cursor shortcut. | -| `Ctrl+Alt+Up` | Insert Cursor Above | `editor.action.insertCursorAbove` | reserved-noop | reservedNoOp | Reserved so Notepad++ defaults do not hijack VS Code multi-cursor shortcut. | +| `Ctrl+Alt+Down` | Insert Cursor Below | `editor.action.insertCursorBelow` | mapped | custom | Move with the shared vertical-cursor rules; add/select one caret below. Virtual-space alignment is configurable. | +| `Ctrl+Alt+Up` | Insert Cursor Above | `editor.action.insertCursorAbove` | mapped | custom | Move with the shared vertical-cursor rules; add/select one caret above. | | `Ctrl+Shift+\` | Jump to matching bracket | `editor.action.jumpToBracket` | mapped | nppCommand | Jump to matching brace. | | `Ctrl+]` | Indent Line | `editor.action.indentLines` | mapped | nppCommand | Approximate VS Code indent with Notepad++ indent command. | | `Ctrl+[` | Outdent Line | `editor.action.outdentLines` | mapped | nppCommand | Approximate VS Code outdent with Notepad++ unindent command. | @@ -66,6 +66,8 @@ Generated from https://code.visualstudio.com/docs/reference/default-keybindings. | `Alt+W` | Toggle Find Whole Word | `toggleFindWholeWord` | documented-unported | documentedUnported | | | `Ctrl+M` | Toggle Use of Tab Key for Setting Focus | `editor.action.toggleTabFocusMode` | documented-unported | documentedUnported | | | `Alt+Z` | Toggle Word Wrap | `editor.action.toggleWordWrap` | mapped | nppCommand | Toggle word wrap. | +| `Ctrl+Alt+Shift+Down` | Column Select Down | `editor.action.cursorColumnSelectDown` | mapped | custom | Move with the shared vertical-cursor rules; rebuild the anchored column-selection range downward. Undocumented on the VS Code default keybindings page, but available in the VS Code Keyboard Shortcuts editor. | +| `Ctrl+Alt+Shift+Up` | Column Select Up | `editor.action.cursorColumnSelectUp` | mapped | custom | Move with the shared vertical-cursor rules; rebuild the anchored column-selection range upward. Undocumented on the VS Code default keybindings page, but available in the VS Code Keyboard Shortcuts editor. | ## Rich Languages Editing | Key | Command | VS Code command id | Status | Handling | Notes | diff --git a/scripts/package-release.ps1 b/scripts/package-release.ps1 index c191aa6..cbe7148 100644 --- a/scripts/package-release.ps1 +++ b/scripts/package-release.ps1 @@ -137,7 +137,6 @@ function Package-Platform { } $stageRoot = Join-Path $DistRoot ("_package-{0}" -f $Spec.Name) - $docDir = Join-Path $stageRoot "doc" $zipPath = Join-Path $DistRoot ("VSCodeKeymapNpp-{0}-{1}.zip" -f $ResolvedVersion, $Spec.Name) if (Test-Path -LiteralPath $stageRoot) { @@ -149,7 +148,6 @@ function Package-Platform { New-Item -ItemType Directory -Path $stageRoot -Force | Out-Null Copy-Item -LiteralPath $dllPath -Destination (Join-Path $stageRoot "VSCodeKeymapNpp.dll") - Copy-Docs -Destination $docDir Compress-Archive ` -Path (Join-Path $stageRoot "*") ` diff --git a/scripts/sync-vscode-keybindings.ps1 b/scripts/sync-vscode-keybindings.ps1 index 1d46492..5ee724e 100644 --- a/scripts/sync-vscode-keybindings.ps1 +++ b/scripts/sync-vscode-keybindings.ps1 @@ -3,7 +3,6 @@ param( [string]$SourceUrl = "https://code.visualstudio.com/docs/reference/default-keybindings", [string]$SourceHtmlPath, [string]$MappingsPath, - [string]$OutputJsonPath, [string]$OutputCoveragePath, [string]$OutputBindingsPath ) @@ -19,9 +18,6 @@ if (-not $SourceHtmlPath) { if (-not $MappingsPath) { $MappingsPath = Join-Path $repoRoot "data\keybinding-mappings.json" } -if (-not $OutputJsonPath) { - $OutputJsonPath = Join-Path $repoRoot "data\vscode-default-keybindings.windows.json" -} if (-not $OutputCoveragePath) { $OutputCoveragePath = Join-Path $repoRoot "docs\VSCodeKeybindingCoverage.MD" } @@ -184,10 +180,43 @@ function Write-GeneratedBindings { $Stroke.Vk } + function Format-WideString { + param([AllowNull()][string]$Value) + + if ([string]::IsNullOrEmpty($Value)) { + return 'L""' + } + + $escaped = $Value.Replace('\', '\\').Replace('"', '\"').Replace("`r", '\r').Replace("`n", '\n').Replace("`t", '\t') + return 'L"{0}"' -f $escaped + } + + function Format-Bool { + param([bool]$Value) + + return $Value.ToString().ToLowerInvariant() + } + + $referenceRows = New-Object System.Collections.Generic.List[string] $shortcutRows = New-Object System.Collections.Generic.List[string] $chordRows = New-Object System.Collections.Generic.List[string] - foreach ($binding in $Bindings) { + for ($referenceIndex = 0; $referenceIndex -lt $Bindings.Count; $referenceIndex++) { + $binding = $Bindings[$referenceIndex] + $referenceRows.Add( + (' {{{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}}},' -f ` + (Format-Bool -Value $binding.runtime), ` + (Format-WideString -Value $binding.section), ` + (Format-WideString -Value $binding.subsection), ` + (Format-WideString -Value $binding.label), ` + (Format-WideString -Value $binding.command), ` + (Format-WideString -Value $binding.winKey), ` + (Format-WideString -Value $binding.status), ` + (Format-WideString -Value $binding.handler), ` + (Format-WideString -Value $binding.target), ` + (Format-WideString -Value $binding.notes)) + ) | Out-Null + if (-not $binding.Runtime) { continue } @@ -198,10 +227,11 @@ function Write-GeneratedBindings { if ($strokes.Count -eq 1) { $stroke = $strokes[0] $shortcutRows.Add( - (' {{{0}, {1}, {2}}}, // {3} -> {4}' -f ` + (' {{{0}, {1}, {2}, {3}}}, // {4} -> {5}' -f ` (Format-Stroke -Stroke $stroke), ` $action.Kind, ` $action.Id, ` + $referenceIndex, ` $binding.winKey, ` $binding.command) ) | Out-Null @@ -211,11 +241,12 @@ function Write-GeneratedBindings { $first = $strokes[0] $second = $strokes[1] $chordRows.Add( - (' {{{0}, {1}, {2}, {3}}}, // {4} -> {5}' -f ` + (' {{{0}, {1}, {2}, {3}, {4}}}, // {5} -> {6}' -f ` (Format-Stroke -Stroke $first), ` (Format-Stroke -Stroke $second), ` $action.Kind, ` $action.Id, ` + $referenceIndex, ` $binding.winKey, ` $binding.command) ) | Out-Null @@ -229,6 +260,15 @@ function Write-GeneratedBindings { "constexpr size_t kReservedNoOpBindingCount = $($Counts.reserved);" "constexpr size_t kDocumentedUnportedBindingCount = $($Counts.unported);" "" + "const std::vector kBindingReferences = {" + ) + + if ($referenceRows.Count -gt 0) { + $content += $referenceRows + } + $content += @( + "};" + "" "const std::vector kShortcutBindings = {" ) @@ -340,6 +380,43 @@ function Get-DefaultBindingRecord { } } +function Get-ManifestOnlyBindingRecord { + param([object]$Mapping) + + $requiredFields = @("section", "label") + foreach ($field in $requiredFields) { + if (-not ($Mapping.PSObject.Properties.Name -contains $field) -or [string]::IsNullOrWhiteSpace($Mapping.$field)) { + throw "Manifest-only binding requires '$field': $($Mapping.command) / $($Mapping.winKey)" + } + } + + $target = $null + $notes = "" + $subsection = "" + if ($Mapping.PSObject.Properties.Name -contains "target") { + $target = $Mapping.target + } + if ($Mapping.PSObject.Properties.Name -contains "notes") { + $notes = $Mapping.notes + } + if ($Mapping.PSObject.Properties.Name -contains "subsection") { + $subsection = $Mapping.subsection + } + + return [ordered]@{ + section = $Mapping.section + subsection = $subsection + label = $Mapping.label + command = $Mapping.command + winKey = $Mapping.winKey + status = Get-StatusFromHandler -Handler $Mapping.handler + handler = $Mapping.handler + target = $target + notes = $notes + runtime = Get-RuntimeFlag -Handler $Mapping.handler + } +} + if ($RefreshSource -or (-not (Test-Path -LiteralPath $SourceHtmlPath))) { New-Item -ItemType Directory -Path (Split-Path -Parent $SourceHtmlPath) -Force | Out-Null curl.exe --silent --show-error -L $SourceUrl -o $SourceHtmlPath | Out-Null @@ -411,12 +488,14 @@ foreach ($row in $parsedRows) { $mappingDoc = Get-Content -Raw $MappingsPath | ConvertFrom-Json $mappingLookup = @{} +$mappingOrder = New-Object System.Collections.Generic.List[string] foreach ($item in $mappingDoc.bindings) { $key = "$($item.command)`n$($item.winKey)" if ($mappingLookup.ContainsKey($key)) { throw "Duplicate mapping manifest entry for $($item.command) / $($item.winKey)." } $mappingLookup[$key] = $item + $mappingOrder.Add($key) | Out-Null } $resolvedBindings = New-Object System.Collections.Generic.List[object] @@ -442,10 +521,25 @@ foreach ($binding in $dedupedRows) { $resolvedBindings.Add([pscustomobject]$record) | Out-Null } -foreach ($lookupKey in $mappingLookup.Keys) { +foreach ($lookupKey in $mappingOrder) { $match = $resolvedBindings | Where-Object { "$($_.command)`n$($_.winKey)" -eq $lookupKey } if (-not $match) { - throw "Manifest entry does not match current source binding: $lookupKey" + $record = [pscustomobject](Get-ManifestOnlyBindingRecord -Mapping $mappingLookup[$lookupKey]) + $insertIndex = -1 + for ($index = $resolvedBindings.Count - 1; $index -ge 0; $index--) { + if (($resolvedBindings[$index].section -eq $record.section) -and + ($resolvedBindings[$index].subsection -eq $record.subsection)) { + $insertIndex = $index + 1 + break + } + } + + if ($insertIndex -ge 0) { + $resolvedBindings.Insert($insertIndex, $record) + } + else { + $resolvedBindings.Add($record) | Out-Null + } } } @@ -481,25 +575,13 @@ $counts = @{ unported = @($resolvedBindings | Where-Object { $_.status -eq "documented-unported" }).Count } -$jsonDoc = [ordered]@{ - source = [ordered]@{ - url = $SourceUrl - msDate = $sourceDate - } - counts = $counts - bindings = $resolvedBindings -} - -New-Item -ItemType Directory -Path (Split-Path -Parent $OutputJsonPath) -Force | Out-Null New-Item -ItemType Directory -Path (Split-Path -Parent $OutputCoveragePath) -Force | Out-Null New-Item -ItemType Directory -Path (Split-Path -Parent $OutputBindingsPath) -Force | Out-Null -$jsonDoc | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputJsonPath -Encoding UTF8 Write-CoverageReport -Path $OutputCoveragePath -Bindings $resolvedBindings -Counts $counts Write-GeneratedBindings -Path $OutputBindingsPath -Bindings $resolvedBindings -Counts $counts Write-Output "[OK] Parsed $($counts.total) Windows-default VS Code bindings." Write-Output "[OK] Mapped: $($counts.mapped); reserved: $($counts.reserved); documented-unported: $($counts.unported)." -Write-Output "[OK] Wrote $OutputJsonPath" Write-Output "[OK] Wrote $OutputCoveragePath" Write-Output "[OK] Wrote $OutputBindingsPath" diff --git a/src/GeneratedBindings.inc b/src/GeneratedBindings.inc index 4ff45d9..32dc7b4 100644 --- a/src/GeneratedBindings.inc +++ b/src/GeneratedBindings.inc @@ -1,124 +1,292 @@ // Generated by scripts/sync-vscode-keybindings.ps1. Do not edit by hand. -constexpr size_t kVsCodeSourceBindingCount = 161; -constexpr size_t kMappedBindingCount = 82; -constexpr size_t kReservedNoOpBindingCount = 39; +constexpr size_t kVsCodeSourceBindingCount = 163; +constexpr size_t kMappedBindingCount = 86; +constexpr size_t kReservedNoOpBindingCount = 37; constexpr size_t kDocumentedUnportedBindingCount = 40; +const std::vector kBindingReferences = { + {true, L"Basic Editing", L"", L"Cut line (empty selection)", L"editor.action.clipboardCutAction", L"Ctrl+X", L"mapped", L"custom", L"CutAllowLine", L"Cut whole line when selection is empty, like VS Code."}, + {true, L"Basic Editing", L"", L"Copy line (empty selection)", L"editor.action.clipboardCopyAction", L"Ctrl+C", L"mapped", L"custom", L"CopyAllowLine", L"Copy whole line when selection is empty, like VS Code."}, + {false, L"Basic Editing", L"", L"Paste", L"editor.action.clipboardPasteAction", L"Ctrl+V", L"mapped", L"nativePassThrough", L"", L"Notepad++ already matches VS Code for paste."}, + {true, L"Basic Editing", L"", L"Delete Line", L"editor.action.deleteLines", L"Ctrl+Shift+K", L"mapped", L"sciCommand", L"SCI_LINEDELETE", L"Delete current line when selection is empty, matching VS Code behavior closely."}, + {true, L"Basic Editing", L"", L"Insert Line Below", L"editor.action.insertLineAfter", L"Ctrl+Enter", L"mapped", L"custom", L"InsertLineBelow", L"Insert blank line below caret."}, + {true, L"Basic Editing", L"", L"Insert Line Above", L"editor.action.insertLineBefore", L"Ctrl+Shift+Enter", L"mapped", L"custom", L"InsertLineAbove", L"Insert blank line above caret."}, + {true, L"Basic Editing", L"", L"Move Line Down", L"editor.action.moveLinesDownAction", L"Alt+Down", L"mapped", L"nppCommand", L"IDM_EDIT_LINE_DOWN", L"Move current line or selection down."}, + {true, L"Basic Editing", L"", L"Move Line Up", L"editor.action.moveLinesUpAction", L"Alt+Up", L"mapped", L"nppCommand", L"IDM_EDIT_LINE_UP", L"Move current line or selection up."}, + {true, L"Basic Editing", L"", L"Copy Line Down", L"editor.action.copyLinesDownAction", L"Shift+Alt+Down", L"mapped", L"nppCommand", L"IDM_EDIT_DUP_LINE", L"Duplicate line downward."}, + {true, L"Basic Editing", L"", L"Copy Line Up", L"editor.action.copyLinesUpAction", L"Shift+Alt+Up", L"mapped", L"custom", L"DuplicateLineUp", L"Duplicate line, then move duplicate upward."}, + {false, L"Basic Editing", L"", L"Undo", L"undo", L"Ctrl+Z", L"mapped", L"nativePassThrough", L"", L"Notepad++ already matches VS Code for undo."}, + {false, L"Basic Editing", L"", L"Redo", L"redo", L"Ctrl+Y", L"mapped", L"nativePassThrough", L"", L"Notepad++ already matches VS Code for redo."}, + {true, L"Basic Editing", L"", L"Add Selection To Next Find Match", L"editor.action.addSelectionToNextFindMatch", L"Ctrl+D", L"mapped", L"nppCommand", L"IDM_EDIT_MULTISELECTNEXT", L"Add next match to multiselection."}, + {true, L"Basic Editing", L"", L"Move Last Selection To Next Find Match", L"editor.action.moveSelectionToNextFindMatch", L"Ctrl+K Ctrl+D", L"mapped", L"nppCommand", L"IDM_EDIT_MULTISELECTSSKIP", L"Skip current occurrence in multi-select flow."}, + {true, L"Basic Editing", L"", L"Undo last cursor operation", L"cursorUndo", L"Ctrl+U", L"mapped", L"nppCommand", L"IDM_EDIT_MULTISELECTUNDO", L"Undo last multi-cursor selection step."}, + {true, L"Basic Editing", L"", L"Insert cursor at end of each line selected", L"editor.action.insertCursorAtEndOfEachLineSelected", L"Shift+Alt+I", L"reserved-noop", L"reservedNoOp", L"", L"Multiple-caret insertion per line is not implemented safely in Notepad++."}, + {true, L"Basic Editing", L"", L"Select all occurrences of current selection", L"editor.action.selectHighlights", L"Ctrl+Shift+L", L"mapped", L"nppCommand", L"IDM_EDIT_MULTISELECTALL", L"Select all current selection matches."}, + {true, L"Basic Editing", L"", L"Select all occurrences of current word", L"editor.action.changeAll", L"Ctrl+F2", L"mapped", L"nppCommand", L"IDM_EDIT_MULTISELECTALLWHOLEWORD", L"Approximate current-word multi-select."}, + {true, L"Basic Editing", L"", L"Select current line", L"expandLineSelection", L"Ctrl+L", L"mapped", L"custom", L"SelectCurrentLine", L"Select current line without trailing newline."}, + {true, L"Basic Editing", L"", L"Insert Cursor Below", L"editor.action.insertCursorBelow", L"Ctrl+Alt+Down", L"mapped", L"custom", L"InsertCursorBelow", L"Move with the shared vertical-cursor rules; add/select one caret below. Virtual-space alignment is configurable."}, + {true, L"Basic Editing", L"", L"Insert Cursor Above", L"editor.action.insertCursorAbove", L"Ctrl+Alt+Up", L"mapped", L"custom", L"InsertCursorAbove", L"Move with the shared vertical-cursor rules; add/select one caret above."}, + {true, L"Basic Editing", L"", L"Jump to matching bracket", L"editor.action.jumpToBracket", L"Ctrl+Shift+\\", L"mapped", L"nppCommand", L"IDM_SEARCH_GOTOMATCHINGBRACE", L"Jump to matching brace."}, + {true, L"Basic Editing", L"", L"Indent Line", L"editor.action.indentLines", L"Ctrl+]", L"mapped", L"nppCommand", L"IDM_EDIT_INS_TAB", L"Approximate VS Code indent with Notepad++ indent command."}, + {true, L"Basic Editing", L"", L"Outdent Line", L"editor.action.outdentLines", L"Ctrl+[", L"mapped", L"nppCommand", L"IDM_EDIT_RMV_TAB", L"Approximate VS Code outdent with Notepad++ unindent command."}, + {false, L"Basic Editing", L"", L"Go to Beginning of Line", L"cursorHome", L"Home", L"mapped", L"nativePassThrough", L"", L"Notepad++ already matches VS Code for line-home movement."}, + {false, L"Basic Editing", L"", L"Go to End of Line", L"cursorEnd", L"End", L"mapped", L"nativePassThrough", L"", L"Notepad++ already matches VS Code for line-end movement."}, + {true, L"Basic Editing", L"", L"Go to End of File", L"cursorBottom", L"Ctrl+End", L"mapped", L"sciCommand", L"SCI_DOCUMENTEND", L"Jump to document end."}, + {true, L"Basic Editing", L"", L"Go to Beginning of File", L"cursorTop", L"Ctrl+Home", L"mapped", L"sciCommand", L"SCI_DOCUMENTSTART", L"Jump to document start."}, + {true, L"Basic Editing", L"", L"Scroll Line Down", L"scrollLineDown", L"Ctrl+Down", L"mapped", L"sciCommand", L"SCI_LINESCROLLDOWN", L"Scroll without moving caret."}, + {true, L"Basic Editing", L"", L"Scroll Line Up", L"scrollLineUp", L"Ctrl+Up", L"mapped", L"sciCommand", L"SCI_LINESCROLLUP", L"Scroll without moving caret."}, + {true, L"Basic Editing", L"", L"Scroll Page Down", L"scrollPageDown", L"Alt+PageDown", L"mapped", L"custom", L"ScrollPageDownNoCaret", L"Scroll a page without moving caret."}, + {true, L"Basic Editing", L"", L"Scroll Page Up", L"scrollPageUp", L"Alt+PageUp", L"mapped", L"custom", L"ScrollPageUpNoCaret", L"Scroll a page without moving caret."}, + {true, L"Basic Editing", L"", L"Fold (collapse) region", L"editor.fold", L"Ctrl+Shift+[", L"mapped", L"nppCommand", L"IDM_VIEW_FOLD_CURRENT", L"Fold current region."}, + {true, L"Basic Editing", L"", L"Unfold (uncollapse) region", L"editor.unfold", L"Ctrl+Shift+]", L"mapped", L"nppCommand", L"IDM_VIEW_UNFOLD_CURRENT", L"Unfold current region."}, + {true, L"Basic Editing", L"", L"Toggle Fold region", L"editor.toggleFold", L"Ctrl+K Ctrl+L", L"mapped", L"custom", L"ToggleFoldAtCaret", L"Toggle fold header at caret, or nearest fold parent."}, + {true, L"Basic Editing", L"", L"Fold (collapse) all subregions", L"editor.foldRecursively", L"Ctrl+K Ctrl+[", L"reserved-noop", L"reservedNoOp", L"", L"Reserved chord; exact recursive fold not implemented."}, + {true, L"Basic Editing", L"", L"Unfold (uncollapse) all subregions", L"editor.unfoldRecursively", L"Ctrl+K Ctrl+]", L"reserved-noop", L"reservedNoOp", L"", L"Reserved chord; exact recursive unfold not implemented."}, + {true, L"Basic Editing", L"", L"Fold (collapse) all regions", L"editor.foldAll", L"Ctrl+K Ctrl+0", L"mapped", L"nppCommand", L"IDM_VIEW_FOLDALL", L"Fold all regions."}, + {true, L"Basic Editing", L"", L"Unfold (uncollapse) all regions", L"editor.unfoldAll", L"Ctrl+K Ctrl+J", L"mapped", L"nppCommand", L"IDM_VIEW_UNFOLDALL", L"Unfold all regions."}, + {true, L"Basic Editing", L"", L"Add Line Comment", L"editor.action.addCommentLine", L"Ctrl+K Ctrl+C", L"mapped", L"nppCommand", L"IDM_EDIT_STREAM_COMMENT", L"Force comment selected lines."}, + {true, L"Basic Editing", L"", L"Remove Line Comment", L"editor.action.removeCommentLine", L"Ctrl+K Ctrl+U", L"mapped", L"nppCommand", L"IDM_EDIT_STREAM_UNCOMMENT", L"Force uncomment selected lines."}, + {true, L"Basic Editing", L"", L"Toggle Line Comment", L"editor.action.commentLine", L"Ctrl+/", L"mapped", L"custom", L"ToggleStreamComment", L"Toggle line-style stream comment based on current selection state."}, + {true, L"Basic Editing", L"", L"Toggle Block Comment", L"editor.action.blockComment", L"Shift+Alt+A", L"mapped", L"nppCommand", L"IDM_EDIT_BLOCK_COMMENT", L"Use Notepad++ block comment command."}, + {false, L"Basic Editing", L"", L"Find", L"actions.find", L"Ctrl+F", L"mapped", L"nativePassThrough", L"", L"Notepad++ already matches VS Code for find."}, + {false, L"Basic Editing", L"", L"Replace", L"editor.action.startFindReplaceAction", L"Ctrl+H", L"mapped", L"nativePassThrough", L"", L"Notepad++ already matches VS Code for replace."}, + {false, L"Basic Editing", L"", L"Find Next", L"editor.action.nextMatchFindAction", L"Enter", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Basic Editing", L"", L"Find Previous", L"editor.action.previousMatchFindAction", L"Shift+Enter", L"documented-unported", L"documentedUnported", L"", L""}, + {true, L"Basic Editing", L"", L"Select All Occurrences of Find Match", L"editor.action.selectAllMatches", L"Alt+Enter", L"mapped", L"nppCommand", L"IDM_EDIT_MULTISELECTALL", L"Select all find matches."}, + {false, L"Basic Editing", L"", L"Toggle Find Case Sensitive", L"toggleFindCaseSensitive", L"Alt+C", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Basic Editing", L"", L"Toggle Find Regex", L"toggleFindRegex", L"Alt+R", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Basic Editing", L"", L"Toggle Find Whole Word", L"toggleFindWholeWord", L"Alt+W", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Basic Editing", L"", L"Toggle Use of Tab Key for Setting Focus", L"editor.action.toggleTabFocusMode", L"Ctrl+M", L"documented-unported", L"documentedUnported", L"", L""}, + {true, L"Basic Editing", L"", L"Toggle Word Wrap", L"editor.action.toggleWordWrap", L"Alt+Z", L"mapped", L"nppCommand", L"IDM_VIEW_WRAP", L"Toggle word wrap."}, + {true, L"Basic Editing", L"", L"Column Select Down", L"editor.action.cursorColumnSelectDown", L"Ctrl+Alt+Shift+Down", L"mapped", L"custom", L"CursorColumnSelectDown", L"Move with the shared vertical-cursor rules; rebuild the anchored column-selection range downward. Undocumented on the VS Code default keybindings page, but available in the VS Code Keyboard Shortcuts editor."}, + {true, L"Basic Editing", L"", L"Column Select Up", L"editor.action.cursorColumnSelectUp", L"Ctrl+Alt+Shift+Up", L"mapped", L"custom", L"CursorColumnSelectUp", L"Move with the shared vertical-cursor rules; rebuild the anchored column-selection range upward. Undocumented on the VS Code default keybindings page, but available in the VS Code Keyboard Shortcuts editor."}, + {true, L"Rich Languages Editing", L"", L"Trigger Suggest", L"editor.action.triggerSuggest", L"Ctrl+Space", L"mapped", L"nppCommand", L"IDM_EDIT_AUTOCOMPLETE", L"Open autocomplete."}, + {true, L"Rich Languages Editing", L"", L"Trigger Parameter Hints", L"editor.action.triggerParameterHints", L"Ctrl+Shift+Space", L"mapped", L"nppCommand", L"IDM_EDIT_FUNCCALLTIP", L"Open function call tip."}, + {true, L"Rich Languages Editing", L"", L"Format Document", L"editor.action.formatDocument", L"Shift+Alt+F", L"reserved-noop", L"reservedNoOp", L"", L"Formatting depends on plugins/language support, so strict mode reserves it."}, + {true, L"Rich Languages Editing", L"", L"Format Selection", L"editor.action.formatSelection", L"Ctrl+K Ctrl+F", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code format-selection chord."}, + {true, L"Rich Languages Editing", L"", L"Go to Definition", L"editor.action.revealDefinition", L"F12", L"reserved-noop", L"reservedNoOp", L"", L"No built-in go-to-definition equivalent."}, + {true, L"Rich Languages Editing", L"", L"Show Hover", L"editor.action.showHover", L"Ctrl+K Ctrl+I", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code hover chord."}, + {true, L"Rich Languages Editing", L"", L"Peek Definition", L"editor.action.peekDefinition", L"Alt+F12", L"reserved-noop", L"reservedNoOp", L"", L"No peek-definition equivalent."}, + {true, L"Rich Languages Editing", L"", L"Open Definition to the Side", L"editor.action.revealDefinitionAside", L"Ctrl+K F12", L"reserved-noop", L"reservedNoOp", L"", L"No open-definition-side equivalent."}, + {true, L"Rich Languages Editing", L"", L"Quick Fix", L"editor.action.quickFix", L"Ctrl+.", L"reserved-noop", L"reservedNoOp", L"", L"No quick-fix provider equivalent."}, + {true, L"Rich Languages Editing", L"", L"Go to References", L"editor.action.goToReferences", L"Shift+F12", L"reserved-noop", L"reservedNoOp", L"", L"No find-references equivalent."}, + {true, L"Rich Languages Editing", L"", L"Rename Symbol", L"editor.action.rename", L"F2", L"reserved-noop", L"reservedNoOp", L"", L"No language-aware rename equivalent."}, + {false, L"Rich Languages Editing", L"", L"Replace with Next Value", L"editor.action.inPlaceReplace.down", L"Ctrl+Shift+.", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Rich Languages Editing", L"", L"Replace with Previous Value", L"editor.action.inPlaceReplace.up", L"Ctrl+Shift+,", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Rich Languages Editing", L"", L"Expand AST Selection", L"editor.action.smartSelect.expand", L"Shift+Alt+Right", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Rich Languages Editing", L"", L"Shrink AST Selection", L"editor.action.smartSelect.shrink", L"Shift+Alt+Left", L"documented-unported", L"documentedUnported", L"", L""}, + {true, L"Rich Languages Editing", L"", L"Trim Trailing Whitespace", L"editor.action.trimTrailingWhitespace", L"Ctrl+K Ctrl+X", L"mapped", L"nppCommand", L"IDM_EDIT_TRIMTRAILING", L"Trim trailing whitespace."}, + {true, L"Rich Languages Editing", L"", L"Change Language Mode", L"workbench.action.editor.changeLanguageMode", L"Ctrl+K M", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code language-mode chord."}, + {true, L"Navigation", L"", L"Show All Symbols", L"workbench.action.showAllSymbols", L"Ctrl+T", L"mapped", L"nppCommand", L"IDM_VIEW_SWITCHTO_FUNC_LIST", L"Approximate workspace symbol search with function list."}, + {true, L"Navigation", L"", L"Go to Line...", L"workbench.action.gotoLine", L"Ctrl+G", L"mapped", L"nppCommand", L"IDM_SEARCH_GOTOLINE", L"Go to line."}, + {true, L"Navigation", L"", L"Go to File..., Quick Open", L"workbench.action.quickOpen", L"Ctrl+P", L"mapped", L"nppCommand", L"IDM_FILE_OPEN", L"Approximate Quick Open with file-open dialog."}, + {true, L"Navigation", L"", L"Go to Symbol...", L"workbench.action.gotoSymbol", L"Ctrl+Shift+O", L"mapped", L"nppCommand", L"IDM_VIEW_SWITCHTO_FUNC_LIST", L"Approximate symbol picker with function list panel."}, + {true, L"Navigation", L"", L"Show Problems", L"workbench.actions.view.problems", L"Ctrl+Shift+M", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code problems shortcut."}, + {true, L"Navigation", L"", L"Go to Next Error or Warning", L"editor.action.marker.nextInFiles", L"F8", L"reserved-noop", L"reservedNoOp", L"", L"No Problems panel equivalent."}, + {true, L"Navigation", L"", L"Go to Previous Error or Warning", L"editor.action.marker.prevInFiles", L"Shift+F8", L"reserved-noop", L"reservedNoOp", L"", L"No Problems panel equivalent."}, + {true, L"Navigation", L"", L"Show All Commands", L"workbench.action.showCommands", L"Ctrl+Shift+P", L"reserved-noop", L"reservedNoOp", L"", L"No Command Palette equivalent."}, + {true, L"Navigation", L"", L"Navigate Editor Group History", L"workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup", L"Ctrl+Tab", L"mapped", L"nppCommand", L"IDM_VIEW_TAB_NEXT", L"Cycle tabs forward."}, + {true, L"Navigation", L"", L"Go Back", L"workbench.action.navigateBack", L"Alt+Left", L"reserved-noop", L"reservedNoOp", L"", L"No stable back-stack equivalent."}, + {false, L"Navigation", L"", L"Go back in Quick Input", L"workbench.action.quickInputBack", L"Alt+Left", L"documented-unported", L"documentedUnported", L"", L""}, + {true, L"Navigation", L"", L"Go Forward", L"workbench.action.navigateForward", L"Alt+Right", L"reserved-noop", L"reservedNoOp", L"", L"No stable forward-stack equivalent."}, + {false, L"Navigation", L"", L"Focus Breadcrumbs", L"breadcrumbs.focus", L"Ctrl+Shift+;", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Navigation", L"", L"Focus and Select Breadcrumbs", L"breadcrumbs.focusAndSelect", L"Ctrl+Shift+.", L"documented-unported", L"documentedUnported", L"", L""}, + {true, L"Editor/Window Management", L"", L"New Window", L"workbench.action.newWindow", L"Ctrl+Shift+N", L"mapped", L"nppCommand", L"IDM_VIEW_GOTO_NEW_INSTANCE", L"Approximate new window with new Notepad++ instance."}, + {false, L"Editor/Window Management", L"", L"Close Window", L"workbench.action.closeWindow", L"Alt+F4", L"documented-unported", L"documentedUnported", L"", L""}, + {true, L"Editor/Window Management", L"", L"Close Editor", L"workbench.action.closeActiveEditor", L"Ctrl+F4", L"mapped", L"nppCommand", L"IDM_FILE_CLOSE", L"Close active file."}, + {true, L"Editor/Window Management", L"", L"Close Folder", L"workbench.action.closeFolder", L"Ctrl+K F", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code close-folder chord."}, + {true, L"Editor/Window Management", L"", L"Split Editor", L"workbench.action.splitEditor", L"Ctrl+\\", L"mapped", L"nppCommand", L"IDM_VIEW_GOTO_ANOTHER_VIEW", L"Approximate split editor by moving document to the other view."}, + {true, L"Editor/Window Management", L"", L"Focus into First Editor Group", L"workbench.action.focusFirstEditorGroup", L"Ctrl+1", L"mapped", L"nppCommand", L"IDM_VIEW_TAB1", L"Approximate first editor group focus with first tab selection."}, + {true, L"Editor/Window Management", L"", L"Focus into Second Editor Group", L"workbench.action.focusSecondEditorGroup", L"Ctrl+2", L"mapped", L"nppCommand", L"IDM_VIEW_TAB2", L"Approximate second editor group focus with second tab selection."}, + {true, L"Editor/Window Management", L"", L"Focus into Third Editor Group", L"workbench.action.focusThirdEditorGroup", L"Ctrl+3", L"mapped", L"nppCommand", L"IDM_VIEW_TAB3", L"Approximate third editor group focus with third tab selection."}, + {true, L"Editor/Window Management", L"", L"Focus into Editor Group on the Left", L"workbench.action.focusLeftGroup", L"Ctrl+K Ctrl+Left", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code focus-group chord."}, + {true, L"Editor/Window Management", L"", L"Focus into Editor Group on the Right", L"workbench.action.focusRightGroup", L"Ctrl+K Ctrl+Right", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code focus-group chord."}, + {true, L"Editor/Window Management", L"", L"Move Editor Left", L"workbench.action.moveEditorLeftInGroup", L"Ctrl+Shift+PageUp", L"mapped", L"nppCommand", L"IDM_VIEW_TAB_MOVEBACKWARD", L"Approximate move editor left with move tab left."}, + {true, L"Editor/Window Management", L"", L"Move Editor Right", L"workbench.action.moveEditorRightInGroup", L"Ctrl+Shift+PageDown", L"mapped", L"nppCommand", L"IDM_VIEW_TAB_MOVEFORWARD", L"Approximate move editor right with move tab right."}, + {true, L"Editor/Window Management", L"", L"Move Active Editor Group Left", L"workbench.action.moveActiveEditorGroupLeft", L"Ctrl+K Left", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code move-group chord."}, + {true, L"Editor/Window Management", L"", L"Move Active Editor Group Right", L"workbench.action.moveActiveEditorGroupRight", L"Ctrl+K Right", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code move-group chord."}, + {true, L"Editor/Window Management", L"", L"Move Editor into Next Group", L"workbench.action.moveEditorToNextGroup", L"Ctrl+Alt+Right", L"mapped", L"nppCommand", L"IDM_VIEW_GOTO_ANOTHER_VIEW", L"Approximate move editor to next group by moving document to the other view."}, + {true, L"Editor/Window Management", L"", L"Move Editor into Previous Group", L"workbench.action.moveEditorToPreviousGroup", L"Ctrl+Alt+Left", L"mapped", L"nppCommand", L"IDM_VIEW_GOTO_ANOTHER_VIEW", L"Approximate move editor to previous group by moving document to the other view."}, + {false, L"File Management", L"", L"New File", L"workbench.action.files.newUntitledFile", L"Ctrl+N", L"mapped", L"nativePassThrough", L"", L"Notepad++ already matches VS Code for new file."}, + {false, L"File Management", L"", L"Open File...", L"workbench.action.files.openFile", L"Ctrl+O", L"mapped", L"nativePassThrough", L"", L"Notepad++ already matches VS Code for open file."}, + {false, L"File Management", L"", L"Save", L"workbench.action.files.save", L"Ctrl+S", L"mapped", L"nativePassThrough", L"", L"Notepad++ already matches VS Code for save."}, + {true, L"File Management", L"", L"Save All", L"saveAll", L"Ctrl+K S", L"mapped", L"nppCommand", L"IDM_FILE_SAVEALL", L"Save all open files."}, + {true, L"File Management", L"", L"Save As...", L"workbench.action.files.saveAs", L"Ctrl+Shift+S", L"mapped", L"nppCommand", L"IDM_FILE_SAVEAS", L"Save current file as."}, + {true, L"File Management", L"", L"Close Group", L"workbench.action.closeEditorsInGroup", L"Ctrl+K W", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code close-group chord."}, + {true, L"File Management", L"", L"Close All", L"workbench.action.closeAllEditors", L"Ctrl+K Ctrl+W", L"mapped", L"nppCommand", L"IDM_FILE_CLOSEALL", L"Close all files."}, + {true, L"File Management", L"", L"Reopen Closed Editor", L"workbench.action.reopenClosedEditor", L"Ctrl+Shift+T", L"mapped", L"nppCommand", L"IDM_FILE_RESTORELASTCLOSEDFILE", L"Restore last closed file."}, + {true, L"File Management", L"", L"Keep Open", L"workbench.action.keepEditor", L"Ctrl+K Enter", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code keep-open chord."}, + {true, L"File Management", L"", L"Copy Path of Active File", L"workbench.action.files.copyPathOfActiveFile", L"Ctrl+K P", L"mapped", L"nppCommand", L"IDM_EDIT_FULLPATHTOCLIP", L"Copy active file path."}, + {true, L"File Management", L"", L"Reveal Active File in Windows", L"workbench.action.files.revealActiveFileInWindows", L"Ctrl+K R", L"mapped", L"nppCommand", L"IDM_EDIT_OPENINFOLDER", L"Reveal active file in Explorer."}, + {true, L"Display", L"", L"Toggle Full Screen", L"workbench.action.toggleFullScreen", L"F11", L"mapped", L"nppCommand", L"IDM_VIEW_FULLSCREENTOGGLE", L"Toggle fullscreen."}, + {true, L"Display", L"", L"Toggle Zen Mode", L"workbench.action.toggleZenMode", L"Ctrl+K Z", L"mapped", L"nppCommand", L"IDM_VIEW_DISTRACTIONFREE", L"Approximate VS Code Zen Mode with Notepad++ distraction-free mode."}, + {false, L"Display", L"", L"Leave Zen Mode", L"workbench.action.exitZenMode", L"Escape Escape", L"documented-unported", L"documentedUnported", L"", L""}, + {true, L"Display", L"", L"Zoom in", L"workbench.action.zoomIn", L"Ctrl+=", L"mapped", L"nppCommand", L"IDM_VIEW_ZOOMIN", L"Zoom in."}, + {true, L"Display", L"", L"Zoom out", L"workbench.action.zoomOut", L"Ctrl+-", L"mapped", L"nppCommand", L"IDM_VIEW_ZOOMOUT", L"Zoom out."}, + {true, L"Display", L"", L"Reset Zoom", L"workbench.action.zoomReset", L"Ctrl+Numpad0", L"mapped", L"nppCommand", L"IDM_VIEW_ZOOMRESTORE", L"Reset zoom."}, + {true, L"Display", L"", L"Toggle Sidebar Visibility", L"workbench.action.toggleSidebarVisibility", L"Ctrl+B", L"mapped", L"nppCommand", L"IDM_VIEW_FILEBROWSER", L"Approximate sidebar toggle with file browser panel."}, + {true, L"Display", L"", L"Show Explorer / Toggle Focus", L"workbench.view.explorer", L"Ctrl+Shift+E", L"mapped", L"nppCommand", L"IDM_VIEW_SWITCHTO_FILEBROWSER", L"Focus file browser panel."}, + {true, L"Display", L"", L"Show Search", L"workbench.view.search", L"Ctrl+Shift+F", L"mapped", L"nppCommand", L"IDM_SEARCH_FINDINFILES", L"Find in files."}, + {true, L"Display", L"", L"Show Source Control", L"workbench.view.scm", L"Ctrl+Shift+G", L"reserved-noop", L"reservedNoOp", L"", L"No source-control sidebar equivalent."}, + {true, L"Display", L"", L"Show Run", L"workbench.view.debug", L"Ctrl+Shift+D", L"reserved-noop", L"reservedNoOp", L"", L"No Run/Debug sidebar equivalent."}, + {true, L"Display", L"", L"Show Extensions", L"workbench.view.extensions", L"Ctrl+Shift+X", L"reserved-noop", L"reservedNoOp", L"", L"No extensions marketplace sidebar equivalent."}, + {true, L"Display", L"", L"Show Output", L"workbench.action.output.toggleOutput", L"Ctrl+Shift+U", L"reserved-noop", L"reservedNoOp", L"", L"No Output panel equivalent."}, + {true, L"Display", L"", L"Quick Open View", L"workbench.action.quickOpenView", L"Ctrl+Q", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code quick-open-view shortcut."}, + {true, L"Display", L"", L"Open New Command Prompt", L"workbench.action.terminal.openNativeConsole", L"Ctrl+Shift+C", L"mapped", L"nppCommand", L"IDM_FILE_OPEN_CMD", L"Approximate with Open Command Prompt Here."}, + {true, L"Display", L"", L"Toggle Markdown Preview", L"markdown.showPreview", L"Ctrl+Shift+V", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code markdown-preview shortcut."}, + {true, L"Display", L"", L"Open Preview to the Side", L"markdown.showPreviewToSide", L"Ctrl+K V", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code markdown-preview chord."}, + {true, L"Display", L"", L"Toggle Integrated Terminal", L"workbench.action.terminal.toggleTerminal", L"Ctrl+`", L"mapped", L"nppCommand", L"IDM_FILE_OPEN_CMD", L"Approximate integrated terminal with Open Command Prompt Here."}, + {true, L"Search", L"", L"Replace in Files", L"workbench.action.replaceInFiles", L"Ctrl+Shift+H", L"mapped", L"nppCommand", L"IDM_SEARCH_FINDINFILES", L"Approximate replace-in-files with Notepad++ find-in-files panel."}, + {false, L"Search", L"", L"Toggle Match Case", L"toggleSearchCaseSensitive", L"Alt+C", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Search", L"", L"Toggle Match Whole Word", L"toggleSearchWholeWord", L"Alt+W", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Search", L"", L"Toggle Use Regular Expression", L"toggleSearchRegex", L"Alt+R", L"documented-unported", L"documentedUnported", L"", L""}, + {true, L"Search", L"", L"Toggle Search Details", L"workbench.action.search.toggleQueryDetails", L"Ctrl+Shift+J", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code search-details shortcut."}, + {false, L"Search", L"", L"Focus Next Search Result", L"search.action.focusNextSearchResult", L"F4", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Search", L"", L"Focus Previous Search Result", L"search.action.focusPreviousSearchResult", L"Shift+F4", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Search", L"", L"Show Next Search Term", L"history.showNext", L"Down", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Search", L"", L"Show Previous Search Term", L"history.showPrevious", L"Up", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Search Editor", L"", L"Open Results In Editor", L"search.action.openInEditor", L"Alt+Enter", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Search Editor", L"", L"Focus Search Editor Input", L"search.action.focusQueryEditorWidget", L"Escape", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Search Editor", L"", L"Search Again", L"rerunSearchEditorSearch", L"Ctrl+Shift+R", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Search Editor", L"", L"Delete File Results", L"search.searchEditor.action.deleteFileResults", L"Ctrl+Shift+Backspace", L"documented-unported", L"documentedUnported", L"", L""}, + {true, L"Preferences", L"", L"Open Settings", L"workbench.action.openSettings", L"Ctrl+,", L"mapped", L"nppCommand", L"IDM_SETTING_PREFERENCE", L"Open Notepad++ preferences."}, + {true, L"Preferences", L"", L"Open Keyboard Shortcuts", L"workbench.action.openGlobalKeybindings", L"Ctrl+K Ctrl+S", L"mapped", L"nppCommand", L"IDM_SETTING_SHORTCUT_MAPPER", L"Open Shortcut Mapper."}, + {true, L"Preferences", L"", L"Select Color Theme", L"workbench.action.selectTheme", L"Ctrl+K Ctrl+T", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code theme chord."}, + {false, L"Chat", L"", L"Open Chat view", L"workbench.action.chat.open", L"Ctrl+Alt+I", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Chat", L"", L"Open chat in agent mode", L"workbench.action.chat.openagent", L"Ctrl+Shift+I", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Chat", L"", L"Open editor inline chat", L"inlineChat.start", L"Ctrl+I", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Chat", L"", L"Open terminal inline chat", L"workbench.action.terminal.chat.start", L"Ctrl+I", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Chat", L"", L"Open quick chat", L"workbench.action.quickchat.toggle", L"Ctrl+Shift+Alt+L", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Chat", L"", L"Open agent picker", L"workbench.action.chat.openModePicker", L"Ctrl+.", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Chat", L"", L"Open language model picker", L"workbench.action.chat.openModelPicker", L"Ctrl+Alt+.", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Chat", L"", L"New chat session", L"workbench.action.chat.newChat", L"Ctrl+N", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Chat", L"", L"Accept inline suggestion", L"editor.action.inlineSuggest.commit", L"Tab", L"documented-unported", L"documentedUnported", L"", L""}, + {true, L"Debug", L"", L"Toggle Breakpoint", L"editor.debug.action.toggleBreakpoint", L"F9", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code breakpoint shortcut."}, + {true, L"Debug", L"", L"Start", L"workbench.action.debug.start", L"F5", L"reserved-noop", L"reservedNoOp", L"", L"Reserved so conflicting Notepad++ behavior does not fire for VS Code debug-start shortcut."}, + {false, L"Debug", L"", L"Continue", L"workbench.action.debug.continue", L"F5", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Debug", L"", L"Start (without debugging)", L"workbench.action.debug.run", L"Ctrl+F5", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Debug", L"", L"Pause", L"workbench.action.debug.pause", L"F6", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Debug", L"", L"Step Into", L"workbench.action.debug.stepInto", L"F11", L"documented-unported", L"documentedUnported", L"", L""}, + {false, L"Tasks", L"", L"Run Build Task", L"workbench.action.tasks.build", L"Ctrl+Shift+B", L"documented-unported", L"documentedUnported", L"", L""}, +}; + const std::vector kShortcutBindings = { - {{true, false, false, 'X'}, ActionKind::CutAllowLine, 0}, // Ctrl+X -> editor.action.clipboardCutAction - {{true, false, false, 'C'}, ActionKind::CopyAllowLine, 0}, // Ctrl+C -> editor.action.clipboardCopyAction - {{true, false, true, 'K'}, ActionKind::SciCommand, SCI_LINEDELETE}, // Ctrl+Shift+K -> editor.action.deleteLines - {{true, false, false, VK_RETURN}, ActionKind::InsertLineBelow, 0}, // Ctrl+Enter -> editor.action.insertLineAfter - {{true, false, true, VK_RETURN}, ActionKind::InsertLineAbove, 0}, // Ctrl+Shift+Enter -> editor.action.insertLineBefore - {{false, true, false, VK_DOWN}, ActionKind::NppCommand, IDM_EDIT_LINE_DOWN}, // Alt+Down -> editor.action.moveLinesDownAction - {{false, true, false, VK_UP}, ActionKind::NppCommand, IDM_EDIT_LINE_UP}, // Alt+Up -> editor.action.moveLinesUpAction - {{false, true, true, VK_DOWN}, ActionKind::NppCommand, IDM_EDIT_DUP_LINE}, // Shift+Alt+Down -> editor.action.copyLinesDownAction - {{false, true, true, VK_UP}, ActionKind::DuplicateLineUp, 0}, // Shift+Alt+Up -> editor.action.copyLinesUpAction - {{true, false, false, 'D'}, ActionKind::NppCommand, IDM_EDIT_MULTISELECTNEXT}, // Ctrl+D -> editor.action.addSelectionToNextFindMatch - {{true, false, false, 'U'}, ActionKind::NppCommand, IDM_EDIT_MULTISELECTUNDO}, // Ctrl+U -> cursorUndo - {{false, true, true, 'I'}, ActionKind::ReservedNoOp, 0}, // Shift+Alt+I -> editor.action.insertCursorAtEndOfEachLineSelected - {{true, false, true, 'L'}, ActionKind::NppCommand, IDM_EDIT_MULTISELECTALL}, // Ctrl+Shift+L -> editor.action.selectHighlights - {{true, false, false, VK_F2}, ActionKind::NppCommand, IDM_EDIT_MULTISELECTALLWHOLEWORD}, // Ctrl+F2 -> editor.action.changeAll - {{true, false, false, 'L'}, ActionKind::SelectCurrentLine, 0}, // Ctrl+L -> expandLineSelection - {{true, true, false, VK_DOWN}, ActionKind::ReservedNoOp, 0}, // Ctrl+Alt+Down -> editor.action.insertCursorBelow - {{true, true, false, VK_UP}, ActionKind::ReservedNoOp, 0}, // Ctrl+Alt+Up -> editor.action.insertCursorAbove - {{true, false, true, VK_OEM_5}, ActionKind::NppCommand, IDM_SEARCH_GOTOMATCHINGBRACE}, // Ctrl+Shift+\ -> editor.action.jumpToBracket - {{true, false, false, VK_OEM_6}, ActionKind::NppCommand, IDM_EDIT_INS_TAB}, // Ctrl+] -> editor.action.indentLines - {{true, false, false, VK_OEM_4}, ActionKind::NppCommand, IDM_EDIT_RMV_TAB}, // Ctrl+[ -> editor.action.outdentLines - {{true, false, false, VK_END}, ActionKind::SciCommand, SCI_DOCUMENTEND}, // Ctrl+End -> cursorBottom - {{true, false, false, VK_HOME}, ActionKind::SciCommand, SCI_DOCUMENTSTART}, // Ctrl+Home -> cursorTop - {{true, false, false, VK_DOWN}, ActionKind::SciCommand, SCI_LINESCROLLDOWN}, // Ctrl+Down -> scrollLineDown - {{true, false, false, VK_UP}, ActionKind::SciCommand, SCI_LINESCROLLUP}, // Ctrl+Up -> scrollLineUp - {{false, true, false, VK_NEXT}, ActionKind::ScrollPageDownNoCaret, 0}, // Alt+PageDown -> scrollPageDown - {{false, true, false, VK_PRIOR}, ActionKind::ScrollPageUpNoCaret, 0}, // Alt+PageUp -> scrollPageUp - {{true, false, true, VK_OEM_4}, ActionKind::NppCommand, IDM_VIEW_FOLD_CURRENT}, // Ctrl+Shift+[ -> editor.fold - {{true, false, true, VK_OEM_6}, ActionKind::NppCommand, IDM_VIEW_UNFOLD_CURRENT}, // Ctrl+Shift+] -> editor.unfold - {{true, false, false, VK_OEM_2}, ActionKind::ToggleStreamComment, 0}, // Ctrl+/ -> editor.action.commentLine - {{false, true, true, 'A'}, ActionKind::NppCommand, IDM_EDIT_BLOCK_COMMENT}, // Shift+Alt+A -> editor.action.blockComment - {{false, true, false, VK_RETURN}, ActionKind::NppCommand, IDM_EDIT_MULTISELECTALL}, // Alt+Enter -> editor.action.selectAllMatches - {{false, true, false, 'Z'}, ActionKind::NppCommand, IDM_VIEW_WRAP}, // Alt+Z -> editor.action.toggleWordWrap - {{true, false, false, VK_SPACE}, ActionKind::NppCommand, IDM_EDIT_AUTOCOMPLETE}, // Ctrl+Space -> editor.action.triggerSuggest - {{true, false, true, VK_SPACE}, ActionKind::NppCommand, IDM_EDIT_FUNCCALLTIP}, // Ctrl+Shift+Space -> editor.action.triggerParameterHints - {{false, true, true, 'F'}, ActionKind::ReservedNoOp, 0}, // Shift+Alt+F -> editor.action.formatDocument - {{false, false, false, VK_F12}, ActionKind::ReservedNoOp, 0}, // F12 -> editor.action.revealDefinition - {{false, true, false, VK_F12}, ActionKind::ReservedNoOp, 0}, // Alt+F12 -> editor.action.peekDefinition - {{true, false, false, VK_OEM_PERIOD}, ActionKind::ReservedNoOp, 0}, // Ctrl+. -> editor.action.quickFix - {{false, false, true, VK_F12}, ActionKind::ReservedNoOp, 0}, // Shift+F12 -> editor.action.goToReferences - {{false, false, false, VK_F2}, ActionKind::ReservedNoOp, 0}, // F2 -> editor.action.rename - {{true, false, false, 'T'}, ActionKind::NppCommand, IDM_VIEW_SWITCHTO_FUNC_LIST}, // Ctrl+T -> workbench.action.showAllSymbols - {{true, false, false, 'G'}, ActionKind::NppCommand, IDM_SEARCH_GOTOLINE}, // Ctrl+G -> workbench.action.gotoLine - {{true, false, false, 'P'}, ActionKind::NppCommand, IDM_FILE_OPEN}, // Ctrl+P -> workbench.action.quickOpen - {{true, false, true, 'O'}, ActionKind::NppCommand, IDM_VIEW_SWITCHTO_FUNC_LIST}, // Ctrl+Shift+O -> workbench.action.gotoSymbol - {{true, false, true, 'M'}, ActionKind::ReservedNoOp, 0}, // Ctrl+Shift+M -> workbench.actions.view.problems - {{false, false, false, VK_F8}, ActionKind::ReservedNoOp, 0}, // F8 -> editor.action.marker.nextInFiles - {{false, false, true, VK_F8}, ActionKind::ReservedNoOp, 0}, // Shift+F8 -> editor.action.marker.prevInFiles - {{true, false, true, 'P'}, ActionKind::ReservedNoOp, 0}, // Ctrl+Shift+P -> workbench.action.showCommands - {{true, false, false, VK_TAB}, ActionKind::NppCommand, IDM_VIEW_TAB_NEXT}, // Ctrl+Tab -> workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup - {{false, true, false, VK_LEFT}, ActionKind::ReservedNoOp, 0}, // Alt+Left -> workbench.action.navigateBack - {{false, true, false, VK_RIGHT}, ActionKind::ReservedNoOp, 0}, // Alt+Right -> workbench.action.navigateForward - {{true, false, true, 'N'}, ActionKind::NppCommand, IDM_VIEW_GOTO_NEW_INSTANCE}, // Ctrl+Shift+N -> workbench.action.newWindow - {{true, false, false, VK_F4}, ActionKind::NppCommand, IDM_FILE_CLOSE}, // Ctrl+F4 -> workbench.action.closeActiveEditor - {{true, false, false, VK_OEM_5}, ActionKind::NppCommand, IDM_VIEW_GOTO_ANOTHER_VIEW}, // Ctrl+\ -> workbench.action.splitEditor - {{true, false, false, '1'}, ActionKind::NppCommand, IDM_VIEW_TAB1}, // Ctrl+1 -> workbench.action.focusFirstEditorGroup - {{true, false, false, '2'}, ActionKind::NppCommand, IDM_VIEW_TAB2}, // Ctrl+2 -> workbench.action.focusSecondEditorGroup - {{true, false, false, '3'}, ActionKind::NppCommand, IDM_VIEW_TAB3}, // Ctrl+3 -> workbench.action.focusThirdEditorGroup - {{true, false, true, VK_PRIOR}, ActionKind::NppCommand, IDM_VIEW_TAB_MOVEBACKWARD}, // Ctrl+Shift+PageUp -> workbench.action.moveEditorLeftInGroup - {{true, false, true, VK_NEXT}, ActionKind::NppCommand, IDM_VIEW_TAB_MOVEFORWARD}, // Ctrl+Shift+PageDown -> workbench.action.moveEditorRightInGroup - {{true, true, false, VK_RIGHT}, ActionKind::NppCommand, IDM_VIEW_GOTO_ANOTHER_VIEW}, // Ctrl+Alt+Right -> workbench.action.moveEditorToNextGroup - {{true, true, false, VK_LEFT}, ActionKind::NppCommand, IDM_VIEW_GOTO_ANOTHER_VIEW}, // Ctrl+Alt+Left -> workbench.action.moveEditorToPreviousGroup - {{true, false, true, 'S'}, ActionKind::NppCommand, IDM_FILE_SAVEAS}, // Ctrl+Shift+S -> workbench.action.files.saveAs - {{true, false, true, 'T'}, ActionKind::NppCommand, IDM_FILE_RESTORELASTCLOSEDFILE}, // Ctrl+Shift+T -> workbench.action.reopenClosedEditor - {{false, false, false, VK_F11}, ActionKind::NppCommand, IDM_VIEW_FULLSCREENTOGGLE}, // F11 -> workbench.action.toggleFullScreen - {{true, false, false, VK_OEM_PLUS}, ActionKind::NppCommand, IDM_VIEW_ZOOMIN}, // Ctrl+= -> workbench.action.zoomIn - {{true, false, false, VK_OEM_MINUS}, ActionKind::NppCommand, IDM_VIEW_ZOOMOUT}, // Ctrl+- -> workbench.action.zoomOut - {{true, false, false, VK_NUMPAD0}, ActionKind::NppCommand, IDM_VIEW_ZOOMRESTORE}, // Ctrl+Numpad0 -> workbench.action.zoomReset - {{true, false, false, 'B'}, ActionKind::NppCommand, IDM_VIEW_FILEBROWSER}, // Ctrl+B -> workbench.action.toggleSidebarVisibility - {{true, false, true, 'E'}, ActionKind::NppCommand, IDM_VIEW_SWITCHTO_FILEBROWSER}, // Ctrl+Shift+E -> workbench.view.explorer - {{true, false, true, 'F'}, ActionKind::NppCommand, IDM_SEARCH_FINDINFILES}, // Ctrl+Shift+F -> workbench.view.search - {{true, false, true, 'G'}, ActionKind::ReservedNoOp, 0}, // Ctrl+Shift+G -> workbench.view.scm - {{true, false, true, 'D'}, ActionKind::ReservedNoOp, 0}, // Ctrl+Shift+D -> workbench.view.debug - {{true, false, true, 'X'}, ActionKind::ReservedNoOp, 0}, // Ctrl+Shift+X -> workbench.view.extensions - {{true, false, true, 'U'}, ActionKind::ReservedNoOp, 0}, // Ctrl+Shift+U -> workbench.action.output.toggleOutput - {{true, false, false, 'Q'}, ActionKind::ReservedNoOp, 0}, // Ctrl+Q -> workbench.action.quickOpenView - {{true, false, true, 'C'}, ActionKind::NppCommand, IDM_FILE_OPEN_CMD}, // Ctrl+Shift+C -> workbench.action.terminal.openNativeConsole - {{true, false, true, 'V'}, ActionKind::ReservedNoOp, 0}, // Ctrl+Shift+V -> markdown.showPreview - {{true, false, false, VK_OEM_3}, ActionKind::NppCommand, IDM_FILE_OPEN_CMD}, // Ctrl+` -> workbench.action.terminal.toggleTerminal - {{true, false, true, 'H'}, ActionKind::NppCommand, IDM_SEARCH_FINDINFILES}, // Ctrl+Shift+H -> workbench.action.replaceInFiles - {{true, false, true, 'J'}, ActionKind::ReservedNoOp, 0}, // Ctrl+Shift+J -> workbench.action.search.toggleQueryDetails - {{true, false, false, VK_OEM_COMMA}, ActionKind::NppCommand, IDM_SETTING_PREFERENCE}, // Ctrl+, -> workbench.action.openSettings - {{false, false, false, VK_F9}, ActionKind::ReservedNoOp, 0}, // F9 -> editor.debug.action.toggleBreakpoint - {{false, false, false, VK_F5}, ActionKind::ReservedNoOp, 0}, // F5 -> workbench.action.debug.start + {{true, false, false, 'X'}, ActionKind::CutAllowLine, 0, 0}, // Ctrl+X -> editor.action.clipboardCutAction + {{true, false, false, 'C'}, ActionKind::CopyAllowLine, 0, 1}, // Ctrl+C -> editor.action.clipboardCopyAction + {{true, false, true, 'K'}, ActionKind::SciCommand, SCI_LINEDELETE, 3}, // Ctrl+Shift+K -> editor.action.deleteLines + {{true, false, false, VK_RETURN}, ActionKind::InsertLineBelow, 0, 4}, // Ctrl+Enter -> editor.action.insertLineAfter + {{true, false, true, VK_RETURN}, ActionKind::InsertLineAbove, 0, 5}, // Ctrl+Shift+Enter -> editor.action.insertLineBefore + {{false, true, false, VK_DOWN}, ActionKind::NppCommand, IDM_EDIT_LINE_DOWN, 6}, // Alt+Down -> editor.action.moveLinesDownAction + {{false, true, false, VK_UP}, ActionKind::NppCommand, IDM_EDIT_LINE_UP, 7}, // Alt+Up -> editor.action.moveLinesUpAction + {{false, true, true, VK_DOWN}, ActionKind::NppCommand, IDM_EDIT_DUP_LINE, 8}, // Shift+Alt+Down -> editor.action.copyLinesDownAction + {{false, true, true, VK_UP}, ActionKind::DuplicateLineUp, 0, 9}, // Shift+Alt+Up -> editor.action.copyLinesUpAction + {{true, false, false, 'D'}, ActionKind::NppCommand, IDM_EDIT_MULTISELECTNEXT, 12}, // Ctrl+D -> editor.action.addSelectionToNextFindMatch + {{true, false, false, 'U'}, ActionKind::NppCommand, IDM_EDIT_MULTISELECTUNDO, 14}, // Ctrl+U -> cursorUndo + {{false, true, true, 'I'}, ActionKind::ReservedNoOp, 0, 15}, // Shift+Alt+I -> editor.action.insertCursorAtEndOfEachLineSelected + {{true, false, true, 'L'}, ActionKind::NppCommand, IDM_EDIT_MULTISELECTALL, 16}, // Ctrl+Shift+L -> editor.action.selectHighlights + {{true, false, false, VK_F2}, ActionKind::NppCommand, IDM_EDIT_MULTISELECTALLWHOLEWORD, 17}, // Ctrl+F2 -> editor.action.changeAll + {{true, false, false, 'L'}, ActionKind::SelectCurrentLine, 0, 18}, // Ctrl+L -> expandLineSelection + {{true, true, false, VK_DOWN}, ActionKind::InsertCursorBelow, 0, 19}, // Ctrl+Alt+Down -> editor.action.insertCursorBelow + {{true, true, false, VK_UP}, ActionKind::InsertCursorAbove, 0, 20}, // Ctrl+Alt+Up -> editor.action.insertCursorAbove + {{true, false, true, VK_OEM_5}, ActionKind::NppCommand, IDM_SEARCH_GOTOMATCHINGBRACE, 21}, // Ctrl+Shift+\ -> editor.action.jumpToBracket + {{true, false, false, VK_OEM_6}, ActionKind::NppCommand, IDM_EDIT_INS_TAB, 22}, // Ctrl+] -> editor.action.indentLines + {{true, false, false, VK_OEM_4}, ActionKind::NppCommand, IDM_EDIT_RMV_TAB, 23}, // Ctrl+[ -> editor.action.outdentLines + {{true, false, false, VK_END}, ActionKind::SciCommand, SCI_DOCUMENTEND, 26}, // Ctrl+End -> cursorBottom + {{true, false, false, VK_HOME}, ActionKind::SciCommand, SCI_DOCUMENTSTART, 27}, // Ctrl+Home -> cursorTop + {{true, false, false, VK_DOWN}, ActionKind::SciCommand, SCI_LINESCROLLDOWN, 28}, // Ctrl+Down -> scrollLineDown + {{true, false, false, VK_UP}, ActionKind::SciCommand, SCI_LINESCROLLUP, 29}, // Ctrl+Up -> scrollLineUp + {{false, true, false, VK_NEXT}, ActionKind::ScrollPageDownNoCaret, 0, 30}, // Alt+PageDown -> scrollPageDown + {{false, true, false, VK_PRIOR}, ActionKind::ScrollPageUpNoCaret, 0, 31}, // Alt+PageUp -> scrollPageUp + {{true, false, true, VK_OEM_4}, ActionKind::NppCommand, IDM_VIEW_FOLD_CURRENT, 32}, // Ctrl+Shift+[ -> editor.fold + {{true, false, true, VK_OEM_6}, ActionKind::NppCommand, IDM_VIEW_UNFOLD_CURRENT, 33}, // Ctrl+Shift+] -> editor.unfold + {{true, false, false, VK_OEM_2}, ActionKind::ToggleStreamComment, 0, 41}, // Ctrl+/ -> editor.action.commentLine + {{false, true, true, 'A'}, ActionKind::NppCommand, IDM_EDIT_BLOCK_COMMENT, 42}, // Shift+Alt+A -> editor.action.blockComment + {{false, true, false, VK_RETURN}, ActionKind::NppCommand, IDM_EDIT_MULTISELECTALL, 47}, // Alt+Enter -> editor.action.selectAllMatches + {{false, true, false, 'Z'}, ActionKind::NppCommand, IDM_VIEW_WRAP, 52}, // Alt+Z -> editor.action.toggleWordWrap + {{true, true, true, VK_DOWN}, ActionKind::CursorColumnSelectDown, 0, 53}, // Ctrl+Alt+Shift+Down -> editor.action.cursorColumnSelectDown + {{true, true, true, VK_UP}, ActionKind::CursorColumnSelectUp, 0, 54}, // Ctrl+Alt+Shift+Up -> editor.action.cursorColumnSelectUp + {{true, false, false, VK_SPACE}, ActionKind::NppCommand, IDM_EDIT_AUTOCOMPLETE, 55}, // Ctrl+Space -> editor.action.triggerSuggest + {{true, false, true, VK_SPACE}, ActionKind::NppCommand, IDM_EDIT_FUNCCALLTIP, 56}, // Ctrl+Shift+Space -> editor.action.triggerParameterHints + {{false, true, true, 'F'}, ActionKind::ReservedNoOp, 0, 57}, // Shift+Alt+F -> editor.action.formatDocument + {{false, false, false, VK_F12}, ActionKind::ReservedNoOp, 0, 59}, // F12 -> editor.action.revealDefinition + {{false, true, false, VK_F12}, ActionKind::ReservedNoOp, 0, 61}, // Alt+F12 -> editor.action.peekDefinition + {{true, false, false, VK_OEM_PERIOD}, ActionKind::ReservedNoOp, 0, 63}, // Ctrl+. -> editor.action.quickFix + {{false, false, true, VK_F12}, ActionKind::ReservedNoOp, 0, 64}, // Shift+F12 -> editor.action.goToReferences + {{false, false, false, VK_F2}, ActionKind::ReservedNoOp, 0, 65}, // F2 -> editor.action.rename + {{true, false, false, 'T'}, ActionKind::NppCommand, IDM_VIEW_SWITCHTO_FUNC_LIST, 72}, // Ctrl+T -> workbench.action.showAllSymbols + {{true, false, false, 'G'}, ActionKind::NppCommand, IDM_SEARCH_GOTOLINE, 73}, // Ctrl+G -> workbench.action.gotoLine + {{true, false, false, 'P'}, ActionKind::NppCommand, IDM_FILE_OPEN, 74}, // Ctrl+P -> workbench.action.quickOpen + {{true, false, true, 'O'}, ActionKind::NppCommand, IDM_VIEW_SWITCHTO_FUNC_LIST, 75}, // Ctrl+Shift+O -> workbench.action.gotoSymbol + {{true, false, true, 'M'}, ActionKind::ReservedNoOp, 0, 76}, // Ctrl+Shift+M -> workbench.actions.view.problems + {{false, false, false, VK_F8}, ActionKind::ReservedNoOp, 0, 77}, // F8 -> editor.action.marker.nextInFiles + {{false, false, true, VK_F8}, ActionKind::ReservedNoOp, 0, 78}, // Shift+F8 -> editor.action.marker.prevInFiles + {{true, false, true, 'P'}, ActionKind::ReservedNoOp, 0, 79}, // Ctrl+Shift+P -> workbench.action.showCommands + {{true, false, false, VK_TAB}, ActionKind::NppCommand, IDM_VIEW_TAB_NEXT, 80}, // Ctrl+Tab -> workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup + {{false, true, false, VK_LEFT}, ActionKind::ReservedNoOp, 0, 81}, // Alt+Left -> workbench.action.navigateBack + {{false, true, false, VK_RIGHT}, ActionKind::ReservedNoOp, 0, 83}, // Alt+Right -> workbench.action.navigateForward + {{true, false, true, 'N'}, ActionKind::NppCommand, IDM_VIEW_GOTO_NEW_INSTANCE, 86}, // Ctrl+Shift+N -> workbench.action.newWindow + {{true, false, false, VK_F4}, ActionKind::NppCommand, IDM_FILE_CLOSE, 88}, // Ctrl+F4 -> workbench.action.closeActiveEditor + {{true, false, false, VK_OEM_5}, ActionKind::NppCommand, IDM_VIEW_GOTO_ANOTHER_VIEW, 90}, // Ctrl+\ -> workbench.action.splitEditor + {{true, false, false, '1'}, ActionKind::NppCommand, IDM_VIEW_TAB1, 91}, // Ctrl+1 -> workbench.action.focusFirstEditorGroup + {{true, false, false, '2'}, ActionKind::NppCommand, IDM_VIEW_TAB2, 92}, // Ctrl+2 -> workbench.action.focusSecondEditorGroup + {{true, false, false, '3'}, ActionKind::NppCommand, IDM_VIEW_TAB3, 93}, // Ctrl+3 -> workbench.action.focusThirdEditorGroup + {{true, false, true, VK_PRIOR}, ActionKind::NppCommand, IDM_VIEW_TAB_MOVEBACKWARD, 96}, // Ctrl+Shift+PageUp -> workbench.action.moveEditorLeftInGroup + {{true, false, true, VK_NEXT}, ActionKind::NppCommand, IDM_VIEW_TAB_MOVEFORWARD, 97}, // Ctrl+Shift+PageDown -> workbench.action.moveEditorRightInGroup + {{true, true, false, VK_RIGHT}, ActionKind::NppCommand, IDM_VIEW_GOTO_ANOTHER_VIEW, 100}, // Ctrl+Alt+Right -> workbench.action.moveEditorToNextGroup + {{true, true, false, VK_LEFT}, ActionKind::NppCommand, IDM_VIEW_GOTO_ANOTHER_VIEW, 101}, // Ctrl+Alt+Left -> workbench.action.moveEditorToPreviousGroup + {{true, false, true, 'S'}, ActionKind::NppCommand, IDM_FILE_SAVEAS, 106}, // Ctrl+Shift+S -> workbench.action.files.saveAs + {{true, false, true, 'T'}, ActionKind::NppCommand, IDM_FILE_RESTORELASTCLOSEDFILE, 109}, // Ctrl+Shift+T -> workbench.action.reopenClosedEditor + {{false, false, false, VK_F11}, ActionKind::NppCommand, IDM_VIEW_FULLSCREENTOGGLE, 113}, // F11 -> workbench.action.toggleFullScreen + {{true, false, false, VK_OEM_PLUS}, ActionKind::NppCommand, IDM_VIEW_ZOOMIN, 116}, // Ctrl+= -> workbench.action.zoomIn + {{true, false, false, VK_OEM_MINUS}, ActionKind::NppCommand, IDM_VIEW_ZOOMOUT, 117}, // Ctrl+- -> workbench.action.zoomOut + {{true, false, false, VK_NUMPAD0}, ActionKind::NppCommand, IDM_VIEW_ZOOMRESTORE, 118}, // Ctrl+Numpad0 -> workbench.action.zoomReset + {{true, false, false, 'B'}, ActionKind::NppCommand, IDM_VIEW_FILEBROWSER, 119}, // Ctrl+B -> workbench.action.toggleSidebarVisibility + {{true, false, true, 'E'}, ActionKind::NppCommand, IDM_VIEW_SWITCHTO_FILEBROWSER, 120}, // Ctrl+Shift+E -> workbench.view.explorer + {{true, false, true, 'F'}, ActionKind::NppCommand, IDM_SEARCH_FINDINFILES, 121}, // Ctrl+Shift+F -> workbench.view.search + {{true, false, true, 'G'}, ActionKind::ReservedNoOp, 0, 122}, // Ctrl+Shift+G -> workbench.view.scm + {{true, false, true, 'D'}, ActionKind::ReservedNoOp, 0, 123}, // Ctrl+Shift+D -> workbench.view.debug + {{true, false, true, 'X'}, ActionKind::ReservedNoOp, 0, 124}, // Ctrl+Shift+X -> workbench.view.extensions + {{true, false, true, 'U'}, ActionKind::ReservedNoOp, 0, 125}, // Ctrl+Shift+U -> workbench.action.output.toggleOutput + {{true, false, false, 'Q'}, ActionKind::ReservedNoOp, 0, 126}, // Ctrl+Q -> workbench.action.quickOpenView + {{true, false, true, 'C'}, ActionKind::NppCommand, IDM_FILE_OPEN_CMD, 127}, // Ctrl+Shift+C -> workbench.action.terminal.openNativeConsole + {{true, false, true, 'V'}, ActionKind::ReservedNoOp, 0, 128}, // Ctrl+Shift+V -> markdown.showPreview + {{true, false, false, VK_OEM_3}, ActionKind::NppCommand, IDM_FILE_OPEN_CMD, 130}, // Ctrl+` -> workbench.action.terminal.toggleTerminal + {{true, false, true, 'H'}, ActionKind::NppCommand, IDM_SEARCH_FINDINFILES, 131}, // Ctrl+Shift+H -> workbench.action.replaceInFiles + {{true, false, true, 'J'}, ActionKind::ReservedNoOp, 0, 135}, // Ctrl+Shift+J -> workbench.action.search.toggleQueryDetails + {{true, false, false, VK_OEM_COMMA}, ActionKind::NppCommand, IDM_SETTING_PREFERENCE, 144}, // Ctrl+, -> workbench.action.openSettings + {{false, false, false, VK_F9}, ActionKind::ReservedNoOp, 0, 156}, // F9 -> editor.debug.action.toggleBreakpoint + {{false, false, false, VK_F5}, ActionKind::ReservedNoOp, 0, 157}, // F5 -> workbench.action.debug.start }; const std::vector kChordBindings = { - {{true, false, false, 'K'}, {true, false, false, 'D'}, ActionKind::NppCommand, IDM_EDIT_MULTISELECTSSKIP}, // Ctrl+K Ctrl+D -> editor.action.moveSelectionToNextFindMatch - {{true, false, false, 'K'}, {true, false, false, 'L'}, ActionKind::ToggleFoldAtCaret, 0}, // Ctrl+K Ctrl+L -> editor.toggleFold - {{true, false, false, 'K'}, {true, false, false, VK_OEM_4}, ActionKind::ReservedNoOp, 0}, // Ctrl+K Ctrl+[ -> editor.foldRecursively - {{true, false, false, 'K'}, {true, false, false, VK_OEM_6}, ActionKind::ReservedNoOp, 0}, // Ctrl+K Ctrl+] -> editor.unfoldRecursively - {{true, false, false, 'K'}, {true, false, false, '0'}, ActionKind::NppCommand, IDM_VIEW_FOLDALL}, // Ctrl+K Ctrl+0 -> editor.foldAll - {{true, false, false, 'K'}, {true, false, false, 'J'}, ActionKind::NppCommand, IDM_VIEW_UNFOLDALL}, // Ctrl+K Ctrl+J -> editor.unfoldAll - {{true, false, false, 'K'}, {true, false, false, 'C'}, ActionKind::NppCommand, IDM_EDIT_STREAM_COMMENT}, // Ctrl+K Ctrl+C -> editor.action.addCommentLine - {{true, false, false, 'K'}, {true, false, false, 'U'}, ActionKind::NppCommand, IDM_EDIT_STREAM_UNCOMMENT}, // Ctrl+K Ctrl+U -> editor.action.removeCommentLine - {{true, false, false, 'K'}, {true, false, false, 'F'}, ActionKind::ReservedNoOp, 0}, // Ctrl+K Ctrl+F -> editor.action.formatSelection - {{true, false, false, 'K'}, {true, false, false, 'I'}, ActionKind::ReservedNoOp, 0}, // Ctrl+K Ctrl+I -> editor.action.showHover - {{true, false, false, 'K'}, {false, false, false, VK_F12}, ActionKind::ReservedNoOp, 0}, // Ctrl+K F12 -> editor.action.revealDefinitionAside - {{true, false, false, 'K'}, {true, false, false, 'X'}, ActionKind::NppCommand, IDM_EDIT_TRIMTRAILING}, // Ctrl+K Ctrl+X -> editor.action.trimTrailingWhitespace - {{true, false, false, 'K'}, {false, false, false, 'M'}, ActionKind::ReservedNoOp, 0}, // Ctrl+K M -> workbench.action.editor.changeLanguageMode - {{true, false, false, 'K'}, {false, false, false, 'F'}, ActionKind::ReservedNoOp, 0}, // Ctrl+K F -> workbench.action.closeFolder - {{true, false, false, 'K'}, {true, false, false, VK_LEFT}, ActionKind::ReservedNoOp, 0}, // Ctrl+K Ctrl+Left -> workbench.action.focusLeftGroup - {{true, false, false, 'K'}, {true, false, false, VK_RIGHT}, ActionKind::ReservedNoOp, 0}, // Ctrl+K Ctrl+Right -> workbench.action.focusRightGroup - {{true, false, false, 'K'}, {false, false, false, VK_LEFT}, ActionKind::ReservedNoOp, 0}, // Ctrl+K Left -> workbench.action.moveActiveEditorGroupLeft - {{true, false, false, 'K'}, {false, false, false, VK_RIGHT}, ActionKind::ReservedNoOp, 0}, // Ctrl+K Right -> workbench.action.moveActiveEditorGroupRight - {{true, false, false, 'K'}, {false, false, false, 'S'}, ActionKind::NppCommand, IDM_FILE_SAVEALL}, // Ctrl+K S -> saveAll - {{true, false, false, 'K'}, {false, false, false, 'W'}, ActionKind::ReservedNoOp, 0}, // Ctrl+K W -> workbench.action.closeEditorsInGroup - {{true, false, false, 'K'}, {true, false, false, 'W'}, ActionKind::NppCommand, IDM_FILE_CLOSEALL}, // Ctrl+K Ctrl+W -> workbench.action.closeAllEditors - {{true, false, false, 'K'}, {false, false, false, VK_RETURN}, ActionKind::ReservedNoOp, 0}, // Ctrl+K Enter -> workbench.action.keepEditor - {{true, false, false, 'K'}, {false, false, false, 'P'}, ActionKind::NppCommand, IDM_EDIT_FULLPATHTOCLIP}, // Ctrl+K P -> workbench.action.files.copyPathOfActiveFile - {{true, false, false, 'K'}, {false, false, false, 'R'}, ActionKind::NppCommand, IDM_EDIT_OPENINFOLDER}, // Ctrl+K R -> workbench.action.files.revealActiveFileInWindows - {{true, false, false, 'K'}, {false, false, false, 'Z'}, ActionKind::NppCommand, IDM_VIEW_DISTRACTIONFREE}, // Ctrl+K Z -> workbench.action.toggleZenMode - {{true, false, false, 'K'}, {false, false, false, 'V'}, ActionKind::ReservedNoOp, 0}, // Ctrl+K V -> markdown.showPreviewToSide - {{true, false, false, 'K'}, {true, false, false, 'S'}, ActionKind::NppCommand, IDM_SETTING_SHORTCUT_MAPPER}, // Ctrl+K Ctrl+S -> workbench.action.openGlobalKeybindings - {{true, false, false, 'K'}, {true, false, false, 'T'}, ActionKind::ReservedNoOp, 0}, // Ctrl+K Ctrl+T -> workbench.action.selectTheme + {{true, false, false, 'K'}, {true, false, false, 'D'}, ActionKind::NppCommand, IDM_EDIT_MULTISELECTSSKIP, 13}, // Ctrl+K Ctrl+D -> editor.action.moveSelectionToNextFindMatch + {{true, false, false, 'K'}, {true, false, false, 'L'}, ActionKind::ToggleFoldAtCaret, 0, 34}, // Ctrl+K Ctrl+L -> editor.toggleFold + {{true, false, false, 'K'}, {true, false, false, VK_OEM_4}, ActionKind::ReservedNoOp, 0, 35}, // Ctrl+K Ctrl+[ -> editor.foldRecursively + {{true, false, false, 'K'}, {true, false, false, VK_OEM_6}, ActionKind::ReservedNoOp, 0, 36}, // Ctrl+K Ctrl+] -> editor.unfoldRecursively + {{true, false, false, 'K'}, {true, false, false, '0'}, ActionKind::NppCommand, IDM_VIEW_FOLDALL, 37}, // Ctrl+K Ctrl+0 -> editor.foldAll + {{true, false, false, 'K'}, {true, false, false, 'J'}, ActionKind::NppCommand, IDM_VIEW_UNFOLDALL, 38}, // Ctrl+K Ctrl+J -> editor.unfoldAll + {{true, false, false, 'K'}, {true, false, false, 'C'}, ActionKind::NppCommand, IDM_EDIT_STREAM_COMMENT, 39}, // Ctrl+K Ctrl+C -> editor.action.addCommentLine + {{true, false, false, 'K'}, {true, false, false, 'U'}, ActionKind::NppCommand, IDM_EDIT_STREAM_UNCOMMENT, 40}, // Ctrl+K Ctrl+U -> editor.action.removeCommentLine + {{true, false, false, 'K'}, {true, false, false, 'F'}, ActionKind::ReservedNoOp, 0, 58}, // Ctrl+K Ctrl+F -> editor.action.formatSelection + {{true, false, false, 'K'}, {true, false, false, 'I'}, ActionKind::ReservedNoOp, 0, 60}, // Ctrl+K Ctrl+I -> editor.action.showHover + {{true, false, false, 'K'}, {false, false, false, VK_F12}, ActionKind::ReservedNoOp, 0, 62}, // Ctrl+K F12 -> editor.action.revealDefinitionAside + {{true, false, false, 'K'}, {true, false, false, 'X'}, ActionKind::NppCommand, IDM_EDIT_TRIMTRAILING, 70}, // Ctrl+K Ctrl+X -> editor.action.trimTrailingWhitespace + {{true, false, false, 'K'}, {false, false, false, 'M'}, ActionKind::ReservedNoOp, 0, 71}, // Ctrl+K M -> workbench.action.editor.changeLanguageMode + {{true, false, false, 'K'}, {false, false, false, 'F'}, ActionKind::ReservedNoOp, 0, 89}, // Ctrl+K F -> workbench.action.closeFolder + {{true, false, false, 'K'}, {true, false, false, VK_LEFT}, ActionKind::ReservedNoOp, 0, 94}, // Ctrl+K Ctrl+Left -> workbench.action.focusLeftGroup + {{true, false, false, 'K'}, {true, false, false, VK_RIGHT}, ActionKind::ReservedNoOp, 0, 95}, // Ctrl+K Ctrl+Right -> workbench.action.focusRightGroup + {{true, false, false, 'K'}, {false, false, false, VK_LEFT}, ActionKind::ReservedNoOp, 0, 98}, // Ctrl+K Left -> workbench.action.moveActiveEditorGroupLeft + {{true, false, false, 'K'}, {false, false, false, VK_RIGHT}, ActionKind::ReservedNoOp, 0, 99}, // Ctrl+K Right -> workbench.action.moveActiveEditorGroupRight + {{true, false, false, 'K'}, {false, false, false, 'S'}, ActionKind::NppCommand, IDM_FILE_SAVEALL, 105}, // Ctrl+K S -> saveAll + {{true, false, false, 'K'}, {false, false, false, 'W'}, ActionKind::ReservedNoOp, 0, 107}, // Ctrl+K W -> workbench.action.closeEditorsInGroup + {{true, false, false, 'K'}, {true, false, false, 'W'}, ActionKind::NppCommand, IDM_FILE_CLOSEALL, 108}, // Ctrl+K Ctrl+W -> workbench.action.closeAllEditors + {{true, false, false, 'K'}, {false, false, false, VK_RETURN}, ActionKind::ReservedNoOp, 0, 110}, // Ctrl+K Enter -> workbench.action.keepEditor + {{true, false, false, 'K'}, {false, false, false, 'P'}, ActionKind::NppCommand, IDM_EDIT_FULLPATHTOCLIP, 111}, // Ctrl+K P -> workbench.action.files.copyPathOfActiveFile + {{true, false, false, 'K'}, {false, false, false, 'R'}, ActionKind::NppCommand, IDM_EDIT_OPENINFOLDER, 112}, // Ctrl+K R -> workbench.action.files.revealActiveFileInWindows + {{true, false, false, 'K'}, {false, false, false, 'Z'}, ActionKind::NppCommand, IDM_VIEW_DISTRACTIONFREE, 114}, // Ctrl+K Z -> workbench.action.toggleZenMode + {{true, false, false, 'K'}, {false, false, false, 'V'}, ActionKind::ReservedNoOp, 0, 129}, // Ctrl+K V -> markdown.showPreviewToSide + {{true, false, false, 'K'}, {true, false, false, 'S'}, ActionKind::NppCommand, IDM_SETTING_SHORTCUT_MAPPER, 145}, // Ctrl+K Ctrl+S -> workbench.action.openGlobalKeybindings + {{true, false, false, 'K'}, {true, false, false, 'T'}, ActionKind::ReservedNoOp, 0, 146}, // Ctrl+K Ctrl+T -> workbench.action.selectTheme }; diff --git a/src/VSCodeKeymapNpp.rc.in b/src/VSCodeKeymapNpp.rc.in index 988f3a0..38d3514 100644 --- a/src/VSCodeKeymapNpp.rc.in +++ b/src/VSCodeKeymapNpp.rc.in @@ -1,5 +1,11 @@ #include +#define IDD_CONFIG_REFERENCE 101 +#define IDC_KEYMAP_ENABLED 1001 +#define IDC_REFERENCE_FILTER 1002 +#define IDC_BINDINGS_LIST 1003 +#define IDC_REFERENCE_SUMMARY 1004 + VS_VERSION_INFO VERSIONINFO FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 @@ -27,3 +33,22 @@ BEGIN VALUE "Translation", 0x0409, 1200 END END + +IDD_CONFIG_REFERENCE DIALOGEX 0, 0, 760, 420 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "VSCode Keymap NPP - Config / Reference" +FONT 9, "Segoe UI" +BEGIN + GROUPBOX "Plugin Mode", IDC_STATIC, 8, 4, 250, 22 + CONTROL "Enable VSCode key handling (unchecked = pass through)", IDC_KEYMAP_ENABLED, "Button", + BS_AUTOCHECKBOX | WS_TABSTOP, 18, 13, 225, 10 + GROUPBOX "Binding Search", IDC_STATIC, 8, 28, 280, 26 + LTEXT "Filter shortcuts", IDC_STATIC, 18, 40, 58, 8 + EDITTEXT IDC_REFERENCE_FILTER, 82, 37, 196, 14, + ES_AUTOHSCROLL | WS_TABSTOP + GROUPBOX "Status", IDC_STATIC, 296, 4, 456, 50 + LTEXT "", IDC_REFERENCE_SUMMARY, 306, 17, 436, 28 + CONTROL "", IDC_BINDINGS_LIST, "SysListView32", + LVS_REPORT | LVS_SHOWSELALWAYS | WS_BORDER | WS_TABSTOP, 10, 60, 740, 340 + DEFPUSHBUTTON "Close", IDOK, 695, 402, 55, 14 +END diff --git a/src/VSCodeKeymapPlugin.cpp b/src/VSCodeKeymapPlugin.cpp index 312ba88..d9b5119 100644 --- a/src/VSCodeKeymapPlugin.cpp +++ b/src/VSCodeKeymapPlugin.cpp @@ -1,511 +1,693 @@ -#include - -#include -#include -#include -#include - -#include "PluginInterface.h" -#include "menuCmdID.h" - -namespace { -constexpr wchar_t kPluginName[] = L"VSCode Keymap NPP"; -constexpr DWORD kChordTimeoutMs = 1500; - -NppData g_nppData{}; -FuncItem g_funcItems[2]{}; -bool g_enabled = true; - -WNDPROC g_mainOldProc = nullptr; -WNDPROC g_scintillaOldProc[2] = {nullptr, nullptr}; -HHOOK g_messageHook = nullptr; -DWORD g_nppThreadId = 0; - -struct KeyStroke { - bool ctrl; - bool alt; - bool shift; - UINT vk; -}; - -bool operator==(const KeyStroke& lhs, const KeyStroke& rhs) { - return lhs.ctrl == rhs.ctrl && lhs.alt == rhs.alt && lhs.shift == rhs.shift && lhs.vk == rhs.vk; -} - -enum class ActionKind { - NppCommand, - SciCommand, - DuplicateLineUp, - CutAllowLine, - CopyAllowLine, - ToggleFoldAtCaret, - ToggleStreamComment, - SelectCurrentLine, - InsertLineBelow, - InsertLineAbove, - ScrollPageUpNoCaret, - ScrollPageDownNoCaret, - ReservedNoOp, -}; - -struct ShortcutBinding { - KeyStroke key; - ActionKind kind; - int id; -}; - -struct ChordBinding { - KeyStroke first; - KeyStroke second; - ActionKind kind; - int id; -}; - -struct PendingChord { - KeyStroke first; - DWORD startedAt; -}; - -std::optional g_pendingChord; - -HWND ActiveScintilla() { - int which = 0; - ::SendMessage(g_nppData._nppHandle, NPPM_GETCURRENTSCINTILLA, 0, reinterpret_cast(&which)); - return (which == 0) ? g_nppData._scintillaMainHandle : g_nppData._scintillaSecondHandle; -} - -int ScintillaIndex(HWND hWnd) { - if (hWnd == g_nppData._scintillaMainHandle) { - return 0; - } - if (hWnd == g_nppData._scintillaSecondHandle) { - return 1; - } - return -1; -} - -bool IsPressed(int vkey) { - return (::GetKeyState(vkey) & 0x8000) != 0; -} - -void RunNppCommand(int commandId) { - ::SendMessage(g_nppData._nppHandle, NPPM_MENUCOMMAND, 0, static_cast(commandId)); -} - -sptr_t RunSci(HWND editor, UINT message, uptr_t wParam = 0, sptr_t lParam = 0) { - return static_cast( - ::SendMessage(editor, message, static_cast(wParam), static_cast(lParam))); -} - -KeyStroke CurrentKeyStroke(UINT vk) { - return KeyStroke{IsPressed(VK_CONTROL), IsPressed(VK_MENU), IsPressed(VK_SHIFT), vk}; -} - -std::string GetLineText(HWND editor, sptr_t line) { - const sptr_t lengthWithEol = RunSci(editor, SCI_GETLINE, static_cast(line), 0); - if (lengthWithEol <= 0) { - return {}; - } - - std::string buffer(static_cast(lengthWithEol), '\0'); - RunSci(editor, SCI_GETLINE, static_cast(line), reinterpret_cast(buffer.data())); - while (!buffer.empty() && (buffer.back() == '\0' || buffer.back() == '\r' || buffer.back() == '\n')) { - buffer.pop_back(); - } - return buffer; -} - -std::string_view TrimLeftAscii(std::string_view text) { - size_t i = 0; - while (i < text.size()) { - const char ch = text[i]; - if (ch != ' ' && ch != '\t') { - break; - } - ++i; - } - return text.substr(i); -} - -bool LooksLineCommented(std::string_view trimmed) { - static constexpr const char* kPrefixes[] = {"//", "#", "--", ";", "'", "REM ", "::"}; - for (const char* prefix : kPrefixes) { - const size_t prefixLen = std::char_traits::length(prefix); - if (trimmed.size() >= prefixLen && trimmed.substr(0, prefixLen) == prefix) { - return true; - } - } - return false; -} - -bool ShouldUncommentSelection(HWND editor) { - const sptr_t selectionStart = RunSci(editor, SCI_GETSELECTIONSTART); - const sptr_t selectionEnd = RunSci(editor, SCI_GETSELECTIONEND); - - sptr_t startLine = RunSci(editor, SCI_LINEFROMPOSITION, static_cast(selectionStart)); - sptr_t endLine = RunSci(editor, SCI_LINEFROMPOSITION, static_cast(selectionEnd)); - if (selectionEnd > selectionStart) { - const sptr_t endLineStart = RunSci(editor, SCI_POSITIONFROMLINE, static_cast(endLine)); - if (selectionEnd == endLineStart && endLine > startLine) { - --endLine; - } - } - - bool sawNonEmptyLine = false; - for (sptr_t line = startLine; line <= endLine; ++line) { - const std::string text = GetLineText(editor, line); - const std::string_view trimmed = TrimLeftAscii(text); - if (trimmed.empty()) { - continue; - } - sawNonEmptyLine = true; - if (!LooksLineCommented(trimmed)) { - return false; - } - } - - return sawNonEmptyLine; -} - -void ConfigureEditor(HWND editor) { - if (editor == nullptr) { - return; - } - - RunSci(editor, SCI_SETMULTIPLESELECTION, 1); - RunSci(editor, SCI_SETADDITIONALSELECTIONTYPING, 1); - RunSci(editor, SCI_SETADDITIONALCARETSVISIBLE, 1); - RunSci(editor, SCI_SETRECTANGULARSELECTIONMODIFIER, SCMOD_ALT); -} - -void ConfigureEditors() { - ConfigureEditor(g_nppData._scintillaMainHandle); - ConfigureEditor(g_nppData._scintillaSecondHandle); -} - -#include "GeneratedBindings.inc" - -bool ExecuteAction(ActionKind kind, int id, HWND editor) { - switch (kind) { - case ActionKind::NppCommand: - RunNppCommand(id); - return true; - - case ActionKind::SciCommand: - RunSci(editor, static_cast(id)); - return true; - - case ActionKind::DuplicateLineUp: - RunNppCommand(IDM_EDIT_DUP_LINE); - RunNppCommand(IDM_EDIT_LINE_UP); - return true; - - case ActionKind::CutAllowLine: { - const bool selectionEmpty = RunSci(editor, SCI_GETSELECTIONEMPTY) != 0; - RunSci(editor, selectionEmpty ? SCI_CUTALLOWLINE : SCI_CUT); - return true; - } - - case ActionKind::CopyAllowLine: { - const bool selectionEmpty = RunSci(editor, SCI_GETSELECTIONEMPTY) != 0; - RunSci(editor, selectionEmpty ? SCI_COPYALLOWLINE : SCI_COPY); - return true; - } - - case ActionKind::ToggleFoldAtCaret: { - sptr_t line = RunSci(editor, SCI_LINEFROMPOSITION, static_cast(RunSci(editor, SCI_GETCURRENTPOS))); - sptr_t level = RunSci(editor, SCI_GETFOLDLEVEL, static_cast(line)); - if ((level & SC_FOLDLEVELHEADERFLAG) == 0) { - const sptr_t parent = RunSci(editor, SCI_GETFOLDPARENT, static_cast(line)); - if (parent >= 0) { - line = parent; - } - } - RunSci(editor, SCI_TOGGLEFOLD, static_cast(line)); - return true; - } - - case ActionKind::ToggleStreamComment: - RunNppCommand(ShouldUncommentSelection(editor) ? IDM_EDIT_STREAM_UNCOMMENT : IDM_EDIT_STREAM_COMMENT); - return true; - - case ActionKind::SelectCurrentLine: { - const sptr_t currentPos = RunSci(editor, SCI_GETCURRENTPOS); - const sptr_t line = RunSci(editor, SCI_LINEFROMPOSITION, static_cast(currentPos)); - const sptr_t lineStart = RunSci(editor, SCI_POSITIONFROMLINE, static_cast(line)); - const sptr_t lineEnd = RunSci(editor, SCI_GETLINEENDPOSITION, static_cast(line)); - RunSci(editor, SCI_SETSEL, static_cast(lineStart), lineEnd); - return true; - } - - case ActionKind::InsertLineBelow: { - const sptr_t currentPos = RunSci(editor, SCI_GETCURRENTPOS); - const sptr_t line = RunSci(editor, SCI_LINEFROMPOSITION, static_cast(currentPos)); - const sptr_t lineEnd = RunSci(editor, SCI_GETLINEENDPOSITION, static_cast(line)); - RunSci(editor, SCI_SETSEL, static_cast(lineEnd), lineEnd); - RunSci(editor, SCI_NEWLINE); - return true; - } - - case ActionKind::InsertLineAbove: { - const sptr_t currentPos = RunSci(editor, SCI_GETCURRENTPOS); - const sptr_t line = RunSci(editor, SCI_LINEFROMPOSITION, static_cast(currentPos)); - const sptr_t lineStart = RunSci(editor, SCI_POSITIONFROMLINE, static_cast(line)); - RunSci(editor, SCI_SETSEL, static_cast(lineStart), lineStart); - RunSci(editor, SCI_NEWLINE); - RunSci(editor, SCI_LINEUP); - RunSci(editor, SCI_HOME); - return true; - } - - case ActionKind::ScrollPageUpNoCaret: { - sptr_t lines = RunSci(editor, SCI_LINESONSCREEN); - if (lines < 1) { - lines = 30; - } - RunSci(editor, SCI_LINESCROLL, 0, -lines); - return true; - } - - case ActionKind::ScrollPageDownNoCaret: { - sptr_t lines = RunSci(editor, SCI_LINESONSCREEN); - if (lines < 1) { - lines = 30; - } - RunSci(editor, SCI_LINESCROLL, 0, lines); - return true; - } - - case ActionKind::ReservedNoOp: - return true; - } - - return false; -} - -bool TryHandleChord(const KeyStroke& currentKey, HWND editor) { - if (g_pendingChord.has_value()) { - const DWORD age = ::GetTickCount() - g_pendingChord->startedAt; - if (age <= kChordTimeoutMs) { - for (const auto& chord : kChordBindings) { - if (chord.first == g_pendingChord->first && chord.second == currentKey) { - g_pendingChord.reset(); - return ExecuteAction(chord.kind, chord.id, editor); - } - } - - // Strict mode: consume unknown second key after a valid chord prefix. - g_pendingChord.reset(); - return true; - } - - g_pendingChord.reset(); - } - - for (const auto& chord : kChordBindings) { - if (chord.first == currentKey) { - g_pendingChord = PendingChord{currentKey, ::GetTickCount()}; - return true; - } - } - - return false; -} - -bool TryHandleShortcut(HWND editor, WPARAM wParam) { - if (!g_enabled || editor == nullptr) { - return false; - } - - const UINT vk = static_cast(wParam); - if (vk == VK_CONTROL || vk == VK_MENU || vk == VK_SHIFT) { - return false; - } - - const KeyStroke currentKey = CurrentKeyStroke(vk); - - if (TryHandleChord(currentKey, editor)) { - return true; - } - - for (const auto& binding : kShortcutBindings) { - if (binding.key == currentKey) { - return ExecuteAction(binding.kind, binding.id, editor); - } - } - - return false; -} - -LRESULT CALLBACK MessageHookProc(int code, WPARAM wParam, LPARAM lParam) { - if (code >= 0 && wParam == PM_REMOVE) { - MSG* msg = reinterpret_cast(lParam); - if (msg != nullptr && (msg->message == WM_KEYDOWN || msg->message == WM_SYSKEYDOWN)) { - if (TryHandleShortcut(ActiveScintilla(), msg->wParam)) { - msg->message = WM_NULL; - msg->wParam = 0; - msg->lParam = 0; - return 1; - } - } - } - - return ::CallNextHookEx(g_messageHook, code, wParam, lParam); -} - -LRESULT CALLBACK ScintillaProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { - if ((msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) && TryHandleShortcut(hWnd, wParam)) { - return 0; - } - - const int idx = ScintillaIndex(hWnd); - if (idx >= 0 && g_scintillaOldProc[idx] != nullptr) { - return ::CallWindowProc(g_scintillaOldProc[idx], hWnd, msg, wParam, lParam); - } - - return ::DefWindowProc(hWnd, msg, wParam, lParam); -} - -LRESULT CALLBACK MainProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { - if ((msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) && TryHandleShortcut(ActiveScintilla(), wParam)) { - return 0; - } - - if (g_mainOldProc != nullptr) { - return ::CallWindowProc(g_mainOldProc, hWnd, msg, wParam, lParam); - } - - return ::DefWindowProc(hWnd, msg, wParam, lParam); -} - -void HookWindows() { - if (g_nppData._nppHandle != nullptr && g_mainOldProc == nullptr) { - g_mainOldProc = reinterpret_cast( - ::SetWindowLongPtr(g_nppData._nppHandle, GWLP_WNDPROC, reinterpret_cast(MainProc))); - } - - if (g_nppData._scintillaMainHandle != nullptr && g_scintillaOldProc[0] == nullptr) { - g_scintillaOldProc[0] = reinterpret_cast(::SetWindowLongPtr( - g_nppData._scintillaMainHandle, GWLP_WNDPROC, reinterpret_cast(ScintillaProc))); - } - - if (g_nppData._scintillaSecondHandle != nullptr && g_scintillaOldProc[1] == nullptr) { - g_scintillaOldProc[1] = reinterpret_cast(::SetWindowLongPtr( - g_nppData._scintillaSecondHandle, GWLP_WNDPROC, reinterpret_cast(ScintillaProc))); - } -} - -void UnhookWindows() { - if (g_nppData._nppHandle != nullptr && g_mainOldProc != nullptr) { - ::SetWindowLongPtr(g_nppData._nppHandle, GWLP_WNDPROC, reinterpret_cast(g_mainOldProc)); - g_mainOldProc = nullptr; - } - - if (g_nppData._scintillaMainHandle != nullptr && g_scintillaOldProc[0] != nullptr) { - ::SetWindowLongPtr(g_nppData._scintillaMainHandle, GWLP_WNDPROC, - reinterpret_cast(g_scintillaOldProc[0])); - g_scintillaOldProc[0] = nullptr; - } - - if (g_nppData._scintillaSecondHandle != nullptr && g_scintillaOldProc[1] != nullptr) { - ::SetWindowLongPtr(g_nppData._scintillaSecondHandle, GWLP_WNDPROC, - reinterpret_cast(g_scintillaOldProc[1])); - g_scintillaOldProc[1] = nullptr; - } -} - -void InstallMessageHook() { - if (g_messageHook != nullptr || g_nppData._nppHandle == nullptr) { - return; - } - - g_nppThreadId = ::GetWindowThreadProcessId(g_nppData._nppHandle, nullptr); - if (g_nppThreadId == 0) { - return; - } - - g_messageHook = ::SetWindowsHookEx(WH_GETMESSAGE, MessageHookProc, nullptr, g_nppThreadId); -} - -void RemoveMessageHook() { - if (g_messageHook != nullptr) { - ::UnhookWindowsHookEx(g_messageHook); - g_messageHook = nullptr; - } - - g_nppThreadId = 0; - g_pendingChord.reset(); -} - -void ToggleKeymap() { - g_enabled = !g_enabled; - g_pendingChord.reset(); - - const wchar_t* status = g_enabled ? L"enabled" : L"disabled"; - std::wstring message = L"VSCode strict keymap is now "; - message += status; - message += L"."; - ::MessageBox(g_nppData._nppHandle, message.c_str(), kPluginName, MB_OK | MB_ICONINFORMATION); -} - -void ShowBindingsSummary() { - std::wstring summary = L"VSCode Keymap NPP strict mode\n\n"; - summary += L"Source bindings: "; - summary += std::to_wstring(kVsCodeSourceBindingCount); - summary += L"\nMapped: "; - summary += std::to_wstring(kMappedBindingCount); - summary += L"\nReserved no-op: "; - summary += std::to_wstring(kReservedNoOpBindingCount); - summary += L"\nDocumented unported: "; - summary += std::to_wstring(kDocumentedUnportedBindingCount); - summary += L"\n\nCoverage report installs under:\n"; - summary += L"plugins\\doc\\VSCodeKeymapNpp\\VSCodeKeybindingCoverage.MD"; - ::MessageBox(g_nppData._nppHandle, summary.c_str(), kPluginName, MB_OK | MB_ICONINFORMATION); -} - -void RegisterPluginCommands() { - std::wmemset(g_funcItems[0]._itemName, 0, menuItemSize); - std::wmemset(g_funcItems[1]._itemName, 0, menuItemSize); - - ::wcsncpy_s(g_funcItems[0]._itemName, menuItemSize, L"Toggle VSCode Strict Keymap", _TRUNCATE); - g_funcItems[0]._pFunc = ToggleKeymap; - - ::wcsncpy_s(g_funcItems[1]._itemName, menuItemSize, L"Show VSCode Coverage Summary", _TRUNCATE); - g_funcItems[1]._pFunc = ShowBindingsSummary; -} - -} // namespace - -extern "C" __declspec(dllexport) void setInfo(NppData notepadPlusData) { - g_nppData = notepadPlusData; - RegisterPluginCommands(); - HookWindows(); - ConfigureEditors(); - InstallMessageHook(); -} - -extern "C" __declspec(dllexport) const wchar_t* getName() { - return kPluginName; -} - -extern "C" __declspec(dllexport) FuncItem* getFuncsArray(int* nbF) { - *nbF = 2; - return g_funcItems; -} - -extern "C" __declspec(dllexport) void beNotified(SCNotification*) { - // Not used for this implementation. -} - -extern "C" __declspec(dllexport) LRESULT messageProc(UINT, WPARAM, LPARAM) { - return TRUE; -} - -extern "C" __declspec(dllexport) BOOL isUnicode() { - return TRUE; -} - -extern "C" __declspec(dllexport) void cleanUp() { - RemoveMessageHook(); - UnhookWindows(); +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Notepad_plus_msgs.h" +#include "PluginInterface.h" +#include "handlers/ActionHandlers.h" + +namespace { +constexpr wchar_t kPluginName[] = L"VSCode Keymap NPP"; +constexpr DWORD kChordTimeoutMs = 1500; +constexpr int kConfigDialogId = 101; +constexpr int kEnableKeymapCheckboxId = 1001; +constexpr int kReferenceFilterEditId = 1002; +constexpr int kBindingsListId = 1003; +constexpr int kReferenceSummaryId = 1004; +constexpr wchar_t kSettingsSectionGeneral[] = L"General"; +constexpr wchar_t kSettingsSectionBindings[] = L"Bindings"; +constexpr wchar_t kSettingsKeyEnabled[] = L"KeymapEnabled"; +constexpr wchar_t kLegacySettingsKeyPluginDisabled[] = L"PluginDisabled"; +constexpr wchar_t kSettingsKeyDisabledBindings[] = L"DisabledBindings"; + +NppData g_nppData{}; +FuncItem g_funcItems[2]{}; +bool g_enabled = true; +bool g_syncingBindingList = false; +std::vector g_bindingEnabled; + +WNDPROC g_mainOldProc = nullptr; +WNDPROC g_scintillaOldProc[2] = {nullptr, nullptr}; +HHOOK g_messageHook = nullptr; +DWORD g_nppThreadId = 0; + +struct PendingChord { + KeyStroke first; + DWORD startedAt; +}; + +std::optional g_pendingChord; + +#include "GeneratedBindings.inc" + +extern "C" IMAGE_DOS_HEADER __ImageBase; + +HINSTANCE ModuleInstance() { + return reinterpret_cast(&__ImageBase); +} + +void ResetBindingStatesToDefaults() { + g_bindingEnabled.assign(kBindingReferences.size(), false); + for (size_t index = 0; index < kBindingReferences.size(); ++index) { + g_bindingEnabled[index] = kBindingReferences[index].pluginHandled; + } +} + +std::filesystem::path SettingsFilePath() { + if (g_nppData._nppHandle == nullptr) { + return {}; + } + + std::vector buffer(MAX_PATH, L'\0'); + const auto copied = static_cast(::SendMessage(g_nppData._nppHandle, + NPPM_GETPLUGINSCONFIGDIR, + static_cast(buffer.size()), + reinterpret_cast(buffer.data()))); + if (copied == 0 || buffer[0] == L'\0') { + return {}; + } + + return std::filesystem::path(buffer.data()) / L"VSCodeKeymapNpp.ini"; +} + +std::wstring SettingsFilePathText() { + const auto path = SettingsFilePath(); + if (path.empty()) { + return L"(Notepad++ plugin config directory unavailable)"; + } + return path.wstring(); +} + +std::wstring GetWindowTextValue(HWND control) { + const int length = ::GetWindowTextLengthW(control); + if (length <= 0) { + return {}; + } + + std::wstring text(static_cast(length) + 1, L'\0'); + ::GetWindowTextW(control, text.data(), length + 1); + text.resize(static_cast(length)); + return text; +} + +std::wstring BuildDisabledBindingList() { + std::wstring value; + for (size_t index = 0; index < kBindingReferences.size(); ++index) { + if (!kBindingReferences[index].pluginHandled || g_bindingEnabled[index]) { + continue; + } + if (!value.empty()) { + value += L","; + } + value += std::to_wstring(index); + } + return value; +} + +void ApplyDisabledBindingList(const std::wstring& value) { + std::wstringstream stream(value); + std::wstring token; + while (std::getline(stream, token, L',')) { + if (token.empty()) { + continue; + } + + wchar_t* parsedEnd = nullptr; + const unsigned long index = std::wcstoul(token.c_str(), &parsedEnd, 10); + if (parsedEnd == token.c_str() || *parsedEnd != L'\0' || index >= kBindingReferences.size()) { + continue; + } + if (kBindingReferences[index].pluginHandled) { + g_bindingEnabled[index] = false; + } + } +} + +void SaveSettings() { + const auto settingsPath = SettingsFilePath(); + if (settingsPath.empty()) { + return; + } + + std::filesystem::create_directories(settingsPath.parent_path()); + ::WritePrivateProfileStringW( + kSettingsSectionGeneral, kSettingsKeyEnabled, g_enabled ? L"1" : L"0", settingsPath.c_str()); + ::WritePrivateProfileStringW(kSettingsSectionGeneral, kLegacySettingsKeyPluginDisabled, nullptr, settingsPath.c_str()); + + const auto disabledBindings = BuildDisabledBindingList(); + ::WritePrivateProfileStringW(kSettingsSectionBindings, + kSettingsKeyDisabledBindings, + disabledBindings.empty() ? L"" : disabledBindings.c_str(), + settingsPath.c_str()); +} + +void LoadSettings() { + ResetBindingStatesToDefaults(); + + const auto settingsPath = SettingsFilePath(); + if (settingsPath.empty()) { + return; + } + + g_enabled = ::GetPrivateProfileIntW(kSettingsSectionGeneral, kSettingsKeyEnabled, 1, settingsPath.c_str()) != 0; + const bool legacyPluginDisabled = + ::GetPrivateProfileIntW(kSettingsSectionGeneral, kLegacySettingsKeyPluginDisabled, 0, settingsPath.c_str()) != + 0; + g_enabled = g_enabled && !legacyPluginDisabled; + + std::wstring disabledBindings(4096, L'\0'); + const auto length = ::GetPrivateProfileStringW(kSettingsSectionBindings, + kSettingsKeyDisabledBindings, + L"", + disabledBindings.data(), + static_cast(disabledBindings.size()), + settingsPath.c_str()); + disabledBindings.resize(length); + ApplyDisabledBindingList(disabledBindings); +} + +bool IsBindingConfigurable(size_t referenceIndex) { + return referenceIndex < kBindingReferences.size() && kBindingReferences[referenceIndex].pluginHandled; +} + +bool IsBindingEnabled(size_t referenceIndex) { + return IsBindingConfigurable(referenceIndex) && g_bindingEnabled[referenceIndex]; +} + +void SetBindingEnabled(size_t referenceIndex, bool enabled) { + if (!IsBindingConfigurable(referenceIndex)) { + return; + } + + g_bindingEnabled[referenceIndex] = enabled; + g_pendingChord.reset(); + SaveSettings(); +} + +std::wstring BuildReferenceSummaryText() { + const auto enabledCount = static_cast(std::count(g_bindingEnabled.begin(), g_bindingEnabled.end(), true)); + + std::wstring text; + if (g_enabled) { + text = L"Mode: intercepting selected VS Code shortcuts"; + } else { + text = L"Mode: pass-through (saved selections are preserved)"; + } + text += L"\r\nChecked rows: "; + text += std::to_wstring(enabledCount); + text += L" / "; + text += std::to_wstring(kMappedBindingCount + kReservedNoOpBindingCount); + text += L" Settings file: "; + text += SettingsFilePathText(); + return text; +} + +std::wstring BuildSectionLabel(const BindingReferenceEntry& entry) { + std::wstring value = entry.section ? entry.section : L""; + if (entry.subsection != nullptr && entry.subsection[0] != L'\0') { + if (!value.empty()) { + value += L" / "; + } + value += entry.subsection; + } + return value; +} + +bool ContainsCaseInsensitive(std::wstring_view text, std::wstring_view needle) { + if (needle.empty()) { + return true; + } + + const auto match = std::search(text.begin(), + text.end(), + needle.begin(), + needle.end(), + [](wchar_t lhs, wchar_t rhs) { return std::towlower(lhs) == std::towlower(rhs); }); + return match != text.end(); +} + +bool BindingMatchesFilter(const BindingReferenceEntry& binding, std::wstring_view filter) { + if (filter.empty()) { + return true; + } + + const auto section = BuildSectionLabel(binding); + return ContainsCaseInsensitive(binding.winKey ? binding.winKey : L"", filter) || + ContainsCaseInsensitive(section, filter) || + ContainsCaseInsensitive(binding.label ? binding.label : L"", filter) || + ContainsCaseInsensitive(binding.command ? binding.command : L"", filter) || + ContainsCaseInsensitive(binding.status ? binding.status : L"", filter) || + ContainsCaseInsensitive(binding.handler ? binding.handler : L"", filter) || + ContainsCaseInsensitive(binding.target ? binding.target : L"", filter) || + ContainsCaseInsensitive(binding.notes ? binding.notes : L"", filter); +} + +void InsertReferenceColumns(HWND listView) { + struct ColumnSpec { + const wchar_t* title; + int width; + }; + + const ColumnSpec columns[] = { + {L"Key", 120}, + {L"Section", 170}, + {L"Label", 170}, + {L"Command", 240}, + {L"Status", 110}, + {L"Handler", 120}, + {L"Target", 180}, + {L"Notes", 420}, + }; + + for (int index = 0; index < static_cast(std::size(columns)); ++index) { + LVCOLUMNW column{}; + column.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; + column.cx = columns[index].width; + column.pszText = const_cast(columns[index].title); + column.iSubItem = index; + ListView_InsertColumn(listView, index, &column); + } +} + +size_t PopulateReferenceList(HWND dialog, HWND listView) { + const auto filter = GetWindowTextValue(::GetDlgItem(dialog, kReferenceFilterEditId)); + g_syncingBindingList = true; + ListView_DeleteAllItems(listView); + + int visibleIndex = 0; + for (size_t referenceIndex = 0; referenceIndex < kBindingReferences.size(); ++referenceIndex) { + const auto& binding = kBindingReferences[referenceIndex]; + if (!BindingMatchesFilter(binding, filter)) { + continue; + } + + LVITEMW item{}; + item.mask = LVIF_TEXT | LVIF_PARAM; + item.iItem = visibleIndex; + item.lParam = static_cast(referenceIndex); + item.pszText = const_cast(binding.winKey); + ListView_InsertItem(listView, &item); + + const auto section = BuildSectionLabel(binding); + ListView_SetItemText(listView, visibleIndex, 1, const_cast(section.c_str())); + ListView_SetItemText(listView, visibleIndex, 2, const_cast(binding.label)); + ListView_SetItemText(listView, visibleIndex, 3, const_cast(binding.command)); + ListView_SetItemText(listView, visibleIndex, 4, const_cast(binding.status)); + ListView_SetItemText(listView, visibleIndex, 5, const_cast(binding.handler)); + ListView_SetItemText(listView, visibleIndex, 6, const_cast(binding.target)); + ListView_SetItemText(listView, visibleIndex, 7, const_cast(binding.notes)); + ListView_SetCheckState(listView, visibleIndex, IsBindingEnabled(referenceIndex) ? TRUE : FALSE); + ++visibleIndex; + } + + g_syncingBindingList = false; + return static_cast(visibleIndex); +} + +size_t RefreshReferenceList(HWND dialog) { + const HWND listView = ::GetDlgItem(dialog, kBindingsListId); + const size_t visibleCount = PopulateReferenceList(dialog, listView); + ::SetDlgItemTextW(dialog, kReferenceSummaryId, BuildReferenceSummaryText().c_str()); + return visibleCount; +} + +void ApplyDialogSettings(HWND dialog) { + g_enabled = (::IsDlgButtonChecked(dialog, kEnableKeymapCheckboxId) == BST_CHECKED); + g_pendingChord.reset(); + SaveSettings(); +} + +size_t GetReferenceIndexForListItem(HWND listView, int itemIndex) { + LVITEMW item{}; + item.mask = LVIF_PARAM; + item.iItem = itemIndex; + item.iSubItem = 0; + if (!ListView_GetItem(listView, &item) || item.lParam < 0) { + return kBindingReferences.size(); + } + return static_cast(item.lParam); +} + +INT_PTR CALLBACK ConfigReferenceDialogProc(HWND dialog, UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_INITDIALOG: { + const HWND listView = ::GetDlgItem(dialog, kBindingsListId); + ListView_SetExtendedListViewStyle( + listView, LVS_EX_CHECKBOXES | LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES); + InsertReferenceColumns(listView); + RefreshReferenceList(dialog); + + ::CheckDlgButton(dialog, kEnableKeymapCheckboxId, g_enabled ? BST_CHECKED : BST_UNCHECKED); + return TRUE; + } + + case WM_NOTIFY: { + auto* header = reinterpret_cast(lParam); + if (header == nullptr || header->idFrom != kBindingsListId || header->code != LVN_ITEMCHANGED) { + break; + } + + auto* changed = reinterpret_cast(lParam); + if (g_syncingBindingList || changed->iItem < 0 || (changed->uChanged & LVIF_STATE) == 0) { + break; + } + + const bool oldChecked = ((changed->uOldState & LVIS_STATEIMAGEMASK) >> 12) == 2; + const bool newChecked = ListView_GetCheckState(header->hwndFrom, changed->iItem) != FALSE; + if (oldChecked == newChecked) { + break; + } + + const auto referenceIndex = GetReferenceIndexForListItem(header->hwndFrom, changed->iItem); + if (referenceIndex >= kBindingReferences.size()) { + return TRUE; + } + + if (!IsBindingConfigurable(referenceIndex)) { + g_syncingBindingList = true; + ListView_SetCheckState(header->hwndFrom, changed->iItem, FALSE); + g_syncingBindingList = false; + return TRUE; + } + + SetBindingEnabled(referenceIndex, newChecked); + ::SetDlgItemTextW(dialog, kReferenceSummaryId, BuildReferenceSummaryText().c_str()); + return TRUE; + } + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case kEnableKeymapCheckboxId: + if (HIWORD(wParam) == BN_CLICKED) { + ApplyDialogSettings(dialog); + ::SetDlgItemTextW(dialog, kReferenceSummaryId, BuildReferenceSummaryText().c_str()); + } + return TRUE; + + case kReferenceFilterEditId: + if (HIWORD(wParam) == EN_CHANGE) { + RefreshReferenceList(dialog); + } + return TRUE; + + case IDOK: + case IDCANCEL: + ::EndDialog(dialog, LOWORD(wParam)); + return TRUE; + } + break; + + case WM_CLOSE: + ::EndDialog(dialog, IDCANCEL); + return TRUE; + } + + return FALSE; +} + +HWND ActiveScintilla() { + int which = 0; + ::SendMessage(g_nppData._nppHandle, NPPM_GETCURRENTSCINTILLA, 0, reinterpret_cast(&which)); + return (which == 0) ? g_nppData._scintillaMainHandle : g_nppData._scintillaSecondHandle; +} + +int ScintillaIndex(HWND hWnd) { + if (hWnd == g_nppData._scintillaMainHandle) { + return 0; + } + if (hWnd == g_nppData._scintillaSecondHandle) { + return 1; + } + return -1; +} + +bool IsPressed(int vkey) { + return (::GetKeyState(vkey) & 0x8000) != 0; +} + +KeyStroke CurrentKeyStroke(UINT vk) { + return KeyStroke{IsPressed(VK_CONTROL), IsPressed(VK_MENU), IsPressed(VK_SHIFT), vk}; +} + +void ConfigureEditor(HWND editor) { + if (editor == nullptr) { + return; + } + + RunSci(editor, SCI_SETMULTIPLESELECTION, 1); + RunSci(editor, SCI_SETADDITIONALSELECTIONTYPING, 1); + RunSci(editor, SCI_SETADDITIONALCARETSVISIBLE, 1); + RunSci(editor, SCI_SETRECTANGULARSELECTIONMODIFIER, SCMOD_ALT); +} + +void ConfigureEditors() { + ConfigureEditor(g_nppData._scintillaMainHandle); + ConfigureEditor(g_nppData._scintillaSecondHandle); +} + +bool TryHandleChord(const KeyStroke& currentKey, HWND editor) { + if (g_pendingChord.has_value()) { + const DWORD age = ::GetTickCount() - g_pendingChord->startedAt; + if (age <= kChordTimeoutMs) { + bool enabledPrefixMatched = false; + for (const auto& chord : kChordBindings) { + if (chord.first != g_pendingChord->first || !IsBindingEnabled(chord.referenceIndex)) { + continue; + } + + enabledPrefixMatched = true; + if (chord.second == currentKey) { + g_pendingChord.reset(); + return ExecuteAction(chord.kind, chord.id, ActionContext{g_nppData._nppHandle, editor}); + } + } + + if (enabledPrefixMatched) { + // Strict mode: consume unknown second key after a valid chord prefix. + g_pendingChord.reset(); + return true; + } + } + + g_pendingChord.reset(); + } + + for (const auto& chord : kChordBindings) { + if (chord.first == currentKey && IsBindingEnabled(chord.referenceIndex)) { + g_pendingChord = PendingChord{currentKey, ::GetTickCount()}; + return true; + } + } + + return false; +} + +bool TryHandleShortcut(HWND editor, WPARAM wParam) { + if (!g_enabled || editor == nullptr) { + return false; + } + + const UINT vk = static_cast(wParam); + if (vk == VK_CONTROL || vk == VK_MENU || vk == VK_SHIFT) { + return false; + } + + const KeyStroke currentKey = CurrentKeyStroke(vk); + + if (TryHandleChord(currentKey, editor)) { + return true; + } + + for (const auto& binding : kShortcutBindings) { + if (binding.key == currentKey && IsBindingEnabled(binding.referenceIndex)) { + return ExecuteAction(binding.kind, binding.id, ActionContext{g_nppData._nppHandle, editor}); + } + } + + return false; +} + +LRESULT CALLBACK MessageHookProc(int code, WPARAM wParam, LPARAM lParam) { + if (code >= 0 && wParam == PM_REMOVE) { + MSG* msg = reinterpret_cast(lParam); + if (msg != nullptr && (msg->message == WM_KEYDOWN || msg->message == WM_SYSKEYDOWN)) { + if (TryHandleShortcut(ActiveScintilla(), msg->wParam)) { + msg->message = WM_NULL; + msg->wParam = 0; + msg->lParam = 0; + return 1; + } + } + } + + return ::CallNextHookEx(g_messageHook, code, wParam, lParam); +} + +LRESULT CALLBACK ScintillaProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { + if ((msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) && TryHandleShortcut(hWnd, wParam)) { + return 0; + } + + const int idx = ScintillaIndex(hWnd); + if (idx >= 0 && g_scintillaOldProc[idx] != nullptr) { + return ::CallWindowProc(g_scintillaOldProc[idx], hWnd, msg, wParam, lParam); + } + + return ::DefWindowProc(hWnd, msg, wParam, lParam); +} + +LRESULT CALLBACK MainProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { + if ((msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) && TryHandleShortcut(ActiveScintilla(), wParam)) { + return 0; + } + + if (g_mainOldProc != nullptr) { + return ::CallWindowProc(g_mainOldProc, hWnd, msg, wParam, lParam); + } + + return ::DefWindowProc(hWnd, msg, wParam, lParam); +} + +void HookWindows() { + if (g_nppData._nppHandle != nullptr && g_mainOldProc == nullptr) { + g_mainOldProc = reinterpret_cast( + ::SetWindowLongPtr(g_nppData._nppHandle, GWLP_WNDPROC, reinterpret_cast(MainProc))); + } + + if (g_nppData._scintillaMainHandle != nullptr && g_scintillaOldProc[0] == nullptr) { + g_scintillaOldProc[0] = reinterpret_cast(::SetWindowLongPtr( + g_nppData._scintillaMainHandle, GWLP_WNDPROC, reinterpret_cast(ScintillaProc))); + } + + if (g_nppData._scintillaSecondHandle != nullptr && g_scintillaOldProc[1] == nullptr) { + g_scintillaOldProc[1] = reinterpret_cast(::SetWindowLongPtr( + g_nppData._scintillaSecondHandle, GWLP_WNDPROC, reinterpret_cast(ScintillaProc))); + } +} + +void UnhookWindows() { + if (g_nppData._nppHandle != nullptr && g_mainOldProc != nullptr) { + ::SetWindowLongPtr(g_nppData._nppHandle, GWLP_WNDPROC, reinterpret_cast(g_mainOldProc)); + g_mainOldProc = nullptr; + } + + if (g_nppData._scintillaMainHandle != nullptr && g_scintillaOldProc[0] != nullptr) { + ::SetWindowLongPtr(g_nppData._scintillaMainHandle, GWLP_WNDPROC, + reinterpret_cast(g_scintillaOldProc[0])); + g_scintillaOldProc[0] = nullptr; + } + + if (g_nppData._scintillaSecondHandle != nullptr && g_scintillaOldProc[1] != nullptr) { + ::SetWindowLongPtr(g_nppData._scintillaSecondHandle, GWLP_WNDPROC, + reinterpret_cast(g_scintillaOldProc[1])); + g_scintillaOldProc[1] = nullptr; + } +} + +void InstallMessageHook() { + if (g_messageHook != nullptr || g_nppData._nppHandle == nullptr) { + return; + } + + g_nppThreadId = ::GetWindowThreadProcessId(g_nppData._nppHandle, nullptr); + if (g_nppThreadId == 0) { + return; + } + + g_messageHook = ::SetWindowsHookEx(WH_GETMESSAGE, MessageHookProc, nullptr, g_nppThreadId); +} + +void RemoveMessageHook() { + if (g_messageHook != nullptr) { + ::UnhookWindowsHookEx(g_messageHook); + g_messageHook = nullptr; + } + + g_nppThreadId = 0; + g_pendingChord.reset(); +} + +void ShowConfigReferenceDialog() { + INITCOMMONCONTROLSEX controls{sizeof(controls), ICC_LISTVIEW_CLASSES}; + ::InitCommonControlsEx(&controls); + ::DialogBoxParamW(ModuleInstance(), + MAKEINTRESOURCEW(kConfigDialogId), + g_nppData._nppHandle, + ConfigReferenceDialogProc, + 0); +} + +void ShowAboutDialog() { + std::wstring summary = L"VSCode Keymap NPP\r\n\r\n"; + summary += L"VSCode Keymap NPP strict mode\r\n\r\n"; + summary += L"- Enforces near 1:1 mappings where Notepad++ equivalents exist\r\n"; + summary += L"- Implements missing line operations (move/duplicate/insert/delete)\r\n"; + summary += L"- Uses a pre-accelerator hook to block conflicting default Notepad++ shortcuts\r\n"; + summary += L"- Unsupported VS Code-only shortcuts are reserved as no-op so they do not trigger non-VS Code Notepad++ actions.\r\n\r\n"; + summary += L"Source bindings: "; + summary += std::to_wstring(kVsCodeSourceBindingCount); + summary += L"\r\nMapped: "; + summary += std::to_wstring(kMappedBindingCount); + summary += L"\r\nReserved no-op: "; + summary += std::to_wstring(kReservedNoOpBindingCount); + summary += L"\r\nDocumented unported: "; + summary += std::to_wstring(kDocumentedUnportedBindingCount); + summary += L"\r\n\r\nUse Config/Reference to review and control handled shortcuts.\r\nRepository:\r\nhttps://github.com/14ag/vscode-to-notepadpp\r\n\r\nSettings file:\r\n"; + summary += SettingsFilePathText(); + ::MessageBoxW(g_nppData._nppHandle, summary.c_str(), L"About VSCode Keymap NPP", MB_OK | MB_ICONINFORMATION); +} + +void RegisterPluginCommands() { + std::wmemset(g_funcItems[0]._itemName, 0, menuItemSize); + std::wmemset(g_funcItems[1]._itemName, 0, menuItemSize); + ::wcsncpy_s(g_funcItems[0]._itemName, menuItemSize, L"Config/Reference", _TRUNCATE); + g_funcItems[0]._pFunc = ShowConfigReferenceDialog; + ::wcsncpy_s(g_funcItems[1]._itemName, menuItemSize, L"About", _TRUNCATE); + g_funcItems[1]._pFunc = ShowAboutDialog; +} + +} // namespace + +extern "C" __declspec(dllexport) void setInfo(NppData notepadPlusData) { + g_nppData = notepadPlusData; + LoadSettings(); + RegisterPluginCommands(); + HookWindows(); + ConfigureEditors(); + InstallMessageHook(); +} + +extern "C" __declspec(dllexport) const wchar_t* getName() { + return kPluginName; +} + +extern "C" __declspec(dllexport) FuncItem* getFuncsArray(int* nbF) { + *nbF = 2; + return g_funcItems; +} + +extern "C" __declspec(dllexport) void beNotified(SCNotification*) { + // Not used for this implementation. +} + +extern "C" __declspec(dllexport) LRESULT messageProc(UINT, WPARAM, LPARAM) { + return TRUE; +} + +extern "C" __declspec(dllexport) BOOL isUnicode() { + return TRUE; +} + +extern "C" __declspec(dllexport) void cleanUp() { + RemoveMessageHook(); + UnhookWindows(); } diff --git a/src/handlers/ActionHandlers.cpp b/src/handlers/ActionHandlers.cpp new file mode 100644 index 0000000..f3cb23f --- /dev/null +++ b/src/handlers/ActionHandlers.cpp @@ -0,0 +1,72 @@ +#include "ActionHandlers.h" + +#include "CustomHandlers.h" +#include "Notepad_plus_msgs.h" + +void RunNppCommand(HWND nppHandle, int commandId) { + ::SendMessage(nppHandle, NPPM_MENUCOMMAND, 0, static_cast(commandId)); +} + +sptr_t RunSci(HWND editor, UINT message, uptr_t wParam, sptr_t lParam) { + return static_cast( + ::SendMessage(editor, message, static_cast(wParam), static_cast(lParam))); +} + +bool ExecuteAction(ActionKind kind, int id, const ActionContext& context) { + switch (kind) { + case ActionKind::NppCommand: + RunNppCommand(context.nppHandle, id); + return true; + + case ActionKind::SciCommand: + RunSci(context.editor, static_cast(id)); + return true; + + case ActionKind::DuplicateLineUp: + return ExecuteDuplicateLineUp(context); + + case ActionKind::CutAllowLine: + return ExecuteCutAllowLine(context); + + case ActionKind::CopyAllowLine: + return ExecuteCopyAllowLine(context); + + case ActionKind::ToggleFoldAtCaret: + return ExecuteToggleFoldAtCaret(context); + + case ActionKind::ToggleStreamComment: + return ExecuteToggleStreamComment(context); + + case ActionKind::SelectCurrentLine: + return ExecuteSelectCurrentLine(context); + + case ActionKind::InsertLineBelow: + return ExecuteInsertLineBelow(context); + + case ActionKind::InsertLineAbove: + return ExecuteInsertLineAbove(context); + + case ActionKind::InsertCursorBelow: + return ExecuteInsertCursorBelow(context); + + case ActionKind::InsertCursorAbove: + return ExecuteInsertCursorAbove(context); + + case ActionKind::CursorColumnSelectDown: + return ExecuteCursorColumnSelectDown(context); + + case ActionKind::CursorColumnSelectUp: + return ExecuteCursorColumnSelectUp(context); + + case ActionKind::ScrollPageUpNoCaret: + return ExecuteScrollPageUpNoCaret(context); + + case ActionKind::ScrollPageDownNoCaret: + return ExecuteScrollPageDownNoCaret(context); + + case ActionKind::ReservedNoOp: + return true; + } + + return false; +} diff --git a/src/handlers/ActionHandlers.h b/src/handlers/ActionHandlers.h new file mode 100644 index 0000000..f781718 --- /dev/null +++ b/src/handlers/ActionHandlers.h @@ -0,0 +1,77 @@ +#pragma once + +#include + +#include + +#include "Scintilla.h" +#include "menuCmdID.h" + +struct KeyStroke { + bool ctrl; + bool alt; + bool shift; + UINT vk; +}; + +inline bool operator==(const KeyStroke& lhs, const KeyStroke& rhs) { + return lhs.ctrl == rhs.ctrl && lhs.alt == rhs.alt && lhs.shift == rhs.shift && lhs.vk == rhs.vk; +} + +enum class ActionKind { + NppCommand, + SciCommand, + DuplicateLineUp, + CutAllowLine, + CopyAllowLine, + ToggleFoldAtCaret, + ToggleStreamComment, + SelectCurrentLine, + InsertLineBelow, + InsertLineAbove, + InsertCursorBelow, + InsertCursorAbove, + CursorColumnSelectDown, + CursorColumnSelectUp, + ScrollPageUpNoCaret, + ScrollPageDownNoCaret, + ReservedNoOp, +}; + +struct ShortcutBinding { + KeyStroke key; + ActionKind kind; + int id; + size_t referenceIndex; +}; + +struct ChordBinding { + KeyStroke first; + KeyStroke second; + ActionKind kind; + int id; + size_t referenceIndex; +}; + +struct BindingReferenceEntry { + bool pluginHandled; + const wchar_t* section; + const wchar_t* subsection; + const wchar_t* label; + const wchar_t* command; + const wchar_t* winKey; + const wchar_t* status; + const wchar_t* handler; + const wchar_t* target; + const wchar_t* notes; +}; + +struct ActionContext { + HWND nppHandle; + HWND editor; +}; + +void RunNppCommand(HWND nppHandle, int commandId); +sptr_t RunSci(HWND editor, UINT message, uptr_t wParam = 0, sptr_t lParam = 0); + +bool ExecuteAction(ActionKind kind, int id, const ActionContext& context); diff --git a/src/handlers/CommentHandlers.cpp b/src/handlers/CommentHandlers.cpp new file mode 100644 index 0000000..43d43f6 --- /dev/null +++ b/src/handlers/CommentHandlers.cpp @@ -0,0 +1,82 @@ +#include "CustomHandlers.h" + +#include +#include + +#include "menuCmdID.h" + +namespace { + +std::string GetLineText(HWND editor, sptr_t line) { + const sptr_t lengthWithEol = RunSci(editor, SCI_GETLINE, static_cast(line), 0); + if (lengthWithEol <= 0) { + return {}; + } + + std::string buffer(static_cast(lengthWithEol), '\0'); + RunSci(editor, SCI_GETLINE, static_cast(line), reinterpret_cast(buffer.data())); + while (!buffer.empty() && (buffer.back() == '\0' || buffer.back() == '\r' || buffer.back() == '\n')) { + buffer.pop_back(); + } + return buffer; +} + +std::string_view TrimLeftAscii(std::string_view text) { + size_t i = 0; + while (i < text.size()) { + const char ch = text[i]; + if (ch != ' ' && ch != '\t') { + break; + } + ++i; + } + return text.substr(i); +} + +bool LooksLineCommented(std::string_view trimmed) { + static constexpr const char* kPrefixes[] = {"//", "#", "--", ";", "'", "REM ", "::"}; + for (const char* prefix : kPrefixes) { + const size_t prefixLen = std::char_traits::length(prefix); + if (trimmed.size() >= prefixLen && trimmed.substr(0, prefixLen) == prefix) { + return true; + } + } + return false; +} + +bool ShouldUncommentSelection(HWND editor) { + const sptr_t selectionStart = RunSci(editor, SCI_GETSELECTIONSTART); + const sptr_t selectionEnd = RunSci(editor, SCI_GETSELECTIONEND); + + sptr_t startLine = RunSci(editor, SCI_LINEFROMPOSITION, static_cast(selectionStart)); + sptr_t endLine = RunSci(editor, SCI_LINEFROMPOSITION, static_cast(selectionEnd)); + if (selectionEnd > selectionStart) { + const sptr_t endLineStart = RunSci(editor, SCI_POSITIONFROMLINE, static_cast(endLine)); + if (selectionEnd == endLineStart && endLine > startLine) { + --endLine; + } + } + + bool sawNonEmptyLine = false; + for (sptr_t line = startLine; line <= endLine; ++line) { + const std::string text = GetLineText(editor, line); + const std::string_view trimmed = TrimLeftAscii(text); + if (trimmed.empty()) { + continue; + } + sawNonEmptyLine = true; + if (!LooksLineCommented(trimmed)) { + return false; + } + } + + return sawNonEmptyLine; +} + +} // namespace + +bool ExecuteToggleStreamComment(const ActionContext& context) { + RunNppCommand(context.nppHandle, + ShouldUncommentSelection(context.editor) ? IDM_EDIT_STREAM_UNCOMMENT : IDM_EDIT_STREAM_COMMENT); + return true; +} diff --git a/src/handlers/CursorHandlers.cpp b/src/handlers/CursorHandlers.cpp new file mode 100644 index 0000000..f202fac --- /dev/null +++ b/src/handlers/CursorHandlers.cpp @@ -0,0 +1,368 @@ +#include "CustomHandlers.h" + +#include +#include + +namespace { + +struct SelectionEndpoint { + sptr_t position; + sptr_t virtualSpace; +}; + +struct SelectionState { + SelectionEndpoint caret; + SelectionEndpoint anchor; +}; + +struct SelectionColumns { + sptr_t caret; + sptr_t anchor; +}; + +struct VerticalSelectionState { + HWND editor; + SelectionState selection; + SelectionColumns columns; + bool active; +}; + +struct ColumnSelectionState { + VerticalSelectionState anchor; + sptr_t extent; +}; + +ColumnSelectionState g_columnSelection{}; +VerticalSelectionState g_insertCursor{}; +CursorVirtualSpaceOptions g_cursorVirtualSpaceOptions{}; + +class ScopedCaretBlinkPause { +public: + explicit ScopedCaretBlinkPause(HWND editor) + : editor_(editor), + caretPeriod_(RunSci(editor, SCI_GETCARETPERIOD)), + additionalCaretsBlink_(RunSci(editor, SCI_GETADDITIONALCARETSBLINK)) { + RunSci(editor_, SCI_SETCARETPERIOD, 0); + RunSci(editor_, SCI_SETADDITIONALCARETSBLINK, 0); + } + + ~ScopedCaretBlinkPause() { + RunSci(editor_, SCI_SETCARETPERIOD, static_cast(caretPeriod_)); + RunSci(editor_, SCI_SETADDITIONALCARETSBLINK, static_cast(additionalCaretsBlink_)); + } + + ScopedCaretBlinkPause(const ScopedCaretBlinkPause&) = delete; + ScopedCaretBlinkPause& operator=(const ScopedCaretBlinkPause&) = delete; + +private: + HWND editor_; + sptr_t caretPeriod_; + sptr_t additionalCaretsBlink_; +}; + +SelectionState GetSelectionState(HWND editor, sptr_t selectionIndex) { + return SelectionState{ + {RunSci(editor, SCI_GETSELECTIONNCARET, static_cast(selectionIndex)), + RunSci(editor, SCI_GETSELECTIONNCARETVIRTUALSPACE, static_cast(selectionIndex))}, + {RunSci(editor, SCI_GETSELECTIONNANCHOR, static_cast(selectionIndex)), + RunSci(editor, SCI_GETSELECTIONNANCHORVIRTUALSPACE, static_cast(selectionIndex))}, + }; +} + +bool SameSelectionState(const SelectionState& lhs, const SelectionState& rhs) { + return lhs.caret.position == rhs.caret.position && lhs.caret.virtualSpace == rhs.caret.virtualSpace && + lhs.anchor.position == rhs.anchor.position && lhs.anchor.virtualSpace == rhs.anchor.virtualSpace; +} + +sptr_t EndpointColumn(HWND editor, const SelectionEndpoint& endpoint) { + return RunSci(editor, SCI_GETCOLUMN, static_cast(endpoint.position)) + endpoint.virtualSpace; +} + +sptr_t EndpointLine(HWND editor, const SelectionEndpoint& endpoint) { + return RunSci(editor, SCI_LINEFROMPOSITION, static_cast(endpoint.position)); +} + +SelectionColumns GetSelectionColumns(HWND editor, const SelectionState& selection) { + return SelectionColumns{EndpointColumn(editor, selection.caret), EndpointColumn(editor, selection.anchor)}; +} + +std::optional GetMainSelection(HWND editor) { + const sptr_t selectionCount = RunSci(editor, SCI_GETSELECTIONS); + if (selectionCount <= 0) { + return std::nullopt; + } + + const sptr_t mainSelection = RunSci(editor, SCI_GETMAINSELECTION); + if (mainSelection < 0 || mainSelection >= selectionCount) { + return std::nullopt; + } + + return GetSelectionState(editor, mainSelection); +} + +std::optional MoveEndpoint(HWND editor, + const SelectionEndpoint& endpoint, + sptr_t lineDelta, + sptr_t desiredColumn, + bool maintainVirtualSpace) { + const sptr_t currentLine = RunSci(editor, SCI_LINEFROMPOSITION, static_cast(endpoint.position)); + const sptr_t targetLine = currentLine + lineDelta; + const sptr_t lineCount = RunSci(editor, SCI_GETLINECOUNT); + if (targetLine < 0 || targetLine >= lineCount) { + return std::nullopt; + } + + const sptr_t targetPos = + RunSci(editor, SCI_FINDCOLUMN, static_cast(targetLine), desiredColumn); + if (targetPos < 0) { + return std::nullopt; + } + + const sptr_t actualColumn = RunSci(editor, SCI_GETCOLUMN, static_cast(targetPos)); + const sptr_t targetVirtualSpace = + (maintainVirtualSpace && desiredColumn > actualColumn) ? (desiredColumn - actualColumn) : 0; + return SelectionEndpoint{targetPos, targetVirtualSpace}; +} + +std::optional MoveSelection(HWND editor, + const SelectionState& selection, + sptr_t lineDelta, + const SelectionColumns& columns, + bool maintainVirtualSpace) { + const auto movedCaret = MoveEndpoint(editor, selection.caret, lineDelta, columns.caret, maintainVirtualSpace); + if (!movedCaret.has_value()) { + return std::nullopt; + } + + const auto movedAnchor = MoveEndpoint(editor, selection.anchor, lineDelta, columns.anchor, maintainVirtualSpace); + if (!movedAnchor.has_value()) { + return std::nullopt; + } + + return SelectionState{*movedCaret, *movedAnchor}; +} + +sptr_t FindSelectionState(HWND editor, const SelectionState& selection) { + const sptr_t selectionCount = RunSci(editor, SCI_GETSELECTIONS); + for (sptr_t i = 0; i < selectionCount; ++i) { + if (SameSelectionState(GetSelectionState(editor, i), selection)) { + return i; + } + } + return -1; +} + +sptr_t FindSelectionOnCaretLine(HWND editor, const SelectionState& selection) { + const sptr_t targetLine = EndpointLine(editor, selection.caret); + const sptr_t selectionCount = RunSci(editor, SCI_GETSELECTIONS); + for (sptr_t i = 0; i < selectionCount; ++i) { + if (EndpointLine(editor, GetSelectionState(editor, i).caret) == targetLine) { + return i; + } + } + return -1; +} + +void SetSelectionState(HWND editor, sptr_t selectionIndex, const SelectionState& selection) { + RunSci(editor, SCI_SETSELECTIONNCARET, static_cast(selectionIndex), selection.caret.position); + RunSci(editor, SCI_SETSELECTIONNANCHOR, static_cast(selectionIndex), selection.anchor.position); + RunSci(editor, SCI_SETSELECTIONNCARETVIRTUALSPACE, static_cast(selectionIndex), + selection.caret.virtualSpace); + RunSci(editor, SCI_SETSELECTIONNANCHORVIRTUALSPACE, static_cast(selectionIndex), + selection.anchor.virtualSpace); +} + +sptr_t AbsExtent(sptr_t value) { + return value < 0 ? -value : value; +} + +std::optional> BuildColumnSelections(HWND editor, + const VerticalSelectionState& anchor, + sptr_t extent) { + const sptr_t firstDelta = extent < 0 ? extent : 0; + const sptr_t lastDelta = extent > 0 ? extent : 0; + std::vector selections; + selections.reserve(static_cast(lastDelta - firstDelta + 1)); + + for (sptr_t delta = firstDelta; delta <= lastDelta; ++delta) { + const auto movedSelection = + MoveSelection(editor, + anchor.selection, + delta, + anchor.columns, + g_cursorVirtualSpaceOptions.maintainVirtualSpace); + if (!movedSelection.has_value()) { + return std::nullopt; + } + selections.push_back(*movedSelection); + } + + return selections; +} + +bool ApplySelectionSet(HWND editor, const std::vector& selections, sptr_t mainSelectionIndex) { + if (selections.empty()) { + return false; + } + + RunSci(editor, SCI_SETSELECTION, static_cast(selections[0].caret.position), + selections[0].anchor.position); + SetSelectionState(editor, 0, selections[0]); + + for (size_t i = 1; i < selections.size(); ++i) { + RunSci(editor, SCI_ADDSELECTION, static_cast(selections[i].caret.position), + selections[i].anchor.position); + SetSelectionState(editor, static_cast(i), selections[i]); + } + + RunSci(editor, SCI_SETMAINSELECTION, static_cast(mainSelectionIndex)); + RunSci(editor, SCI_SCROLLCARET); + return true; +} + +bool ApplyColumnSelection(HWND editor, const VerticalSelectionState& anchor, sptr_t extent) { + const auto selections = BuildColumnSelections(editor, anchor, extent); + if (!selections.has_value()) { + return false; + } + + const sptr_t firstDelta = extent < 0 ? extent : 0; + const sptr_t mainSelectionIndex = extent - firstDelta; + return ApplySelectionSet(editor, *selections, mainSelectionIndex); +} + +void ResetColumnSelection() { + g_columnSelection = ColumnSelectionState{}; +} + +void ResetInsertCursorSelection() { + g_insertCursor = VerticalSelectionState{}; +} + +bool IsColumnSelectionCurrent(HWND editor) { + if (!g_columnSelection.anchor.active || g_columnSelection.anchor.editor != editor) { + return false; + } + + const sptr_t expectedCount = AbsExtent(g_columnSelection.extent) + 1; + if (RunSci(editor, SCI_GETSELECTIONS) != expectedCount) { + return false; + } + + const auto selections = BuildColumnSelections(editor, g_columnSelection.anchor, g_columnSelection.extent); + if (!selections.has_value()) { + return false; + } + + for (const auto& selection : *selections) { + if (FindSelectionState(editor, selection) < 0) { + return false; + } + } + + return true; +} + +bool StartColumnSelection(HWND editor) { + const auto mainSelection = GetMainSelection(editor); + if (!mainSelection.has_value()) { + return false; + } + + g_columnSelection = ColumnSelectionState{ + VerticalSelectionState{editor, *mainSelection, GetSelectionColumns(editor, *mainSelection), true}, 0}; + return true; +} + +bool UpdateColumnSelection(HWND editor, sptr_t direction) { + ScopedCaretBlinkPause caretBlinkPause(editor); + + if (!IsColumnSelectionCurrent(editor) && !StartColumnSelection(editor)) { + return true; + } + + const sptr_t newExtent = g_columnSelection.extent + direction; + if (!ApplyColumnSelection(editor, g_columnSelection.anchor, newExtent)) { + return true; + } + + g_columnSelection.extent = newExtent; + return true; +} + +bool InsertCursorOnAdjacentLine(HWND editor, sptr_t lineDelta) { + ScopedCaretBlinkPause caretBlinkPause(editor); + + const auto currentSelection = GetMainSelection(editor); + if (!currentSelection.has_value()) { + return true; + } + + if (!g_insertCursor.active || g_insertCursor.editor != editor || + !SameSelectionState(g_insertCursor.selection, *currentSelection)) { + g_insertCursor = VerticalSelectionState{ + editor, *currentSelection, GetSelectionColumns(editor, *currentSelection), true}; + } + + const auto movedSelection = MoveSelection( + editor, + *currentSelection, + lineDelta, + g_insertCursor.columns, + g_cursorVirtualSpaceOptions.maintainVirtualSpace); + if (!movedSelection.has_value()) { + return true; + } + + const sptr_t existingSelection = FindSelectionOnCaretLine(editor, *movedSelection); + if (existingSelection >= 0) { + RunSci(editor, SCI_SETMAINSELECTION, static_cast(existingSelection)); + RunSci(editor, SCI_SCROLLCARET); + g_insertCursor.selection = GetSelectionState(editor, existingSelection); + return true; + } + + RunSci(editor, SCI_ADDSELECTION, static_cast(movedSelection->caret.position), + movedSelection->anchor.position); + const sptr_t newSelectionIndex = RunSci(editor, SCI_GETMAINSELECTION); + if (newSelectionIndex < 0) { + return true; + } + SetSelectionState(editor, newSelectionIndex, *movedSelection); + RunSci(editor, SCI_SETMAINSELECTION, static_cast(newSelectionIndex)); + RunSci(editor, SCI_SCROLLCARET); + g_insertCursor.selection = *movedSelection; + return true; +} + +} // namespace + +CursorVirtualSpaceOptions GetCursorVirtualSpaceOptions() { + return g_cursorVirtualSpaceOptions; +} + +void SetCursorVirtualSpaceOptions(const CursorVirtualSpaceOptions& options) { + g_cursorVirtualSpaceOptions = options; + ResetColumnSelection(); + ResetInsertCursorSelection(); +} + +bool ExecuteInsertCursorBelow(const ActionContext& context) { + ResetColumnSelection(); + return InsertCursorOnAdjacentLine(context.editor, 1); +} + +bool ExecuteInsertCursorAbove(const ActionContext& context) { + ResetColumnSelection(); + return InsertCursorOnAdjacentLine(context.editor, -1); +} + +bool ExecuteCursorColumnSelectDown(const ActionContext& context) { + ResetInsertCursorSelection(); + return UpdateColumnSelection(context.editor, 1); +} + +bool ExecuteCursorColumnSelectUp(const ActionContext& context) { + ResetInsertCursorSelection(); + return UpdateColumnSelection(context.editor, -1); +} diff --git a/src/handlers/CustomHandlers.h b/src/handlers/CustomHandlers.h new file mode 100644 index 0000000..714a9f7 --- /dev/null +++ b/src/handlers/CustomHandlers.h @@ -0,0 +1,25 @@ +#pragma once + +#include "ActionHandlers.h" + +struct CursorVirtualSpaceOptions { + bool maintainVirtualSpace = false; +}; + +CursorVirtualSpaceOptions GetCursorVirtualSpaceOptions(); +void SetCursorVirtualSpaceOptions(const CursorVirtualSpaceOptions& options); + +bool ExecuteDuplicateLineUp(const ActionContext& context); +bool ExecuteCutAllowLine(const ActionContext& context); +bool ExecuteCopyAllowLine(const ActionContext& context); +bool ExecuteToggleFoldAtCaret(const ActionContext& context); +bool ExecuteToggleStreamComment(const ActionContext& context); +bool ExecuteSelectCurrentLine(const ActionContext& context); +bool ExecuteInsertLineBelow(const ActionContext& context); +bool ExecuteInsertLineAbove(const ActionContext& context); +bool ExecuteInsertCursorBelow(const ActionContext& context); +bool ExecuteInsertCursorAbove(const ActionContext& context); +bool ExecuteCursorColumnSelectDown(const ActionContext& context); +bool ExecuteCursorColumnSelectUp(const ActionContext& context); +bool ExecuteScrollPageUpNoCaret(const ActionContext& context); +bool ExecuteScrollPageDownNoCaret(const ActionContext& context); diff --git a/src/handlers/FoldHandlers.cpp b/src/handlers/FoldHandlers.cpp new file mode 100644 index 0000000..621a7fc --- /dev/null +++ b/src/handlers/FoldHandlers.cpp @@ -0,0 +1,15 @@ +#include "CustomHandlers.h" + +bool ExecuteToggleFoldAtCaret(const ActionContext& context) { + sptr_t line = + RunSci(context.editor, SCI_LINEFROMPOSITION, static_cast(RunSci(context.editor, SCI_GETCURRENTPOS))); + const sptr_t level = RunSci(context.editor, SCI_GETFOLDLEVEL, static_cast(line)); + if ((level & SC_FOLDLEVELHEADERFLAG) == 0) { + const sptr_t parent = RunSci(context.editor, SCI_GETFOLDPARENT, static_cast(line)); + if (parent >= 0) { + line = parent; + } + } + RunSci(context.editor, SCI_TOGGLEFOLD, static_cast(line)); + return true; +} diff --git a/src/handlers/LineHandlers.cpp b/src/handlers/LineHandlers.cpp new file mode 100644 index 0000000..c1b5330 --- /dev/null +++ b/src/handlers/LineHandlers.cpp @@ -0,0 +1,50 @@ +#include "CustomHandlers.h" + +#include "menuCmdID.h" + +bool ExecuteDuplicateLineUp(const ActionContext& context) { + RunNppCommand(context.nppHandle, IDM_EDIT_DUP_LINE); + RunNppCommand(context.nppHandle, IDM_EDIT_LINE_UP); + return true; +} + +bool ExecuteCutAllowLine(const ActionContext& context) { + const bool selectionEmpty = RunSci(context.editor, SCI_GETSELECTIONEMPTY) != 0; + RunSci(context.editor, selectionEmpty ? SCI_CUTALLOWLINE : SCI_CUT); + return true; +} + +bool ExecuteCopyAllowLine(const ActionContext& context) { + const bool selectionEmpty = RunSci(context.editor, SCI_GETSELECTIONEMPTY) != 0; + RunSci(context.editor, selectionEmpty ? SCI_COPYALLOWLINE : SCI_COPY); + return true; +} + +bool ExecuteSelectCurrentLine(const ActionContext& context) { + const sptr_t currentPos = RunSci(context.editor, SCI_GETCURRENTPOS); + const sptr_t line = RunSci(context.editor, SCI_LINEFROMPOSITION, static_cast(currentPos)); + const sptr_t lineStart = RunSci(context.editor, SCI_POSITIONFROMLINE, static_cast(line)); + const sptr_t lineEnd = RunSci(context.editor, SCI_GETLINEENDPOSITION, static_cast(line)); + RunSci(context.editor, SCI_SETSEL, static_cast(lineStart), lineEnd); + return true; +} + +bool ExecuteInsertLineBelow(const ActionContext& context) { + const sptr_t currentPos = RunSci(context.editor, SCI_GETCURRENTPOS); + const sptr_t line = RunSci(context.editor, SCI_LINEFROMPOSITION, static_cast(currentPos)); + const sptr_t lineEnd = RunSci(context.editor, SCI_GETLINEENDPOSITION, static_cast(line)); + RunSci(context.editor, SCI_SETSEL, static_cast(lineEnd), lineEnd); + RunSci(context.editor, SCI_NEWLINE); + return true; +} + +bool ExecuteInsertLineAbove(const ActionContext& context) { + const sptr_t currentPos = RunSci(context.editor, SCI_GETCURRENTPOS); + const sptr_t line = RunSci(context.editor, SCI_LINEFROMPOSITION, static_cast(currentPos)); + const sptr_t lineStart = RunSci(context.editor, SCI_POSITIONFROMLINE, static_cast(line)); + RunSci(context.editor, SCI_SETSEL, static_cast(lineStart), lineStart); + RunSci(context.editor, SCI_NEWLINE); + RunSci(context.editor, SCI_LINEUP); + RunSci(context.editor, SCI_HOME); + return true; +} diff --git a/src/handlers/ScrollHandlers.cpp b/src/handlers/ScrollHandlers.cpp new file mode 100644 index 0000000..56e617a --- /dev/null +++ b/src/handlers/ScrollHandlers.cpp @@ -0,0 +1,22 @@ +#include "CustomHandlers.h" + +namespace { + +bool ScrollPageNoCaret(HWND editor, sptr_t direction) { + sptr_t lines = RunSci(editor, SCI_LINESONSCREEN); + if (lines < 1) { + lines = 30; + } + RunSci(editor, SCI_LINESCROLL, 0, direction * lines); + return true; +} + +} // namespace + +bool ExecuteScrollPageUpNoCaret(const ActionContext& context) { + return ScrollPageNoCaret(context.editor, -1); +} + +bool ExecuteScrollPageDownNoCaret(const ActionContext& context) { + return ScrollPageNoCaret(context.editor, 1); +} From c658c4029f3eb54cc5cacfdfa77e21f953d2aba8 Mon Sep 17 00:00:00 2001 From: NULL0B Date: Fri, 1 May 2026 19:05:33 -0400 Subject: [PATCH 2/3] fix(plugin): harden dialog and cursor selection handling Pause shortcut interception while modal plugin dialogs are open so Config/Reference and About input is not consumed by the editor hook. Persist disabled binding settings with a stable command+key identity while still accepting legacy numeric entries, preventing regenerated binding tables from remapping saved user preferences. Also fix the dialog summary to count only configurable bindings and tighten insert-cursor deduping so it matches full selection state instead of any caret on the same line. --- src/VSCodeKeymapPlugin.cpp | 94 ++++++++++++++++++++++++++++++--- src/handlers/CursorHandlers.cpp | 13 +---- 2 files changed, 87 insertions(+), 20 deletions(-) diff --git a/src/VSCodeKeymapPlugin.cpp b/src/VSCodeKeymapPlugin.cpp index d9b5119..7755406 100644 --- a/src/VSCodeKeymapPlugin.cpp +++ b/src/VSCodeKeymapPlugin.cpp @@ -33,6 +33,7 @@ NppData g_nppData{}; FuncItem g_funcItems[2]{}; bool g_enabled = true; bool g_syncingBindingList = false; +size_t g_shortcutHandlingPauseDepth = 0; std::vector g_bindingEnabled; WNDPROC g_mainOldProc = nullptr; @@ -49,8 +50,29 @@ std::optional g_pendingChord; #include "GeneratedBindings.inc" +constexpr unsigned long long kBindingStorageHashOffset = 14695981039346656037ull; +constexpr unsigned long long kBindingStorageHashPrime = 1099511628211ull; + extern "C" IMAGE_DOS_HEADER __ImageBase; +class ScopedShortcutHandlingPause { +public: + ScopedShortcutHandlingPause() { + ++g_shortcutHandlingPauseDepth; + g_pendingChord.reset(); + } + + ~ScopedShortcutHandlingPause() { + if (g_shortcutHandlingPauseDepth > 0) { + --g_shortcutHandlingPauseDepth; + } + g_pendingChord.reset(); + } + + ScopedShortcutHandlingPause(const ScopedShortcutHandlingPause&) = delete; + ScopedShortcutHandlingPause& operator=(const ScopedShortcutHandlingPause&) = delete; +}; + HINSTANCE ModuleInstance() { return reinterpret_cast(&__ImageBase); } @@ -99,6 +121,57 @@ std::wstring GetWindowTextValue(HWND control) { return text; } +unsigned long long HashBindingStorageComponent(unsigned long long hash, std::wstring_view value) { + for (const wchar_t ch : value) { + hash ^= static_cast(static_cast(ch)); + hash *= kBindingStorageHashPrime; + } + hash ^= 0xffull; + hash *= kBindingStorageHashPrime; + return hash; +} + +unsigned long long BindingStorageHash(const BindingReferenceEntry& binding) { + auto hash = kBindingStorageHashOffset; + hash = HashBindingStorageComponent(hash, binding.command ? binding.command : L""); + hash = HashBindingStorageComponent(hash, binding.winKey ? binding.winKey : L""); + return hash; +} + +std::wstring BuildBindingStorageToken(const BindingReferenceEntry& binding) { + wchar_t token[19]{}; + std::swprintf(token, std::size(token), L"h:%016llx", BindingStorageHash(binding)); + return token; +} + +std::optional FindBindingIndexFromPersistedToken(std::wstring_view token) { + std::wstring tokenText(token); + if (tokenText.empty()) { + return std::nullopt; + } + + if (tokenText.rfind(L"h:", 0) == 0) { + wchar_t* parsedEnd = nullptr; + const auto parsedHash = std::wcstoull(tokenText.c_str() + 2, &parsedEnd, 16); + if (parsedEnd != tokenText.c_str() + 2 && *parsedEnd == L'\0') { + for (size_t index = 0; index < kBindingReferences.size(); ++index) { + if (kBindingReferences[index].pluginHandled && BindingStorageHash(kBindingReferences[index]) == parsedHash) { + return index; + } + } + } + return std::nullopt; + } + + wchar_t* parsedEnd = nullptr; + const auto legacyIndex = std::wcstoul(tokenText.c_str(), &parsedEnd, 10); + if (parsedEnd == tokenText.c_str() || *parsedEnd != L'\0' || legacyIndex >= kBindingReferences.size()) { + return std::nullopt; + } + + return static_cast(legacyIndex); +} + std::wstring BuildDisabledBindingList() { std::wstring value; for (size_t index = 0; index < kBindingReferences.size(); ++index) { @@ -108,7 +181,7 @@ std::wstring BuildDisabledBindingList() { if (!value.empty()) { value += L","; } - value += std::to_wstring(index); + value += BuildBindingStorageToken(kBindingReferences[index]); } return value; } @@ -121,13 +194,12 @@ void ApplyDisabledBindingList(const std::wstring& value) { continue; } - wchar_t* parsedEnd = nullptr; - const unsigned long index = std::wcstoul(token.c_str(), &parsedEnd, 10); - if (parsedEnd == token.c_str() || *parsedEnd != L'\0' || index >= kBindingReferences.size()) { + const auto index = FindBindingIndexFromPersistedToken(token); + if (!index.has_value()) { continue; } - if (kBindingReferences[index].pluginHandled) { - g_bindingEnabled[index] = false; + if (kBindingReferences[*index].pluginHandled) { + g_bindingEnabled[*index] = false; } } } @@ -195,6 +267,10 @@ void SetBindingEnabled(size_t referenceIndex, bool enabled) { std::wstring BuildReferenceSummaryText() { const auto enabledCount = static_cast(std::count(g_bindingEnabled.begin(), g_bindingEnabled.end(), true)); + const auto configurableCount = static_cast( + std::count_if(kBindingReferences.begin(), kBindingReferences.end(), [](const auto& binding) { + return binding.pluginHandled; + })); std::wstring text; if (g_enabled) { @@ -205,7 +281,7 @@ std::wstring BuildReferenceSummaryText() { text += L"\r\nChecked rows: "; text += std::to_wstring(enabledCount); text += L" / "; - text += std::to_wstring(kMappedBindingCount + kReservedNoOpBindingCount); + text += std::to_wstring(configurableCount); text += L" Settings file: "; text += SettingsFilePathText(); return text; @@ -492,7 +568,7 @@ bool TryHandleChord(const KeyStroke& currentKey, HWND editor) { } bool TryHandleShortcut(HWND editor, WPARAM wParam) { - if (!g_enabled || editor == nullptr) { + if (g_shortcutHandlingPauseDepth > 0 || !g_enabled || editor == nullptr) { return false; } @@ -617,6 +693,7 @@ void RemoveMessageHook() { } void ShowConfigReferenceDialog() { + const ScopedShortcutHandlingPause pause; INITCOMMONCONTROLSEX controls{sizeof(controls), ICC_LISTVIEW_CLASSES}; ::InitCommonControlsEx(&controls); ::DialogBoxParamW(ModuleInstance(), @@ -627,6 +704,7 @@ void ShowConfigReferenceDialog() { } void ShowAboutDialog() { + const ScopedShortcutHandlingPause pause; std::wstring summary = L"VSCode Keymap NPP\r\n\r\n"; summary += L"VSCode Keymap NPP strict mode\r\n\r\n"; summary += L"- Enforces near 1:1 mappings where Notepad++ equivalents exist\r\n"; diff --git a/src/handlers/CursorHandlers.cpp b/src/handlers/CursorHandlers.cpp index f202fac..8ae82d6 100644 --- a/src/handlers/CursorHandlers.cpp +++ b/src/handlers/CursorHandlers.cpp @@ -152,17 +152,6 @@ sptr_t FindSelectionState(HWND editor, const SelectionState& selection) { return -1; } -sptr_t FindSelectionOnCaretLine(HWND editor, const SelectionState& selection) { - const sptr_t targetLine = EndpointLine(editor, selection.caret); - const sptr_t selectionCount = RunSci(editor, SCI_GETSELECTIONS); - for (sptr_t i = 0; i < selectionCount; ++i) { - if (EndpointLine(editor, GetSelectionState(editor, i).caret) == targetLine) { - return i; - } - } - return -1; -} - void SetSelectionState(HWND editor, sptr_t selectionIndex, const SelectionState& selection) { RunSci(editor, SCI_SETSELECTIONNCARET, static_cast(selectionIndex), selection.caret.position); RunSci(editor, SCI_SETSELECTIONNANCHOR, static_cast(selectionIndex), selection.anchor.position); @@ -314,7 +303,7 @@ bool InsertCursorOnAdjacentLine(HWND editor, sptr_t lineDelta) { return true; } - const sptr_t existingSelection = FindSelectionOnCaretLine(editor, *movedSelection); + const sptr_t existingSelection = FindSelectionState(editor, *movedSelection); if (existingSelection >= 0) { RunSci(editor, SCI_SETMAINSELECTION, static_cast(existingSelection)); RunSci(editor, SCI_SCROLLCARET); From ba94e3c71f63084e82f72b195d37759592d039f5 Mon Sep 17 00:00:00 2001 From: NULL0B Date: Fri, 1 May 2026 19:10:30 -0400 Subject: [PATCH 3/3] fix(bindings): keep caret in duplicated line for copy-line actions Route Shift+Alt+Down and Shift+Alt+Up through custom handlers so copy-line behavior leaves the caret in the duplicated line for empty selections. Update the keybinding manifest and regenerate the embedded bindings and coverage report so the generated runtime tables stay aligned with the new custom action. --- data/keybinding-mappings.json | 8 +++---- docs/VSCodeKeybindingCoverage.MD | 4 ++-- src/GeneratedBindings.inc | 6 ++--- src/handlers/ActionHandlers.cpp | 3 +++ src/handlers/ActionHandlers.h | 1 + src/handlers/CustomHandlers.h | 1 + src/handlers/LineHandlers.cpp | 39 ++++++++++++++++++++++++++++++-- 7 files changed, 51 insertions(+), 11 deletions(-) diff --git a/data/keybinding-mappings.json b/data/keybinding-mappings.json index 5584d70..bcbea2b 100644 --- a/data/keybinding-mappings.json +++ b/data/keybinding-mappings.json @@ -58,16 +58,16 @@ { "command": "editor.action.copyLinesDownAction", "winKey": "Shift+Alt+Down", - "handler": "nppCommand", - "target": "IDM_EDIT_DUP_LINE", - "notes": "Duplicate line downward." + "handler": "custom", + "target": "DuplicateLineDown", + "notes": "Duplicate line downward and place the caret in the duplicate." }, { "command": "editor.action.copyLinesUpAction", "winKey": "Shift+Alt+Up", "handler": "custom", "target": "DuplicateLineUp", - "notes": "Duplicate line, then move duplicate upward." + "notes": "Duplicate line upward and place the caret in the duplicate." }, { "command": "undo", diff --git a/docs/VSCodeKeybindingCoverage.MD b/docs/VSCodeKeybindingCoverage.MD index 88d002a..5770ed5 100644 --- a/docs/VSCodeKeybindingCoverage.MD +++ b/docs/VSCodeKeybindingCoverage.MD @@ -21,8 +21,8 @@ Generated from https://code.visualstudio.com/docs/reference/default-keybindings. | `Ctrl+Shift+Enter` | Insert Line Above | `editor.action.insertLineBefore` | mapped | custom | Insert blank line above caret. | | `Alt+Down` | Move Line Down | `editor.action.moveLinesDownAction` | mapped | nppCommand | Move current line or selection down. | | `Alt+Up` | Move Line Up | `editor.action.moveLinesUpAction` | mapped | nppCommand | Move current line or selection up. | -| `Shift+Alt+Down` | Copy Line Down | `editor.action.copyLinesDownAction` | mapped | nppCommand | Duplicate line downward. | -| `Shift+Alt+Up` | Copy Line Up | `editor.action.copyLinesUpAction` | mapped | custom | Duplicate line, then move duplicate upward. | +| `Shift+Alt+Down` | Copy Line Down | `editor.action.copyLinesDownAction` | mapped | custom | Duplicate line downward and place the caret in the duplicate. | +| `Shift+Alt+Up` | Copy Line Up | `editor.action.copyLinesUpAction` | mapped | custom | Duplicate line upward and place the caret in the duplicate. | | `Ctrl+Z` | Undo | `undo` | mapped | nativePassThrough | Notepad++ already matches VS Code for undo. | | `Ctrl+Y` | Redo | `redo` | mapped | nativePassThrough | Notepad++ already matches VS Code for redo. | | `Ctrl+D` | Add Selection To Next Find Match | `editor.action.addSelectionToNextFindMatch` | mapped | nppCommand | Add next match to multiselection. | diff --git a/src/GeneratedBindings.inc b/src/GeneratedBindings.inc index 32dc7b4..1f3e268 100644 --- a/src/GeneratedBindings.inc +++ b/src/GeneratedBindings.inc @@ -14,8 +14,8 @@ const std::vector kBindingReferences = { {true, L"Basic Editing", L"", L"Insert Line Above", L"editor.action.insertLineBefore", L"Ctrl+Shift+Enter", L"mapped", L"custom", L"InsertLineAbove", L"Insert blank line above caret."}, {true, L"Basic Editing", L"", L"Move Line Down", L"editor.action.moveLinesDownAction", L"Alt+Down", L"mapped", L"nppCommand", L"IDM_EDIT_LINE_DOWN", L"Move current line or selection down."}, {true, L"Basic Editing", L"", L"Move Line Up", L"editor.action.moveLinesUpAction", L"Alt+Up", L"mapped", L"nppCommand", L"IDM_EDIT_LINE_UP", L"Move current line or selection up."}, - {true, L"Basic Editing", L"", L"Copy Line Down", L"editor.action.copyLinesDownAction", L"Shift+Alt+Down", L"mapped", L"nppCommand", L"IDM_EDIT_DUP_LINE", L"Duplicate line downward."}, - {true, L"Basic Editing", L"", L"Copy Line Up", L"editor.action.copyLinesUpAction", L"Shift+Alt+Up", L"mapped", L"custom", L"DuplicateLineUp", L"Duplicate line, then move duplicate upward."}, + {true, L"Basic Editing", L"", L"Copy Line Down", L"editor.action.copyLinesDownAction", L"Shift+Alt+Down", L"mapped", L"custom", L"DuplicateLineDown", L"Duplicate line downward and place the caret in the duplicate."}, + {true, L"Basic Editing", L"", L"Copy Line Up", L"editor.action.copyLinesUpAction", L"Shift+Alt+Up", L"mapped", L"custom", L"DuplicateLineUp", L"Duplicate line upward and place the caret in the duplicate."}, {false, L"Basic Editing", L"", L"Undo", L"undo", L"Ctrl+Z", L"mapped", L"nativePassThrough", L"", L"Notepad++ already matches VS Code for undo."}, {false, L"Basic Editing", L"", L"Redo", L"redo", L"Ctrl+Y", L"mapped", L"nativePassThrough", L"", L"Notepad++ already matches VS Code for redo."}, {true, L"Basic Editing", L"", L"Add Selection To Next Find Match", L"editor.action.addSelectionToNextFindMatch", L"Ctrl+D", L"mapped", L"nppCommand", L"IDM_EDIT_MULTISELECTNEXT", L"Add next match to multiselection."}, @@ -179,7 +179,7 @@ const std::vector kShortcutBindings = { {{true, false, true, VK_RETURN}, ActionKind::InsertLineAbove, 0, 5}, // Ctrl+Shift+Enter -> editor.action.insertLineBefore {{false, true, false, VK_DOWN}, ActionKind::NppCommand, IDM_EDIT_LINE_DOWN, 6}, // Alt+Down -> editor.action.moveLinesDownAction {{false, true, false, VK_UP}, ActionKind::NppCommand, IDM_EDIT_LINE_UP, 7}, // Alt+Up -> editor.action.moveLinesUpAction - {{false, true, true, VK_DOWN}, ActionKind::NppCommand, IDM_EDIT_DUP_LINE, 8}, // Shift+Alt+Down -> editor.action.copyLinesDownAction + {{false, true, true, VK_DOWN}, ActionKind::DuplicateLineDown, 0, 8}, // Shift+Alt+Down -> editor.action.copyLinesDownAction {{false, true, true, VK_UP}, ActionKind::DuplicateLineUp, 0, 9}, // Shift+Alt+Up -> editor.action.copyLinesUpAction {{true, false, false, 'D'}, ActionKind::NppCommand, IDM_EDIT_MULTISELECTNEXT, 12}, // Ctrl+D -> editor.action.addSelectionToNextFindMatch {{true, false, false, 'U'}, ActionKind::NppCommand, IDM_EDIT_MULTISELECTUNDO, 14}, // Ctrl+U -> cursorUndo diff --git a/src/handlers/ActionHandlers.cpp b/src/handlers/ActionHandlers.cpp index f3cb23f..66de92c 100644 --- a/src/handlers/ActionHandlers.cpp +++ b/src/handlers/ActionHandlers.cpp @@ -22,6 +22,9 @@ bool ExecuteAction(ActionKind kind, int id, const ActionContext& context) { RunSci(context.editor, static_cast(id)); return true; + case ActionKind::DuplicateLineDown: + return ExecuteDuplicateLineDown(context); + case ActionKind::DuplicateLineUp: return ExecuteDuplicateLineUp(context); diff --git a/src/handlers/ActionHandlers.h b/src/handlers/ActionHandlers.h index f781718..34e84d8 100644 --- a/src/handlers/ActionHandlers.h +++ b/src/handlers/ActionHandlers.h @@ -21,6 +21,7 @@ inline bool operator==(const KeyStroke& lhs, const KeyStroke& rhs) { enum class ActionKind { NppCommand, SciCommand, + DuplicateLineDown, DuplicateLineUp, CutAllowLine, CopyAllowLine, diff --git a/src/handlers/CustomHandlers.h b/src/handlers/CustomHandlers.h index 714a9f7..80fd622 100644 --- a/src/handlers/CustomHandlers.h +++ b/src/handlers/CustomHandlers.h @@ -9,6 +9,7 @@ struct CursorVirtualSpaceOptions { CursorVirtualSpaceOptions GetCursorVirtualSpaceOptions(); void SetCursorVirtualSpaceOptions(const CursorVirtualSpaceOptions& options); +bool ExecuteDuplicateLineDown(const ActionContext& context); bool ExecuteDuplicateLineUp(const ActionContext& context); bool ExecuteCutAllowLine(const ActionContext& context); bool ExecuteCopyAllowLine(const ActionContext& context); diff --git a/src/handlers/LineHandlers.cpp b/src/handlers/LineHandlers.cpp index c1b5330..bd36993 100644 --- a/src/handlers/LineHandlers.cpp +++ b/src/handlers/LineHandlers.cpp @@ -2,12 +2,47 @@ #include "menuCmdID.h" -bool ExecuteDuplicateLineUp(const ActionContext& context) { +namespace { + +bool DuplicateLineWithCaretTarget(const ActionContext& context, sptr_t targetLineOffset, bool moveDuplicateUp) { + const bool selectionEmpty = RunSci(context.editor, SCI_GETSELECTIONEMPTY) != 0; + if (!selectionEmpty) { + RunNppCommand(context.nppHandle, IDM_EDIT_DUP_LINE); + if (moveDuplicateUp) { + RunNppCommand(context.nppHandle, IDM_EDIT_LINE_UP); + } + return true; + } + + const sptr_t currentPos = RunSci(context.editor, SCI_GETCURRENTPOS); + const sptr_t currentLine = RunSci(context.editor, SCI_LINEFROMPOSITION, static_cast(currentPos)); + const sptr_t currentColumn = RunSci(context.editor, SCI_GETCOLUMN, static_cast(currentPos)); + RunNppCommand(context.nppHandle, IDM_EDIT_DUP_LINE); - RunNppCommand(context.nppHandle, IDM_EDIT_LINE_UP); + if (moveDuplicateUp) { + RunNppCommand(context.nppHandle, IDM_EDIT_LINE_UP); + } + + const sptr_t targetLine = currentLine + targetLineOffset; + const sptr_t targetPos = RunSci(context.editor, SCI_FINDCOLUMN, static_cast(targetLine), currentColumn); + if (targetPos >= 0) { + RunSci(context.editor, SCI_SETSEL, static_cast(targetPos), targetPos); + RunSci(context.editor, SCI_SCROLLCARET); + } + return true; } +} // namespace + +bool ExecuteDuplicateLineDown(const ActionContext& context) { + return DuplicateLineWithCaretTarget(context, 1, false); +} + +bool ExecuteDuplicateLineUp(const ActionContext& context) { + return DuplicateLineWithCaretTarget(context, 0, true); +} + bool ExecuteCutAllowLine(const ActionContext& context) { const bool selectionEmpty = RunSci(context.editor, SCI_GETSELECTIONEMPTY) != 0; RunSci(context.editor, selectionEmpty ? SCI_CUTALLOWLINE : SCI_CUT);