Skip to content

Commit

Permalink
[input-] fix editline() for characters having screen width > 1
Browse files Browse the repository at this point in the history
  • Loading branch information
midichef committed Jan 3, 2025
1 parent f40ba4c commit b92bacf
Showing 1 changed file with 82 additions and 14 deletions.
96 changes: 82 additions & 14 deletions visidata/_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,62 @@ def clean_printable(s):
return ''.join(c if c.isprintable() else vd.options.disp_unprintable for c in str(s))


def fit_left(dispval, i, w):
# return the largest # of elements from the left side of dispval,
# before element *i*, that can fit in a space that is *w* characters wide
j = 0
frag_w = 0
while i-j-1 > 0:
sym_w = dispwidth(dispval[i-j-1])
if frag_w + sym_w <= w:
frag_w += sym_w
j += 1
else:
break
frag = dispval[i-j:i]
return frag

def fit_right(dispval, i, w):
# return the largest # of elements from the right side of dispval,
# starting at element *i*, that can fit in a space that is *w* characters wide
j = 0
frag_w = 0
while i+j < len(dispval):
sym_w = dispwidth(dispval[i+j])
if frag_w + sym_w <= w:
frag_w += sym_w
j += 1
else:
break
frag = dispval[i:i+j]
return frag

def fit_middle(dispval, i, w, left_truncchar, right_truncchar):
# *i* is the index of the cursor into the list *dispval*.
# Returns a string of length *w*, possibly padded with left_truncchar and
# right_truncchar.
# The string will align so that *cursor_offset* is at the character in
# the center of the space.
left_w = w//2
right_w = left_w + (w%2) # odd widths have one character more on the right side

# determine the fragment that is left of the cursor
l_frag = fit_left(dispval, i, left_w)
if dispwidth(l_frag) < left_w: # pad it with extra left_truncchar
# left_truncchar must have dispwidth of 1
l_frag = left_truncchar * (left_w - dispwidth(l_frag)) + l_frag

# determine the fragment that starts at the cursor and continues right
r_frag = fit_right(dispval, i, right_w)
if dispwidth(r_frag) < right_w:
# right_truncchar must have dispwidth of 1
r_frag += right_truncchar * (right_w - dispwidth(r_frag))

frag = l_frag + r_frag
cursor_offset = len(l_frag)
return cursor_offset, frag


def delchar(s, i, remove=1):
'Delete `remove` characters from str `s` beginning at position `i`.'
return s if i < 0 else s[:i] + s[i+remove:]
Expand Down Expand Up @@ -195,28 +251,41 @@ def editline(self, scr, y, x, w, attr=ColorAttr(), updater=lambda val: None, bin
def draw(self, scr, y, x, w, attr=ColorAttr(), clear=True):
i = self.current_i # the onscreen offset within the field where v[i] is displayed
left_truncchar = right_truncchar = self.truncchar
tr_w = dispwidth(self.truncchar)
fill_w = dispwidth(self.fillchar)

if self.display:
dispval = clean_printable(self.value)
else:
dispval = '*' * len(self.value)

if len(dispval) < w: # entire value fits
dispval += self.fillchar*(w-len(dispval)-1)
elif i == len(dispval): # cursor after value (will append)
i = w-1
dispval = left_truncchar + dispval[len(dispval)-w+2:] + self.fillchar
elif i >= len(dispval)-w//2: # cursor within halfwidth of end
i = w-(len(dispval)-i)
dispval = left_truncchar + dispval[len(dispval)-w+1:]
elif i <= w//2: # cursor within halfwidth of beginning
dispval = dispval[:w-1] + right_truncchar
if dispwidth(dispval) < w: # entire value fits
dispval += self.fillchar*(w-dispwidth(dispval)-1)
elif i == len(dispval): # cursor is at the end of the value
# Draw a truncchar, then as much of the right side of the string as it can fit.
# Then a fillchar, so the user sees room to type.
frag = fit_left(dispval, len(dispval), w - tr_w - fill_w)
dispval = left_truncchar + frag + self.fillchar
i = 1 + len(frag) # put the cursor on the fillchar
elif dispwidth(dispval[i:]) <= w - 2*tr_w:
# width can hold the value on the cursor and after it, but not before it
frag = fit_left(dispval, len(dispval), w - 2*tr_w)
offset = len(dispval) - i
dispval = left_truncchar + frag
i = len(dispval) - offset
elif dispwidth(dispval[:i+1]) < w - tr_w:
# width can hold the value up to and including the cursor, but not the elements after it
frag = fit_right(dispval, 0, w - tr_w)
dispval = frag + right_truncchar
else:
i = w//2 # visual cursor stays right in the middle
k = 1 if w%2==0 else 0 # odd widths have one character more
dispval = left_truncchar + dispval[self.current_i-w//2+1:self.current_i+w//2-k] + right_truncchar
# align the fragment so that the cursor is in the middle of the space
cursor_offset, frag = fit_middle(dispval, i, w - 2*tr_w, left_truncchar, right_truncchar)
dispval = left_truncchar + frag + right_truncchar
i = len(left_truncchar) + cursor_offset

# draw everything before the cursor
prew = clipdraw(scr, y, x, dispval[:i], attr, w, clear=clear, literal=True)
# draw everything after the cursor
clipdraw(scr, y, x+prew, dispval[i:], attr, w-prew+1, clear=clear, literal=True)
if scr:
scr.move(y, x+prew)
Expand Down Expand Up @@ -340,7 +409,6 @@ def next_history(self, v, i):
i = len(str(v))
return v, i


@VisiData.api
def editText(vd, y, x, w, attr=ColorAttr(), value='',
help='',
Expand Down

0 comments on commit b92bacf

Please sign in to comment.