Repository: junegunn/fzf
Pull request #3137 (2023-01-20 02:47:51)

Add Helix editor to bash autocompletion
Adding [Helix editor](https://github.com/helix-editor/helix) to bash autocompletion for "hx **<tab><tab>" to work


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4ca63072..08da4026 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,15 @@
 CHANGELOG
 =========
 
+0.37.0
+------
+- New actions
+    - `change-border-label`
+    - `change-preview-label`
+    - `transform-border-label`
+    - `transform-preview-label`
+- Bug fixes and improvements
+
 0.36.0
 ------
 - Added `--listen=HTTP_PORT` option to start HTTP server. It allows external
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index 846a5bd0..70faf8a3 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -972,89 +972,93 @@ e.g.
 .SS AVAILABLE ACTIONS:
 A key or an event can be bound to one or more of the following actions.
 
-  \fBACTION:                   DEFAULT BINDINGS (NOTES):
-    \fBabort\fR                      \fIctrl-c  ctrl-g  ctrl-q  esc\fR
-    \fBaccept\fR                     \fIenter   double-click\fR
-    \fBaccept-non-empty\fR           (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
-    \fBbackward-char\fR              \fIctrl-b  left\fR
-    \fBbackward-delete-char\fR       \fIctrl-h  bspace\fR
-    \fBbackward-delete-char/eof\fR   (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
-    \fBbackward-kill-word\fR         \fIalt-bs\fR
-    \fBbackward-word\fR              \fIalt-b   shift-left\fR
-    \fBbeginning-of-line\fR          \fIctrl-a  home\fR
-    \fBcancel\fR                     (clear query string if not empty, abort fzf otherwise)
-    \fBchange-preview(...)\fR        (change \fB--preview\fR option)
-    \fBchange-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
-    \fBchange-prompt(...)\fR         (change prompt to the given string)
-    \fBchange-query(...)\fR          (change query string to the given string)
-    \fBclear-screen\fR               \fIctrl-l\fR
-    \fBclear-selection\fR            (clear multi-selection)
-    \fBclose\fR                      (close preview window if open, abort fzf otherwise)
-    \fBclear-query\fR                (clear query string)
-    \fBdelete-char\fR                \fIdel\fR
-    \fBdelete-char/eof\fR            \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
+  \fBACTION:                      DEFAULT BINDINGS (NOTES):
+    \fBabort\fR                        \fIctrl-c  ctrl-g  ctrl-q  esc\fR
+    \fBaccept\fR                       \fIenter   double-click\fR
+    \fBaccept-non-empty\fR             (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
+    \fBbackward-char\fR                \fIctrl-b  left\fR
+    \fBbackward-delete-char\fR         \fIctrl-h  bspace\fR
+    \fBbackward-delete-char/eof\fR     (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
+    \fBbackward-kill-word\fR           \fIalt-bs\fR
+    \fBbackward-word\fR                \fIalt-b   shift-left\fR
+    \fBbeginning-of-line\fR            \fIctrl-a  home\fR
+    \fBcancel\fR                       (clear query string if not empty, abort fzf otherwise)
+    \fBchange-preview(...)\fR          (change \fB--preview\fR option)
+    \fBchange-preview-window(...)\fR   (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
+    \fBchange-preview-label(...)\fR    (change \fB--preview-label\fR to the given string)
+    \fBchange-prompt(...)\fR           (change prompt to the given string)
+    \fBchange-border-label(...)\fR     (change \fB--border-label\fR to the given string)
+    \fBchange-query(...)\fR            (change query string to the given string)
+    \fBclear-screen\fR                 \fIctrl-l\fR
+    \fBclear-selection\fR              (clear multi-selection)
+    \fBclose\fR                        (close preview window if open, abort fzf otherwise)
+    \fBclear-query\fR                  (clear query string)
+    \fBdelete-char\fR                  \fIdel\fR
+    \fBdelete-char/eof\fR              \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
     \fBdeselect\fR
-    \fBdeselect-all\fR               (deselect all matches)
-    \fBdisable-search\fR             (disable search functionality)
-    \fBdown\fR                       \fIctrl-j  ctrl-n  down\fR
-    \fBenable-search\fR              (enable search functionality)
-    \fBend-of-line\fR                \fIctrl-e  end\fR
-    \fBexecute(...)\fR               (see below for the details)
-    \fBexecute-silent(...)\fR        (see below for the details)
-    \fBfirst\fR                      (move to the first match; same as \fBpos(1)\fR)
-    \fBforward-char\fR               \fIctrl-f  right\fR
-    \fBforward-word\fR               \fIalt-f   shift-right\fR
+    \fBdeselect-all\fR                 (deselect all matches)
+    \fBdisable-search\fR               (disable search functionality)
+    \fBdown\fR                         \fIctrl-j  ctrl-n  down\fR
+    \fBenable-search\fR                (enable search functionality)
+    \fBend-of-line\fR                  \fIctrl-e  end\fR
+    \fBexecute(...)\fR                 (see below for the details)
+    \fBexecute-silent(...)\fR          (see below for the details)
+    \fBfirst\fR                        (move to the first match; same as \fBpos(1)\fR)
+    \fBforward-char\fR                 \fIctrl-f  right\fR
+    \fBforward-word\fR                 \fIalt-f   shift-right\fR
     \fBignore\fR
-    \fBjump\fR                       (EasyMotion-like 2-keystroke movement)
-    \fBjump-accept\fR                (jump and accept)
+    \fBjump\fR                         (EasyMotion-like 2-keystroke movement)
+    \fBjump-accept\fR                  (jump and accept)
     \fBkill-line\fR
-    \fBkill-word\fR                  \fIalt-d\fR
-    \fBlast\fR                       (move to the last match; same as \fBpos(-1)\fR)
-    \fBnext-history\fR               (\fIctrl-n\fR on \fB--history\fR)
-    \fBnext-selected\fR              (move to the next selected item)
-    \fBpage-down\fR                  \fIpgdn\fR
-    \fBpage-up\fR                    \fIpgup\fR
+    \fBkill-word\fR                    \fIalt-d\fR
+    \fBlast\fR                         (move to the last match; same as \fBpos(-1)\fR)
+    \fBnext-history\fR                 (\fIctrl-n\fR on \fB--history\fR)
+    \fBnext-selected\fR                (move to the next selected item)
+    \fBpage-down\fR                    \fIpgdn\fR
+    \fBpage-up\fR                      \fIpgup\fR
     \fBhalf-page-down\fR
     \fBhalf-page-up\fR
-    \fBpos(...)\fR                   (move cursor to the numeric position; negative number to count from the end)
-    \fBprev-history\fR               (\fIctrl-p\fR on \fB--history\fR)
-    \fBprev-selected\fR              (move to the previous selected item)
-    \fBpreview(...)\fR               (see below for the details)
-    \fBpreview-down\fR               \fIshift-down\fR
-    \fBpreview-up\fR                 \fIshift-up\fR
+    \fBpos(...)\fR                     (move cursor to the numeric position; negative number to count from the end)
+    \fBprev-history\fR                 (\fIctrl-p\fR on \fB--history\fR)
+    \fBprev-selected\fR                (move to the previous selected item)
+    \fBpreview(...)\fR                 (see below for the details)
+    \fBpreview-down\fR                 \fIshift-down\fR
+    \fBpreview-up\fR                   \fIshift-up\fR
     \fBpreview-page-down\fR
     \fBpreview-page-up\fR
     \fBpreview-half-page-down\fR
     \fBpreview-half-page-up\fR
     \fBpreview-bottom\fR
     \fBpreview-top\fR
-    \fBprint-query\fR                (print query and exit)
-    \fBput\fR                        (put the character to the prompt)
-    \fBput(...)\fR                   (put the given string to the prompt)
+    \fBprint-query\fR                  (print query and exit)
+    \fBput\fR                          (put the character to the prompt)
+    \fBput(...)\fR                     (put the given string to the prompt)
     \fBrefresh-preview\fR
-    \fBrebind(...)\fR                (rebind bindings after \fBunbind\fR)
-    \fBreload(...)\fR                (see below for the details)
-    \fBreload-sync(...)\fR           (see below for the details)
-    \fBreplace-query\fR              (replace query string with the current selection)
+    \fBrebind(...)\fR                  (rebind bindings after \fBunbind\fR)
+    \fBreload(...)\fR                  (see below for the details)
+    \fBreload-sync(...)\fR             (see below for the details)
+    \fBreplace-query\fR                (replace query string with the current selection)
     \fBselect\fR
-    \fBselect-all\fR                 (select all matches)
-    \fBtoggle\fR                     (\fIright-click\fR)
-    \fBtoggle-all\fR                 (toggle all matches)
-    \fBtoggle+down\fR                \fIctrl-i  (tab)\fR
-    \fBtoggle-in\fR                  (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
-    \fBtoggle-out\fR                 (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
+    \fBselect-all\fR                   (select all matches)
+    \fBtoggle\fR                       (\fIright-click\fR)
+    \fBtoggle-all\fR                   (toggle all matches)
+    \fBtoggle+down\fR                  \fIctrl-i  (tab)\fR
+    \fBtoggle-in\fR                    (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
+    \fBtoggle-out\fR                   (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
     \fBtoggle-preview\fR
     \fBtoggle-preview-wrap\fR
-    \fBtoggle-search\fR              (toggle search functionality)
+    \fBtoggle-search\fR                (toggle search functionality)
     \fBtoggle-sort\fR
-    \fBtoggle+up\fR                  \fIbtab    (shift-tab)\fR
-    \fBtransform-prompt(...)\fR      (transform prompt string using an external command)
-    \fBtransform-query(...)\fR       (transform query string using an external command)
-    \fBunbind(...)\fR                (unbind bindings)
-    \fBunix-line-discard\fR          \fIctrl-u\fR
-    \fBunix-word-rubout\fR           \fIctrl-w\fR
-    \fBup\fR                         \fIctrl-k  ctrl-p  up\fR
-    \fByank\fR                       \fIctrl-y\fR
+    \fBtoggle+up\fR                    \fIbtab    (shift-tab)\fR
+    \fBtransform-border-label(...)\fR  (transform border label using an external command)
+    \fBtransform-preview-label(...)\fR (transform preview label using an external command)
+    \fBtransform-prompt(...)\fR        (transform prompt string using an external command)
+    \fBtransform-query(...)\fR         (transform query string using an external command)
+    \fBunbind(...)\fR                  (unbind bindings)
+    \fBunix-line-discard\fR            \fIctrl-u\fR
+    \fBunix-word-rubout\fR             \fIctrl-w\fR
+    \fBup\fR                           \fIctrl-k  ctrl-p  up\fR
+    \fByank\fR                         \fIctrl-y\fR
 
 .SS ACTION COMPOSITION
 
diff --git a/shell/completion.bash b/shell/completion.bash
index 51687a08..b3612c86 100644
--- a/shell/completion.bash
+++ b/shell/completion.bash
@@ -310,7 +310,7 @@ complete -o default -F _fzf_opts_completion fzf-tmux
 d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
 a_cmds="
   awk cat diff diff3
-  emacs emacsclient ex file ftp g++ gcc gvim head hg java
+  emacs emacsclient ex file ftp g++ gcc gvim head hg hx java
   javac ld less more mvim nvim patch perl python ruby
   sed sftp sort source tail tee uniq vi view vim wc xdg-open
   basename bunzip2 bzip2 chmod chown curl cp dirname du
diff --git a/src/options.go b/src/options.go
index fd160367..fa238d7e 100644
--- a/src/options.go
+++ b/src/options.go
@@ -912,7 +912,7 @@ const (
 
 func init() {
 	executeRegexp = regexp.MustCompile(
-		`(?si)[:+](execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt)|change-preview-window|change-preview|(?:re|un)bind|pos|put)`)
+		`(?si)[:+](execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|border-label|preview-label)|change-preview-window|change-preview|(?:re|un)bind|pos|put)`)
 	splitRegexp = regexp.MustCompile("[,:]+")
 	actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
 }
@@ -954,7 +954,7 @@ Loop:
 		ce = regexp.QuoteMeta(ce)
 
 		// @$ or @+
-		loc = regexp.MustCompile(fmt.Sprintf(`^%s.*?(%s[+,]|%s$)`, cs, ce, ce)).FindStringIndex(action)
+		loc = regexp.MustCompile(fmt.Sprintf(`(?s)^%s.*?(%s[+,]|%s$)`, cs, ce, ce)).FindStringIndex(action)
 		if loc == nil {
 			masked += action
 			break
@@ -1220,6 +1220,10 @@ func isExecuteAction(str string) actionType {
 		return actRebind
 	case "preview":
 		return actPreview
+	case "change-border-label":
+		return actChangeBorderLabel
+	case "change-preview-label":
+		return actChangePreviewLabel
 	case "change-preview-window":
 		return actChangePreviewWindow
 	case "change-preview":
@@ -1238,6 +1242,10 @@ func isExecuteAction(str string) actionType {
 		return actExecuteMulti
 	case "put":
 		return actPut
+	case "transform-border-label":
+		return actTransformBorderLabel
+	case "transform-preview-label":
+		return actTransformPreviewLabel
 	case "transform-prompt":
 		return actTransformPrompt
 	case "transform-query":
diff --git a/src/terminal.go b/src/terminal.go
index b21aca73..b2a99134 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -276,6 +276,8 @@ const (
 	reqRefresh
 	reqReinit
 	reqFullRedraw
+	reqRedrawBorderLabel
+	reqRedrawPreviewLabel
 	reqClose
 	reqPrintQuery
 	reqPreviewEnqueue
@@ -306,6 +308,8 @@ const (
 	actBackwardDeleteCharEOF
 	actBackwardWord
 	actCancel
+	actChangeBorderLabel
+	actChangePreviewLabel
 	actChangePrompt
 	actChangeQuery
 	actClearScreen
@@ -347,6 +351,8 @@ const (
 	actToggleSort
 	actTogglePreview
 	actTogglePreviewWrap
+	actTransformBorderLabel
+	actTransformPreviewLabel
 	actTransformPrompt
 	actTransformQuery
 	actPreview
@@ -1250,37 +1256,41 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
 	}
 
 	// Print border label
-	printLabel := func(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape) {
-		if window == nil || render == nil {
-			return
-		}
-
-		switch borderShape {
-		case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble:
-			var col int
-			if opts.column == 0 {
-				col = util.Max(0, (window.Width()-length)/2)
-			} else if opts.column < 0 {
-				col = util.Max(0, window.Width()+opts.column+1-length)
-			} else {
-				col = util.Min(opts.column-1, window.Width()-length)
-			}
-			row := 0
-			if borderShape == tui.BorderBottom || opts.bottom {
-				row = window.Height() - 1
-			}
-			window.Move(row, col)
-			render(window, window.Width())
-		}
-	}
-	printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape)
-	printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border)
+	t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, false)
+	t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, false)
 
 	for i := 0; i < t.window.Height(); i++ {
 		t.window.MoveAndClear(i, 0)
 	}
 }
 
+func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape, redrawBorder bool) {
+	if window == nil || render == nil {
+		return
+	}
+
+	switch borderShape {
+	case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble:
+		if redrawBorder {
+			window.DrawHBorder()
+		}
+		var col int
+		if opts.column == 0 {
+			col = util.Max(0, (window.Width()-length)/2)
+		} else if opts.column < 0 {
+			col = util.Max(0, window.Width()+opts.column+1-length)
+		} else {
+			col = util.Min(opts.column-1, window.Width()-length)
+		}
+		row := 0
+		if borderShape == tui.BorderBottom || opts.bottom {
+			row = window.Height() - 1
+		}
+		window.Move(row, col)
+		render(window, window.Width())
+	}
+}
+
 func (t *Terminal) move(y int, x int, clear bool) {
 	h := t.window.Height()
 
@@ -2657,6 +2667,10 @@ func (t *Terminal) Loop() {
 						t.printHeader()
 					case reqRefresh:
 						t.suppress = false
+					case reqRedrawBorderLabel:
+						t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true)
+					case reqRedrawPreviewLabel:
+						t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, true)
 					case reqReinit:
 						t.tui.Resume(t.fullscreen, t.sigstop)
 						t.redraw()
@@ -2909,6 +2923,28 @@ func (t *Terminal) Loop() {
 			case actChangeQuery:
 				t.input = []rune(a.a)
 				t.cx = len(t.input)
+			case actChangeBorderLabel:
+				if t.border != nil {
+					t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(a.a, &tui.ColBorderLabel, false)
+					req(reqRedrawBorderLabel)
+				}
+			case actChangePreviewLabel:
+				if t.pborder != nil {
+					t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(a.a, &tui.ColPreviewLabel, false)
+					req(reqRedrawPreviewLabel)
+				}
+			case actTransformBorderLabel:
+				if t.border != nil {
+					label := t.executeCommand(a.a, false, true, true)
+					t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
+					req(reqRedrawBorderLabel)
+				}
+			case actTransformPreviewLabel:
+				if t.pborder != nil {
+					label := t.executeCommand(a.a, false, true, true)
+					t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
+					req(reqRedrawPreviewLabel)
+				}
 			case actChangePrompt:
 				t.prompt, t.promptLen = t.parsePrompt(a.a)
 				req(reqPrompt)
diff --git a/src/tui/light.go b/src/tui/light.go
index a336cac0..3ff4ded7 100644
--- a/src/tui/light.go
+++ b/src/tui/light.go
@@ -719,25 +719,38 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
 		w.fg = r.theme.Fg.Color
 		w.bg = r.theme.Bg.Color
 	}
-	w.drawBorder()
+	w.drawBorder(false)
 	return w
 }
 
-func (w *LightWindow) drawBorder() {
+func (w *LightWindow) DrawHBorder() {
+	w.drawBorder(true)
+}
+
+func (w *LightWindow) drawBorder(onlyHorizontal bool) {
 	switch w.border.shape {
 	case BorderRounded, BorderSharp, BorderBold, BorderDouble:
-		w.drawBorderAround()
+		w.drawBorderAround(onlyHorizontal)
 	case BorderHorizontal:
 		w.drawBorderHorizontal(true, true)
 	case BorderVertical:
+		if onlyHorizontal {
+			return
+		}
 		w.drawBorderVertical(true, true)
 	case BorderTop:
 		w.drawBorderHorizontal(true, false)
 	case BorderBottom:
 		w.drawBorderHorizontal(false, true)
 	case BorderLeft:
+		if onlyHorizontal {
+			return
+		}
 		w.drawBorderVertical(true, false)
 	case BorderRight:
+		if onlyHorizontal {
+			return
+		}
 		w.drawBorderVertical(false, true)
 	}
 }
@@ -779,23 +792,25 @@ func (w *LightWindow) drawBorderVertical(left, right bool) {
 	}
 }
 
-func (w *LightWindow) drawBorderAround() {
+func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
 	w.Move(0, 0)
 	color := ColBorder
 	if w.preview {
 		color = ColPreviewBorder
 	}
 	hw := runewidth.RuneWidth(w.border.horizontal)
-	vw := runewidth.RuneWidth(w.border.vertical)
 	tcw := runewidth.RuneWidth(w.border.topLeft) + runewidth.RuneWidth(w.border.topRight)
 	bcw := runewidth.RuneWidth(w.border.bottomLeft) + runewidth.RuneWidth(w.border.bottomRight)
 	rem := (w.width - tcw) % hw
 	w.CPrint(color, string(w.border.topLeft)+repeat(w.border.horizontal, (w.width-tcw)/hw)+repeat(' ', rem)+string(w.border.topRight))
-	for y := 1; y < w.height-1; y++ {
-		w.Move(y, 0)
-		w.CPrint(color, string(w.border.vertical))
-		w.CPrint(color, repeat(' ', w.width-vw*2))
-		w.CPrint(color, string(w.border.vertical))
+	if !onlyHorizontal {
+		vw := runewidth.RuneWidth(w.border.vertical)
+		for y := 1; y < w.height-1; y++ {
+			w.Move(y, 0)
+			w.CPrint(color, string(w.border.vertical))
+			w.CPrint(color, repeat(' ', w.width-vw*2))
+			w.CPrint(color, string(w.border.vertical))
+		}
 	}
 	w.Move(w.height-1, 0)
 	rem = (w.width - bcw) % hw
@@ -1040,7 +1055,7 @@ func (w *LightWindow) FinishFill() {
 }
 
 func (w *LightWindow) Erase() {
-	w.drawBorder()
+	w.drawBorder(false)
 	// We don't erase the window here to avoid flickering during scroll
 	w.Move(0, 0)
 }
diff --git a/src/tui/tcell.go b/src/tui/tcell.go
index 366cb775..ad0182cf 100644
--- a/src/tui/tcell.go
+++ b/src/tui/tcell.go
@@ -512,7 +512,7 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
 		height:      height,
 		normal:      normal,
 		borderStyle: borderStyle}
-	w.drawBorder()
+	w.drawBorder(false)
 	return w
 }
 
@@ -670,7 +670,11 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
 	return w.fillString(str, NewColorPair(fg, bg, a))
 }
 
-func (w *TcellWindow) drawBorder() {
+func (w *TcellWindow) DrawHBorder() {
+	w.drawBorder(true)
+}
+
+func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
 	shape := w.borderStyle.shape
 	if shape == BorderNone {
 		return
@@ -718,17 +722,19 @@ func (w *TcellWindow) drawBorder() {
 			_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
 		}
 	}
-	switch shape {
-	case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderVertical, BorderLeft:
-		for y := top; y < bot; y++ {
-			_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
+	if !onlyHorizontal {
+		switch shape {
+		case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderVertical, BorderLeft:
+			for y := top; y < bot; y++ {
+				_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
+			}
 		}
-	}
-	switch shape {
-	case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderVertical, BorderRight:
-		vw := runewidth.RuneWidth(w.borderStyle.vertical)
-		for y := top; y < bot; y++ {
-			_screen.SetContent(right-vw, y, w.borderStyle.vertical, nil, style)
+		switch shape {
+		case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderVertical, BorderRight:
+			vw := runewidth.RuneWidth(w.borderStyle.vertical)
+			for y := top; y < bot; y++ {
+				_screen.SetContent(right-vw, y, w.borderStyle.vertical, nil, style)
+			}
 		}
 	}
 	switch shape {
diff --git a/src/tui/tui.go b/src/tui/tui.go
index 5a86453b..203da76c 100644
--- a/src/tui/tui.go
+++ b/src/tui/tui.go
@@ -426,6 +426,7 @@ type Window interface {
 	Width() int
 	Height() int
 
+	DrawHBorder()
 	Refresh()
 	FinishFill()
 	Close()


Test patch:
diff --git a/src/options_test.go b/src/options_test.go
index 8754716c..4e300db7 100644
--- a/src/options_test.go
+++ b/src/options_test.go
@@ -268,7 +268,7 @@ func TestBind(t *testing.T) {
 	}
 	parseKeymap(keymap,
 		"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
-			"f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
+			"f1:execute(ls {+})+abort+execute(echo \n{+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
 			"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
 			"x:Execute(foo+bar),X:execute/bar+baz/"+
 			",f1:+first,f1:+top"+
diff --git a/test/test_go.rb b/test/test_go.rb
index 5a3659d5..5bf5ab58 100755
--- a/test/test_go.rb
+++ b/test/test_go.rb
@@ -2474,11 +2474,21 @@ class TestGoFZF < TestBase
   end
 
   def test_labels_center
-    tmux.send_keys ': | fzf --border --border-label foobar --preview : --preview-label barfoo', :Enter
+    tmux.send_keys 'echo x | fzf --border --border-label foobar --preview : --preview-label barfoo --bind "space:change-border-label(foobarfoo)+change-preview-label(barfoobar),enter:transform-border-label(echo foo{}foo)+transform-preview-label(echo bar{}bar)"', :Enter
     tmux.until do
       assert_includes(_1[0], '─foobar─')
       assert_includes(_1[1], '─barfoo─')
     end
+    tmux.send_keys :space
+    tmux.until do
+      assert_includes(_1[0], '─foobarfoo─')
+      assert_includes(_1[1], '─barfoobar─')
+    end
+    tmux.send_keys :Enter
+    tmux.until do
+      assert_includes(_1[0], '─fooxfoo─')
+      assert_includes(_1[1], '─barxbar─')
+    end
   end
 
   def test_labels_left