mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-12-01 09:13:55 +00:00
Add --wrap option and 'toggle-wrap' action (#3887)
* `--wrap` * `--wrap-sign` * `toggle-wrap` Close #3619 Close #2236 Close #577 Close #461
This commit is contained in:
parent
724f8a1d45
commit
70bf8bc35d
10
CHANGELOG.md
10
CHANGELOG.md
@ -3,6 +3,16 @@ CHANGELOG
|
|||||||
|
|
||||||
0.54.0
|
0.54.0
|
||||||
------
|
------
|
||||||
|
- Implemented line wrap of long items
|
||||||
|
- `--wrap` option enables line wrap
|
||||||
|
- `--wrap-sign` customizes the sign for wrapped lines (default: `↳ `)
|
||||||
|
- `toggle-wrap` action toggles line wrap
|
||||||
|
```sh
|
||||||
|
history | fzf --tac --wrap --bind 'ctrl-/:toggle-wrap'
|
||||||
|
|
||||||
|
# You can press CTRL-/ to toggle line wrap in CTRL-R binding
|
||||||
|
export FZF_CTRL_R_OPTS=$'--bind ctrl-/:toggle-wrap --wrap-sign "\t↳ "'
|
||||||
|
```
|
||||||
- Added `--info-command` option for customizing the info line
|
- Added `--info-command` option for customizing the info line
|
||||||
```sh
|
```sh
|
||||||
# Prepend the current cursor position in yellow
|
# Prepend the current cursor position in yellow
|
||||||
|
@ -198,6 +198,13 @@ the details.
|
|||||||
.B "\-\-cycle"
|
.B "\-\-cycle"
|
||||||
Enable cyclic scroll
|
Enable cyclic scroll
|
||||||
.TP
|
.TP
|
||||||
|
.B "\-\-wrap"
|
||||||
|
Enable line wrap
|
||||||
|
.TP
|
||||||
|
.BI "\-\-wrap\-sign" "=INDICATOR"
|
||||||
|
Indicator for wrapped lines. The default is '↳ ' or '> ' depending on
|
||||||
|
\fB\-\-no\-unicode\fR.
|
||||||
|
.TP
|
||||||
.B "\-\-no\-multi\-line"
|
.B "\-\-no\-multi\-line"
|
||||||
Disable multi-line display of items when using \fB\-\-read0\fR
|
Disable multi-line display of items when using \fB\-\-read0\fR
|
||||||
.TP
|
.TP
|
||||||
@ -1490,6 +1497,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBtoggle\-sort\fR
|
\fBtoggle\-sort\fR
|
||||||
\fBtoggle\-track\fR (toggle global tracking option (\fB\-\-track\fR))
|
\fBtoggle\-track\fR (toggle global tracking option (\fB\-\-track\fR))
|
||||||
\fBtoggle\-track\-current\fR (toggle tracking of the current item)
|
\fBtoggle\-track\-current\fR (toggle tracking of the current item)
|
||||||
|
\fBtoggle\-wrap\fR
|
||||||
\fBtoggle+up\fR \fIbtab (shift\-tab)\fR
|
\fBtoggle+up\fR \fIbtab (shift\-tab)\fR
|
||||||
\fBtrack\-current\fR (track the current item; automatically disabled if focus changes)
|
\fBtrack\-current\fR (track the current item; automatically disabled if focus changes)
|
||||||
\fBtransform(...)\fR (transform states using the output of an external command)
|
\fBtransform(...)\fR (transform states using the output of an external command)
|
||||||
|
@ -58,73 +58,74 @@ func _() {
|
|||||||
_ = x[actToggleTrack-47]
|
_ = x[actToggleTrack-47]
|
||||||
_ = x[actToggleTrackCurrent-48]
|
_ = x[actToggleTrackCurrent-48]
|
||||||
_ = x[actToggleHeader-49]
|
_ = x[actToggleHeader-49]
|
||||||
_ = x[actTrackCurrent-50]
|
_ = x[actToggleWrap-50]
|
||||||
_ = x[actUntrackCurrent-51]
|
_ = x[actTrackCurrent-51]
|
||||||
_ = x[actDown-52]
|
_ = x[actUntrackCurrent-52]
|
||||||
_ = x[actUp-53]
|
_ = x[actDown-53]
|
||||||
_ = x[actPageUp-54]
|
_ = x[actUp-54]
|
||||||
_ = x[actPageDown-55]
|
_ = x[actPageUp-55]
|
||||||
_ = x[actPosition-56]
|
_ = x[actPageDown-56]
|
||||||
_ = x[actHalfPageUp-57]
|
_ = x[actPosition-57]
|
||||||
_ = x[actHalfPageDown-58]
|
_ = x[actHalfPageUp-58]
|
||||||
_ = x[actOffsetUp-59]
|
_ = x[actHalfPageDown-59]
|
||||||
_ = x[actOffsetDown-60]
|
_ = x[actOffsetUp-60]
|
||||||
_ = x[actOffsetMiddle-61]
|
_ = x[actOffsetDown-61]
|
||||||
_ = x[actJump-62]
|
_ = x[actOffsetMiddle-62]
|
||||||
_ = x[actJumpAccept-63]
|
_ = x[actJump-63]
|
||||||
_ = x[actPrintQuery-64]
|
_ = x[actJumpAccept-64]
|
||||||
_ = x[actRefreshPreview-65]
|
_ = x[actPrintQuery-65]
|
||||||
_ = x[actReplaceQuery-66]
|
_ = x[actRefreshPreview-66]
|
||||||
_ = x[actToggleSort-67]
|
_ = x[actReplaceQuery-67]
|
||||||
_ = x[actShowPreview-68]
|
_ = x[actToggleSort-68]
|
||||||
_ = x[actHidePreview-69]
|
_ = x[actShowPreview-69]
|
||||||
_ = x[actTogglePreview-70]
|
_ = x[actHidePreview-70]
|
||||||
_ = x[actTogglePreviewWrap-71]
|
_ = x[actTogglePreview-71]
|
||||||
_ = x[actTransform-72]
|
_ = x[actTogglePreviewWrap-72]
|
||||||
_ = x[actTransformBorderLabel-73]
|
_ = x[actTransform-73]
|
||||||
_ = x[actTransformHeader-74]
|
_ = x[actTransformBorderLabel-74]
|
||||||
_ = x[actTransformPreviewLabel-75]
|
_ = x[actTransformHeader-75]
|
||||||
_ = x[actTransformPrompt-76]
|
_ = x[actTransformPreviewLabel-76]
|
||||||
_ = x[actTransformQuery-77]
|
_ = x[actTransformPrompt-77]
|
||||||
_ = x[actPreview-78]
|
_ = x[actTransformQuery-78]
|
||||||
_ = x[actChangePreview-79]
|
_ = x[actPreview-79]
|
||||||
_ = x[actChangePreviewWindow-80]
|
_ = x[actChangePreview-80]
|
||||||
_ = x[actPreviewTop-81]
|
_ = x[actChangePreviewWindow-81]
|
||||||
_ = x[actPreviewBottom-82]
|
_ = x[actPreviewTop-82]
|
||||||
_ = x[actPreviewUp-83]
|
_ = x[actPreviewBottom-83]
|
||||||
_ = x[actPreviewDown-84]
|
_ = x[actPreviewUp-84]
|
||||||
_ = x[actPreviewPageUp-85]
|
_ = x[actPreviewDown-85]
|
||||||
_ = x[actPreviewPageDown-86]
|
_ = x[actPreviewPageUp-86]
|
||||||
_ = x[actPreviewHalfPageUp-87]
|
_ = x[actPreviewPageDown-87]
|
||||||
_ = x[actPreviewHalfPageDown-88]
|
_ = x[actPreviewHalfPageUp-88]
|
||||||
_ = x[actPrevHistory-89]
|
_ = x[actPreviewHalfPageDown-89]
|
||||||
_ = x[actPrevSelected-90]
|
_ = x[actPrevHistory-90]
|
||||||
_ = x[actPrint-91]
|
_ = x[actPrevSelected-91]
|
||||||
_ = x[actPut-92]
|
_ = x[actPrint-92]
|
||||||
_ = x[actNextHistory-93]
|
_ = x[actPut-93]
|
||||||
_ = x[actNextSelected-94]
|
_ = x[actNextHistory-94]
|
||||||
_ = x[actExecute-95]
|
_ = x[actNextSelected-95]
|
||||||
_ = x[actExecuteSilent-96]
|
_ = x[actExecute-96]
|
||||||
_ = x[actExecuteMulti-97]
|
_ = x[actExecuteSilent-97]
|
||||||
_ = x[actSigStop-98]
|
_ = x[actExecuteMulti-98]
|
||||||
_ = x[actFirst-99]
|
_ = x[actSigStop-99]
|
||||||
_ = x[actLast-100]
|
_ = x[actFirst-100]
|
||||||
_ = x[actReload-101]
|
_ = x[actLast-101]
|
||||||
_ = x[actReloadSync-102]
|
_ = x[actReload-102]
|
||||||
_ = x[actDisableSearch-103]
|
_ = x[actReloadSync-103]
|
||||||
_ = x[actEnableSearch-104]
|
_ = x[actDisableSearch-104]
|
||||||
_ = x[actSelect-105]
|
_ = x[actEnableSearch-105]
|
||||||
_ = x[actDeselect-106]
|
_ = x[actSelect-106]
|
||||||
_ = x[actUnbind-107]
|
_ = x[actDeselect-107]
|
||||||
_ = x[actRebind-108]
|
_ = x[actUnbind-108]
|
||||||
_ = x[actBecome-109]
|
_ = x[actRebind-109]
|
||||||
_ = x[actShowHeader-110]
|
_ = x[actBecome-110]
|
||||||
_ = x[actHideHeader-111]
|
_ = x[actShowHeader-111]
|
||||||
|
_ = x[actHideHeader-112]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
|
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
|
||||||
|
|
||||||
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 692, 709, 716, 721, 730, 741, 752, 765, 780, 791, 804, 819, 826, 839, 852, 869, 884, 897, 911, 925, 941, 961, 973, 996, 1014, 1038, 1056, 1073, 1083, 1099, 1121, 1134, 1150, 1162, 1176, 1192, 1210, 1230, 1252, 1266, 1281, 1289, 1295, 1309, 1324, 1334, 1350, 1365, 1375, 1383, 1390, 1399, 1412, 1428, 1443, 1452, 1463, 1472, 1481, 1490, 1503, 1516}
|
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 690, 705, 722, 729, 734, 743, 754, 765, 778, 793, 804, 817, 832, 839, 852, 865, 882, 897, 910, 924, 938, 954, 974, 986, 1009, 1027, 1051, 1069, 1086, 1096, 1112, 1134, 1147, 1163, 1175, 1189, 1205, 1223, 1243, 1265, 1279, 1294, 1302, 1308, 1322, 1337, 1347, 1363, 1378, 1388, 1396, 1403, 1412, 1425, 1441, 1456, 1465, 1476, 1485, 1494, 1503, 1516, 1529}
|
||||||
|
|
||||||
func (i actionType) String() string {
|
func (i actionType) String() string {
|
||||||
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
||||||
|
@ -53,6 +53,8 @@ Usage: fzf [options]
|
|||||||
--no-mouse Disable mouse
|
--no-mouse Disable mouse
|
||||||
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
||||||
--cycle Enable cyclic scroll
|
--cycle Enable cyclic scroll
|
||||||
|
--wrap Enable line wrap
|
||||||
|
--wrap-sign=STR Indicator for wrapped lines
|
||||||
--no-multi-line Disable multi-line display of items when using --read0
|
--no-multi-line Disable multi-line display of items when using --read0
|
||||||
--keep-right Keep the right end of the line visible on overflow
|
--keep-right Keep the right end of the line visible on overflow
|
||||||
--scroll-off=LINES Number of screen lines to keep above or below when
|
--scroll-off=LINES Number of screen lines to keep above or below when
|
||||||
@ -435,6 +437,8 @@ type Options struct {
|
|||||||
MinHeight int
|
MinHeight int
|
||||||
Layout layoutType
|
Layout layoutType
|
||||||
Cycle bool
|
Cycle bool
|
||||||
|
Wrap bool
|
||||||
|
WrapSign *string
|
||||||
MultiLine bool
|
MultiLine bool
|
||||||
CursorLine bool
|
CursorLine bool
|
||||||
KeepRight bool
|
KeepRight bool
|
||||||
@ -543,6 +547,7 @@ func defaultOptions() *Options {
|
|||||||
MinHeight: 10,
|
MinHeight: 10,
|
||||||
Layout: layoutDefault,
|
Layout: layoutDefault,
|
||||||
Cycle: false,
|
Cycle: false,
|
||||||
|
Wrap: false,
|
||||||
MultiLine: true,
|
MultiLine: true,
|
||||||
KeepRight: false,
|
KeepRight: false,
|
||||||
Hscroll: true,
|
Hscroll: true,
|
||||||
@ -1366,6 +1371,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actToggleTrackCurrent)
|
appendAction(actToggleTrackCurrent)
|
||||||
case "toggle-header":
|
case "toggle-header":
|
||||||
appendAction(actToggleHeader)
|
appendAction(actToggleHeader)
|
||||||
|
case "toggle-wrap":
|
||||||
|
appendAction(actToggleWrap)
|
||||||
case "show-header":
|
case "show-header":
|
||||||
appendAction(actShowHeader)
|
appendAction(actShowHeader)
|
||||||
case "hide-header":
|
case "hide-header":
|
||||||
@ -2163,6 +2170,16 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
opts.CursorLine = false
|
opts.CursorLine = false
|
||||||
case "--no-cycle":
|
case "--no-cycle":
|
||||||
opts.Cycle = false
|
opts.Cycle = false
|
||||||
|
case "--wrap":
|
||||||
|
opts.Wrap = true
|
||||||
|
case "--no-wrap":
|
||||||
|
opts.Wrap = false
|
||||||
|
case "--wrap-sign":
|
||||||
|
str, err := nextString(allArgs, &i, "wrap sign required")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opts.WrapSign = &str
|
||||||
case "--multi-line":
|
case "--multi-line":
|
||||||
opts.MultiLine = true
|
opts.MultiLine = true
|
||||||
case "--no-multi-line":
|
case "--no-multi-line":
|
||||||
@ -2513,6 +2530,8 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if err := parseLabelPosition(&opts.PreviewLabel, value); err != nil {
|
if err := parseLabelPosition(&opts.PreviewLabel, value); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else if match, value := optString(arg, "--wrap-sign="); match {
|
||||||
|
opts.WrapSign = &value
|
||||||
} else if match, value := optString(arg, "--prompt="); match {
|
} else if match, value := optString(arg, "--prompt="); match {
|
||||||
opts.Prompt = value
|
opts.Prompt = value
|
||||||
} else if match, value := optString(arg, "--pointer="); match {
|
} else if match, value := optString(arg, "--pointer="); match {
|
||||||
|
184
src/terminal.go
184
src/terminal.go
@ -155,6 +155,7 @@ type eachLine struct {
|
|||||||
|
|
||||||
type itemLine struct {
|
type itemLine struct {
|
||||||
firstLine int
|
firstLine int
|
||||||
|
numLines int
|
||||||
cy int
|
cy int
|
||||||
current bool
|
current bool
|
||||||
selected bool
|
selected bool
|
||||||
@ -215,6 +216,9 @@ type Terminal struct {
|
|||||||
infoCommand string
|
infoCommand string
|
||||||
infoStyle infoStyle
|
infoStyle infoStyle
|
||||||
infoPrefix string
|
infoPrefix string
|
||||||
|
wrap bool
|
||||||
|
wrapSign string
|
||||||
|
wrapSignWidth int
|
||||||
separator labelPrinter
|
separator labelPrinter
|
||||||
separatorLen int
|
separatorLen int
|
||||||
spinner []string
|
spinner []string
|
||||||
@ -446,6 +450,7 @@ const (
|
|||||||
actToggleTrack
|
actToggleTrack
|
||||||
actToggleTrackCurrent
|
actToggleTrackCurrent
|
||||||
actToggleHeader
|
actToggleHeader
|
||||||
|
actToggleWrap
|
||||||
actTrackCurrent
|
actTrackCurrent
|
||||||
actUntrackCurrent
|
actUntrackCurrent
|
||||||
actDown
|
actDown
|
||||||
@ -787,6 +792,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
input: input,
|
input: input,
|
||||||
multi: opts.Multi,
|
multi: opts.Multi,
|
||||||
multiLine: opts.ReadZero && opts.MultiLine,
|
multiLine: opts.ReadZero && opts.MultiLine,
|
||||||
|
wrap: opts.Wrap,
|
||||||
sort: opts.Sort > 0,
|
sort: opts.Sort > 0,
|
||||||
toggleSort: opts.ToggleSort,
|
toggleSort: opts.ToggleSort,
|
||||||
track: opts.Track,
|
track: opts.Track,
|
||||||
@ -876,8 +882,15 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true)
|
t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true)
|
||||||
}
|
}
|
||||||
if t.unicode {
|
if t.unicode {
|
||||||
|
t.wrapSign = "↳ "
|
||||||
t.borderWidth = uniseg.StringWidth("│")
|
t.borderWidth = uniseg.StringWidth("│")
|
||||||
|
} else {
|
||||||
|
t.wrapSign = "> "
|
||||||
}
|
}
|
||||||
|
if opts.WrapSign != nil {
|
||||||
|
t.wrapSign = *opts.WrapSign
|
||||||
|
}
|
||||||
|
t.wrapSign, t.wrapSignWidth = t.processTabs([]rune(t.wrapSign), 0)
|
||||||
if opts.Scrollbar == nil {
|
if opts.Scrollbar == nil {
|
||||||
if t.unicode && t.borderWidth == 1 {
|
if t.unicode && t.borderWidth == 1 {
|
||||||
t.scrollbar = "│"
|
t.scrollbar = "│"
|
||||||
@ -1067,8 +1080,11 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
|||||||
}
|
}
|
||||||
output := func() {
|
output := func() {
|
||||||
line := t.promptLine()
|
line := t.promptLine()
|
||||||
|
wrap := t.wrap
|
||||||
|
t.wrap = false
|
||||||
t.printHighlighted(
|
t.printHighlighted(
|
||||||
Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, line, line, true, nil, nil)
|
Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, line, line, true, nil, nil)
|
||||||
|
t.wrap = wrap
|
||||||
}
|
}
|
||||||
_, promptLen := t.processTabs([]rune(trimmed), 0)
|
_, promptLen := t.processTabs([]rune(trimmed), 0)
|
||||||
|
|
||||||
@ -1103,10 +1119,37 @@ func getScrollbar(perLine int, total int, height int, offset int) (int, int) {
|
|||||||
return barLength, barStart
|
return barLength, barStart
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) wrapCols() int {
|
||||||
|
if !t.wrap {
|
||||||
|
return 0 // No wrap
|
||||||
|
}
|
||||||
|
return util.Max(t.window.Width()-(t.pointerLen+t.markerLen+1), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) numItemLines(item *Item, atMost int) (int, bool) {
|
||||||
|
if !t.wrap && !t.multiLine {
|
||||||
|
return 1, false
|
||||||
|
}
|
||||||
|
if !t.wrap && t.multiLine {
|
||||||
|
return item.text.NumLines(atMost)
|
||||||
|
}
|
||||||
|
lines, overflow := item.text.Lines(t.multiLine, atMost, t.wrapCols(), t.wrapSignWidth, t.tabstop)
|
||||||
|
return len(lines), overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) itemLines(item *Item, atMost int) ([][]rune, bool) {
|
||||||
|
if !t.wrap && !t.multiLine {
|
||||||
|
text := make([]rune, item.text.Length())
|
||||||
|
copy(text, item.text.ToRunes())
|
||||||
|
return [][]rune{text}, false
|
||||||
|
}
|
||||||
|
return item.text.Lines(t.multiLine, atMost, t.wrapCols(), t.wrapSignWidth, t.tabstop)
|
||||||
|
}
|
||||||
|
|
||||||
// Estimate the average number of lines per item. Instead of going through all
|
// Estimate the average number of lines per item. Instead of going through all
|
||||||
// items, we only check a few items around the current cursor position.
|
// items, we only check a few items around the current cursor position.
|
||||||
func (t *Terminal) avgNumLines() int {
|
func (t *Terminal) avgNumLines() int {
|
||||||
if !t.multiLine {
|
if !t.wrap && !t.multiLine {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1116,8 +1159,8 @@ func (t *Terminal) avgNumLines() int {
|
|||||||
total := t.merger.Length()
|
total := t.merger.Length()
|
||||||
offset := util.Max(0, util.Min(t.offset, total-maxItems-1))
|
offset := util.Max(0, util.Min(t.offset, total-maxItems-1))
|
||||||
for idx := 0; idx < maxItems && idx+offset < total; idx++ {
|
for idx := 0; idx < maxItems && idx+offset < total; idx++ {
|
||||||
item := t.merger.Get(idx + offset)
|
result := t.merger.Get(idx + offset)
|
||||||
lines, _ := item.item.text.NumLines(maxItems)
|
lines, _ := t.numItemLines(result.item, maxItems)
|
||||||
numLines += lines
|
numLines += lines
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
@ -1964,6 +2007,9 @@ func (t *Terminal) printHeader() {
|
|||||||
case layoutDefault, layoutReverseList:
|
case layoutDefault, layoutReverseList:
|
||||||
needReverse = true
|
needReverse = true
|
||||||
}
|
}
|
||||||
|
// Wrapping is not supported for header
|
||||||
|
wrap := t.wrap
|
||||||
|
t.wrap = false
|
||||||
for idx, lineStr := range append(append([]string{}, t.header0...), t.header...) {
|
for idx, lineStr := range append(append([]string{}, t.header0...), t.header...) {
|
||||||
line := idx
|
line := idx
|
||||||
if needReverse && idx < len(t.header0) {
|
if needReverse && idx < len(t.header0) {
|
||||||
@ -1988,6 +2034,7 @@ func (t *Terminal) printHeader() {
|
|||||||
tui.ColHeader, tui.ColHeader, false, false, line, line, true,
|
tui.ColHeader, tui.ColHeader, false, false, line, line, true,
|
||||||
func(markerClass) { t.window.Print(" ") }, nil)
|
func(markerClass) { t.window.Print(" ") }, nil)
|
||||||
}
|
}
|
||||||
|
t.wrap = wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printList() {
|
func (t *Terminal) printList() {
|
||||||
@ -2015,7 +2062,7 @@ func (t *Terminal) printList() {
|
|||||||
// If the screen is not filled with the list in non-multi-line mode,
|
// If the screen is not filled with the list in non-multi-line mode,
|
||||||
// scrollbar is not visible at all. But in multi-line mode, we may need
|
// scrollbar is not visible at all. But in multi-line mode, we may need
|
||||||
// to redraw the scrollbar character at the end.
|
// to redraw the scrollbar character at the end.
|
||||||
if t.multiLine {
|
if t.multiLine || t.wrap {
|
||||||
t.prevLines[line].hasBar = t.printBar(line, true, barRange)
|
t.prevLines[line].hasBar = t.printBar(line, true, barRange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2048,7 +2095,8 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Avoid unnecessary redraw
|
// Avoid unnecessary redraw
|
||||||
newLine := itemLine{firstLine: line, cy: index + t.offset, current: current, selected: selected, label: label,
|
numLines, _ := t.numItemLines(item, maxLine-line+1)
|
||||||
|
newLine := itemLine{firstLine: line, numLines: numLines, cy: index + t.offset, current: current, selected: selected, label: label,
|
||||||
result: result, queryLen: len(t.input), width: 0, hasBar: line >= barRange[0] && line < barRange[1]}
|
result: result, queryLen: len(t.input), width: 0, hasBar: line >= barRange[0] && line < barRange[1]}
|
||||||
prevLine := t.prevLines[line]
|
prevLine := t.prevLines[line]
|
||||||
forceRedraw := prevLine.other || prevLine.firstLine != newLine.firstLine
|
forceRedraw := prevLine.other || prevLine.firstLine != newLine.firstLine
|
||||||
@ -2057,37 +2105,46 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !forceRedraw &&
|
if !forceRedraw &&
|
||||||
|
prevLine.numLines == newLine.numLines &&
|
||||||
prevLine.current == newLine.current &&
|
prevLine.current == newLine.current &&
|
||||||
prevLine.selected == newLine.selected &&
|
prevLine.selected == newLine.selected &&
|
||||||
prevLine.label == newLine.label &&
|
prevLine.label == newLine.label &&
|
||||||
prevLine.queryLen == newLine.queryLen &&
|
prevLine.queryLen == newLine.queryLen &&
|
||||||
prevLine.result == newLine.result {
|
prevLine.result == newLine.result {
|
||||||
t.prevLines[line].hasBar = printBar(line, false)
|
t.prevLines[line].hasBar = printBar(line, false)
|
||||||
if !t.multiLine {
|
if !t.multiLine && !t.wrap {
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
lines, _ := item.text.NumLines(maxLine - line + 1)
|
return line + numLines - 1
|
||||||
return line + lines - 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
|
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
|
||||||
postTask := func(lineNum int, width int) {
|
postTask := func(lineNum int, width int, wrapped bool) {
|
||||||
if (current || selected) && t.highlightLine {
|
if (current || selected) && t.highlightLine {
|
||||||
color := tui.ColSelected
|
color := tui.ColSelected
|
||||||
if current {
|
if current {
|
||||||
color = tui.ColCurrent
|
color = tui.ColCurrent
|
||||||
}
|
}
|
||||||
fillSpaces := maxWidth - width
|
fillSpaces := maxWidth - width
|
||||||
|
if wrapped {
|
||||||
|
fillSpaces -= t.wrapSignWidth
|
||||||
|
}
|
||||||
if fillSpaces > 0 {
|
if fillSpaces > 0 {
|
||||||
t.window.CPrint(color, strings.Repeat(" ", fillSpaces))
|
t.window.CPrint(color, strings.Repeat(" ", fillSpaces))
|
||||||
}
|
}
|
||||||
newLine.width = maxWidth
|
newLine.width = maxWidth
|
||||||
} else {
|
} else {
|
||||||
fillSpaces := t.prevLines[lineNum].width - width
|
fillSpaces := t.prevLines[lineNum].width - width
|
||||||
|
if wrapped {
|
||||||
|
fillSpaces -= t.wrapSignWidth
|
||||||
|
}
|
||||||
if fillSpaces > 0 {
|
if fillSpaces > 0 {
|
||||||
t.window.Print(strings.Repeat(" ", fillSpaces))
|
t.window.Print(strings.Repeat(" ", fillSpaces))
|
||||||
}
|
}
|
||||||
newLine.width = width
|
newLine.width = width
|
||||||
|
if wrapped {
|
||||||
|
newLine.width += t.wrapSignWidth
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// When width is 0, line is completely cleared. We need to redraw scrollbar
|
// When width is 0, line is completely cleared. We need to redraw scrollbar
|
||||||
newLine.hasBar = printBar(lineNum, forceRedraw || width == 0)
|
newLine.hasBar = printBar(lineNum, forceRedraw || width == 0)
|
||||||
@ -2185,7 +2242,7 @@ func (t *Terminal) overflow(runes []rune, max int) bool {
|
|||||||
return t.displayWidthWithLimit(runes, 0, max) > max
|
return t.displayWidthWithLimit(runes, 0, max) > max
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool, lineNum int, maxLineNum int, forceRedraw bool, preTask func(markerClass), postTask func(int, int)) int {
|
func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool, lineNum int, maxLineNum int, forceRedraw bool, preTask func(markerClass), postTask func(int, int, bool)) int {
|
||||||
var displayWidth int
|
var displayWidth int
|
||||||
item := result.item
|
item := result.item
|
||||||
matchOffsets := []Offset{}
|
matchOffsets := []Offset{}
|
||||||
@ -2204,57 +2261,63 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
}
|
}
|
||||||
allOffsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current)
|
allOffsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current)
|
||||||
|
|
||||||
from := 0
|
maxLines := 1
|
||||||
text := make([]rune, item.text.Length())
|
if t.multiLine || t.wrap {
|
||||||
copy(text, item.text.ToRunes())
|
maxLines = maxLineNum - lineNum + 1
|
||||||
|
}
|
||||||
|
lines, overflow := t.itemLines(item, maxLines)
|
||||||
|
numItemLines := len(lines)
|
||||||
|
|
||||||
finalLineNum := lineNum
|
finalLineNum := lineNum
|
||||||
numItemLines := 1
|
|
||||||
cutoff := 0
|
|
||||||
overflow := false
|
|
||||||
topCutoff := false
|
topCutoff := false
|
||||||
if t.multiLine {
|
wrapped := false
|
||||||
maxLines := maxLineNum - lineNum + 1
|
if t.multiLine || t.wrap {
|
||||||
numItemLines, overflow = item.text.NumLines(maxLines)
|
|
||||||
// Cut off the upper lines in the 'default' layout
|
// Cut off the upper lines in the 'default' layout
|
||||||
if t.layout == layoutDefault && !current && maxLines == numItemLines && overflow {
|
if t.layout == layoutDefault && !current && maxLines == numItemLines && overflow {
|
||||||
actualLines, _ := item.text.NumLines(math.MaxInt32)
|
lines, _ = t.itemLines(item, math.MaxInt)
|
||||||
cutoff = actualLines - maxLines
|
|
||||||
|
// To see if the first visible line is wrapped, we need to check the last cut-off line
|
||||||
|
prevLine := lines[len(lines)-maxLines-1]
|
||||||
|
if len(prevLine) == 0 || prevLine[len(prevLine)-1] != '\n' {
|
||||||
|
wrapped = true
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = lines[len(lines)-maxLines:]
|
||||||
topCutoff = true
|
topCutoff = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for lineOffset := 0; from <= len(text) && (lineNum <= maxLineNum || maxLineNum == 0); lineOffset++ {
|
from := 0
|
||||||
|
for lineOffset := 0; lineOffset < len(lines) && (lineNum <= maxLineNum || maxLineNum == 0); lineOffset++ {
|
||||||
|
line := lines[lineOffset]
|
||||||
finalLineNum = lineNum
|
finalLineNum = lineNum
|
||||||
|
|
||||||
line := text[from:]
|
|
||||||
if t.multiLine {
|
|
||||||
for idx, r := range text[from:] {
|
|
||||||
if r == '\n' {
|
|
||||||
line = line[:idx]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offsets := []colorOffset{}
|
offsets := []colorOffset{}
|
||||||
for _, offset := range allOffsets {
|
for _, offset := range allOffsets {
|
||||||
if offset.offset[0] >= int32(from) && offset.offset[1] <= int32(from+len(line)) {
|
if offset.offset[0] >= int32(from+len(line)) {
|
||||||
|
allOffsets = allOffsets[len(offsets):]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset.offset[0] < int32(from) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset.offset[1] < int32(from+len(line)) {
|
||||||
offset.offset[0] -= int32(from)
|
offset.offset[0] -= int32(from)
|
||||||
offset.offset[1] -= int32(from)
|
offset.offset[1] -= int32(from)
|
||||||
offsets = append(offsets, offset)
|
offsets = append(offsets, offset)
|
||||||
} else {
|
} else {
|
||||||
allOffsets = allOffsets[len(offsets):]
|
dupe := offset
|
||||||
|
dupe.offset[0] = int32(from + len(line))
|
||||||
|
|
||||||
|
offset.offset[0] -= int32(from)
|
||||||
|
offset.offset[1] = int32(from + len(line))
|
||||||
|
offsets = append(offsets, offset)
|
||||||
|
|
||||||
|
allOffsets = append([]colorOffset{dupe}, allOffsets[len(offsets):]...)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
from += len(line)
|
||||||
from += len(line) + 1
|
|
||||||
|
|
||||||
if cutoff > 0 {
|
|
||||||
cutoff--
|
|
||||||
lineOffset--
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var maxe int
|
var maxe int
|
||||||
for _, offset := range offsets {
|
for _, offset := range offsets {
|
||||||
@ -2301,10 +2364,24 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
}
|
}
|
||||||
|
|
||||||
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
|
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
|
||||||
|
wasWrapped := false
|
||||||
|
if wrapped {
|
||||||
|
maxWidth -= t.wrapSignWidth
|
||||||
|
t.window.CPrint(colBase.WithAttr(tui.Dim), t.wrapSign)
|
||||||
|
wrapped = false
|
||||||
|
wasWrapped = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(line) > 0 && line[len(line)-1] == '\n' {
|
||||||
|
line = line[:len(line)-1]
|
||||||
|
} else {
|
||||||
|
wrapped = true
|
||||||
|
}
|
||||||
|
|
||||||
|
displayWidth = t.displayWidthWithLimit(line, 0, maxWidth)
|
||||||
|
if !t.wrap && displayWidth > maxWidth {
|
||||||
ellipsis, ellipsisWidth := util.Truncate(t.ellipsis, maxWidth/2)
|
ellipsis, ellipsisWidth := util.Truncate(t.ellipsis, maxWidth/2)
|
||||||
maxe = util.Constrain(maxe+util.Min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(line))
|
maxe = util.Constrain(maxe+util.Min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(line))
|
||||||
displayWidth = t.displayWidthWithLimit(line, 0, maxWidth)
|
|
||||||
if displayWidth > maxWidth {
|
|
||||||
transformOffsets := func(diff int32, rightTrim bool) {
|
transformOffsets := func(diff int32, rightTrim bool) {
|
||||||
for idx, offset := range offsets {
|
for idx, offset := range offsets {
|
||||||
b, e := offset.offset[0], offset.offset[1]
|
b, e := offset.offset[0], offset.offset[1]
|
||||||
@ -2357,7 +2434,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
|
|
||||||
t.printColoredString(t.window, line, offsets, colBase)
|
t.printColoredString(t.window, line, offsets, colBase)
|
||||||
if postTask != nil {
|
if postTask != nil {
|
||||||
postTask(actualLineNum, displayWidth)
|
postTask(actualLineNum, displayWidth, wasWrapped)
|
||||||
} else {
|
} else {
|
||||||
t.markOtherLine(actualLineNum)
|
t.markOtherLine(actualLineNum)
|
||||||
}
|
}
|
||||||
@ -4369,6 +4446,9 @@ func (t *Terminal) Loop() error {
|
|||||||
case actToggleHeader:
|
case actToggleHeader:
|
||||||
t.headerVisible = !t.headerVisible
|
t.headerVisible = !t.headerVisible
|
||||||
req(reqList, reqInfo, reqPrompt, reqHeader)
|
req(reqList, reqInfo, reqPrompt, reqHeader)
|
||||||
|
case actToggleWrap:
|
||||||
|
t.wrap = !t.wrap
|
||||||
|
req(reqList, reqHeader)
|
||||||
case actTrackCurrent:
|
case actTrackCurrent:
|
||||||
if t.track == trackDisabled {
|
if t.track == trackDisabled {
|
||||||
t.track = trackCurrent
|
t.track = trackCurrent
|
||||||
@ -4751,12 +4831,12 @@ func (t *Terminal) constrain() {
|
|||||||
for tries := 0; tries < maxLines; tries++ {
|
for tries := 0; tries < maxLines; tries++ {
|
||||||
numItems := maxLines
|
numItems := maxLines
|
||||||
// How many items can be fit on screen including the current item?
|
// How many items can be fit on screen including the current item?
|
||||||
if t.multiLine && t.merger.Length() > 0 {
|
if (t.multiLine || t.wrap) && t.merger.Length() > 0 {
|
||||||
numItemsFound := 0
|
numItemsFound := 0
|
||||||
linesSum := 0
|
linesSum := 0
|
||||||
|
|
||||||
add := func(i int) bool {
|
add := func(i int) bool {
|
||||||
lines, _ := t.merger.Get(i).item.text.NumLines(numItems - linesSum)
|
lines, _ := t.numItemLines(t.merger.Get(i).item, numItems-linesSum)
|
||||||
linesSum += lines
|
linesSum += lines
|
||||||
if linesSum >= numItems {
|
if linesSum >= numItems {
|
||||||
if numItemsFound == 0 {
|
if numItemsFound == 0 {
|
||||||
@ -4800,14 +4880,14 @@ func (t *Terminal) constrain() {
|
|||||||
prevOffset := newOffset
|
prevOffset := newOffset
|
||||||
numItems := t.merger.Length()
|
numItems := t.merger.Length()
|
||||||
itemLines := 1
|
itemLines := 1
|
||||||
if t.multiLine && t.cy < numItems {
|
if (t.multiLine || t.wrap) && t.cy < numItems {
|
||||||
itemLines, _ = t.merger.Get(t.cy).item.text.NumLines(maxLines)
|
itemLines, _ = t.numItemLines(t.merger.Get(t.cy).item, maxLines)
|
||||||
}
|
}
|
||||||
linesBefore := t.cy - newOffset
|
linesBefore := t.cy - newOffset
|
||||||
if t.multiLine {
|
if t.multiLine || t.wrap {
|
||||||
linesBefore = 0
|
linesBefore = 0
|
||||||
for i := newOffset; i < t.cy && i < numItems; i++ {
|
for i := newOffset; i < t.cy && i < numItems; i++ {
|
||||||
lines, _ := t.merger.Get(i).item.text.NumLines(maxLines - linesBefore - itemLines)
|
lines, _ := t.numItemLines(t.merger.Get(i).item, maxLines-linesBefore-itemLines)
|
||||||
linesBefore += lines
|
linesBefore += lines
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,3 +226,85 @@ func (chars *Chars) Prepend(prefix string) {
|
|||||||
chars.slice = append([]byte(prefix), chars.slice...)
|
chars.slice = append([]byte(prefix), chars.slice...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWidth int, tabstop int) ([][]rune, bool) {
|
||||||
|
text := make([]rune, chars.Length())
|
||||||
|
copy(text, chars.ToRunes())
|
||||||
|
|
||||||
|
lines := [][]rune{}
|
||||||
|
overflow := false
|
||||||
|
if !multiLine {
|
||||||
|
lines = append(lines, text)
|
||||||
|
} else {
|
||||||
|
from := 0
|
||||||
|
for off := 0; off < len(text); off++ {
|
||||||
|
if text[off] == '\n' {
|
||||||
|
lines = append(lines, text[from:off+1]) // Include '\n'
|
||||||
|
from = off + 1
|
||||||
|
if len(lines) >= maxLines {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastLine []rune
|
||||||
|
if from < len(text) {
|
||||||
|
lastLine = text[from:]
|
||||||
|
}
|
||||||
|
|
||||||
|
overflow = false
|
||||||
|
if len(lines) >= maxLines {
|
||||||
|
overflow = true
|
||||||
|
} else {
|
||||||
|
lines = append(lines, lastLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If wrapping is disabled, we're done
|
||||||
|
if wrapCols == 0 {
|
||||||
|
return lines, overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapped := [][]rune{}
|
||||||
|
for _, line := range lines {
|
||||||
|
// Remove trailing '\n' and remember if it was there
|
||||||
|
newline := len(line) > 0 && line[len(line)-1] == '\n'
|
||||||
|
if newline {
|
||||||
|
line = line[:len(line)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
cols := wrapCols
|
||||||
|
if len(wrapped) > 0 {
|
||||||
|
cols -= wrapSignWidth
|
||||||
|
}
|
||||||
|
_, overflowIdx := RunesWidth(line, 0, tabstop, cols)
|
||||||
|
if overflowIdx >= 0 {
|
||||||
|
// Might be a wide character
|
||||||
|
if overflowIdx == 0 {
|
||||||
|
overflowIdx = 1
|
||||||
|
}
|
||||||
|
if len(wrapped) >= maxLines {
|
||||||
|
return wrapped, true
|
||||||
|
}
|
||||||
|
wrapped = append(wrapped, line[:overflowIdx])
|
||||||
|
line = line[overflowIdx:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore trailing '\n'
|
||||||
|
if newline {
|
||||||
|
line = append(line, '\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(wrapped) >= maxLines {
|
||||||
|
return wrapped, true
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapped = append(wrapped, line)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapped, false
|
||||||
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestToCharsAscii(t *testing.T) {
|
func TestToCharsAscii(t *testing.T) {
|
||||||
chars := ToChars([]byte("foobar"))
|
chars := ToChars([]byte("foobar"))
|
||||||
@ -44,3 +47,37 @@ func TestTrimLength(t *testing.T) {
|
|||||||
check(" h o ", 5)
|
check(" h o ", 5)
|
||||||
check(" ", 0)
|
check(" ", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCharsLines(t *testing.T) {
|
||||||
|
chars := ToChars([]byte("abcdef\n가나다\n\tdef"))
|
||||||
|
check := func(multiLine bool, maxLines int, wrapCols int, wrapSignWidth int, tabstop int, expectedNumLines int, expectedOverflow bool) {
|
||||||
|
lines, overflow := chars.Lines(multiLine, maxLines, wrapCols, wrapSignWidth, tabstop)
|
||||||
|
fmt.Println(lines, overflow)
|
||||||
|
if len(lines) != expectedNumLines || overflow != expectedOverflow {
|
||||||
|
t.Errorf("Invalid result: %d %v (expected %d %v)", len(lines), overflow, expectedNumLines, expectedOverflow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No wrap
|
||||||
|
check(true, 1, 0, 0, 8, 1, true)
|
||||||
|
check(true, 2, 0, 0, 8, 2, true)
|
||||||
|
check(true, 3, 0, 0, 8, 3, false)
|
||||||
|
|
||||||
|
// Wrap (2)
|
||||||
|
check(true, 4, 2, 0, 8, 4, true)
|
||||||
|
check(true, 5, 2, 0, 8, 5, true)
|
||||||
|
check(true, 6, 2, 0, 8, 6, true)
|
||||||
|
check(true, 7, 2, 0, 8, 7, true)
|
||||||
|
check(true, 8, 2, 0, 8, 8, true)
|
||||||
|
check(true, 9, 2, 0, 8, 9, false)
|
||||||
|
check(true, 9, 2, 0, 1, 8, false) // Smaller tab size
|
||||||
|
|
||||||
|
// With wrap sign (3 + 1)
|
||||||
|
check(true, 100, 3, 1, 1, 8, false)
|
||||||
|
|
||||||
|
// With wrap sign (3 + 2)
|
||||||
|
check(true, 100, 3, 2, 1, 12, false)
|
||||||
|
|
||||||
|
// With wrap sign (3 + 2) and no multi-line
|
||||||
|
check(false, 100, 3, 2, 1, 13, false)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user