Skip to content

Commit a3121e1

Browse files
authored
Merge pull request #1 from apple1417/master
replace key misspelling matching with difflib
2 parents c233bca + 566a822 commit a3121e1

File tree

3 files changed

+5
-126
lines changed

3 files changed

+5
-126
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: CI
22

3-
on: [push, pull_request]
3+
on: [push, pull_request, workflow_dispatch]
44

55
jobs:
66
spelling:

Readme.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ a message as the second (both positionally), and show them in a temporary messag
2121
# Changelog
2222

2323
### v1.4
24-
- Grouped options with no visible children no longer show their header.
24+
- Improved suggestions when trying to bind a key by name, and misspelling it.
2525
- Swap known controller key names between UE3/UE4 versions, based on game.
26+
- Grouped options with no visible children no longer show their header.
2627

2728
### Older
2829
Versions 1.0 through 1.3 were developed as part of the

key_matching.py

Lines changed: 2 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from collections.abc import Iterable
2-
from os import path
2+
from difflib import get_close_matches
33

44
from mods_base import Game
55

@@ -178,101 +178,6 @@
178178
# endregion
179179
# region Misspellings
180180

181-
# Python has code to suggest other names on an attribute or name error - we want to do the same when
182-
# someone gives an invalid key name.
183-
# Unfortunately, it doesn't seem to be exposed, so we manually replicate it instead
184-
# Based on Python/suggestions.c:calculate_suggestions
185-
186-
# ruff: noqa: ERA001
187-
188-
MOVE_COST = 2
189-
CASE_COST = 1
190-
MAX_STRING_SIZE = 40
191-
192-
193-
def _substitution_cost(a: str, b: str) -> int:
194-
if a == b:
195-
return 0
196-
if a.lower() == b.lower():
197-
return CASE_COST
198-
199-
return MOVE_COST
200-
201-
202-
def _levenshtein_distance(a: str, b: str, max_cost: int) -> int:
203-
"""Calculate the Levenshtein distance between string1 and string2."""
204-
205-
# Both strings are the same
206-
if a == b:
207-
return 0
208-
209-
# Trim away common affixes.
210-
common_prefix = path.commonprefix((a, b))
211-
a = a.removeprefix(common_prefix)
212-
b = b.removeprefix(common_prefix)
213-
214-
common_suffix = path.commonprefix((a[::-1], b[::-1]))
215-
a = a.removesuffix(common_suffix)
216-
b = b.removesuffix(common_suffix)
217-
218-
a_size = len(a)
219-
b_size = len(b)
220-
if a_size == 0 or b_size == 0:
221-
return (a_size + b_size) * MOVE_COST
222-
223-
if a_size > MAX_STRING_SIZE or b_size > MAX_STRING_SIZE:
224-
return max_cost + 1
225-
226-
# Prefer shorter buffer
227-
if b_size < a_size:
228-
a, b = b, a
229-
a_size, b_size = b_size, a_size
230-
231-
# quick fail when a match is impossible.
232-
if (b_size - a_size) * MOVE_COST > max_cost:
233-
return max_cost + 1
234-
235-
# Instead of producing the whole traditional len(a)-by-len(b)
236-
# matrix, we can update just one row in place.
237-
# Initialize the buffer row
238-
# cost from b[:0] to a[:i+1]
239-
buffer = [(i + 1) * MOVE_COST for i in range(a_size)]
240-
241-
result = 0
242-
for b_index in range(b_size):
243-
code = b[b_index]
244-
# cost(b[:b_index], a[:0]) == b_index * MOVE_COST
245-
distance = result = b_index * MOVE_COST
246-
minimum = None
247-
for index in range(a_size):
248-
# cost(b[:b_index+1], a[:index+1]) = min(
249-
# # 1) substitute
250-
# cost(b[:b_index], a[:index])
251-
# + substitution_cost(b[b_index], a[index]),
252-
# # 2) delete from b
253-
# cost(b[:b_index], a[:index+1]) + MOVE_COST,
254-
# # 3) delete from a
255-
# cost(b[:b_index+1], a[index]) + MOVE_COST
256-
# )
257-
258-
# 1) Previous distance in this row is cost(b[:b_index], a[:index])
259-
substitute = distance + _substitution_cost(code, a[index])
260-
# 2) cost(b[:b_index], a[:index+1]) from previous row
261-
distance = buffer[index]
262-
# 3) existing result is cost(b[:b_index+1], a[index])
263-
264-
insert_delete = min(result, distance) + MOVE_COST
265-
result = min(insert_delete, substitute)
266-
267-
# cost(b[:b_index+1], a[:index+1])
268-
buffer[index] = result
269-
if minimum is None or result < minimum:
270-
minimum = result
271-
if minimum is None or minimum > max_cost:
272-
# Everything in this row is too big, so bail early.
273-
return max_cost + 1
274-
return result
275-
276181

277182
def suggest_misspelt_key(invalid_key: str) -> Iterable[str]:
278183
"""
@@ -283,34 +188,7 @@ def suggest_misspelt_key(invalid_key: str) -> Iterable[str]:
283188
Returns:
284189
A list of possible misspellings (which may be empty).
285190
"""
286-
suggestion_distance: int | None = None
287-
suggestion: str | None = None
288-
for item in KNOWN_KEYS:
289-
if item == invalid_key:
290-
continue
291-
292-
# No more than 1/3 of the involved characters should need changed.
293-
max_distance = (len(invalid_key) + len(item) + 3) * MOVE_COST // 6
294-
295-
# Don't take matches we've already beaten.
296-
if suggestion_distance is not None and (suggestion_distance - 1) < max_distance:
297-
max_distance = suggestion_distance - 1
298-
299-
current_distance = _levenshtein_distance(invalid_key, item, max_distance)
300-
301-
if current_distance > max_distance:
302-
continue
303-
if (
304-
suggestion is None
305-
or suggestion_distance is None
306-
or current_distance < suggestion_distance
307-
):
308-
suggestion = item
309-
suggestion_distance = current_distance
310-
311-
if suggestion is None:
312-
return ()
313-
return (suggestion,)
191+
return get_close_matches(invalid_key, KNOWN_KEYS)
314192

315193

316194
# endregion

0 commit comments

Comments
 (0)