Browse Source

Add support for tab completion (#12411)

* Add support for tab completion

* make flake8 happy

* Add documentation
pull/12595/head 0.12.35
Zach White 6 months ago
committed by GitHub
parent
commit
588bcdc8ca
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 169 additions and 86 deletions
  1. +1
    -0
      docs/_summary.md
  2. +27
    -0
      docs/cli_tab_complete.md
  3. +12
    -2
      lib/python/qmk/cli/__init__.py
  4. +4
    -3
      lib/python/qmk/cli/c2json.py
  5. +2
    -1
      lib/python/qmk/cli/cformat.py
  6. +6
    -4
      lib/python/qmk/cli/compile.py
  7. +4
    -3
      lib/python/qmk/cli/flash.py
  8. +2
    -2
      lib/python/qmk/cli/generate/config_h.py
  9. +2
    -1
      lib/python/qmk/cli/generate/dfu_header.py
  10. +2
    -2
      lib/python/qmk/cli/generate/info_json.py
  11. +2
    -2
      lib/python/qmk/cli/generate/layouts.py
  12. +2
    -2
      lib/python/qmk/cli/generate/rules_mk.py
  13. +2
    -2
      lib/python/qmk/cli/info.py
  14. +2
    -1
      lib/python/qmk/cli/json2c.py
  15. +2
    -1
      lib/python/qmk/cli/kle2json.py
  16. +2
    -1
      lib/python/qmk/cli/lint.py
  17. +2
    -2
      lib/python/qmk/cli/list/keymaps.py
  18. +2
    -2
      lib/python/qmk/cli/new/keymap.py
  19. +11
    -49
      lib/python/qmk/decorators.py
  20. +25
    -1
      lib/python/qmk/keyboard.py
  21. +52
    -4
      lib/python/qmk/keymap.py
  22. +1
    -1
      requirements.txt
  23. +2
    -0
      util/qmk_tab_complete.sh

+ 1
- 0
docs/_summary.md View File

@ -29,6 +29,7 @@
* [Overview](cli.md)
* [Configuration](cli_configuration.md)
* [Commands](cli_commands.md)
* [Tab Completion](cli_tab_complete.md)
* Using QMK
* Guides


+ 27
- 0
docs/cli_tab_complete.md View File

@ -0,0 +1,27 @@
# Tab Completion for QMK
If you are using Bash 4.2 or later, Zsh, or FiSH you can enable Tab Completion for the QMK CLI. This will let you tab complete the names of flags, keyboards, files, and other `qmk` options.
## Setup
There are several ways you can setup tab completion.
### For Your User Only
Add this to the end of your `.profile` or `.bashrc`:
source ~/qmk_firmware/util/qmk_tab_complete.sh
If you put `qmk_firmware` into another location you will need to adjust this path.
### System Wide Symlink
If you want the tab completion available to all users of the system you can add a symlink to the `qmk_tab_complete.sh` script:
`ln -s ~/qmk_firmware/util/qmk_tab_complete.sh /etc/profile.d/qmk_tab_complete.sh`
### System Wide Copy
In some cases a symlink may not work. Instead you can copy the file directly into place. Be aware that updates to the tab complete script may happen from time to time, you will want to recopy the file periodically.
cp util/qmk_tab_complete.sh /etc/profile.d

+ 12
- 2
lib/python/qmk/cli/__init__.py View File

@ -4,7 +4,7 @@ We list each subcommand here explicitly because all the reliable ways of searchi
"""
import sys
from milc import cli
from milc import cli, __VERSION__
from . import c2json
from . import cformat
@ -47,5 +47,15 @@ from . import pytest
# void: 3.9
if sys.version_info[0] != 3 or sys.version_info[1] < 7:
cli.log.error('Your Python is too old! Please upgrade to Python 3.7 or later.')
print('Error: Your Python is too old! Please upgrade to Python 3.7 or later.')
exit(127)
milc_version = __VERSION__.split('.')
if int(milc_version[0]) < 2 and int(milc_version[1]) < 3:
from pathlib import Path
requirements = Path('requirements.txt').resolve()
print(f'Your MILC library is too old! Please upgrade: python3 -m pip install -U -r {str(requirements)}')
exit(127)

+ 4
- 3
lib/python/qmk/cli/c2json.py View File

@ -2,20 +2,21 @@
"""
import json
from argcomplete.completers import FilesCompleter
from milc import cli
import qmk.keymap
import qmk.path
from qmk.json_encoders import InfoJSONEncoder
from qmk.keyboard import keyboard_folder
from qmk.keyboard import keyboard_completer, keyboard_folder
@cli.argument('--no-cpp', arg_only=True, action='store_false', help='Do not use \'cpp\' on keymap.c')
@cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to')
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, required=True, help='The keyboard\'s name')
@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='The keyboard\'s name')
@cli.argument('-km', '--keymap', arg_only=True, required=True, help='The keymap\'s name')
@cli.argument('filename', arg_only=True, help='keymap.c file')
@cli.argument('filename', arg_only=True, completer=FilesCompleter('.c'), help='keymap.c file')
@cli.subcommand('Creates a keymap.json from a keymap.c file.')
def c2json(cli):
"""Generate a keymap.json from a keymap.c file.


+ 2
- 1
lib/python/qmk/cli/cformat.py View File

@ -3,6 +3,7 @@
import subprocess
from shutil import which
from argcomplete.completers import FilesCompleter
from milc import cli
from qmk.path import normpath
@ -33,7 +34,7 @@ def cformat_run(files, all_files):
@cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.')
@cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.')
@cli.argument('files', nargs='*', arg_only=True, help='Filename(s) to format.')
@cli.argument('files', nargs='*', arg_only=True, completer=FilesCompleter('.c'), help='Filename(s) to format.')
@cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True)
def cformat(cli):
"""Format C code according to QMK's style.


