Repository: gokcehan/lf
Pull request #1307 (2023-06-14 02:32:57)

Remove test log statement during autocompletion
To reproduce, type `$a<tab>` with logging enabled, and all the executables starting with 'a' will be logged. I'm quite sure this is debugging code that was accidentally committed in e1b6d38.


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index f8ae79c..7124b0f 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -8,7 +8,7 @@ on:
 
 jobs:
 
-  build:
+  tests:
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
@@ -34,6 +34,21 @@ jobs:
         version: "2022.1.3"
         install-go: false
 
+  cross-compile:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        fetch-depth: 1
+
+    - name: Set up Go
+      uses: actions/setup-go@v4
+      with:
+        go-version: 1.18
+
+    - name: Cross-compile on all supported platforms
+      run: gen/xbuild.sh
+
   generate:
     runs-on: ubuntu-latest
     steps:
diff --git a/complete.go b/complete.go
index cae5082..f891071 100644
--- a/complete.go
+++ b/complete.go
@@ -218,6 +218,7 @@ var (
 		"infotimefmtnew",
 		"infotimefmtold",
 		"truncatechar",
+		"truncatepct",
 	}
 )
 
@@ -282,7 +283,6 @@ func matchExec(s string) (matches []string, longest []rune) {
 				continue
 			}
 
-			log.Print(finfo.Name())
 			words = append(words, finfo.Name())
 		}
 	}
diff --git a/doc.go b/doc.go
index 806b6aa..782f167 100644
--- a/doc.go
+++ b/doc.go
@@ -172,6 +172,7 @@ The following options can be used to customize the behavior of lf:
 	tempmarks        string    (default '')
 	timefmt          string    (default 'Mon Jan _2 15:04:05 2006')
 	truncatechar     string    (default '~')
+	truncatepct      int       (default 100)
 	waitmsg          string    (default 'Press any key to continue')
 	wrapscan         bool      (default true)
 	wrapscroll       bool      (default false)
@@ -941,6 +942,23 @@ Format string of the file modification time shown in the bottom line.
 
 Truncate character shown at the end when the file name does not fit to the pane.
 
+	truncatepct  int       (default 100)
+
+When a filename is too long to be shown completely, the available space is
+partitioned in two pieces. truncatepct defines a fraction (in percent
+between 0 and 100) for the size of the first piece, which will show the
+beginning of the filename. The second piece will show the end of the filename
+and will use the rest of the available space. Both pieces are separated by the
+truncation character (truncatechar).
+A value of 100 will only show the beginning of the filename,
+while a value of 0 will only show the end of the filename, e.g.:
+
+- `set truncatepct 100` -> "very-long-filename-tr~" (default)
+
+- `set truncatepct 50`  -> "very-long-f~-truncated"
+
+- `set truncatepct 0`   -> "~ng-filename-truncated"
+
 	waitmsg        string    (default 'Press any key to continue')
 
 String shown after commands of shell-wait type.
diff --git a/docstring.go b/docstring.go
index 61692de..24f6275 100644
--- a/docstring.go
+++ b/docstring.go
@@ -175,6 +175,7 @@ The following options can be used to customize the behavior of lf:
     tempmarks        string    (default '')
     timefmt          string    (default 'Mon Jan _2 15:04:05 2006')
     truncatechar     string    (default '~')
+    truncatepct      int       (default 100)
     waitmsg          string    (default 'Press any key to continue')
     wrapscan         bool      (default true)
     wrapscroll       bool      (default false)
@@ -1013,6 +1014,22 @@ Format string of the file modification time shown in the bottom line.
 
 Truncate character shown at the end when the file name does not fit to the pane.
 
+    truncatepct  int       (default 100)
+
+When a filename is too long to be shown completely, the available space is
+partitioned in two pieces. truncatepct defines a fraction (in percent between
+0 and 100) for the size of the first piece, which will show the beginning of
+the filename. The second piece will show the end of the filename and will use
+the rest of the available space. Both pieces are separated by the truncation
+character (truncatechar). A value of 100 will only show the beginning of the
+filename, while a value of 0 will only show the end of the filename, e.g.:
+
+- 'set truncatepct 100' -> "very-long-filename-tr~" (default)
+
+- 'set truncatepct 50' -> "very-long-f~-truncated"
+
+- 'set truncatepct 0' -> "~ng-filename-truncated"
+
     waitmsg        string    (default 'Press any key to continue')
 
 String shown after commands of shell-wait type.
