mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-11-10 15:50:56 +00:00
parent
b8737b724b
commit
2bbc12063c
@ -113,7 +113,8 @@ const (
|
|||||||
ColCursor
|
ColCursor
|
||||||
ColSelected
|
ColSelected
|
||||||
ColHeader
|
ColHeader
|
||||||
ColUser
|
ColBorder
|
||||||
|
ColUser // Should be the last entry
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -136,6 +137,7 @@ type ColorTheme struct {
|
|||||||
Cursor int16
|
Cursor int16
|
||||||
Selected int16
|
Selected int16
|
||||||
Header int16
|
Header int16
|
||||||
|
Border int16
|
||||||
}
|
}
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
@ -170,6 +172,31 @@ var (
|
|||||||
DarkBG int
|
DarkBG int
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Window struct {
|
||||||
|
win *C.WINDOW
|
||||||
|
Top int
|
||||||
|
Left int
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWindow(top int, left int, width int, height int, border bool) *Window {
|
||||||
|
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
|
||||||
|
if border {
|
||||||
|
attr := _color(ColBorder, false)
|
||||||
|
C.wattron(win, attr)
|
||||||
|
C.box(win, 0, 0)
|
||||||
|
C.wattroff(win, attr)
|
||||||
|
}
|
||||||
|
return &Window{
|
||||||
|
win: win,
|
||||||
|
Top: top,
|
||||||
|
Left: left,
|
||||||
|
Width: width,
|
||||||
|
Height: height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func EmptyTheme() *ColorTheme {
|
func EmptyTheme() *ColorTheme {
|
||||||
return &ColorTheme{
|
return &ColorTheme{
|
||||||
UseDefault: true,
|
UseDefault: true,
|
||||||
@ -184,7 +211,8 @@ func EmptyTheme() *ColorTheme {
|
|||||||
Info: colUndefined,
|
Info: colUndefined,
|
||||||
Cursor: colUndefined,
|
Cursor: colUndefined,
|
||||||
Selected: colUndefined,
|
Selected: colUndefined,
|
||||||
Header: colUndefined}
|
Header: colUndefined,
|
||||||
|
Border: colUndefined}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -204,7 +232,8 @@ func init() {
|
|||||||
Info: C.COLOR_WHITE,
|
Info: C.COLOR_WHITE,
|
||||||
Cursor: C.COLOR_RED,
|
Cursor: C.COLOR_RED,
|
||||||
Selected: C.COLOR_MAGENTA,
|
Selected: C.COLOR_MAGENTA,
|
||||||
Header: C.COLOR_CYAN}
|
Header: C.COLOR_CYAN,
|
||||||
|
Border: C.COLOR_BLACK}
|
||||||
Dark256 = &ColorTheme{
|
Dark256 = &ColorTheme{
|
||||||
UseDefault: true,
|
UseDefault: true,
|
||||||
Fg: 15,
|
Fg: 15,
|
||||||
@ -218,7 +247,8 @@ func init() {
|
|||||||
Info: 144,
|
Info: 144,
|
||||||
Cursor: 161,
|
Cursor: 161,
|
||||||
Selected: 168,
|
Selected: 168,
|
||||||
Header: 109}
|
Header: 109,
|
||||||
|
Border: 59}
|
||||||
Light256 = &ColorTheme{
|
Light256 = &ColorTheme{
|
||||||
UseDefault: true,
|
UseDefault: true,
|
||||||
Fg: 15,
|
Fg: 15,
|
||||||
@ -232,7 +262,8 @@ func init() {
|
|||||||
Info: 101,
|
Info: 101,
|
||||||
Cursor: 161,
|
Cursor: 161,
|
||||||
Selected: 168,
|
Selected: 168,
|
||||||
Header: 31}
|
Header: 31,
|
||||||
|
Border: 145}
|
||||||
}
|
}
|
||||||
|
|
||||||
func attrColored(pair int, bold bool) C.int {
|
func attrColored(pair int, bold bool) C.int {
|
||||||
@ -360,6 +391,7 @@ func initPairs(baseTheme *ColorTheme, theme *ColorTheme, black bool) {
|
|||||||
C.init_pair(ColCursor, override(baseTheme.Cursor, theme.Cursor), darkBG)
|
C.init_pair(ColCursor, override(baseTheme.Cursor, theme.Cursor), darkBG)
|
||||||
C.init_pair(ColSelected, override(baseTheme.Selected, theme.Selected), darkBG)
|
C.init_pair(ColSelected, override(baseTheme.Selected, theme.Selected), darkBG)
|
||||||
C.init_pair(ColHeader, override(baseTheme.Header, theme.Header), bg)
|
C.init_pair(ColHeader, override(baseTheme.Header, theme.Header), bg)
|
||||||
|
C.init_pair(ColBorder, override(baseTheme.Border, theme.Border), bg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Close() {
|
func Close() {
|
||||||
@ -415,7 +447,9 @@ func mouseSequence(sz *int) Event {
|
|||||||
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
|
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
|
||||||
mod := _buf[3] >= 100
|
mod := _buf[3] >= 100
|
||||||
s := 1 - int(_buf[3]%2)*2
|
s := 1 - int(_buf[3]%2)*2
|
||||||
return Event{Mouse, 0, &MouseEvent{0, 0, s, false, false, mod}}
|
x := int(_buf[4] - 33)
|
||||||
|
y := int(_buf[5] - 33)
|
||||||
|
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, mod}}
|
||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
@ -588,17 +622,25 @@ func GetChar() Event {
|
|||||||
return Event{Rune, r, nil}
|
return Event{Rune, r, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Move(y int, x int) {
|
func (w *Window) Close() {
|
||||||
C.move(C.int(y), C.int(x))
|
C.delwin(w.win)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MoveAndClear(y int, x int) {
|
func (w *Window) Enclose(y int, x int) bool {
|
||||||
Move(y, x)
|
return bool(C.wenclose(w.win, C.int(y), C.int(x)))
|
||||||
C.clrtoeol()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Print(text string) {
|
func (w *Window) Move(y int, x int) {
|
||||||
C.addstr(C.CString(strings.Map(func(r rune) rune {
|
C.wmove(w.win, C.int(y), C.int(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) MoveAndClear(y int, x int) {
|
||||||
|
w.Move(y, x)
|
||||||
|
C.wclrtoeol(w.win)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) Print(text string) {
|
||||||
|
C.waddstr(w.win, C.CString(strings.Map(func(r rune) rune {
|
||||||
if r < 32 {
|
if r < 32 {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
@ -606,11 +648,11 @@ func Print(text string) {
|
|||||||
}, text)))
|
}, text)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func CPrint(pair int, bold bool, text string) {
|
func (w *Window) CPrint(pair int, bold bool, text string) {
|
||||||
attr := _color(pair, bold)
|
attr := _color(pair, bold)
|
||||||
C.attron(attr)
|
C.wattron(w.win, attr)
|
||||||
Print(text)
|
w.Print(text)
|
||||||
C.attroff(attr)
|
C.wattroff(w.win, attr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Clear() {
|
func Clear() {
|
||||||
@ -625,6 +667,30 @@ func Refresh() {
|
|||||||
C.refresh()
|
C.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Window) Erase() {
|
||||||
|
C.werase(w.win)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) Fill(str string) bool {
|
||||||
|
return C.waddstr(w.win, C.CString(str)) == C.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) CFill(str string, fg int, bg int, bold bool) bool {
|
||||||
|
attr := _color(PairFor(fg, bg), bold)
|
||||||
|
C.wattron(w.win, attr)
|
||||||
|
ret := w.Fill(str)
|
||||||
|
C.wattroff(w.win, attr)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Window) Refresh() {
|
||||||
|
C.wnoutrefresh(w.win)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DoUpdate() {
|
||||||
|
C.doupdate()
|
||||||
|
}
|
||||||
|
|
||||||
func PairFor(fg int, bg int) int {
|
func PairFor(fg int, bg int) int {
|
||||||
key := (fg << 8) + bg
|
key := (fg << 8) + bg
|
||||||
if found, prs := _colorMap[key]; prs {
|
if found, prs := _colorMap[key]; prs {
|
||||||
|
172
src/options.go
172
src/options.go
@ -1,6 +1,7 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -23,36 +24,47 @@ const usage = `usage: fzf [options]
|
|||||||
-n, --nth=N[,..] Comma-separated list of field index expressions
|
-n, --nth=N[,..] Comma-separated list of field index expressions
|
||||||
for limiting search scope. Each can be a non-zero
|
for limiting search scope. Each can be a non-zero
|
||||||
integer or a range expression ([BEGIN]..[END]).
|
integer or a range expression ([BEGIN]..[END]).
|
||||||
--with-nth=N[,..] Transform item using index expressions within finder
|
--with-nth=N[,..] Transform the presentation of each line using
|
||||||
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
|
field index expressions
|
||||||
|
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
|
||||||
+s, --no-sort Do not sort the result
|
+s, --no-sort Do not sort the result
|
||||||
--tac Reverse the order of the input
|
--tac Reverse the order of the input
|
||||||
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
||||||
when the scores are tied;
|
when the scores are tied [length|begin|end|index]
|
||||||
[length|begin|end|index] (default: length)
|
(default: length)
|
||||||
|
|
||||||
Interface
|
Interface
|
||||||
-m, --multi Enable multi-select with tab/shift-tab
|
-m, --multi Enable multi-select with tab/shift-tab
|
||||||
--ansi Enable processing of ANSI color codes
|
|
||||||
--no-mouse Disable mouse
|
--no-mouse Disable mouse
|
||||||
--color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors
|
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
||||||
--black Use black background
|
|
||||||
--reverse Reverse orientation
|
|
||||||
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
|
||||||
--tabstop=SPACES Number of spaces for a tab character (default: 8)
|
|
||||||
--cycle Enable cyclic scroll
|
--cycle Enable cyclic scroll
|
||||||
--no-hscroll Disable horizontal scroll
|
--no-hscroll Disable horizontal scroll
|
||||||
--hscroll-off=COL Number of screen columns to keep to the right of the
|
--hscroll-off=COL Number of screen columns to keep to the right of the
|
||||||
highlighted substring (default: 10)
|
highlighted substring (default: 10)
|
||||||
--inline-info Display finder info inline with the query
|
|
||||||
--jump-labels=CHARS Label characters for jump and jump-accept
|
--jump-labels=CHARS Label characters for jump and jump-accept
|
||||||
|
|
||||||
|
Layout
|
||||||
|
--reverse Reverse orientation
|
||||||
|
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
||||||
|
--inline-info Display finder info inline with the query
|
||||||
--prompt=STR Input prompt (default: '> ')
|
--prompt=STR Input prompt (default: '> ')
|
||||||
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
|
||||||
--history=FILE History file
|
|
||||||
--history-size=N Maximum number of history entries (default: 1000)
|
|
||||||
--header=STR String to print as header
|
--header=STR String to print as header
|
||||||
--header-lines=N The first N lines of the input are treated as header
|
--header-lines=N The first N lines of the input are treated as header
|
||||||
|
|
||||||
|
Display
|
||||||
|
--ansi Enable processing of ANSI color codes
|
||||||
|
--tabstop=SPACES Number of spaces for a tab character (default: 8)
|
||||||
|
--color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors
|
||||||
|
|
||||||
|
History
|
||||||
|
--history=FILE History file
|
||||||
|
--history-size=N Maximum number of history entries (default: 1000)
|
||||||
|
|
||||||
|
Preview
|
||||||
|
--preview=COMMAND Command to preview highlighted line ({})
|
||||||
|
--preview-window=OPT Preview window layout (default: right:50%)
|
||||||
|
[up|down|left|right][:SIZE[%]][:hidden]
|
||||||
|
|
||||||
Scripting
|
Scripting
|
||||||
-q, --query=STR Start the finder with the given query
|
-q, --query=STR Start the finder with the given query
|
||||||
-1, --select-1 Automatically select the only match
|
-1, --select-1 Automatically select the only match
|
||||||
@ -88,8 +100,29 @@ const (
|
|||||||
byEnd
|
byEnd
|
||||||
)
|
)
|
||||||
|
|
||||||
func defaultMargin() [4]string {
|
type sizeSpec struct {
|
||||||
return [4]string{"0", "0", "0", "0"}
|
size float64
|
||||||
|
percent bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultMargin() [4]sizeSpec {
|
||||||
|
return [4]sizeSpec{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type windowPosition int
|
||||||
|
|
||||||
|
const (
|
||||||
|
posUp windowPosition = iota
|
||||||
|
posDown
|
||||||
|
posLeft
|
||||||
|
posRight
|
||||||
|
)
|
||||||
|
|
||||||
|
type previewOpts struct {
|
||||||
|
command string
|
||||||
|
position windowPosition
|
||||||
|
size sizeSpec
|
||||||
|
hidden bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options stores the values of command-line options
|
// Options stores the values of command-line options
|
||||||
@ -123,13 +156,14 @@ type Options struct {
|
|||||||
Expect map[int]string
|
Expect map[int]string
|
||||||
Keymap map[int]actionType
|
Keymap map[int]actionType
|
||||||
Execmap map[int]string
|
Execmap map[int]string
|
||||||
|
Preview previewOpts
|
||||||
PrintQuery bool
|
PrintQuery bool
|
||||||
ReadZero bool
|
ReadZero bool
|
||||||
Sync bool
|
Sync bool
|
||||||
History *History
|
History *History
|
||||||
Header []string
|
Header []string
|
||||||
HeaderLines int
|
HeaderLines int
|
||||||
Margin [4]string
|
Margin [4]sizeSpec
|
||||||
Tabstop int
|
Tabstop int
|
||||||
Version bool
|
Version bool
|
||||||
}
|
}
|
||||||
@ -165,6 +199,7 @@ func defaultOptions() *Options {
|
|||||||
Expect: make(map[int]string),
|
Expect: make(map[int]string),
|
||||||
Keymap: make(map[int]actionType),
|
Keymap: make(map[int]actionType),
|
||||||
Execmap: make(map[int]string),
|
Execmap: make(map[int]string),
|
||||||
|
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false},
|
||||||
PrintQuery: false,
|
PrintQuery: false,
|
||||||
ReadZero: false,
|
ReadZero: false,
|
||||||
Sync: false,
|
Sync: false,
|
||||||
@ -458,6 +493,8 @@ func parseTheme(defaultTheme *curses.ColorTheme, str string) *curses.ColorTheme
|
|||||||
theme.Match = ansi
|
theme.Match = ansi
|
||||||
case "hl+":
|
case "hl+":
|
||||||
theme.CurrentMatch = ansi
|
theme.CurrentMatch = ansi
|
||||||
|
case "border":
|
||||||
|
theme.Border = ansi
|
||||||
case "prompt":
|
case "prompt":
|
||||||
theme.Prompt = ansi
|
theme.Prompt = ansi
|
||||||
case "spinner":
|
case "spinner":
|
||||||
@ -604,6 +641,8 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, str string)
|
|||||||
keymap[key] = actPreviousHistory
|
keymap[key] = actPreviousHistory
|
||||||
case "next-history":
|
case "next-history":
|
||||||
keymap[key] = actNextHistory
|
keymap[key] = actNextHistory
|
||||||
|
case "toggle-preview":
|
||||||
|
keymap[key] = actTogglePreview
|
||||||
case "toggle-sort":
|
case "toggle-sort":
|
||||||
keymap[key] = actToggleSort
|
keymap[key] = actToggleSort
|
||||||
default:
|
default:
|
||||||
@ -659,40 +698,86 @@ func strLines(str string) []string {
|
|||||||
return strings.Split(strings.TrimSuffix(str, "\n"), "\n")
|
return strings.Split(strings.TrimSuffix(str, "\n"), "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMargin(margin string) [4]string {
|
func parseSize(str string, maxPercent float64, label string) sizeSpec {
|
||||||
margins := strings.Split(margin, ",")
|
var val float64
|
||||||
checked := func(str string) string {
|
percent := strings.HasSuffix(str, "%")
|
||||||
if strings.HasSuffix(str, "%") {
|
if percent {
|
||||||
val := atof(str[:len(str)-1])
|
val = atof(str[:len(str)-1])
|
||||||
if val < 0 {
|
if val < 0 {
|
||||||
errorExit("margin must be non-negative")
|
errorExit(label + " must be non-negative")
|
||||||
}
|
|
||||||
if val > 100 {
|
|
||||||
errorExit("margin too large")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val := atoi(str)
|
|
||||||
if val < 0 {
|
|
||||||
errorExit("margin must be non-negative")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return str
|
if val > maxPercent {
|
||||||
|
errorExit(fmt.Sprintf("%s too large (max: %d%%)", label, int(maxPercent)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if strings.Contains(str, ".") {
|
||||||
|
errorExit(label + " (without %) must be a non-negative integer")
|
||||||
|
}
|
||||||
|
|
||||||
|
val = float64(atoi(str))
|
||||||
|
if val < 0 {
|
||||||
|
errorExit(label + " must be non-negative")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sizeSpec{val, percent}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
|
layout := input
|
||||||
|
if strings.HasSuffix(layout, ":hidden") {
|
||||||
|
opts.hidden = true
|
||||||
|
layout = strings.TrimSuffix(layout, ":hidden")
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens := strings.Split(layout, ":")
|
||||||
|
if len(tokens) == 0 || len(tokens) > 2 {
|
||||||
|
errorExit("invalid window layout: " + input)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tokens) > 1 {
|
||||||
|
opts.size = parseSize(tokens[1], 99, "window size")
|
||||||
|
} else {
|
||||||
|
opts.size = sizeSpec{50, true}
|
||||||
|
}
|
||||||
|
if !opts.size.percent && opts.size.size > 0 {
|
||||||
|
// Adjust size for border
|
||||||
|
opts.size.size += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tokens[0] {
|
||||||
|
case "up":
|
||||||
|
opts.position = posUp
|
||||||
|
case "down":
|
||||||
|
opts.position = posDown
|
||||||
|
case "left":
|
||||||
|
opts.position = posLeft
|
||||||
|
case "right":
|
||||||
|
opts.position = posRight
|
||||||
|
default:
|
||||||
|
errorExit("invalid window position: " + input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMargin(margin string) [4]sizeSpec {
|
||||||
|
margins := strings.Split(margin, ",")
|
||||||
|
checked := func(str string) sizeSpec {
|
||||||
|
return parseSize(str, 49, "margin")
|
||||||
}
|
}
|
||||||
switch len(margins) {
|
switch len(margins) {
|
||||||
case 1:
|
case 1:
|
||||||
m := checked(margins[0])
|
m := checked(margins[0])
|
||||||
return [4]string{m, m, m, m}
|
return [4]sizeSpec{m, m, m, m}
|
||||||
case 2:
|
case 2:
|
||||||
tb := checked(margins[0])
|
tb := checked(margins[0])
|
||||||
rl := checked(margins[1])
|
rl := checked(margins[1])
|
||||||
return [4]string{tb, rl, tb, rl}
|
return [4]sizeSpec{tb, rl, tb, rl}
|
||||||
case 3:
|
case 3:
|
||||||
t := checked(margins[0])
|
t := checked(margins[0])
|
||||||
rl := checked(margins[1])
|
rl := checked(margins[1])
|
||||||
b := checked(margins[2])
|
b := checked(margins[2])
|
||||||
return [4]string{t, rl, b, rl}
|
return [4]sizeSpec{t, rl, b, rl}
|
||||||
case 4:
|
case 4:
|
||||||
return [4]string{
|
return [4]sizeSpec{
|
||||||
checked(margins[0]), checked(margins[1]),
|
checked(margins[0]), checked(margins[1]),
|
||||||
checked(margins[2]), checked(margins[3])}
|
checked(margins[2]), checked(margins[3])}
|
||||||
default:
|
default:
|
||||||
@ -858,6 +943,13 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--header-lines":
|
case "--header-lines":
|
||||||
opts.HeaderLines = atoi(
|
opts.HeaderLines = atoi(
|
||||||
nextString(allArgs, &i, "number of header lines required"))
|
nextString(allArgs, &i, "number of header lines required"))
|
||||||
|
case "--preview":
|
||||||
|
opts.Preview.command = nextString(allArgs, &i, "preview command required")
|
||||||
|
case "--no-preview":
|
||||||
|
opts.Preview.command = ""
|
||||||
|
case "--preview-window":
|
||||||
|
parsePreviewWindow(&opts.Preview,
|
||||||
|
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]]"))
|
||||||
case "--no-margin":
|
case "--no-margin":
|
||||||
opts.Margin = defaultMargin()
|
opts.Margin = defaultMargin()
|
||||||
case "--margin":
|
case "--margin":
|
||||||
@ -900,6 +992,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Header = strLines(value)
|
opts.Header = strLines(value)
|
||||||
} else if match, value := optString(arg, "--header-lines="); match {
|
} else if match, value := optString(arg, "--header-lines="); match {
|
||||||
opts.HeaderLines = atoi(value)
|
opts.HeaderLines = atoi(value)
|
||||||
|
} else if match, value := optString(arg, "--preview="); match {
|
||||||
|
opts.Preview.command = value
|
||||||
|
} else if match, value := optString(arg, "--preview-window="); match {
|
||||||
|
parsePreviewWindow(&opts.Preview, value)
|
||||||
} else if match, value := optString(arg, "--margin="); match {
|
} else if match, value := optString(arg, "--margin="); match {
|
||||||
opts.Margin = parseMargin(value)
|
opts.Margin = parseMargin(value)
|
||||||
} else if match, value := optString(arg, "--tabstop="); match {
|
} else if match, value := optString(arg, "--tabstop="); match {
|
||||||
|
295
src/terminal.go
295
src/terminal.go
@ -7,7 +7,6 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -53,8 +52,10 @@ type Terminal struct {
|
|||||||
header []string
|
header []string
|
||||||
header0 []string
|
header0 []string
|
||||||
ansi bool
|
ansi bool
|
||||||
margin [4]string
|
margin [4]sizeSpec
|
||||||
marginInt [4]int
|
window *C.Window
|
||||||
|
bwindow *C.Window
|
||||||
|
pwindow *C.Window
|
||||||
count int
|
count int
|
||||||
progress int
|
progress int
|
||||||
reading bool
|
reading bool
|
||||||
@ -63,6 +64,10 @@ type Terminal struct {
|
|||||||
merger *Merger
|
merger *Merger
|
||||||
selected map[int32]selectedItem
|
selected map[int32]selectedItem
|
||||||
reqBox *util.EventBox
|
reqBox *util.EventBox
|
||||||
|
preview previewOpts
|
||||||
|
previewing bool
|
||||||
|
previewTxt string
|
||||||
|
previewBox *util.EventBox
|
||||||
eventBox *util.EventBox
|
eventBox *util.EventBox
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
initFunc func()
|
initFunc func()
|
||||||
@ -103,6 +108,8 @@ const (
|
|||||||
reqRedraw
|
reqRedraw
|
||||||
reqClose
|
reqClose
|
||||||
reqPrintQuery
|
reqPrintQuery
|
||||||
|
reqPreviewEnqueue
|
||||||
|
reqPreviewDisplay
|
||||||
reqQuit
|
reqQuit
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -148,6 +155,7 @@ const (
|
|||||||
actJumpAccept
|
actJumpAccept
|
||||||
actPrintQuery
|
actPrintQuery
|
||||||
actToggleSort
|
actToggleSort
|
||||||
|
actTogglePreview
|
||||||
actPreviousHistory
|
actPreviousHistory
|
||||||
actNextHistory
|
actNextHistory
|
||||||
actExecute
|
actExecute
|
||||||
@ -220,6 +228,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
} else {
|
} else {
|
||||||
delay = initialDelay
|
delay = initialDelay
|
||||||
}
|
}
|
||||||
|
var previewBox *util.EventBox
|
||||||
|
if len(opts.Preview.command) > 0 {
|
||||||
|
previewBox = util.NewEventBox()
|
||||||
|
}
|
||||||
return &Terminal{
|
return &Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
inlineInfo: opts.InlineInfo,
|
inlineInfo: opts.InlineInfo,
|
||||||
@ -242,7 +254,6 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
printQuery: opts.PrintQuery,
|
printQuery: opts.PrintQuery,
|
||||||
history: opts.History,
|
history: opts.History,
|
||||||
margin: opts.Margin,
|
margin: opts.Margin,
|
||||||
marginInt: [4]int{0, 0, 0, 0},
|
|
||||||
cycle: opts.Cycle,
|
cycle: opts.Cycle,
|
||||||
header: header,
|
header: header,
|
||||||
header0: header,
|
header0: header,
|
||||||
@ -253,6 +264,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
merger: EmptyMerger,
|
merger: EmptyMerger,
|
||||||
selected: make(map[int32]selectedItem),
|
selected: make(map[int32]selectedItem),
|
||||||
reqBox: util.NewEventBox(),
|
reqBox: util.NewEventBox(),
|
||||||
|
preview: opts.Preview,
|
||||||
|
previewing: previewBox != nil && !opts.Preview.hidden,
|
||||||
|
previewTxt: "",
|
||||||
|
previewBox: previewBox,
|
||||||
eventBox: eventBox,
|
eventBox: eventBox,
|
||||||
mutex: sync.Mutex{},
|
mutex: sync.Mutex{},
|
||||||
suppress: true,
|
suppress: true,
|
||||||
@ -332,7 +347,7 @@ func (t *Terminal) output() bool {
|
|||||||
if !found {
|
if !found {
|
||||||
cnt := t.merger.Length()
|
cnt := t.merger.Length()
|
||||||
if cnt > 0 && cnt > t.cy {
|
if cnt > 0 && cnt > t.cy {
|
||||||
fmt.Println(t.merger.Get(t.cy).AsString(t.ansi))
|
fmt.Println(t.current())
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -372,56 +387,113 @@ func displayWidth(runes []rune) int {
|
|||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
const minWidth = 16
|
const (
|
||||||
const minHeight = 4
|
minWidth = 16
|
||||||
|
minHeight = 4
|
||||||
|
)
|
||||||
|
|
||||||
func (t *Terminal) calculateMargins() {
|
func calculateSize(base int, size sizeSpec, margin int, minSize int) int {
|
||||||
|
max := base - margin
|
||||||
|
if size.percent {
|
||||||
|
return util.Constrain(int(float64(base)*0.01*size.size), minSize, max)
|
||||||
|
}
|
||||||
|
return util.Constrain(int(size.size), minSize, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) resizeWindows() {
|
||||||
screenWidth := C.MaxX()
|
screenWidth := C.MaxX()
|
||||||
screenHeight := C.MaxY()
|
screenHeight := C.MaxY()
|
||||||
for idx, str := range t.margin {
|
marginInt := [4]int{}
|
||||||
if str == "0" {
|
for idx, sizeSpec := range t.margin {
|
||||||
t.marginInt[idx] = 0
|
if sizeSpec.percent {
|
||||||
} else if strings.HasSuffix(str, "%") {
|
var max float64
|
||||||
num, _ := strconv.ParseFloat(str[:len(str)-1], 64)
|
|
||||||
var val float64
|
|
||||||
if idx%2 == 0 {
|
if idx%2 == 0 {
|
||||||
val = float64(screenHeight)
|
max = float64(screenHeight)
|
||||||
} else {
|
} else {
|
||||||
val = float64(screenWidth)
|
max = float64(screenWidth)
|
||||||
}
|
}
|
||||||
t.marginInt[idx] = int(val * num * 0.01)
|
marginInt[idx] = int(max * sizeSpec.size * 0.01)
|
||||||
} else {
|
} else {
|
||||||
num, _ := strconv.Atoi(str)
|
marginInt[idx] = int(sizeSpec.size)
|
||||||
t.marginInt[idx] = num
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adjust := func(idx1 int, idx2 int, max int, min int) {
|
adjust := func(idx1 int, idx2 int, max int, min int) {
|
||||||
if max >= min {
|
if max >= min {
|
||||||
margin := t.marginInt[idx1] + t.marginInt[idx2]
|
margin := marginInt[idx1] + marginInt[idx2]
|
||||||
if max-margin < min {
|
if max-margin < min {
|
||||||
desired := max - min
|
desired := max - min
|
||||||
t.marginInt[idx1] = desired * t.marginInt[idx1] / margin
|
marginInt[idx1] = desired * marginInt[idx1] / margin
|
||||||
t.marginInt[idx2] = desired * t.marginInt[idx2] / margin
|
marginInt[idx2] = desired * marginInt[idx2] / margin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adjust(1, 3, screenWidth, minWidth)
|
minAreaWidth := minWidth
|
||||||
adjust(0, 2, screenHeight, minHeight)
|
minAreaHeight := minHeight
|
||||||
|
if t.isPreviewEnabled() {
|
||||||
|
switch t.preview.position {
|
||||||
|
case posUp, posDown:
|
||||||
|
minAreaHeight *= 2
|
||||||
|
case posLeft, posRight:
|
||||||
|
minAreaWidth *= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adjust(1, 3, screenWidth, minAreaWidth)
|
||||||
|
adjust(0, 2, screenHeight, minAreaHeight)
|
||||||
|
if t.window != nil {
|
||||||
|
t.window.Close()
|
||||||
|
}
|
||||||
|
if t.bwindow != nil {
|
||||||
|
t.bwindow.Close()
|
||||||
|
t.pwindow.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
width := screenWidth - marginInt[1] - marginInt[3]
|
||||||
|
height := screenHeight - marginInt[0] - marginInt[2]
|
||||||
|
if t.isPreviewEnabled() {
|
||||||
|
createPreviewWindow := func(y int, x int, w int, h int) {
|
||||||
|
t.bwindow = C.NewWindow(y, x, w, h, true)
|
||||||
|
t.pwindow = C.NewWindow(y+1, x+2, w-4, h-2, false)
|
||||||
|
}
|
||||||
|
switch t.preview.position {
|
||||||
|
case posUp:
|
||||||
|
pheight := calculateSize(height, t.preview.size, minHeight, 3)
|
||||||
|
t.window = C.NewWindow(
|
||||||
|
marginInt[0]+pheight, marginInt[3], width, height-pheight, false)
|
||||||
|
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
|
||||||
|
case posDown:
|
||||||
|
pheight := calculateSize(height, t.preview.size, minHeight, 3)
|
||||||
|
t.window = C.NewWindow(
|
||||||
|
marginInt[0], marginInt[3], width, height-pheight, false)
|
||||||
|
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
|
||||||
|
case posLeft:
|
||||||
|
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
|
||||||
|
t.window = C.NewWindow(
|
||||||
|
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false)
|
||||||
|
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
|
||||||
|
case posRight:
|
||||||
|
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
|
||||||
|
t.window = C.NewWindow(
|
||||||
|
marginInt[0], marginInt[3], width-pwidth, height, false)
|
||||||
|
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.window = C.NewWindow(
|
||||||
|
marginInt[0],
|
||||||
|
marginInt[3],
|
||||||
|
width,
|
||||||
|
height, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) move(y int, x int, clear bool) {
|
func (t *Terminal) move(y int, x int, clear bool) {
|
||||||
x += t.marginInt[3]
|
|
||||||
maxy := C.MaxY()
|
|
||||||
if !t.reverse {
|
if !t.reverse {
|
||||||
y = maxy - y - 1 - t.marginInt[2]
|
y = t.window.Height - y - 1
|
||||||
} else {
|
|
||||||
y += t.marginInt[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if clear {
|
if clear {
|
||||||
C.MoveAndClear(y, x)
|
t.window.MoveAndClear(y, x)
|
||||||
} else {
|
} else {
|
||||||
C.Move(y, x)
|
t.window.Move(y, x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,24 +503,24 @@ func (t *Terminal) placeCursor() {
|
|||||||
|
|
||||||
func (t *Terminal) printPrompt() {
|
func (t *Terminal) printPrompt() {
|
||||||
t.move(0, 0, true)
|
t.move(0, 0, true)
|
||||||
C.CPrint(C.ColPrompt, true, t.prompt)
|
t.window.CPrint(C.ColPrompt, true, t.prompt)
|
||||||
C.CPrint(C.ColNormal, true, string(t.input))
|
t.window.CPrint(C.ColNormal, true, string(t.input))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printInfo() {
|
func (t *Terminal) printInfo() {
|
||||||
if t.inlineInfo {
|
if t.inlineInfo {
|
||||||
t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input)+1, true)
|
t.move(0, displayWidth([]rune(t.prompt))+displayWidth(t.input)+1, true)
|
||||||
if t.reading {
|
if t.reading {
|
||||||
C.CPrint(C.ColSpinner, true, " < ")
|
t.window.CPrint(C.ColSpinner, true, " < ")
|
||||||
} else {
|
} else {
|
||||||
C.CPrint(C.ColPrompt, true, " < ")
|
t.window.CPrint(C.ColPrompt, true, " < ")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.move(1, 0, true)
|
t.move(1, 0, true)
|
||||||
if t.reading {
|
if t.reading {
|
||||||
duration := int64(spinnerDuration)
|
duration := int64(spinnerDuration)
|
||||||
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
|
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
|
||||||
C.CPrint(C.ColSpinner, true, _spinner[idx])
|
t.window.CPrint(C.ColSpinner, true, _spinner[idx])
|
||||||
}
|
}
|
||||||
t.move(1, 2, false)
|
t.move(1, 2, false)
|
||||||
}
|
}
|
||||||
@ -467,18 +539,14 @@ func (t *Terminal) printInfo() {
|
|||||||
if t.progress > 0 && t.progress < 100 {
|
if t.progress > 0 && t.progress < 100 {
|
||||||
output += fmt.Sprintf(" (%d%%)", t.progress)
|
output += fmt.Sprintf(" (%d%%)", t.progress)
|
||||||
}
|
}
|
||||||
C.CPrint(C.ColInfo, false, output)
|
t.window.CPrint(C.ColInfo, false, output)
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) maxHeight() int {
|
|
||||||
return C.MaxY() - t.marginInt[0] - t.marginInt[2]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printHeader() {
|
func (t *Terminal) printHeader() {
|
||||||
if len(t.header) == 0 {
|
if len(t.header) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
max := t.maxHeight()
|
max := t.window.Height
|
||||||
var state *ansiState
|
var state *ansiState
|
||||||
for idx, lineStr := range t.header {
|
for idx, lineStr := range t.header {
|
||||||
line := idx + 2
|
line := idx + 2
|
||||||
@ -529,19 +597,19 @@ func (t *Terminal) printItem(item *Item, i int, current bool) {
|
|||||||
} else if current {
|
} else if current {
|
||||||
label = ">"
|
label = ">"
|
||||||
}
|
}
|
||||||
C.CPrint(C.ColCursor, true, label)
|
t.window.CPrint(C.ColCursor, true, label)
|
||||||
if current {
|
if current {
|
||||||
if selected {
|
if selected {
|
||||||
C.CPrint(C.ColSelected, true, ">")
|
t.window.CPrint(C.ColSelected, true, ">")
|
||||||
} else {
|
} else {
|
||||||
C.CPrint(C.ColCurrent, true, " ")
|
t.window.CPrint(C.ColCurrent, true, " ")
|
||||||
}
|
}
|
||||||
t.printHighlighted(item, true, C.ColCurrent, C.ColCurrentMatch, true)
|
t.printHighlighted(item, true, C.ColCurrent, C.ColCurrentMatch, true)
|
||||||
} else {
|
} else {
|
||||||
if selected {
|
if selected {
|
||||||
C.CPrint(C.ColSelected, true, ">")
|
t.window.CPrint(C.ColSelected, true, ">")
|
||||||
} else {
|
} else {
|
||||||
C.Print(" ")
|
t.window.Print(" ")
|
||||||
}
|
}
|
||||||
t.printHighlighted(item, false, 0, C.ColMatch, false)
|
t.printHighlighted(item, false, 0, C.ColMatch, false)
|
||||||
}
|
}
|
||||||
@ -593,7 +661,7 @@ func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, c
|
|||||||
text := make([]rune, len(item.text))
|
text := make([]rune, len(item.text))
|
||||||
copy(text, item.text)
|
copy(text, item.text)
|
||||||
offsets := item.colorOffsets(col2, bold, current)
|
offsets := item.colorOffsets(col2, bold, current)
|
||||||
maxWidth := C.MaxX() - 3 - t.marginInt[1] - t.marginInt[3]
|
maxWidth := t.window.Width - 3
|
||||||
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
|
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
|
||||||
fullWidth := displayWidth(text)
|
fullWidth := displayWidth(text)
|
||||||
if fullWidth > maxWidth {
|
if fullWidth > maxWidth {
|
||||||
@ -643,11 +711,11 @@ func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, c
|
|||||||
e := util.Constrain32(offset.offset[1], index, maxOffset)
|
e := util.Constrain32(offset.offset[1], index, maxOffset)
|
||||||
|
|
||||||
substr, prefixWidth = processTabs(text[index:b], prefixWidth)
|
substr, prefixWidth = processTabs(text[index:b], prefixWidth)
|
||||||
C.CPrint(col1, bold, substr)
|
t.window.CPrint(col1, bold, substr)
|
||||||
|
|
||||||
if b < e {
|
if b < e {
|
||||||
substr, prefixWidth = processTabs(text[b:e], prefixWidth)
|
substr, prefixWidth = processTabs(text[b:e], prefixWidth)
|
||||||
C.CPrint(offset.color, offset.bold, substr)
|
t.window.CPrint(offset.color, offset.bold, substr)
|
||||||
}
|
}
|
||||||
|
|
||||||
index = e
|
index = e
|
||||||
@ -657,7 +725,29 @@ func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, c
|
|||||||
}
|
}
|
||||||
if index < maxOffset {
|
if index < maxOffset {
|
||||||
substr, _ = processTabs(text[index:], prefixWidth)
|
substr, _ = processTabs(text[index:], prefixWidth)
|
||||||
C.CPrint(col1, bold, substr)
|
t.window.CPrint(col1, bold, substr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) printPreview() {
|
||||||
|
trimmed, ansiOffsets, _ := extractColor(t.previewTxt, nil)
|
||||||
|
var index int32
|
||||||
|
t.pwindow.Erase()
|
||||||
|
for _, o := range ansiOffsets {
|
||||||
|
b := o.offset[0]
|
||||||
|
e := o.offset[1]
|
||||||
|
if b > index {
|
||||||
|
if !t.pwindow.Fill(trimmed[index:b]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !t.pwindow.CFill(trimmed[b:e], o.color.fg, o.color.bg, o.color.bold) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
index = e
|
||||||
|
}
|
||||||
|
if int(index) < len(trimmed) {
|
||||||
|
t.pwindow.Fill(trimmed[index:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -677,16 +767,24 @@ func processTabs(runes []rune, prefixWidth int) (string, int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printAll() {
|
func (t *Terminal) printAll() {
|
||||||
t.calculateMargins()
|
t.resizeWindows()
|
||||||
t.printList()
|
t.printList()
|
||||||
t.printPrompt()
|
t.printPrompt()
|
||||||
t.printInfo()
|
t.printInfo()
|
||||||
t.printHeader()
|
t.printHeader()
|
||||||
|
if t.isPreviewEnabled() {
|
||||||
|
t.printPreview()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) refresh() {
|
func (t *Terminal) refresh() {
|
||||||
if !t.suppress {
|
if !t.suppress {
|
||||||
C.Refresh()
|
if t.isPreviewEnabled() {
|
||||||
|
t.bwindow.Refresh()
|
||||||
|
t.pwindow.Refresh()
|
||||||
|
}
|
||||||
|
t.window.Refresh()
|
||||||
|
C.DoUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -746,7 +844,7 @@ func quoteEntry(entry string) string {
|
|||||||
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
||||||
}
|
}
|
||||||
|
|
||||||
func executeCommand(template string, replacement string) {
|
func (t *Terminal) executeCommand(template string, replacement string) {
|
||||||
command := strings.Replace(template, "{}", replacement, -1)
|
command := strings.Replace(template, "{}", replacement, -1)
|
||||||
cmd := util.ExecCommand(command)
|
cmd := util.ExecCommand(command)
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
@ -754,7 +852,19 @@ func executeCommand(template string, replacement string) {
|
|||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
C.Endwin()
|
C.Endwin()
|
||||||
cmd.Run()
|
cmd.Run()
|
||||||
C.Refresh()
|
t.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) hasPreviewWindow() bool {
|
||||||
|
return t.previewBox != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) isPreviewEnabled() bool {
|
||||||
|
return t.previewBox != nil && t.previewing
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) current() string {
|
||||||
|
return t.merger.Get(t.cy).AsString(t.ansi)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop is called to start Terminal I/O
|
// Loop is called to start Terminal I/O
|
||||||
@ -779,10 +889,10 @@ func (t *Terminal) Loop() {
|
|||||||
|
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
t.initFunc()
|
t.initFunc()
|
||||||
t.calculateMargins()
|
t.resizeWindows()
|
||||||
t.printPrompt()
|
t.printPrompt()
|
||||||
t.placeCursor()
|
t.placeCursor()
|
||||||
C.Refresh()
|
t.refresh()
|
||||||
t.printInfo()
|
t.printInfo()
|
||||||
t.printHeader()
|
t.printHeader()
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
@ -807,6 +917,29 @@ func (t *Terminal) Loop() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if t.hasPreviewWindow() {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
focused := ""
|
||||||
|
t.previewBox.Wait(func(events *util.Events) {
|
||||||
|
for req, value := range *events {
|
||||||
|
switch req {
|
||||||
|
case reqPreviewEnqueue:
|
||||||
|
focused = value.(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
events.Clear()
|
||||||
|
})
|
||||||
|
if len(focused) > 0 {
|
||||||
|
command := strings.Replace(t.preview.command, "{}", quoteEntry(focused), -1)
|
||||||
|
cmd := util.ExecCommand(command)
|
||||||
|
out, _ := cmd.CombinedOutput()
|
||||||
|
t.reqBox.Set(reqPreviewDisplay, string(out))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
exit := func(code int) {
|
exit := func(code int) {
|
||||||
if code <= exitNoMatch && t.history != nil {
|
if code <= exitNoMatch && t.history != nil {
|
||||||
t.history.append(string(t.input))
|
t.history.append(string(t.input))
|
||||||
@ -815,11 +948,12 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
focused := ""
|
||||||
for {
|
for {
|
||||||
t.reqBox.Wait(func(events *util.Events) {
|
t.reqBox.Wait(func(events *util.Events) {
|
||||||
defer events.Clear()
|
defer events.Clear()
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
for req := range *events {
|
for req, value := range *events {
|
||||||
switch req {
|
switch req {
|
||||||
case reqPrompt:
|
case reqPrompt:
|
||||||
t.printPrompt()
|
t.printPrompt()
|
||||||
@ -830,6 +964,21 @@ func (t *Terminal) Loop() {
|
|||||||
t.printInfo()
|
t.printInfo()
|
||||||
case reqList:
|
case reqList:
|
||||||
t.printList()
|
t.printList()
|
||||||
|
cnt := t.merger.Length()
|
||||||
|
if cnt > 0 && cnt > t.cy {
|
||||||
|
currentFocus := t.current()
|
||||||
|
if currentFocus != focused {
|
||||||
|
focused = currentFocus
|
||||||
|
if t.isPreviewEnabled() {
|
||||||
|
t.previewBox.Set(reqPreviewEnqueue, focused)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if focused != "" && t.isPreviewEnabled() {
|
||||||
|
t.pwindow.Erase()
|
||||||
|
}
|
||||||
|
focused = ""
|
||||||
|
}
|
||||||
case reqJump:
|
case reqJump:
|
||||||
if t.merger.Length() == 0 {
|
if t.merger.Length() == 0 {
|
||||||
t.jumping = jumpDisabled
|
t.jumping = jumpDisabled
|
||||||
@ -850,6 +999,9 @@ func (t *Terminal) Loop() {
|
|||||||
exit(exitOk)
|
exit(exitOk)
|
||||||
}
|
}
|
||||||
exit(exitNoMatch)
|
exit(exitNoMatch)
|
||||||
|
case reqPreviewDisplay:
|
||||||
|
t.previewTxt = value.(string)
|
||||||
|
t.printPreview()
|
||||||
case reqPrintQuery:
|
case reqPrintQuery:
|
||||||
C.Close()
|
C.Close()
|
||||||
fmt.Println(string(t.input))
|
fmt.Println(string(t.input))
|
||||||
@ -915,7 +1067,7 @@ func (t *Terminal) Loop() {
|
|||||||
case actExecute:
|
case actExecute:
|
||||||
if t.cy >= 0 && t.cy < t.merger.Length() {
|
if t.cy >= 0 && t.cy < t.merger.Length() {
|
||||||
item := t.merger.Get(t.cy)
|
item := t.merger.Get(t.cy)
|
||||||
executeCommand(t.execmap[mapkey], quoteEntry(item.AsString(t.ansi)))
|
t.executeCommand(t.execmap[mapkey], quoteEntry(item.AsString(t.ansi)))
|
||||||
}
|
}
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
if len(t.selected) > 0 {
|
if len(t.selected) > 0 {
|
||||||
@ -923,13 +1075,23 @@ func (t *Terminal) Loop() {
|
|||||||
for i, sel := range t.sortSelected() {
|
for i, sel := range t.sortSelected() {
|
||||||
sels[i] = quoteEntry(*sel.text)
|
sels[i] = quoteEntry(*sel.text)
|
||||||
}
|
}
|
||||||
executeCommand(t.execmap[mapkey], strings.Join(sels, " "))
|
t.executeCommand(t.execmap[mapkey], strings.Join(sels, " "))
|
||||||
} else {
|
} else {
|
||||||
return doAction(actExecute, mapkey)
|
return doAction(actExecute, mapkey)
|
||||||
}
|
}
|
||||||
case actInvalid:
|
case actInvalid:
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
|
case actTogglePreview:
|
||||||
|
if t.hasPreviewWindow() {
|
||||||
|
t.previewing = !t.previewing
|
||||||
|
t.resizeWindows()
|
||||||
|
cnt := t.merger.Length()
|
||||||
|
if t.previewing && cnt > 0 && cnt > t.cy {
|
||||||
|
t.previewBox.Set(reqPreviewEnqueue, t.current())
|
||||||
|
}
|
||||||
|
req(reqList, reqInfo)
|
||||||
|
}
|
||||||
case actToggleSort:
|
case actToggleSort:
|
||||||
t.sort = !t.sort
|
t.sort = !t.sort
|
||||||
t.eventBox.Set(EvtSearchNew, t.sort)
|
t.eventBox.Set(EvtSearchNew, t.sort)
|
||||||
@ -1097,20 +1259,19 @@ func (t *Terminal) Loop() {
|
|||||||
mx, my := me.X, me.Y
|
mx, my := me.X, me.Y
|
||||||
if me.S != 0 {
|
if me.S != 0 {
|
||||||
// Scroll
|
// Scroll
|
||||||
if t.merger.Length() > 0 {
|
if t.window.Enclose(my, mx) && t.merger.Length() > 0 {
|
||||||
if t.multi && me.Mod {
|
if t.multi && me.Mod {
|
||||||
toggle()
|
toggle()
|
||||||
}
|
}
|
||||||
t.vmove(me.S)
|
t.vmove(me.S)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
}
|
}
|
||||||
} else if mx >= t.marginInt[3] && mx < C.MaxX()-t.marginInt[1] &&
|
} else if t.window.Enclose(my, mx) {
|
||||||
my >= t.marginInt[0] && my < C.MaxY()-t.marginInt[2] {
|
mx -= t.window.Left
|
||||||
mx -= t.marginInt[3]
|
my -= t.window.Top
|
||||||
my -= t.marginInt[0]
|
|
||||||
mx = util.Constrain(mx-displayWidth([]rune(t.prompt)), 0, len(t.input))
|
mx = util.Constrain(mx-displayWidth([]rune(t.prompt)), 0, len(t.input))
|
||||||
if !t.reverse {
|
if !t.reverse {
|
||||||
my = t.maxHeight() - my - 1
|
my = t.window.Height - my - 1
|
||||||
}
|
}
|
||||||
min := 2 + len(t.header)
|
min := 2 + len(t.header)
|
||||||
if t.inlineInfo {
|
if t.inlineInfo {
|
||||||
@ -1217,7 +1378,7 @@ func (t *Terminal) vset(o int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) maxItems() int {
|
func (t *Terminal) maxItems() int {
|
||||||
max := t.maxHeight() - 2 - len(t.header)
|
max := t.window.Height - 2 - len(t.header)
|
||||||
if t.inlineInfo {
|
if t.inlineInfo {
|
||||||
max++
|
max++
|
||||||
}
|
}
|
||||||
|
@ -1228,6 +1228,28 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal '3', readonce.chomp
|
assert_equal '3', readonce.chomp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preview
|
||||||
|
tmux.send_keys %[seq 1000 | #{FZF} --preview 'echo {{}-{}}' --bind ?:toggle-preview], :Enter
|
||||||
|
tmux.until { |lines| lines[1].include?(' {1-1}') }
|
||||||
|
tmux.send_keys '555'
|
||||||
|
tmux.until { |lines| lines[1].include?(' {555-555}') }
|
||||||
|
tmux.send_keys '?'
|
||||||
|
tmux.until { |lines| !lines[1].include?(' {555-555}') }
|
||||||
|
tmux.send_keys '?'
|
||||||
|
tmux.until { |lines| lines[1].include?(' {555-555}') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_hidden
|
||||||
|
tmux.send_keys %[seq 1000 | #{FZF} --preview 'echo {{}-{}}' --preview-window down:1:hidden --bind ?:toggle-preview], :Enter
|
||||||
|
tmux.until { |lines| lines[-1] == '>' }
|
||||||
|
tmux.send_keys '?'
|
||||||
|
tmux.until { |lines| lines[-2].include?(' {1-1}') }
|
||||||
|
tmux.send_keys '555'
|
||||||
|
tmux.until { |lines| lines[-2].include?(' {555-555}') }
|
||||||
|
tmux.send_keys '?'
|
||||||
|
tmux.until { |lines| lines[-1] == '> 555' }
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def writelines path, lines
|
def writelines path, lines
|
||||||
File.unlink path while File.exists? path
|
File.unlink path while File.exists? path
|
||||||
|
Loading…
Reference in New Issue
Block a user