+ 6
- 4
lib/python/qmk/cli/compile.py View File

@ -2,17 +2,19 @@
You can compile a keymap already in the repo or using a QMK Configurator export.
"""
from argcomplete.completers import FilesCompleter
from milc import cli
import qmk.path
from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json
from qmk.keyboard import keyboard_folder
from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.keymap import keymap_completer
@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), help='The configurator export to compile')
@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.')
@cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Ignored when a configurator export is supplied.')
@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), completer=FilesCompleter('.json'), help='The configurator export to compile')
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.')
@cli.argument('-km', '--keymap', completer=keymap_completer, help='The keymap to build a firmware for. Ignored when a configurator export is supplied.')
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.")
@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.")
@cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.")


+ 4
- 3
lib/python/qmk/cli/flash.py View File

@ -4,12 +4,13 @@ You can compile a keymap already in the repo or using a QMK Configurator export.
A bootloader must be specified.
"""
from argcomplete.completers import FilesCompleter
from milc import cli
import qmk.path
from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json
from qmk.keyboard import keyboard_folder
from qmk.keyboard import keyboard_completer, keyboard_folder
def print_bootloader_help():
@ -30,11 +31,11 @@ def print_bootloader_help():
cli.echo('For more info, visit https://docs.qmk.fm/#/flashing')
@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), help='The configurator export JSON to compile.')
@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), completer=FilesCompleter('.json'), help='The configurator export JSON to compile.')
@cli.argument('-b', '--bootloaders', action='store_true', help='List the available bootloaders.')
@cli.argument('-bl', '--bootloader', default='flash', help='The flash command, corresponding to qmk\'s make options of bootloaders.')
@cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.')
@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='The keyboard to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.')
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.')
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.")
@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.")
@cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.")


+ 2
- 2
lib/python/qmk/cli/generate/config_h.py View File