diff --git a/eval.go b/eval.go
index 24975c3..2eea96d 100644
--- a/eval.go
+++ b/eval.go
@@ -831,6 +831,17 @@ func (e *setExpr) eval(app *app, args []string) {
 		}
 
 		gOpts.truncatechar = e.val
+	case "truncatepct":
+		n, err := strconv.Atoi(e.val)
+		if err != nil {
+			app.ui.echoerrf("truncatepct: %s", err)
+			return
+		}
+		if n < 0 || n > 100 {
+			app.ui.echoerrf("truncatepct: must be between 0 and 100 (both inclusive), got %d", n)
+			return
+		}
+		gOpts.truncatepct = n
 	case "waitmsg":
 		gOpts.waitmsg = e.val
 	case "wrapscan":
diff --git a/gen/xbuild.sh b/gen/xbuild.sh
index af8c814..0dc423f 100755
--- a/gen/xbuild.sh
+++ b/gen/xbuild.sh
@@ -8,51 +8,85 @@
 # compressed in an archive form (`.zip` for windows and `.tar.gz` for the rest)
 # within a folder named `dist`.
 
-set -o verbose
+[ -z "$version" ] && version=$(git describe --tags)
 
-[ -z $version ] && version=$(git describe --tags)
+test -d dist && {
+    echo "gen/xbuild.sh: WARNING: removing preexisting subdir 'dist'"
+    rm -r dist || exit 5
+}
+mkdir dist || exit 5
 
-mkdir -p dist
+ERRORS=
 
-# https://golang.org/doc/install/source#environment
+build() {
+    # https://golang.org/doc/install/source#environment
+    echo "=== Building for GOOS=$1 and GOARCH=$2."
+    CGO_ENABLED=0 GOOS="$1" GOARCH="$2" go build -o dist/ \
+        -ldflags="-s -w -X main.gVersion=$version"
+    if test "$?" != "0"; then
+        ERRORS=1
+    else
+        package "$1" "$2" || exit 5
+    fi
+}
 
-CGO_ENABLED=0 GOOS=android   GOARCH=arm64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-android-arm64.tar.gz   lf --remove-files
-CGO_ENABLED=0 GOOS=darwin    GOARCH=amd64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-darwin-amd64.tar.gz    lf --remove-files
-CGO_ENABLED=0 GOOS=dragonfly GOARCH=amd64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-dragonfly-amd64.tar.gz lf --remove-files
-CGO_ENABLED=0 GOOS=freebsd   GOARCH=386      go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-freebsd-386.tar.gz     lf --remove-files
-CGO_ENABLED=0 GOOS=freebsd   GOARCH=amd64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-freebsd-amd64.tar.gz   lf --remove-files
-CGO_ENABLED=0 GOOS=freebsd   GOARCH=arm      go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-freebsd-arm.tar.gz     lf --remove-files
-CGO_ENABLED=0 GOOS=illumos   GOARCH=amd64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-illumos-amd64.tar.gz   lf --remove-files
-CGO_ENABLED=0 GOOS=linux     GOARCH=386      go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-linux-386.tar.gz       lf --remove-files
-CGO_ENABLED=0 GOOS=linux     GOARCH=amd64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-linux-amd64.tar.gz     lf --remove-files
-CGO_ENABLED=0 GOOS=linux     GOARCH=arm      go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-linux-arm.tar.gz       lf --remove-files
-CGO_ENABLED=0 GOOS=linux     GOARCH=arm64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-linux-arm64.tar.gz     lf --remove-files
-CGO_ENABLED=0 GOOS=linux     GOARCH=ppc64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-linux-ppc64.tar.gz     lf --remove-files
-CGO_ENABLED=0 GOOS=linux     GOARCH=ppc64le  go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-linux-ppc64le.tar.gz   lf --remove-files
-CGO_ENABLED=0 GOOS=linux     GOARCH=mips     go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-linux-mips.tar.gz      lf --remove-files
-CGO_ENABLED=0 GOOS=linux     GOARCH=mipsle   go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-linux-mipsle.tar.gz    lf --remove-files
-CGO_ENABLED=0 GOOS=linux     GOARCH=mips64   go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-linux-mips64.tar.gz    lf --remove-files
-CGO_ENABLED=0 GOOS=linux     GOARCH=mips64le go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-linux-mips64le.tar.gz  lf --remove-files
-CGO_ENABLED=0 GOOS=linux     GOARCH=s390x    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-linux-s390x.tar.gz     lf --remove-files
-CGO_ENABLED=0 GOOS=netbsd    GOARCH=386      go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-netbsd-386.tar.gz      lf --remove-files
-CGO_ENABLED=0 GOOS=netbsd    GOARCH=amd64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-netbsd-amd64.tar.gz    lf --remove-files
-CGO_ENABLED=0 GOOS=netbsd    GOARCH=arm      go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-netbsd-arm.tar.gz      lf --remove-files
-CGO_ENABLED=0 GOOS=openbsd   GOARCH=386      go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-openbsd-386.tar.gz     lf --remove-files
-CGO_ENABLED=0 GOOS=openbsd   GOARCH=amd64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-openbsd-amd64.tar.gz   lf --remove-files
-CGO_ENABLED=0 GOOS=openbsd   GOARCH=arm      go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-openbsd-arm.tar.gz     lf --remove-files
-CGO_ENABLED=0 GOOS=openbsd   GOARCH=arm64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-openbsd-arm64.tar.gz   lf --remove-files
-CGO_ENABLED=0 GOOS=solaris   GOARCH=amd64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-solaris-amd64.tar.gz   lf --remove-files
+package() (
+    cd dist || return 1 
+    # Since the function is surrounded by (), the cd only affects a subshell
+    OUTFILE=
+    case "$1" in
+    windows)
+        OUTFILE="lf-$1-$2.zip"
+        zip "$OUTFILE" lf.exe --move || return 1
+        ;;
+    *)
+        OUTFILE="lf-$1-$2.tar.gz"
+        tar czf "$OUTFILE" lf --remove-files || return 1
+        ;;
+    esac
+    echo "dist/$OUTFILE successfully created."
+)
 
