Skip to content

Commit 61f5395

Browse files
committed
feat: Support Kanata deflayermap
1 parent b05f5cb commit 61f5395

File tree

1 file changed

+53
-28
lines changed

1 file changed

+53
-28
lines changed

keymap_drawer/parse/kanata.py

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import json
44
import logging
5-
from itertools import chain, islice
5+
from itertools import batched, chain
66
from pathlib import Path
77
from typing import Iterable, Sequence
88

@@ -127,7 +127,6 @@ def _str_to_key( # pylint: disable=too-many-return-statements,too-many-locals,t
127127
def recurse(new_binding):
128128
return self._str_to_key(new_binding, current_layer, key_positions)
129129

130-
131130
if isinstance(binding, str):
132131
if binding.startswith("@") and (mapped := self.aliases.get(binding.lstrip("@"), False)):
133132
return recurse(mapped)
@@ -177,34 +176,54 @@ def _get_layers(self, nodes: list[pp.ParseResults]) -> dict[str, list[LayoutKey]
177176
assert self.defsrc_indices is not None
178177
assert self.defsrc_to_pos is not None
179178

179+
layer_names = [
180+
node[1] if node[0] == "deflayer" else node[1][0] for node in nodes if node[0] in ("deflayer", "deflayermap")
181+
]
180182
layer_nodes = {node[1]: node[2:] for node in nodes if node[0] == "deflayer"}
181-
self.update_layer_names(list(layer_nodes))
183+
layermap_nodes = {node[1][0]: node[2:] for node in nodes if node[0] == "deflayermap"}
182184

183-
layers: dict[str, list[LayoutKey]] = {}
184-
for layer_ind, (layer_name, layer) in enumerate(layer_nodes.items()):
185-
layers[layer_name] = [LayoutKey() for _ in range(len(self.defsrc_to_pos))]
186-
for key_pos, layer_key in zip(self.defsrc_indices, layer):
185+
self.update_layer_names(layer_names)
186+
187+
def create_from_deflayer(ind: int, name: str, keys: list[pp.ParseResults]) -> list[LayoutKey]:
188+
assert self.defsrc_indices is not None
189+
assert self.defsrc_to_pos is not None
190+
layer = [LayoutKey() for _ in range(len(self.defsrc_to_pos))]
191+
for key_pos, key in zip(self.defsrc_indices, keys):
187192
try:
188-
layers[layer_name][key_pos] = self._str_to_key(layer_key, layer_ind, [key_pos])
193+
layer[key_pos] = self._str_to_key(key, ind, [key_pos])
189194
except Exception as err:
190195
raise ParseError(
191-
f'Could not parse keycode "{layer_key}" in layer "{layer_name}" with exception "{err}"'
196+
f'Could not parse keycode "{key}" in layer "{name}" with exception "{err}"'
192197
) from err
193-
return layers
198+
return layer
194199

195-
@staticmethod
196-
def _get_raw_combo_nodes(nodes: list[pp.ParseResults]) -> list[tuple[(str | pp.ParseResults), ...]]:
197-
try:
198-
chords_node = next(node[1:] for node in nodes if node[0] in ("defchordsv2", "defchordsv2-experimental"))
199-
except StopIteration:
200-
return []
201-
202-
def batched(iterable, n):
203-
it = iter(iterable)
204-
while batch := tuple(islice(it, n)):
205-
yield batch
200+
def create_from_deflayermap(ind: int, name: str, mappings: list[pp.ParseResults]) -> list[LayoutKey]:
201+
assert self.defsrc_to_pos is not None
202+
default_action = LayoutKey()
203+
layer: list[LayoutKey | None] = [None for _ in range(len(self.defsrc_to_pos))]
204+
for input_elt, action_elt in batched(mappings, 2):
205+
try:
206+
match input_elt:
207+
case "_" | "__" | "___":
208+
logger.warning('"_", "__" and "___" in deflayermap are not distinguished at the moment')
209+
default_action = self._str_to_key(action_elt, ind, [])
210+
case _:
211+
assert isinstance(input_elt, str)
212+
key_pos = self.defsrc_to_pos[self._canonicalize_defsrc(input_elt)]
213+
layer[key_pos] = self._str_to_key(action_elt, ind, [key_pos])
214+
except Exception as err:
215+
raise ParseError(
216+
f'Could not parse action element "{action_elt}" in layermap "{name}" with exception "{err}"'
217+
) from err
218+
return [default_action.copy() if key is None else key for key in layer]
206219

207-
return list(batched(chords_node, 5))
220+
layers: dict[str, list[LayoutKey]] = {}
221+
for layer_ind, layer_name in enumerate(layer_names):
222+
if layer_keys := layer_nodes.get(layer_name): # deflayer node
223+
layers[layer_name] = create_from_deflayer(layer_ind, layer_name, layer_keys)
224+
else: # deflayermap node
225+
layers[layer_name] = create_from_deflayermap(layer_ind, layer_name, layermap_nodes[layer_name])
226+
return layers
208227

209228
def _get_combos(self, raw_combo_nodes: list[tuple[(str | pp.ParseResults), ...]]) -> list[ComboSpec]:
210229
assert self.layer_names is not None
@@ -236,16 +255,22 @@ def _parse(self, in_str: str, file_name: str | None = None) -> tuple[dict, Keyma
236255
"""
237256
nodes = self._parse_cfg(in_str, Path(file_name) if file_name else None)
238257

239-
if any(node[1:] for node in nodes if node[0] == "deflayermap"):
240-
logger.warning("deflayermap is not currently supported")
241-
242-
if any(node[1:] for node in nodes if node[0] == "deflocalkeys"):
258+
if any(node for node in nodes if node[0] == "deflocalkeys"):
243259
logger.warning("deflocalkeys is not currently supported")
244260

245261
defsrc = next(node[1:] for node in nodes if node[0] == "defsrc")
246-
raw_combo_nodes = self._get_raw_combo_nodes(nodes)
262+
raw_combo_nodes = list(
263+
chain.from_iterable(
264+
batched(node[1:], 5) for node in nodes if node[0] in ("defchordsv2", "defchordsv2-experimental")
265+
)
266+
)
267+
deflayermap_srcs = chain.from_iterable(node[2::2] for node in nodes if node[0] == "deflayermap")
247268

248-
self._find_physical_layout(defsrc, set(pos for combo_def in raw_combo_nodes for pos in combo_def[0]))
269+
self._find_physical_layout(
270+
defsrc,
271+
{pos for combo_def in raw_combo_nodes for pos in combo_def[0]}
272+
| {pos for pos in deflayermap_srcs if pos not in ("_", "__", "___")},
273+
)
249274
assert self.physical_layout is not None
250275

251276
self._get_aliases_vars(nodes)

0 commit comments

Comments
 (0)