@ -8,7 +8,7 @@ from milc import cli
from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.info import info_json
from qmk.json_schema import json_load
from qmk.keyboard import keyboard_folder
from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.path import is_keyboard, normpath
@ -75,7 +75,7 @@ def matrix_pins(matrix_pins):
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to generate config.h for.')
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.')
@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
@automagic_keyboard
@automagic_keymap


+ 2
- 1
lib/python/qmk/cli/generate/dfu_header.py View File

@ -6,11 +6,12 @@ from milc import cli
from qmk.decorators import automagic_keyboard
from qmk.info import info_json
from qmk.path import is_keyboard, normpath
from qmk.keyboard import keyboard_completer
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
@cli.argument('-kb', '--keyboard', help='Keyboard to generate LUFA Keyboard.h for.')
@cli.argument('-kb', '--keyboard', completer=keyboard_completer, help='Keyboard to generate LUFA Keyboard.h for.')
@cli.subcommand('Used by the make system to generate LUFA Keyboard.h from info.json', hidden=True)
@automagic_keyboard
def generate_dfu_header(cli):


+ 2
- 2
lib/python/qmk/cli/generate/info_json.py View File

@ -11,7 +11,7 @@ from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.info import info_json
from qmk.json_encoders import InfoJSONEncoder
from qmk.json_schema import load_jsonschema
from qmk.keyboard import keyboard_folder
from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.path import is_keyboard
@ -41,7 +41,7 @@ def strip_info_json(kb_info_json):
return validator(kb_info_json)
@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to show info for.')
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.')
@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.')
@cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True)
@automagic_keyboard


+ 2
- 2
lib/python/qmk/cli/generate/layouts.py View File

