diff --git a/.vscodeignore b/.vscodeignore index bfd0dc7..f8afa34 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,3 +1,4 @@ +images/** .vscode/** .vscode-test-web/** src/** diff --git a/README.md b/README.md index df9b300..e78d142 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,6 @@ [![codecov](https://codecov.io/gh/haberdashPI/vscode-master-key/graph/badge.svg?token=099XZY1KR9)](https://codecov.io/gh/haberdashPI/vscode-master-key) [![Code Style: Google](https://img.shields.io/badge/code%20style-google-blueviolet.svg)](https://github.com/google/gts) -**TODO**: doc badges - -> [!WARNING] -> 🚧 Master Key is still under construction. 🚧 -> -> The README is a WIP document that will eventually reflect the intended state for release 0.3.0 at which point this extension will be published to VSCode's extension marketplace. For now expect to find missing features, a variety of bugs and incomplete documentation. - Master key helps you to learn, create and use powerful keybindings in [VSCode](https://code.visualstudio.com/). If you want to improve your text editing super powers in VSCode, Master Key might just be the tool for you. @@ -26,65 +19,98 @@ The easiest way to get started is to activate the built-in keybindings that come ## Examples -Master Key includes the following features: +### Discoverability Features + +#### Visual documentation of keybindings + +Learn and review your bindings on a keyboard layout + +![example of visual docs](images/readme/visualdoc.jpg) -**TODO**: insert example gif of each feature below +#### Cheet sheet of keybindings + +Review your bindings in a cheet sheet organized by theme + +![example of cheet sheet](images/readme/cheatsheet.png) + +#### Keybinding hints + +See a quick pick palette of possible bindings for the current mode and prefix of keys already pressed. + +![example of palette](images/readme/palette.png) + +The example above shows the bindings available after pressing `m` in the Larkin keybinding +set that is included with Master Key. ### Editing Features -Here are some of the cool features that come with the built-in `Larkin` keybindings provided by Master Key with the help of [selection utilities](https://github.com/haberdashPI/vscode-selection-utilities). These bindings following in the footsteps of Vim, Kakaune and Helix. +Here are some of the cool editing features that come with the built-in `Larkin` keybindings provided by Master Key with the help of [selection utilities](https://github.com/haberdashPI/vscode-selection-utilities). These bindings follow in the footsteps of Vim, Kakaune and Helix. #### Move by Object -Select by word, line, block and more. Expand by indent, quotes and brackets. +Select by word, line, paragraph and more. + +![examples of moving by word, line and paragraph](images/readme/selectby.webp) + +Expand by indent, quotes and brackets. + +![examples of expanding by indent, quote and brackets](images/readme/expandby.webp) Once you've selected the object, run commands to do stuff (e.g. delete/change/comment) #### Multi-Cursor Creation and Filtering -Quickly create multiple selections by splitting selections or searching within selections. -Filter out the ones you don't want either by some filter, or by manually picking out -one or more you don't want. +Quickly create multiple selections by splitting selections: + +![example of splitting a selection](images/readme/splitselect.webp) + +matching by word: + +![example of selecting by match](images/readme/selectmatch.webp) + +using saved selections: + +![example of using saved selections](images/readme/selectsaved.webp) + +Filter out the ones you don't want either by pattern: + +![example of filtering selections](images/readme/filterselect.webp) + +or manuall removal: + +![example of seelection deletion](images/readme/deleteselect.webp) #### Exchange Objects Swap selected objects with one another. +![example of text exchange](images/readme/exchangetext.webp) + #### Repeat Last Selection / Action Avoid lengthy key sequences by repeating the last action-related selection with "," and the last action with "." +![example of repeating select/action](images/readme/repeat.webp) + #### Record Commands Record longer command sequences and replay them. +![example of recording key sequence](images/readme/record.webp) + > [!NOTE] -> Command command recording comes with a few limitations, refer to the documentation for details +> Command recording comes with a few limitations, refer to the cheet sheet on Larkin macros for details #### Symmetric Insert Insert appropriate characters before and after each selection -### Discoverability Features - -#### Visual documentation of keybindings - -Learn and review your bindings on a keyboard layout - -**NOTE**: demo the ability to toggle bindings on the keys - -#### Cheet sheet of keybindings - -Review your bindings in a cheet sheet organized by theme - -#### Keybinding hints - -See a quick pick palette of possible bindings for the current mode and prefix of keys already pressed +![example of syminsert mode](images/readme/syminsert.webp) ### Keybinding Features > [!WARNING] -> For the initial release of Master Key, the Keybinding Features are not yet well documented. The main goal of the 0.3.0 release was to make the default keybindings accessible to new users. See the roadmap section below for details. The finer points of implementing your own keybindings will require some digging into source code and/or asking questions in the discussions section of this repo. +> For the initial release of Master Key, the Keybinding Features are not yet well documented. You can review the features when copying Larking to your own customization file. The main goal of the 0.3.0 release was to make the default keybindings accessible to new users. See the roadmap section below for details. The finer points of implementing your own keybindings will require some digging into source code and/or asking questions in the discussions section of this repo. When you create your own keybindings using Mater Key's special `.toml` keybinding format you get several powerful features that make it possible to easily create keybindings that would be difficult or impossible to implement without writing your own extension. @@ -92,21 +118,68 @@ When you create your own keybindings using Mater Key's special `.toml` keybindin Your bindings can be modalβ€”a special key (like escape) switches you to a different mode where all the keys on your keyboard can be used to issue commands specific to that mode. +```toml +[[bind]] +key = "j" +mode = "normal" +command = ... +``` + #### Parameteric Bindings Express an entire series of bindings using the `foreach` field. +```toml +[[bind]] +path = "edit.count" +foreach.num = ['{key: [0-9]}'] +name = "count {num}" +key = "{num}" +command = "master-key.updateCount" +args.value = "{num}" +``` + #### Stateful Bindings Update state with the `master-key.captureKeys`, `master-key.updateCount`, `master-key.setFlag` or `master-key.storeNamed` and then use this state in downstream commands using `computedArgs` instead of `args` in your keybinding. +```toml +[[bind]] +name = "between pair" +key = "m t" +description = """ +Select between pairs of the same N characters +""" +command = "runCommands" + +[[bind.args.commands]] +command = "master-key.captureKeys" +args.acceptAfter = 1 + +[[bind.args.commands]] +command = "selection-utilities.selectBetween" +computedArgs.str = "captured" +args.inclusive = false +``` + #### Record and Repeat Commands Master key records recent key presses, allowing you to create commands that quickly repeat a previous sequence using `master-key.replayFromHistory` or `master-key.pushHistoryToStack` and `master-key.replayFromStack`. You can disable key press recording by setting `master-key.maxCommandHistory` to 0 in your settings. +```toml +[[bind]] +key = ";" +name = "repeat motion" +repeat = "count" +command = "master-key.replayFromHistory" +args.at = "commandHistory[i].path.startsWith('edit.motion') && commandHistory[i].name != 'repeat motion'" +``` + #### Documented Bindings Of course, just like all of the built-in bindings in Master Key, you can document your bindings so that they show up legibly within the discoverability features above. +The toml file is a literate document used to generate the textual documentation +and all binding's names will show up in the visual documentation as appropriate. ## Customized Bindings @@ -153,9 +226,10 @@ And of course, there are many existing editors that Master Key draws inspiration ## Developer Notes -This repository relies on a working versions of `nvm` installed in bash and a npm version -matching the version specified in `.nvmrc`. You can satisfy this requirement by copying and -running the following in bash. +This repository was designed to be worked with in unix-like environemtns. No effort to +support development on Windows has been made. The setup relies on a working versions of +`nvm` installed in bash and an npm version matching the version specified in `.nvmrc`. You +can satisfy this requirement by copying and running the following in bash. ```sh curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash # install nvm @@ -168,5 +242,5 @@ You can then install all dependencies for this project as follows: ```sh nvm use -npm i +npm ic ``` diff --git a/docview/style.css b/docview/style.css index 8e659dd..4d97d1a 100644 --- a/docview/style.css +++ b/docview/style.css @@ -111,6 +111,8 @@ .key-length-1-75 { flex: 1.75; } .key-length-2-25 { flex: 2.25; } .key-length-5 { flex: 7; } +.key-height-0-5 { height: calc(var(--key-height) / 2); } +/* .key-height-1 { height: var(--key-height) } */ .label { height: calc(var(--key-height) / 2 - var(--key-padding)); @@ -138,6 +140,10 @@ border-top-left-radius: var(--key-radius); } +.key.key-height-0-5 .label.bottom.no-top{ + height: calc(var(--key-height) / 2 - 2*var(--key-padding)) +} + .name { height: calc(var(--key-height) / 2 - var(--key-padding)); font-size: 0.6rem; @@ -167,3 +173,7 @@ border-top: var(--key-border-width) solid var(--vscode-foreground); border-top-right-radius: var(--key-radius); } + +.key.key-height-0-5 .name.bottom.no-top{ + height: calc(var(--key-height) / 2 - 2*var(--key-padding)) +} diff --git a/images/readme/cheatsheet.png b/images/readme/cheatsheet.png new file mode 100644 index 0000000..7fa89cf Binary files /dev/null and b/images/readme/cheatsheet.png differ diff --git a/images/readme/deleteselect.webp b/images/readme/deleteselect.webp new file mode 100644 index 0000000..76c2c63 Binary files /dev/null and b/images/readme/deleteselect.webp differ diff --git a/images/readme/exchangetext.webp b/images/readme/exchangetext.webp new file mode 100644 index 0000000..e1dd4b4 Binary files /dev/null and b/images/readme/exchangetext.webp differ diff --git a/images/readme/expandby.webp b/images/readme/expandby.webp new file mode 100644 index 0000000..823d3e7 Binary files /dev/null and b/images/readme/expandby.webp differ diff --git a/images/readme/filterselect.webp b/images/readme/filterselect.webp new file mode 100644 index 0000000..6c8a896 Binary files /dev/null and b/images/readme/filterselect.webp differ diff --git a/images/readme/palette.png b/images/readme/palette.png new file mode 100644 index 0000000..4d972a3 Binary files /dev/null and b/images/readme/palette.png differ diff --git a/images/readme/record.webp b/images/readme/record.webp new file mode 100644 index 0000000..5ac6637 Binary files /dev/null and b/images/readme/record.webp differ diff --git a/images/readme/repeat.webp b/images/readme/repeat.webp new file mode 100644 index 0000000..3fbe5a7 Binary files /dev/null and b/images/readme/repeat.webp differ diff --git a/images/readme/selectby.webp b/images/readme/selectby.webp new file mode 100644 index 0000000..9982104 Binary files /dev/null and b/images/readme/selectby.webp differ diff --git a/images/readme/selectmatch.webp b/images/readme/selectmatch.webp new file mode 100644 index 0000000..6a869a2 Binary files /dev/null and b/images/readme/selectmatch.webp differ diff --git a/images/readme/selectsaved.webp b/images/readme/selectsaved.webp new file mode 100644 index 0000000..6ed295b Binary files /dev/null and b/images/readme/selectsaved.webp differ diff --git a/images/readme/splitselect.webp b/images/readme/splitselect.webp new file mode 100644 index 0000000..69ec011 Binary files /dev/null and b/images/readme/splitselect.webp differ diff --git a/images/readme/syminsert.webp b/images/readme/syminsert.webp new file mode 100644 index 0000000..db8c63e Binary files /dev/null and b/images/readme/syminsert.webp differ diff --git a/images/readme/visualdoc.jpg b/images/readme/visualdoc.jpg new file mode 100644 index 0000000..5d0ecb4 Binary files /dev/null and b/images/readme/visualdoc.jpg differ diff --git a/notes.md b/notes.md index c0ebc0f..0c03f96 100644 --- a/notes.md +++ b/notes.md @@ -1,33 +1,22 @@ -## Optimization - -release 0.2.4 - -observation: from what I can tell of profiling, it doesn't look like master key is consuming many CPU cycles at all; which is great!! no reason to work on this further - ## More visual doc improvements -IMPROVEMENT: show escape/function key row in the visual key doc - -release 0.2.2 +release 0.2.5 - IMPROVEMENT: put some examples of cool features from `Larkin` in the README -- IMPROVEMENT: use `getExtension` or some such on each required extension, and offer to - install if it fails (does this work for any extension? or does `activate` have to return - something) - -release 0.2.3 - by the end of this milestone I'm satisfied with the documentation features of the package for an initial release ## Binding Cleanup -release 0.2.y +release 0.2.6 - feature: specify user-specific binding file, apart from activated keybindings - Split out any of the commands that are really custom for me that don't make sense to publish. -- Pair down some of the required extensions. -- Offer to install extensions? (maybe when a keybinding fails to run??) +- Pair down some of the required extensions? + +## Trailing fixes + +Working with release 0.2.6 for a while and mark sure there aren't an bugs to fix ## Before VSCode publish @@ -47,10 +36,18 @@ thoughts: things I must have to release: WHEN PUBLISHING: get this to work on both stores (the one from microsoft and the one that vscodium uses) -## Additional test coverage +## Stability / test coverage SMALL BUG: should 'esc' really be appended in the status bar since it cancels a prefix sequence... πŸ€” +SMALL BUG: I think there are issues when synching across machines and handling +storage of the keybindings + +NEW TESTS: keybindings +- tests for running various binding commands +- tests for selecting bindings +- tests for selecting extensions? + NEW TEST: store/restore named commands unit tests: edge cases with recording edits diff --git a/package.json b/package.json index 1e7de1c..5b103a4 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Master Key", "publisher": "haberdashPI", "description": "Master your keybindings with documentation, discoverability, modal bindings and expressive configuration", - "version": "0.2.4", + "version": "0.2.5", "repository": { "url": "https://github.com/haberdashPi/vscode-master-key" }, @@ -40,6 +40,11 @@ "category": "Master Key", "title": "Show Visual Documentation" }, + { + "command": "master-key.installRequiredExtensions", + "category": "Master Key", + "title": "Install Extensions Required by Keybindings" + }, { "command": "master-key.setVisualDocTopModifiers", "category": "Master Key", @@ -280,9 +285,5 @@ "yaml": "^2.3.4", "zod": "^3.22.4", "zod-validation-error": "^2.1.0" - }, - "main": "./dist/desktop/extension.js", - "env": { - "NODE_ENV": "wdio" } } diff --git a/presets/larkin.toml b/presets/larkin.toml index 1e656dc..be95021 100644 --- a/presets/larkin.toml +++ b/presets/larkin.toml @@ -7,10 +7,13 @@ # Larkin is the default keybinding set that ships with Master Key. It follows in the footsteps of Vim, Kakaune and Helix. -# Like its predecessors, Larkin makes use of modal keybindings: in the default mode (Normal) each key on the keyboard, even when you aren't pressing a modifier key, issues a command. You can exit this Normal mode, and return to behavior that is more familiar to users of VSCode (called insert mode) by typing `i` (and a variety of other commands: see [Simple Actions](#simple-actions)). +# Some key features: -# The overarching logic of these commands generally follow noun-then-verb logic logic. Motions generally cause some region of text to be selected, and then actions modify these selections. This is the inverse of vim's motions, but consistent with most modern editors, kakaune and helx: in Larkin you would first select a word (using `w`) and then delete it (`d`). This organization integrates well with many of the existing VSCode extensions which operate on selections or add commands that modify selections. +# - Commands are modal: most actions are available in `normal` mode, while typing occurs in `insert` mode +# - Command are organized around noun-verb ordering: e.g. `w` selects a word, and `d` will then deletes it. +# - Selection is sticky: once a selection is created, further commands tend to extend that selection until it is reset (using `v`). However a number of commands are defined to automatically reset the selection (e.g. `mw` creates a new selection around a word). +# # These bindings are named after the middle name of my first child (Larkin). #- NOTE: this file is used to generate both keybindings and to generate a markdown file that documents the bindings. When comments are prefixed with a `-` they are ignored in the final markdown output. All other comments will become part of the markdown output. Any [[bind]] entries unbroken by normal, markdown-displayed comments will show up in a table in the markdown output. Make sure that the normal comments do not break up TOML data in a way that means it is invalid TOML. @@ -19,16 +22,12 @@ name = "Larkin Key Bindings" version = "1.0" -#- NOTE: currently this field (`requiredExtensions`) has no functional effect, it merely serves as documentation. However, future version of Master Key may leverage the required extensions in some way to make them easy to install when they are missing - requiredExtensions = [ "dbankier.vscode-quick-select", "haberdashPI.vscode-select-by-indent", "haberdashPI.selection-utilities", - "haberdashPI.move-cursor-by-argument", "pustelto.bracketeer", "wmaurer.change-case", - "pranshuagrawal.toggle-case", "albymor.increment-selection", "pkief.markdown-checkbox", "stkb.rewrap", @@ -37,7 +36,6 @@ requiredExtensions = [ "koalamer.labeled-bookmarks", ] - [[mode]] name = "insert" lineNumbers = "on" @@ -125,8 +123,7 @@ hideInPalette = true prefixes = [] mode = ["!capture", "!insert"] description = """ -show command suggestsion within the context of the current mode and keybinding prefix -(if any keys have already been typed) +show command suggestions within the context of the current mode and keybinding prefix (if any). E.g. `TAB, ⇧;` in `normal` mode will show all `normal` command suggestions that start with `TAB`. """ command = "master-key.commandSuggestions" @@ -146,19 +143,18 @@ mode = "normal" description = "Show visual documentation for keybindings" command = "master-key.showVisualDoc" -# The suggestion palette can be triggered at any point during a key sequence. So if you type `m` (which is the prefix for all [Match Commands](#match-commands)) and then type `:` you will get a list of all available match commands. - # The visual keybinding documentation is modified by three additional commands that can be used to determine which modifiers are shown. -# - `Master Key: Toggle Visual Doc Modifier by frequency`: changes keybinding modifiers least to most common modifiers across all modifiers defined by Master Key. +# - `Master Key: Toggle Visual Doc Modifier by frequency`: changes keybinding modifiers from most to least common modifiers across all modifiers defined by the current keybindings. +# # - `Master Key: Toggle Visual Doc Modifier for Top of Key`: allows you to explicitly change the modifier shown on the top of a key. # - `Master Key: Toggle Visual Doc Modifier for Bottom of Key`: allows you to explicitly change the modifier shown on the bottom of a key. - +# # There are no keybindings for these commands by default. # ## Normal Mode -# The default mode in Larkin, is Normal. In this mode, instead of the keys entering text, all keys are commands that modify selections or perform actions on those selections. To exist normal mode you can use `i` to enter Insert mode, which returns VSCode keybindings to their normal state. +# The default mode in Larkin, is Normal. In this mode, instead of the keys entering text, all keys are commands that modify selections or perform actions on those selections. To exit normal mode you can use `i` to enter `insert` mode, which returns VSCode keybindings to their normal state (see [Simple actions](#simple-actions) for more ways to enter insert mode). # While in Normal model you will see a highlighted section with the text "normal" in the lower left hand corner of the status bar. @@ -178,7 +174,7 @@ combinedDescription = "Enter normal mode" key = "{key}" mode = [] hideInPalette = true -hideInDocs = true +hideInDocs = false command = "master-key.enterNormal" when = "!findWidgetVisible" prefixes = "" @@ -225,7 +221,7 @@ default.kind = "count" # These are the most common, simple motions that can be performed in Larkin. -# Selection behavior uses the following logic: motions that move more than one character generally select the text "under" the motion. If a selection already exists (e.g. from a previous motion) additional motions extend that selection. You can always reset the selection using `v`, and several command (e.g. `x`) operate on the next character rather than the current selection. +# Selection behavior uses the following logic: motions that move more than one character generally select the text "under" the motion. If a selection already exists (e.g. from a previous motion) additional motions extend that selection. You can always reset the selection using `v`, and several commands (e.g. `x`) operate on the next character rather than the current selection. [[path]] id = "edit.motion" @@ -600,12 +596,10 @@ args.boundary = "both" # ## Repeating Motions -# Like VIM, the numbers keys (0-9) can be typed as a prefix to most commands and serve as `count` +# Like VIM, the number keys (0-9) can be typed as a prefix to most commands and serve as `count` # argument to that command. In most cases this causes the command to be repeated `count` # times e.g. 2w would select the next two words start from the current cursor position. -# **TODO**: add a manual table here - [[bind]] path = "edit.count" foreach.num = ['{key: [0-9]}'] @@ -645,7 +639,7 @@ name = "repeat subject" description = """ Repeat the subject: a motion command that occurred right before an action. For instance `w` followed by `d` selects a word and deletes it. The `w` command would be the -last subject until some new action is run after `d`. +last subject until some new action is run after `d`. See also `.` which repeats the last action. """ key = "," command = "master-key.replayFromHistory" @@ -739,7 +733,7 @@ args.register = "search" [[bind]] path = "edit.motion.search" key = "shift+/" -name = "search bk" +name = "search ←" description = "search backwards" combinedName = "search β†’ (←)" args.offset = "start" @@ -1159,13 +1153,9 @@ args.commands = ["jupyter.gotoPrevCellInFile", "jupyter.selectCell"] # ## Match Commands -# Match commands select some syntactical region of text, e.g. in or around parenthesis -# brackets, idnent level etc...where the `g` prefixed commands move forward or backward -# these commands move both the start and the end of the selection away from -# the active cursor position. +# Match commands select some syntactical region of text, e.g. in or around parenthesis, brackets, indent level etc... Where the `g` prefixed commands move forward or backward, these commands move both the start and the end of the selection away from the active cursor position. Repeating the command moves to the next (or previous) match, depending on the command. -# If you accidentally select `around` instead of `in`, you can revise your selection using -# `R` to narrow to non-white space or `z` to narrow to a subword (e.g. excludes `_`) +# If you accidentally select `around` instead of `in`, you can revise your selection using `R` to narrow to non-white space or `z` to narrow to a subword (e.g. excludes `_`) [[path]] id = "edit.motion.match" @@ -1613,6 +1603,8 @@ mode = ["normal", "selectedit", "syminsert"] [[bind]] path = "edit.action.basic" key = "a" +name = "append" +description = "insert after cursor" mode = ["normal", "selectedit", "syminsert"] command = "runCommands" args.commands = [ @@ -2113,13 +2105,6 @@ description = "Swap first character to upper case" key = "` u" command = "extension.changeCase.upperFirst" -[[bind]] -path = "edit.action.capitals" -name = 'toggle' -description = "Toggle through all possible cases" -key = "` `" -command = "extension.toggleCase" - # ## "Do" Actions # These keys are all organized under the `space` key and they "do" something. These are generally more elaborate editing operations in the current editor pane. @@ -2204,8 +2189,10 @@ command = "master-key.captureKeys" args.acceptAfter = 1 [[bind.args.commands]] -command = "type" -computedArgs.text = "captured" +command = "selection-utilities.insertAround" +computedArgs.before = "braces[captured].before || captured" +computedArgs.after = "braces[captured].after || captured" +args.followCursor = true [[bind]] path = "edit.action.do" @@ -2925,8 +2912,7 @@ command = "workbench.action.editor.previousChange" # ## Window Manipulation -# These are commands that interact with VSCode's user-interface in some way. -# rather than edit or moving around in the editor pane. +# These are commands that interact with VSCode's user-interface in some way, rather than editing or moving around in the editor pane. [[path]] id = "window" @@ -3166,7 +3152,12 @@ command = "workbench.action.debug.stepOut" # ## Select-edit Mode -# Select-edit mode allows you make a variety of kakune-like modifications to multiple cursors. This lends it self to work flows where you select, e.g. ever line of text as a separate cursor and then filter out the lines you don't want, or e.g. split an array by the comma delimiters and edit each element of the array. +# Select-edit mode allows you make a variety of kakune-like modifications of one or more cursors. This lends it self to work flows such as: +# +# - select every line of text as a separate cursor, then filter out the lines you don't want +# - split an array by comma delimiters and edit each element of the array. +# - save a single selection to a list of selections to use for later and +# then move the cursor to the next selection you wish to add [[path]] id = "edit.select_edit" @@ -3504,6 +3495,7 @@ command = "selection-utilities.excludeByRegex" path = "edit.select_edit" key = "o" name = "active to front" +combinedKey = "o/shift+o" combinedName = "active to start/end" combinedDescription = "move cursor to start/end of selection" description = "move cursor to start of selection" @@ -3654,6 +3646,7 @@ computedArgs.count = "count" path = "edit.motion.symmetric" key = "o" name = "active to front" +combinedKey = "o/shift+o" combinedName = "active to start/end" combinedDescription = "move cursor to start/end of selection" description = "move cursor to start of selection" diff --git a/src/web/commands/visualKeyDoc.ts b/src/web/commands/visualKeyDoc.ts index c11e1ea..0f2fd86 100644 --- a/src/web/commands/visualKeyDoc.ts +++ b/src/web/commands/visualKeyDoc.ts @@ -17,16 +17,36 @@ import {Map} from 'immutable'; interface IKeyTemplate { name?: string; length?: string; - modifier?: true; + modifier?: boolean; + firstRow?: boolean; + height?: string; } interface IKeyRow { top?: string; bottom?: string; length?: string; + height?: string; } +const KEY_ABBREV: Record = {}; + const keyRowsTemplate: IKeyTemplate[][] = [ + [ + {name: 'ESC', height: '0-5', firstRow: true}, + {name: 'F1', height: '0-5', firstRow: true}, + {name: 'F2', height: '0-5', firstRow: true}, + {name: 'F3', height: '0-5', firstRow: true}, + {name: 'F4', height: '0-5', firstRow: true}, + {name: 'F5', height: '0-5', firstRow: true}, + {name: 'F6', height: '0-5', firstRow: true}, + {name: 'F7', height: '0-5', firstRow: true}, + {name: 'F8', height: '0-5', firstRow: true}, + {name: 'F9', height: '0-5', firstRow: true}, + {name: 'F10', height: '0-5', firstRow: true}, + {name: 'F11', height: '0-5', firstRow: true}, + {name: 'F12', height: '0-5', firstRow: true}, + ], [ {name: '`'}, {name: '1'}, @@ -108,16 +128,18 @@ function keyRows( ): IKeyRow[][] { return keyRowsTemplate.map(row => row.map(key => { - if (key.name && !key.modifier) { + if (key.name && !key.modifier && !key.firstRow) { return { top: topModifier?.join() + key.name, bottom: bottomModifier?.join() + key.name, length: key.length, + height: key.height, }; } else { return { - top: key.name, + bottom: key.name, length: key.length, + height: key.height, }; } }) @@ -224,7 +246,7 @@ export class DocViewProvider implements vscode.WebviewViewProvider { } let curBindings = allBindings.filter( - filterBindingFn(values.get(MODE), values.get(PREFIX_CODE)) + filterBindingFn(values.get(MODE), values.get(PREFIX_CODE), true) ); curBindings = reverse(uniqBy(reverse(curBindings), b => b.args.key)); this._bindingMap = {}; @@ -242,14 +264,17 @@ export class DocViewProvider implements vscode.WebviewViewProvider { for (const row of keyRows(this._topModifier, this._bottomModifier)) { for (const key of row) { if (key.top) { - this._keymap[i++] = {label: key.top, ...this._bindingMap[key.top]}; + this._keymap[i++] = { + label: key.top, + ...this._bindingMap[get(KEY_ABBREV, key.top, key.top)], + }; } else { this._keymap[i++] = {empty: true}; } if (key.bottom) { this._keymap[i++] = { label: key.bottom, - ...this._bindingMap[key.bottom], + ...this._bindingMap[get(KEY_ABBREV, key.bottom, key.bottom)], }; } else { this._keymap[i++] = {empty: true}; @@ -324,8 +349,9 @@ export class DocViewProvider implements vscode.WebviewViewProvider { const topId = num++; const bottomId = num++; const topLabel = get(key, 'top', ''); + const noTop = !(topLabel || false); return ` -
+
${ topLabel && ` @@ -335,10 +361,10 @@ export class DocViewProvider implements vscode.WebviewViewProvider { ` } -
+
${get(key, 'bottom', '')}
-
+
`; diff --git a/src/web/keybindings/docParsing.ts b/src/web/keybindings/docParsing.ts index 3395519..6ad441a 100644 --- a/src/web/keybindings/docParsing.ts +++ b/src/web/keybindings/docParsing.ts @@ -154,7 +154,7 @@ export function asBindingTable(parsed: BindingItem[]) { const mode = asArray(item.mode) .map(m => '`' + m + '`') .join(', '); - result += `|${mode}| ${key} |${item.args.name}|${stripNewlines(item.args.description || '')}|\n`; + result += `|${mode}|${key}|${item.args.name}|${stripNewlines(item.args.description || '')}|\n`; } result += '\n'; return result; diff --git a/src/web/keybindings/index.ts b/src/web/keybindings/index.ts index eb7f868..2736dfe 100644 --- a/src/web/keybindings/index.ts +++ b/src/web/keybindings/index.ts @@ -87,9 +87,16 @@ function formatBindings(name: string, items: IConfigKeyBinding[]) { ); } -export function filterBindingFn(mode?: string, prefixCode?: number) { +export function filterBindingFn( + mode?: string, + prefixCode?: number, + forVisualDoc: boolean = false +) { return function filterBinding(binding: IConfigKeyBinding) { - if (binding.args.hideInPalette) { + if (binding.args.hideInPalette && !forVisualDoc) { + return false; + } + if (binding.args.hideInDocs && forVisualDoc) { return false; } if (isSingleCommand(binding.args.do, 'master-key.ignore')) { @@ -403,12 +410,90 @@ async function copyBindingsToNewFile() { } } +async function handleRequireExtensions(bindings: Bindings) { + const items: vscode.QuickPickItem[] = bindings.requiredExtensions.map(id => { + const exts = vscode.extensions.all.filter(x => x.id === id); + if (exts.length > 0) { + return {label: id, detail: 'installed', picked: true}; + } else { + return { + label: id, + detail: 'uninstalled', + picked: false, + buttons: [ + {iconPath: new vscode.ThemeIcon('eye'), tooltip: 'view extension'}, + ], + }; + } + }); + if (items.length === 0 || items.every(it => it.detail === 'installed')) { + return; + } + items.unshift({ + label: 'Install All', + picked: false, + alwaysShow: true, + }); + const picker = vscode.window.createQuickPick(); + picker.title = `Extensions Used by ${bindings.name}`; + picker.items = items; + picker.canSelectMany = true; + picker.placeholder = + 'Indicate extensions to install (review them by clicking on the eye to the right)'; + picker.ignoreFocusOut = true; + + picker.onDidTriggerItemButton(e => { + vscode.commands.executeCommand('workbench.extensions.search', e.item.label); + }); + + let resolveFn: () => void; + const pickPromise = new Promise((res, _rej) => { + resolveFn = res; + }); + picker.onDidHide(_ => { + resolveFn(); + picker.dispose(); + }); + let accept = false; + picker.onDidAccept(_ => { + accept = true; + picker.hide(); + }); + picker.show(); + + await pickPromise; + if (!accept) { + return; + } + + if (picker.selectedItems.some(it => it.label === 'Install All')) { + for (const item of picker.items) { + if (item.detail === 'uninstalled') { + await vscode.commands.executeCommand( + 'workbench.extensions.installExtension', + item.label + ); + } + } + } + + for (const item of picker.selectedItems) { + if (item.detail === 'uninstalled') { + await vscode.commands.executeCommand( + 'workbench.extensions.installExtension', + item.label + ); + } + } +} + export async function selectPreset(preset?: Preset) { if (!preset) { preset = await queryPreset(); } if (preset) { const label = await createBindings(preset.bindings); + await handleRequireExtensions(preset.bindings); await insertKeybindingsIntoConfig( preset.bindings.name || 'none', label, @@ -496,6 +581,12 @@ export async function activate(context: vscode.ExtensionContext) { copyCommandResultIntoBindingFile('workbench.action.openDefaultKeybindingsFile') ) ); + context.subscriptions.push( + vscode.commands.registerCommand( + 'master-key.installRequiredExtensions', + handleRequireExtensions + ) + ); context.subscriptions.push( vscode.commands.registerCommand('master-key.removePreset', removeKeybindings) ); diff --git a/src/web/keybindings/processing.ts b/src/web/keybindings/processing.ts index 2d3a7c4..f7ec3e6 100644 --- a/src/web/keybindings/processing.ts +++ b/src/web/keybindings/processing.ts @@ -39,6 +39,7 @@ export interface Bindings { mode: Record; bind: IConfigKeyBinding[]; docs: string; + requiredExtensions: string[]; } export function processBindings(spec: FullBindingSpec): [Bindings, string[]] { @@ -63,6 +64,7 @@ export function processBindings(spec: FullBindingSpec): [Bindings, string[]] { define: definitions, mode: mapByName(spec.mode), bind: configItems, + requiredExtensions: spec.header.requiredExtensions || [], docs, }; return [result, problems]; @@ -505,6 +507,7 @@ function expandDocsToDuplicates(items: BindingItem[]) { const oldDocs = itemDocs[key] || {}; itemDocs[key] = merge( pick(item.args, [ + 'name', 'description', 'combinedName', 'combinedDescription', @@ -656,7 +659,7 @@ function updatePrefixItemAndPrefix( ], path: item.args.path, name: automated ? 'prefix' : item.args.name, - kind: automated ? 'prefix' : item.args.name || 'prefix', + kind: automated ? 'prefix' : item.args.kind || 'prefix', priority: automated ? 0 : item.args.priority, hideInPalette: automated ? false : item.args.hideInPalette, hideInDocs: automated ? false : item.args.hideInPalette,