Skip to content

Commit 5aaec2f

Browse files
committed
fix: Handle wide characters in easymotion.py
1 parent 885d0a7 commit 5aaec2f

File tree

1 file changed

+27
-10
lines changed

1 file changed

+27
-10
lines changed

easymotion.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,27 @@
77

88
KEYS='asdghklqwertyuiopzxcvbnmfj'
99

10+
def get_char_width(char):
11+
"""Get visual width of a single character"""
12+
return 2 if unicodedata.east_asian_width(char) in 'WF' else 1
13+
1014
def get_string_width(s):
1115
"""Calculate visual width of string, accounting for double-width characters"""
1216
width = 0
1317
for c in s:
14-
# East Asian Width (W, F) characters are full width (2 columns)
15-
width += 2 if unicodedata.east_asian_width(c) in 'WF' else 1
18+
width += get_char_width(c)
1619
return width
1720

21+
def get_true_position(line, target_col):
22+
"""Calculate true position accounting for wide characters"""
23+
visual_pos = 0
24+
true_pos = 0
25+
while true_pos < len(line) and visual_pos < target_col:
26+
char_width = get_char_width(line[true_pos])
27+
visual_pos += char_width
28+
true_pos += 1
29+
return true_pos
30+
1831
def pyshell(cmd):
1932
debug = os.environ.get('TMUX_EASYMOTION_DEBUG') == 'true'
2033
if debug:
@@ -140,18 +153,19 @@ def main(stdscr):
140153
lines = captured_pane.splitlines()
141154
for line_num, line in enumerate(lines):
142155
for match in re.finditer(search_ch, line.lower()):
143-
col = match.start()
144-
positions.append((line_num, col))
156+
visual_col = sum(get_char_width(c) for c in line[:match.start()])
157+
positions.append((line_num, visual_col, line[match.start()]))
145158

146159
# render 1st hints
147-
for i, (line_num, col) in enumerate(positions):
160+
for i, (line_num, col, char) in enumerate(positions):
148161
if i >= len(hints):
149162
break
150163
y = line_num
151164
x = col
152165
stdscr.addstr(y, x, hints[i][0], curses.color_pair(RED))
153-
if x+1 < width:
154-
stdscr.addstr(y, x+1, hints[i][1], curses.color_pair(GREEN))
166+
char_width = get_char_width(char)
167+
if x + char_width < width:
168+
stdscr.addstr(y, x + char_width, hints[i][1], curses.color_pair(GREEN))
155169
stdscr.refresh()
156170

157171
ch1 = stdscr.getkey()
@@ -164,12 +178,14 @@ def main(stdscr):
164178
stdscr.addstr(y, 0, line)
165179
except curses.error:
166180
pass
167-
for i, (line_num, col) in enumerate(positions):
181+
for i, (line_num, col, char) in enumerate(positions):
168182
if not hints[i].startswith(ch1) or len(hints[i]) < 2:
169183
continue
170184
y = line_num
171185
x = col
172-
stdscr.addstr(y, x, hints[i][1], curses.color_pair(GREEN))
186+
char_width = get_char_width(char)
187+
if x + char_width < width:
188+
stdscr.addstr(y, x + char_width, hints[i][1], curses.color_pair(GREEN))
173189
stdscr.refresh()
174190

175191
ch2 = stdscr.getkey()
@@ -179,7 +195,8 @@ def main(stdscr):
179195
# Calculate final position based on line and column
180196
target_pos = positions[hints_dict[ch1+ch2]]
181197
line_offset = sum(len(line) + 1 for line in lines[:target_pos[0]])
182-
final_pos = line_offset + target_pos[1]
198+
true_col = get_true_position(lines[target_pos[0]], target_pos[1])
199+
final_pos = line_offset + true_col # Convert visual position to true position
183200

184201
# Adjust for scroll position
185202
if scroll_position > 0:

0 commit comments

Comments
 (0)