@ -5,7 +5,7 @@ from milc import cli
from qmk.constants import COL_LETTERS, ROW_LETTERS
from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.info import info_json
from qmk.keyboard import keyboard_folder
from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.path import is_keyboard, normpath
usb_properties = {
@ -17,7 +17,7 @@ usb_properties = {
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to generate config.h for.')
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.')
@cli.subcommand('Used by the make system to generate layouts.h from info.json', hidden=True)
@automagic_keyboard
@automagic_keymap


+ 2
- 2
lib/python/qmk/cli/generate/rules_mk.py View File

@ -8,7 +8,7 @@ from milc import cli
from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.info import info_json
from qmk.json_schema import json_load
from qmk.keyboard import keyboard_folder
from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.path import is_keyboard, normpath
@ -39,7 +39,7 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
@cli.argument('-e', '--escape', arg_only=True, action='store_true', help="Escape spaces in quiet mode")
@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to generate config.h for.')
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.')
@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
@automagic_keyboard
@automagic_keymap


+ 2
- 2
lib/python/qmk/cli/info.py View File

@ -10,7 +10,7 @@ from milc import cli
from qmk.json_encoders import InfoJSONEncoder
from qmk.constants import COL_LETTERS, ROW_LETTERS
from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.keyboard import keyboard_folder, render_layouts, render_layout
from qmk.keyboard import keyboard_completer, keyboard_folder, render_layouts, render_layout
from qmk.keymap import locate_keymap
from qmk.info import info_json
from qmk.path import is_keyboard
@ -124,7 +124,7 @@ def print_text_output(kb_info_json):
show_keymap(kb_info_json, False)
@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to show info for.')
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.')
@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.')
@cli.argument('-l', '--layouts', action='store_true', help='Render the layouts.')
@cli.argument('-m', '--matrix', action='store_true', help='Render the layouts with matrix information.')


+ 2
- 1
lib/python/qmk/cli/json2c.py View File

@ -2,6 +2,7 @@
"""
import json
from argcomplete.completers import FilesCompleter
from milc import cli
import qmk.keymap
@ -10,7 +11,7 @@ import qmk.path
@cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to')
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
@cli.argument('filename', type=qmk.path.FileType('r'), arg_only=True, help='Configurator JSON file')
@cli.argument('filename', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file')
@cli.subcommand('Creates a keymap.c from a QMK Configurator export.')
def json2c(cli):
"""Generate a keymap.c from a configurator export.


+ 2
- 1
lib/python/qmk/cli/kle2json.py View File

@ -4,6 +4,7 @@ import json
import os
from pathlib import Path
from argcomplete.completers import FilesCompleter
from milc import cli
from kle2xy import KLE2xy
@ -11,7 +12,7 @@ from qmk.converter import kle2qmk
from qmk.json_encoders import InfoJSONEncoder
@cli.argument('filename', help='The KLE raw txt to convert')
@cli.argument('filename', completer=FilesCompleter('.json'), help='The KLE raw txt to convert')
@cli.argument('-f', '--force', action='store_true', help='Flag to overwrite current info.json')
@cli.subcommand('Convert a KLE layout to a Configurator JSON', hidden=False if cli.config.user.developer else True)
def kle2json(cli):


+ 2
- 1
lib/python/qmk/cli/lint.py View File

@ -4,12 +4,13 @@ from milc import cli
from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.info import info_json
from qmk.keyboard import keyboard_completer
from qmk.keymap import locate_keymap
from qmk.path import is_keyboard, keyboard
@cli.argument('--strict', action='store_true', help='Treat warnings as errors.')
@cli.argument('-kb', '--keyboard', help='The keyboard to check.')
@cli.argument('-kb', '--keyboard', completer=keyboard_completer, help='The keyboard to check.')
@cli.argument('-km', '--keymap', help='The keymap to check.')
@cli.subcommand('Check keyboard and keymap for common mistakes.')
@automagic_keyboard


+ 2
- 2
lib/python/qmk/cli/list/keymaps.py View File

@ -4,10 +4,10 @@ from milc import cli
import qmk.keymap
from qmk.decorators import automagic_keyboard
from qmk.keyboard import keyboard_folder
from qmk.keyboard import keyboard_completer, keyboard_folder
@cli.argument("-kb", "--keyboard", type=keyboard_folder, help="Specify keyboard name. Example: 1upkeyboards/1up60hse")
@cli.argument("-kb", "--keyboard", type=keyboard_folder, completer=keyboard_completer, help="Specify keyboard name. Example: 1upkeyboards/1up60hse")
@cli.subcommand("List the keymaps for a specific keyboard")
@automagic_keyboard
def list_keymaps(cli):


+ 2
- 2
lib/python/qmk/cli/new/keymap.py View File

@ -5,11 +5,11 @@ from pathlib import Path
import qmk.path
from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.keyboard import keyboard_folder
from qmk.keyboard import keyboard_completer, keyboard_folder
from milc import cli
@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Specify keyboard name. Example: 1upkeyboards/1up60hse')
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Specify keyboard name. Example: 1upkeyboards/1up60hse')
@cli.argument('-km', '--keymap', help='Specify the name for the new keymap directory')
@cli.subcommand('Creates a new keymap for the keyboard of your choosing')
@automagic_keyboard


+ 11
- 49
lib/python/qmk/decorators.py View File

@ -1,13 +1,12 @@
"""Helpful decorators that subcommands can use.
"""
import functools
from pathlib import Path
from time import monotonic
from milc import cli
from qmk.keymap import is_keymap_dir
from qmk.path import is_keyboard, under_qmk_firmware
from qmk.keyboard import find_keyboard_from_dir
from qmk.keymap import find_keymap_from_dir
def automagic_keyboard(func):
@ -17,27 +16,13 @@ def automagic_keyboard(func):
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Check to make sure their copy of MILC supports config_source
if not hasattr(cli, 'config_source'):
cli.log.error("This subcommand requires a newer version of the QMK CLI. Please upgrade using `pip3 install --upgrade qmk` or your package manager.")
exit(1)
# Ensure that `--keyboard` was not passed and CWD is under `qmk_firmware/keyboards`
if cli.config_source[cli._entrypoint.__name__]['keyboard'] != 'argument':
relative_cwd = under_qmk_firmware()
if relative_cwd and len(relative_cwd.parts) > 1 and relative_cwd.parts[0] == 'keyboards':
# Attempt to extract the keyboard name from the current directory
current_path = Path('/'.join(relative_cwd.parts[1:]))
if 'keymaps' in current_path.parts:
# Strip current_path of anything after `keymaps`
keymap_index = len(current_path.parts) - current_path.parts.index('keymaps') - 1
current_path = current_path.parents[keymap_index]
keyboard = find_keyboard_from_dir()
if is_keyboard(current_path):
cli.config[cli._entrypoint.__name__]['keyboard'] = str(current_path)
cli.config_source[cli._entrypoint.__name__]['keyboard'] = 'keyboard_directory'
if keyboard:
cli.config[cli._entrypoint.__name__]['keyboard'] = keyboard
cli.config_source[cli._entrypoint.__name__]['keyboard'] = 'keyboard_directory'
return func(*args, **kwargs)
@ -51,36 +36,13 @@ def automagic_keymap(func):
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Check to make sure their copy of MILC supports config_source
if not hasattr(cli, 'config_source'):
cli.log.error("This subcommand requires a newer version of the QMK CLI. Please upgrade using `pip3 install --upgrade qmk` or your package manager.")
exit(1)
# Ensure that `--keymap` was not passed and that we're under `qmk_firmware`
if cli.config_source[cli._entrypoint.__name__]['keymap'] != 'argument':
relative_cwd = under_qmk_firmware()
if relative_cwd and len(relative_cwd.parts) > 1:
# If we're in `qmk_firmware/keyboards` and `keymaps` is in our path, try to find the keyboard name.
if relative_cwd.parts[0] == 'keyboards' and 'keymaps' in relative_cwd.parts:
current_path = Path('/'.join(relative_cwd.parts[1:])) # Strip 'keyboards' from the front
if 'keymaps' in current_path.parts and current_path.name != 'keymaps':
while current_path.parent.name != 'keymaps':
current_path = current_path.parent
cli.config[cli._entrypoint.__name__]['keymap'] = current_path.name
cli.config_source[cli._entrypoint.__name__]['keymap'] = 'keymap_directory'
# If we're in `qmk_firmware/layouts` guess the name from the community keymap they're in
elif relative_cwd.parts[0] == 'layouts' and is_keymap_dir(relative_cwd):
cli.config[cli._entrypoint.__name__]['keymap'] = relative_cwd.name
cli.config_source[cli._entrypoint.__name__]['keymap'] = 'layouts_directory'
# If we're in `qmk_firmware/users` guess the name from the userspace they're in
elif relative_cwd.parts[0] == 'users':
# Guess the keymap name based on which userspace they're in
cli.config[cli._entrypoint.__name__]['keymap'] = relative_cwd.parts[1]
cli.config_source[cli._entrypoint.__name__]['keymap'] = 'users_directory'
keymap_name, keymap_type = find_keymap_from_dir()
if keymap_name:
cli.config[cli._entrypoint.__name__]['keymap'] = keymap_name
cli.config_source[cli._entrypoint.__name__]['keymap'] = keymap_type
return func(*args, **kwargs)


+ 25
- 1
lib/python/qmk/keyboard.py View File

@ -9,7 +9,7 @@ from glob import glob
from qmk.c_parse import parse_config_h_file
from qmk.json_schema import json_load
from qmk.makefile import parse_rules_mk_file
from qmk.path import is_keyboard
from qmk.path import is_keyboard, under_qmk_firmware
BOX_DRAWING_CHARACTERS = {
"unicode": {
@ -33,6 +33,24 @@ BOX_DRAWING_CHARACTERS = {
base_path = os.path.join(os.getcwd(), "keyboards") + os.path.sep
def find_keyboard_from_dir():
"""Returns a keyboard name based on the user's current directory.
"""
relative_cwd = under_qmk_firmware()
if relative_cwd and len(relative_cwd.parts) > 1 and relative_cwd.parts[0] == 'keyboards':
# Attempt to extract the keyboard name from the current directory
current_path = Path('/'.join(relative_cwd.parts[1:]))
if 'keymaps' in current_path.parts:
# Strip current_path of anything after `keymaps`
keymap_index = len(current_path.parts) - current_path.parts.index('keymaps') - 1
current_path = current_path.parents[keymap_index]
if is_keyboard(current_path):
return str(current_path)
def keyboard_folder(keyboard):
"""Returns the actual keyboard folder.
@ -61,6 +79,12 @@ def _find_name(path):
return path.replace(base_path, "").replace(os.path.sep + "rules.mk", "")
def keyboard_completer(prefix, action, parser, parsed_args):
"""Returns a list of keyboards for tab completion.
"""
return list_keyboards()
def list_keyboards():
"""Returns a list of all keyboards.
"""


+ 52
- 4
lib/python/qmk/keymap.py View File

@ -1,19 +1,19 @@
"""Functions that help you work with QMK keymaps.
"""
from pathlib import Path
import json
import subprocess
import sys
from pathlib import Path
import argcomplete
from milc import cli
from pygments.lexers.c_cpp import CLexer
from pygments.token import Token
from pygments import lex
from milc import cli
from qmk.keyboard import rules_mk
import qmk.path
import qmk.commands
from qmk.keyboard import find_keyboard_from_dir, rules_mk
# The `keymap.c` template to use when a keyboard doesn't have its own
DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H
@ -74,6 +74,54 @@ def _strip_any(keycode):
return keycode
def find_keymap_from_dir():
"""Returns `(keymap_name, source)` for the directory we're currently in.
"""
relative_cwd = qmk.path.under_qmk_firmware()
if relative_cwd and len(relative_cwd.parts) > 1:
# If we're in `qmk_firmware/keyboards` and `keymaps` is in our path, try to find the keyboard name.
if relative_cwd.parts[0] == 'keyboards' and 'keymaps' in relative_cwd.parts:
current_path = Path('/'.join(relative_cwd.parts[1:])) # Strip 'keyboards' from the front
if 'keymaps' in current_path.parts and current_path.name != 'keymaps':
while current_path.parent.name != 'keymaps':
current_path = current_path.parent
return current_path.name, 'keymap_directory'
# If we're in `qmk_firmware/layouts` guess the name from the community keymap they're in
elif relative_cwd.parts[0] == 'layouts' and is_keymap_dir(relative_cwd):
return relative_cwd.name, 'layouts_directory'
# If we're in `qmk_firmware/users` guess the name from the userspace they're in
elif relative_cwd.parts[0] == 'users':
# Guess the keymap name based on which userspace they're in
return relative_cwd.parts[1], 'users_directory'
return None, None
def keymap_completer(prefix, action, parser, parsed_args):
"""Returns a list of keymaps for tab completion.
"""
try:
if parsed_args.keyboard:
return list_keymaps(parsed_args.keyboard)
keyboard = find_keyboard_from_dir()
if keyboard:
return list_keymaps(keyboard)
except Exception as e:
argcomplete.warn(f'Error: {e.__class__.__name__}: {str(e)}')
return []
return []
def is_keymap_dir(keymap, c=True, json=True, additional_files=None):
"""Return True if Path object `keymap` has a keymap file inside.


+ 1
- 1
requirements.txt View File

@ -5,5 +5,5 @@ colorama
dotty-dict
hjson
jsonschema>=3
milc>=1.1.0
milc>=1.3.0
pygments

+ 2
- 0
util/qmk_tab_complete.sh View File

@ -0,0 +1,2 @@
# Register qmk with tab completion
eval "$(register-python-argcomplete --no-defaults qmk)"

Loading…
Cancel
Save