diff --git a/src/borg/archive.py b/src/borg/archive.py index 6cc7aacc3..dd5eba215 100644 --- a/src/borg/archive.py +++ b/src/borg/archive.py @@ -28,7 +28,7 @@ from .helpers import uid2user, user2uid, gid2group, group2gid from .helpers import parse_timestamp, to_localtime from .helpers import format_time, format_timedelta, format_file_size, file_status -from .helpers import safe_encode, safe_decode, make_path_safe, remove_surrogates +from .helpers import safe_encode, safe_decode, make_path_safe, remove_surrogates, swidth_slice from .helpers import decode_dict, StableDict from .helpers import int_to_bigint, bigint_to_int, bin_to_hex from .helpers import ProgressIndicatorPercent, log_multi @@ -94,7 +94,8 @@ def show_progress(self, item=None, final=False, stream=None, dt=None): space = columns - swidth(msg) if space >= 8: if space < swidth('...') + swidth(path): - path = '%s...%s' % (path[:(space // 2) - swidth('...')], path[-space // 2:]) + path = '%s...%s' % (swidth_slice(path, space // 2 - swidth('...')), + swidth_slice(path, -space // 2)) space -= swidth(path) msg += path + ' ' * space else: diff --git a/src/borg/helpers.py b/src/borg/helpers.py index e44569533..d08c6ba77 100644 --- a/src/borg/helpers.py +++ b/src/borg/helpers.py @@ -1696,3 +1696,29 @@ def write(self, s): except OSError: pass return len(s) + + +def swidth_slice(string, max_width): + """ + Return a slice of *max_width* cells from *string*. + + Negative *max_width* means from the end of string. + + *max_width* is in units of character cells (or "columns"). + Latin characters are usually one cell wide, many CJK characters are two cells wide. + """ + from .platform import swidth + reverse = max_width < 0 + max_width = abs(max_width) + if reverse: + string = reversed(string) + current_swidth = 0 + result = [] + for character in string: + current_swidth += swidth(character) + if current_swidth > max_width: + break + result.append(character) + if reverse: + result.reverse() + return ''.join(result) diff --git a/src/borg/testsuite/helpers.py b/src/borg/testsuite/helpers.py index d1c7c95bb..b2b568d7c 100644 --- a/src/borg/testsuite/helpers.py +++ b/src/borg/testsuite/helpers.py @@ -9,6 +9,7 @@ import msgpack import msgpack.fallback +from .. import platform from ..helpers import Location from ..helpers import Buffer from ..helpers import partial_format, format_file_size, parse_file_size, format_timedelta, format_line, PlaceholderError, replace_placeholders @@ -23,6 +24,7 @@ from ..helpers import load_excludes from ..helpers import CompressionSpec, CompressionDecider1, CompressionDecider2 from ..helpers import parse_pattern, PatternMatcher, RegexPattern, PathPrefixPattern, FnmatchPattern, ShellPattern +from ..helpers import swidth_slice from . import BaseTestCase, environment_variable, FakeInputs @@ -1041,3 +1043,23 @@ def test_replace_placeholders(): now = datetime.now() assert " " not in replace_placeholders('{now}') assert int(replace_placeholders('{now:%Y}')) == now.year + + +def working_swidth(): + return platform.swidth('선') == 2 + + +@pytest.mark.skipif(not working_swidth(), reason='swidth() is not supported / active') +def test_swidth_slice(): + string = '나윤선나윤선나윤선나윤선나윤선' + assert swidth_slice(string, 1) == '' + assert swidth_slice(string, -1) == '' + assert swidth_slice(string, 4) == '나윤' + assert swidth_slice(string, -4) == '윤선' + + +@pytest.mark.skipif(not working_swidth(), reason='swidth() is not supported / active') +def test_swidth_slice_mixed_characters(): + string = '나윤a선나윤선나윤선나윤선나윤선' + assert swidth_slice(string, 5) == '나윤a' + assert swidth_slice(string, 6) == '나윤a'