-CGO_ENABLED=0 GOOS=windows   GOARCH=386      go build -ldflags="-s -w -X main.gVersion=$version" && zip dist/lf-windows-386.zip            lf.exe --move
-CGO_ENABLED=0 GOOS=windows   GOARCH=amd64    go build -ldflags="-s -w -X main.gVersion=$version" && zip dist/lf-windows-amd64.zip          lf.exe --move
+build android arm64
+build darwin amd64
+build darwin arm64
+build dragonfly amd64
+build freebsd 386
+build freebsd amd64
+build freebsd arm
+build illumos amd64
+build linux 386
+build linux amd64
+build linux arm
+build linux arm64
+build linux ppc64
+build linux ppc64le
+build linux mips
+build linux mipsle
+build linux mips64
+build linux mips64le
+build linux s390x
+build netbsd 386
+build netbsd amd64
+build netbsd arm
+build openbsd 386
+build openbsd amd64
+build openbsd arm
+build openbsd arm64
+build solaris amd64
+build windows 386
+build windows amd64
+# Unsupported
+# build aix ppc64
+# build android 386
+# build android amd64
+# build android arm
+# build js wasm
+# build plan9 386
+# build plan9 amd64
+# build plan9 arm
 
-# not supported
-# CGO_ENABLED=0 GOOS=aix       GOARCH=ppc64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-aix-ppc64.tar.gz       lf --remove-files
-# CGO_ENABLED=0 GOOS=android   GOARCH=386      go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-android-386.tar.gz     lf --remove-files
-# CGO_ENABLED=0 GOOS=android   GOARCH=amd64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-android-amd64.tar.gz   lf --remove-files
-# CGO_ENABLED=0 GOOS=android   GOARCH=arm      go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-android-arm.tar.gz     lf --remove-files
-# CGO_ENABLED=0 GOOS=darwin    GOARCH=arm64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-darwin-arm64.tar.gz    lf --remove-files
-# CGO_ENABLED=0 GOOS=js        GOARCH=wasm     go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-js-wasm.tar.gz         lf --remove-files
-# CGO_ENABLED=0 GOOS=plan9     GOARCH=386      go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-plan9-386.tar.gz       lf --remove-files
-# CGO_ENABLED=0 GOOS=plan9     GOARCH=amd64    go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-plan9-amd64.tar.gz     lf --remove-files
-# CGO_ENABLED=0 GOOS=plan9     GOARCH=arm      go build -ldflags="-s -w -X main.gVersion=$version" && tar czf dist/lf-plan9-arm.tar.gz       lf --remove-files
+if test -n "$ERRORS"; then
+    printf "\ngen/xbuild.sh: some targets failed to compile.\n"
+    exit 1
+fi
diff --git a/go.mod b/go.mod
index 9e31c3a..a2298d5 100644
--- a/go.mod
+++ b/go.mod
@@ -6,8 +6,8 @@ require (
 	github.com/djherbis/times v1.5.0
 	github.com/gdamore/tcell/v2 v2.6.0
 	github.com/mattn/go-runewidth v0.0.14
-	golang.org/x/sys v0.8.0
-	golang.org/x/term v0.8.0
+	golang.org/x/sys v0.9.0
+	golang.org/x/term v0.9.0
 )
 
 require (
diff --git a/go.sum b/go.sum
index 96e8bb1..8f6e8c5 100644
--- a/go.sum
+++ b/go.sum
@@ -26,13 +26,13 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
-golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
+golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
-golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
+golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
diff --git a/lf.1 b/lf.1
index 561edc5..2804db4 100644
--- a/lf.1
+++ b/lf.1
@@ -191,6 +191,7 @@ The following options can be used to customize the behavior of lf:
     tempmarks        string    (default '')
     timefmt          string    (default 'Mon Jan _2 15:04:05 2006')
     truncatechar     string    (default '~')
+    truncatepct      int       (default 100)
     waitmsg          string    (default 'Press any key to continue')
     wrapscan         bool      (default true)
     wrapscroll       bool      (default false)
@@ -1119,6 +1120,18 @@ Format string of the file modification time shown in the bottom line.
 .PP
 Truncate character shown at the end when the file name does not fit to the pane.
 .PP
+.EX
+    truncatepct  int       (default 100)
+.EE
+.PP
+When a filename is too long to be shown completely, the available space is partitioned in two pieces. truncatepct defines a fraction (in percent between 0 and 100) for the size of the first piece, which will show the beginning of the filename. The second piece will show the end of the filename and will use the rest of the available space. Both pieces are separated by the truncation character (truncatechar). A value of 100 will only show the beginning of the filename, while a value of 0 will only show the end of the filename, e.g.:
+.PP
+- `set truncatepct 100` -> "very-long-filename-tr~" (default)
+.PP
+- `set truncatepct 50` -> "very-long-f~-truncated"
+.PP
+- `set truncatepct 0` -> "~ng-filename-truncated"
+.PP
 .EX
     waitmsg        string    (default 'Press any key to continue')
 .EE
diff --git a/misc.go b/misc.go
index b0303ea..8199697 100644
--- a/misc.go
+++ b/misc.go
@@ -53,6 +53,19 @@ func runeSliceWidthRange(rs []rune, beg, end int) []rune {
 	return rs[b:]
 }
 
+// Returns the last runes of `rs` that take up at most `maxWidth` space.
+func runeSliceWidthLastRange(rs []rune, maxWidth int) []rune {
+	lastWidth := 0
+	for i := len(rs) - 1; i >= 0; i-- {
+		w := runewidth.RuneWidth(rs[i])
+		if lastWidth+w > maxWidth {
+			return rs[i+1:]
+		}
+		lastWidth += w
+	}
+	return rs
+}
+
 // This function is used to escape whitespaces and special characters with
 // backlashes in a given string.
 func escape(s string) string {
diff --git a/opts.go b/opts.go
index 21d624c..5b8feaa 100644
--- a/opts.go
+++ b/opts.go
@@ -72,6 +72,7 @@ var gOpts struct {
 	infotimefmtnew   string
 	infotimefmtold   string
 	truncatechar     string
+	truncatepct      int
 	ratios           []int
 	hiddenfiles      []string
 	history          bool
@@ -133,6 +134,7 @@ func init() {
 	gOpts.infotimefmtnew = "Jan _2 15:04"
 	gOpts.infotimefmtold = "Jan _2  2006"
 	gOpts.truncatechar = "~"
+	gOpts.truncatepct = 100
 	gOpts.ratios = []int{1, 2, 3}
 	gOpts.hiddenfiles = []string{".*"}
 	gOpts.history = true
diff --git a/ui.go b/ui.go
index 635a428..4b8cb55 100644
--- a/ui.go
+++ b/ui.go
@@ -468,8 +468,11 @@ func (win *win) printDir(screen tcell.Screen, dir *dir, context *dirContext, dir
 
 		filename := []rune(f.Name())
 		if runeSliceWidth(filename) > maxFilenameWidth {
-			filename = runeSliceWidthRange(filename, 0, maxFilenameWidth-1)
+			truncatePos := (maxFilenameWidth - 1) * gOpts.truncatepct / 100
+			lastPart := runeSliceWidthLastRange(filename, maxFilenameWidth-truncatePos-1)
+			filename = runeSliceWidthRange(filename, 0, truncatePos)
 			filename = append(filename, []rune(gOpts.truncatechar)...)
+			filename = append(filename, lastPart...)
 		}
 		for i := runeSliceWidth(filename); i < maxFilenameWidth; i++ {
 			filename = append(filename, ' ')


Test patch:
diff --git a/misc_test.go b/misc_test.go
index aa6f6df..758e342 100644
--- a/misc_test.go
+++ b/misc_test.go
@@ -83,6 +83,47 @@ func TestRuneSliceWidthRange(t *testing.T) {
 	}
 }
 
+func TestRuneSliceWidthLastRange(t *testing.T) {
+	tests := []struct {
+		rs       []rune
+		maxWidth int
+		exp      []rune
+	}{
+		{[]rune{}, 0, []rune{}},
+		{[]rune{}, 1, []rune{}},
+		{[]rune{'a', 'ı', 'b', ' '}, 0, []rune{}},
+		{[]rune{'a', 'ı', 'b', ' '}, 1, []rune{' '}},
+		{[]rune{'a', 'ı', 'b', ' '}, 2, []rune{'b', ' '}},
+		{[]rune{'a', 'ı', 'b', ' '}, 3, []rune{'ı', 'b', ' '}},
+		{[]rune{'a', 'ı', 'b', ' '}, 4, []rune{'a', 'ı', 'b', ' '}},
+		{[]rune{'a', 'ı', 'b', ' '}, 5, []rune{'a', 'ı', 'b', ' '}},
+		{[]rune{'世', '界', '世', '界'}, 0, []rune{}},
+		{[]rune{'世', '界', '世', '界'}, 1, []rune{}},
+		{[]rune{'世', '界', '世', '界'}, 2, []rune{'界'}},
+		{[]rune{'世', '界', '世', '界'}, 3, []rune{'界'}},
+		{[]rune{'世', '界', '世', '界'}, 4, []rune{'世', '界'}},
+		{[]rune{'世', '界', '世', '界'}, 5, []rune{'世', '界'}},
+		{[]rune{'世', '界', '世', '界'}, 6, []rune{'界', '世', '界'}},
+		{[]rune{'世', '界', '世', '界'}, 7, []rune{'界', '世', '界'}},
+		{[]rune{'世', '界', '世', '界'}, 8, []rune{'世', '界', '世', '界'}},
+		{[]rune{'世', '界', '世', '界'}, 9, []rune{'世', '界', '世', '界'}},
+		{[]rune{'世', 'a', '界', 'ı'}, 0, []rune{}},
+		{[]rune{'世', 'a', '界', 'ı'}, 1, []rune{'ı'}},
+		{[]rune{'世', 'a', '界', 'ı'}, 2, []rune{'ı'}},
+		{[]rune{'世', 'a', '界', 'ı'}, 3, []rune{'界', 'ı'}},
+		{[]rune{'世', 'a', '界', 'ı'}, 4, []rune{'a', '界', 'ı'}},
+		{[]rune{'世', 'a', '界', 'ı'}, 5, []rune{'a', '界', 'ı'}},
+		{[]rune{'世', 'a', '界', 'ı'}, 6, []rune{'世', 'a', '界', 'ı'}},
+		{[]rune{'世', 'a', '界', 'ı'}, 7, []rune{'世', 'a', '界', 'ı'}},
+	}
+
+	for _, test := range tests {
+		if got := runeSliceWidthLastRange(test.rs, test.maxWidth); !reflect.DeepEqual(got, test.exp) {
+			t.Errorf("at input '%v' expected '%v' but got '%v'", test.rs, test.exp, got)
+		}
+	}
+}
+
 func TestEscape(t *testing.T) {
 	tests := []struct {
 		s   string