Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to show key under transparent keys, and find the layer name during parsing #67

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions keymap_drawer/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ class ParseConfig(BaseSettings, env_prefix="KEYMAP_", extra="ignore"):
# legend to output for transparent keys
trans_legend: str | dict = {"t": "▽", "type": "trans"}

# whether to show the keycode this transparent key will activate
trans_show_lower_key: bool = False
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add an item to CONFIGURATION.md as well?


# rather than only marking the first sequence of key positions to reach a layer as "held",
# mark all of the sequences to reach a given layer. this is disabled by default because it
# creates ambiguity: you cannot tell if *all* the marked keys need to be held down while a
Expand Down
3 changes: 3 additions & 0 deletions keymap_drawer/keymap.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ def dict(self, *args, no_tapstr: bool = False, **kwargs):
return dict_repr
return dict_repr.get("t") or dict_repr.get("tap", "")

def __hash__(self):
return hash((self.tap, self.hold, self.shifted, self.type))


class ComboSpec(BaseModel, allow_population_by_field_name=True):
"""
Expand Down
47 changes: 44 additions & 3 deletions keymap_drawer/parse/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ def __init__(
self.columns = columns if columns is not None else 0
self.layer_names: list[str] | None = layer_names
self.base_keymap = base_keymap
self.layer_activated_from: dict[int, set[int]] = {} # layer to key positions
self.layer_activated_from: dict[
int, set[tuple[int, int]]
] = {} # layer to [layer index, key position] from keys
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
] = {} # layer to [layer index, key position] from keys
] = {} # layer to (layer index, key position) from keys

self.conditional_layers: dict[int, list[int]] = {} # then-layer to if-layers mapping
self.trans_key = LayoutKey.from_key_spec(self.cfg.trans_legend)
self.raw_binding_map = self.cfg.raw_binding_map.copy()
Expand Down Expand Up @@ -53,7 +55,9 @@ def update_layer_activated_from(

if to_layer not in self.layer_activated_from:
self.layer_activated_from[to_layer] = set()
self.layer_activated_from[to_layer] |= set(key_positions) # came here through these key(s)

for from_layer in from_layers:
self.layer_activated_from[to_layer] |= set((from_layer, key_pos) for key_pos in key_positions)

# also consider how the layer we are coming from got activated
for from_layer in from_layers:
Expand All @@ -69,7 +73,7 @@ def add_held_keys(self, layers: dict[str, list[LayoutKey]]) -> dict[str, list[La

# assign held keys
for layer_index, activating_keys in self.layer_activated_from.items():
for key_idx in activating_keys:
for _, key_idx in activating_keys:
key = layers[self.layer_names[layer_index]][key_idx]
if key == self.trans_key: # clear legend if it is a transparent key
layers[self.layer_names[layer_index]][key_idx] = LayoutKey(type="held")
Expand All @@ -78,6 +82,43 @@ def add_held_keys(self, layers: dict[str, list[LayoutKey]]) -> dict[str, list[La

return layers

def fill_trans_keys(self, layers: dict[str, list[LayoutKey]]) -> dict[str, list[LayoutKey]]:
"""Fill in transparent keys with the legend of the key that is underneath."""
assert self.layer_names is not None

for layer_name, keys in layers.items():
layer_idx = self.layer_names.index(layer_name)
for key_idx, key in enumerate(keys):
if key == self.trans_key:
lower_keys = self._find_lower_keys(layer_idx, key_idx, layers)
# usually there should only be one lower key, but if you can get to this layer
# that have this key defined, show all of them from multiple different layers
if len(lower_keys) > 0:
layers[layer_name][key_idx] = LayoutKey(
tap="|".join(key.tap for key in lower_keys),
hold="|".join(key.hold for key in lower_keys),
shifted="|".join(key.shifted for key in lower_keys),
type=self.trans_key.type,
)
return layers

def _find_lower_keys(self, layer: int, key: int, layers: dict[str, list[LayoutKey]]) -> set[LayoutKey]:
assert self.layer_names is not None

lower_keys = set()
activated_from_keys = self.layer_activated_from.get(layer, set())

for layer_idx, _ in activated_from_keys:
layer_name = self.layer_names[layer_idx]
from_layer = layers[layer_name]
Comment on lines +112 to +113
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
layer_name = self.layer_names[layer_idx]
from_layer = layers[layer_name]
from_layer = layers[self.layer_names[layer_idx]]

lower_key = from_layer[key]
if lower_key == self.trans_key:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comparison why the hash function is necessary?

lower_keys |= self._find_lower_keys(layer_idx, key, layers)
else:
lower_keys.add(lower_key)

return lower_keys

def _parse(self, in_str: str, file_name: str | None = None) -> tuple[dict, KeymapData]:
raise NotImplementedError

Expand Down
25 changes: 19 additions & 6 deletions keymap_drawer/parse/qmk.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class QmkJsonParser(KeymapParser):
_tog_re = re.compile(r"(TG|TO|DF)\((\d+)\)")
_mts_re = re.compile(r"([A-Z_]+)_T\((\S+)\)")
_mtl_re = re.compile(r"MT\((\S+), *(\S+)\)")
_lt_re = re.compile(r"LT\((\d+), *(\S+)\)")
_lt_re = re.compile(r"LT\((\S+), *(\S+)\)")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I understand how this happens, can you actually have layer name defines instead of indices in keymap.json? Do you use it alongside e.g. config.h that contains layer defines?

_osm_re = re.compile(r"OSM\(MOD_(\S+)\)")
_osl_re = re.compile(r"OSL\((\d+)\)")

Expand Down Expand Up @@ -50,23 +50,32 @@ def mapped(key: str) -> LayoutKey:
key = self._prefix_re.sub("", key)
return LayoutKey.from_key_spec(self.cfg.qmk_keycode_map.get(key, key.replace("_", " ")))

def parse_layer(layer: str) -> int:
assert self.layer_names is not None
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am not wrong, the only way we know layer names is still via user input, i.e. --layer-names. If so, I think it would be nice to have an informative error message here, perhaps like below?

Suggested change
assert self.layer_names is not None
assert self.layer_names is not None, "Please provide `--layer-names` if you use layer defines in keymap.json"


if layer.isdigit():
return int(layer)
return self.layer_names.index(layer)

if m := self._trans_re.fullmatch(key_str): # transparent
return self.trans_key
if m := self._mo_re.fullmatch(key_str): # momentary layer
to_layer = int(m.group(1).strip())
to_layer = parse_layer(m.group(1).strip())
self.update_layer_activated_from([current_layer], to_layer, key_positions)
return LayoutKey(tap=self.layer_names[to_layer])
if m := self._tog_re.fullmatch(key_str): # toggled layer
to_layer = int(m.group(2).strip())
to_layer = parse_layer(m.group(2).strip())
return LayoutKey(tap=self.layer_names[to_layer], hold=self.cfg.toggle_label)
if m := self._mts_re.fullmatch(key_str): # short mod-tap syntax
tap_key = mapped(m.group(2).strip())
return LayoutKey(tap=tap_key.tap, hold=m.group(1), shifted=tap_key.shifted)
hold_key = mapped(m.group(1).strip())
return LayoutKey(tap=tap_key.tap, hold=hold_key.tap, shifted=tap_key.shifted)
if m := self._mtl_re.fullmatch(key_str): # long mod-tap syntax
tap_key = mapped(m.group(2).strip())
return LayoutKey(tap=tap_key.tap, hold=m.group(1).strip(), shifted=tap_key.shifted)
hold_key = mapped(m.group(1).strip())
return LayoutKey(tap=tap_key.tap, hold=hold_key.tap, shifted=tap_key.shifted)
if m := self._lt_re.fullmatch(key_str): # layer-tap
to_layer = int(m.group(1).strip())
to_layer = parse_layer(m.group(1).strip())
self.update_layer_activated_from([current_layer], to_layer, key_positions)
tap_key = mapped(m.group(2).strip())
return LayoutKey(tap=tap_key.tap, hold=self.layer_names[to_layer], shifted=tap_key.shifted)
Expand Down Expand Up @@ -109,6 +118,10 @@ def _parse(self, in_str: str, file_name: str | None = None) -> tuple[dict, Keyma
}

layers = self.add_held_keys(layers)

if self.cfg.trans_show_lower_key:
layers = self.fill_trans_keys(layers)

keymap_data = KeymapData(layers=layers, layout=None, config=None)

return layout, keymap_data
3 changes: 3 additions & 0 deletions keymap_drawer/parse/zmk.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ def _parse(self, in_str: str, file_name: str | None = None) -> tuple[dict, Keyma
combos = self._get_combos(dts)
layers = self.add_held_keys(layers)

if self.cfg.trans_show_lower_key:
layers = self.fill_trans_keys(layers)

keymap_data = KeymapData(layers=layers, combos=combos, layout=None, config=None)

return self._get_physical_layout(file_name, dts), keymap_data