mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-13 13:53:47 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d471067e3f | ||
|
|
d0b7780239 | ||
|
|
e627ca6bd7 | ||
|
|
c97172bdd4 | ||
|
|
ce8a745fb4 | ||
|
|
3e9efd1401 | ||
|
|
20340190b5 | ||
|
|
265040a78c | ||
|
|
448d7e0c5a | ||
|
|
6eb1874c5a | ||
|
|
4c70745cc1 | ||
|
|
7795748a3f |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,6 +1,27 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.42.0
|
||||||
|
------
|
||||||
|
- Added new info style: `--info=right`
|
||||||
|
- Added new info style: `--info=inline-right`
|
||||||
|
- Added new border style `thinblock` which uses symbols for legacy computing
|
||||||
|
[one eighth block elements](https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing)
|
||||||
|
- Similarly to `block`, this style is suitable when using a different
|
||||||
|
background color because the window is completely contained within the border.
|
||||||
|
```sh
|
||||||
|
BAT_THEME=GitHub fzf --info=right --border=thinblock --preview-window=border-thinblock \
|
||||||
|
--margin=3 --scrollbar=▏▕ --preview='bat --color=always --style=numbers {}' \
|
||||||
|
--color=light,query:238,fg:238,bg:251,bg+:249,gutter:251,border:248,preview-bg:253
|
||||||
|
```
|
||||||
|
- This style may not render correctly depending on the font and the
|
||||||
|
terminal emulator.
|
||||||
|
|
||||||
|
0.41.1
|
||||||
|
------
|
||||||
|
- Fixed a bug where preview window is not updated when `--disabled` is set and
|
||||||
|
a reload is triggered by `change:reload` binding
|
||||||
|
|
||||||
0.41.0
|
0.41.0
|
||||||
------
|
------
|
||||||
- Added color name `preview-border` and `preview-scrollbar`
|
- Added color name `preview-border` and `preview-scrollbar`
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ if [[ "$opt" =~ "-E" ]]; then
|
|||||||
fi
|
fi
|
||||||
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
||||||
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
||||||
|
[[ -n "$BAT_THEME" ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")"
|
||||||
echo "$envs;" > "$argsf"
|
echo "$envs;" > "$argsf"
|
||||||
|
|
||||||
# Build arguments to fzf
|
# Build arguments to fzf
|
||||||
|
|||||||
2
install
2
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.41.0
|
version=0.42.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$version="0.41.0"
|
$version="0.42.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -5,7 +5,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.41"
|
var version string = "0.42"
|
||||||
var revision string = "devel"
|
var revision string = "devel"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf-tmux 1 "May 2023" "fzf 0.41.0" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Jun 2023" "fzf 0.42.0" "fzf-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "May 2023" "fzf 0.41.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Jun 2023" "fzf 0.42.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -228,6 +228,10 @@ Draw border around the finder
|
|||||||
.br
|
.br
|
||||||
.BR double " Border with double lines"
|
.BR double " Border with double lines"
|
||||||
.br
|
.br
|
||||||
|
.BR block " Border using block elements; suitable when using different background colors"
|
||||||
|
.br
|
||||||
|
.BR thinblock " Border using legacy computing symbols; may not be displayed on some terminals"
|
||||||
|
.br
|
||||||
.BR horizontal " Horizontal lines above and below the finder"
|
.BR horizontal " Horizontal lines above and below the finder"
|
||||||
.br
|
.br
|
||||||
.BR vertical " Vertical lines on each side of the finder"
|
.BR vertical " Vertical lines on each side of the finder"
|
||||||
@@ -357,6 +361,8 @@ Determines the display style of finder info (match counters).
|
|||||||
.br
|
.br
|
||||||
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
|
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
|
||||||
.br
|
.br
|
||||||
|
.BR inline-right " Display on the right end of the same line
|
||||||
|
.br
|
||||||
.BR hidden " Do not display finder info"
|
.BR hidden " Do not display finder info"
|
||||||
.br
|
.br
|
||||||
|
|
||||||
@@ -599,6 +605,10 @@ Should be used with one of the following \fB--preview-window\fR options.
|
|||||||
.br
|
.br
|
||||||
.B * border-double
|
.B * border-double
|
||||||
.br
|
.br
|
||||||
|
.B * border-block
|
||||||
|
.br
|
||||||
|
.B * border-thinblock
|
||||||
|
.br
|
||||||
.B * border-horizontal
|
.B * border-horizontal
|
||||||
.br
|
.br
|
||||||
.B * border-top
|
.B * border-top
|
||||||
|
|||||||
@@ -456,6 +456,30 @@ function! s:writefile(...)
|
|||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! s:extract_option(opts, name)
|
||||||
|
let opt = ''
|
||||||
|
let expect = 0
|
||||||
|
" There are a few cases where this function doesn't work as expected.
|
||||||
|
" Let's just assume such cases are extremely unlikely in real world.
|
||||||
|
" e.g. --query --border
|
||||||
|
for word in split(a:opts)
|
||||||
|
if expect && word !~ '^"\=-'
|
||||||
|
let opt = opt . ' ' . word
|
||||||
|
let expect = 0
|
||||||
|
elseif word == '--no-'.a:name
|
||||||
|
let opt = ''
|
||||||
|
elseif word =~ '^--'.a:name.'='
|
||||||
|
let opt = word
|
||||||
|
elseif word =~ '^--'.a:name.'$'
|
||||||
|
let opt = word
|
||||||
|
let expect = 1
|
||||||
|
elseif expect
|
||||||
|
let expect = 0
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
return opt
|
||||||
|
endfunction
|
||||||
|
|
||||||
function! fzf#run(...) abort
|
function! fzf#run(...) abort
|
||||||
try
|
try
|
||||||
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
||||||
@@ -511,8 +535,8 @@ try
|
|||||||
let height = s:calc_size(&lines, dict.down, dict)
|
let height = s:calc_size(&lines, dict.down, dict)
|
||||||
let optstr .= ' --height='.height
|
let optstr .= ' --height='.height
|
||||||
endif
|
endif
|
||||||
" Respect --border option given in 'options'
|
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
|
||||||
let optstr = join([s:border_opt(get(dict, 'window', 0)), optstr])
|
let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])
|
||||||
let prev_default_command = $FZF_DEFAULT_COMMAND
|
let prev_default_command = $FZF_DEFAULT_COMMAND
|
||||||
if len(source_command)
|
if len(source_command)
|
||||||
let $FZF_DEFAULT_COMMAND = source_command
|
let $FZF_DEFAULT_COMMAND = source_command
|
||||||
|
|||||||
32
src/core.go
32
src/core.go
@@ -138,7 +138,9 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
|
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
|
||||||
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
|
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
|
||||||
}
|
}
|
||||||
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
|
inputRevision := 0
|
||||||
|
snapshotRevision := 0
|
||||||
|
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox, inputRevision)
|
||||||
|
|
||||||
// Filtering mode
|
// Filtering mode
|
||||||
if opts.Filter != nil {
|
if opts.Filter != nil {
|
||||||
@@ -209,8 +211,6 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
|
|
||||||
// Event coordination
|
// Event coordination
|
||||||
reading := true
|
reading := true
|
||||||
clearCache := util.Once(false)
|
|
||||||
clearSelection := util.Once(false)
|
|
||||||
ticks := 0
|
ticks := 0
|
||||||
var nextCommand *string
|
var nextCommand *string
|
||||||
eventBox.Watch(EvtReadNew)
|
eventBox.Watch(EvtReadNew)
|
||||||
@@ -231,26 +231,20 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
|
|
||||||
useSnapshot := false
|
useSnapshot := false
|
||||||
var snapshot []*Chunk
|
var snapshot []*Chunk
|
||||||
var prevSnapshot []*Chunk
|
|
||||||
var count int
|
var count int
|
||||||
restart := func(command string) {
|
restart := func(command string) {
|
||||||
reading = true
|
reading = true
|
||||||
clearCache = util.Once(true)
|
|
||||||
clearSelection = util.Once(true)
|
|
||||||
// We should not update snapshot if reload is triggered again while
|
|
||||||
// the previous reload is in progress
|
|
||||||
if useSnapshot && prevSnapshot != nil {
|
|
||||||
snapshot, count = chunkList.Snapshot()
|
|
||||||
}
|
|
||||||
chunkList.Clear()
|
chunkList.Clear()
|
||||||
itemIndex = 0
|
itemIndex = 0
|
||||||
|
inputRevision++
|
||||||
header = make([]string, 0, opts.HeaderLines)
|
header = make([]string, 0, opts.HeaderLines)
|
||||||
go reader.restart(command)
|
go reader.restart(command)
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
delay := true
|
delay := true
|
||||||
ticks++
|
ticks++
|
||||||
input := func(reloaded bool) []rune {
|
input := func() []rune {
|
||||||
|
reloaded := snapshotRevision != inputRevision
|
||||||
paused, input := terminal.Input()
|
paused, input := terminal.Input()
|
||||||
if reloaded && paused {
|
if reloaded && paused {
|
||||||
query = []rune{}
|
query = []rune{}
|
||||||
@@ -280,22 +274,21 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
}
|
}
|
||||||
if useSnapshot && evt == EvtReadFin {
|
if useSnapshot && evt == EvtReadFin {
|
||||||
useSnapshot = false
|
useSnapshot = false
|
||||||
prevSnapshot = nil
|
|
||||||
}
|
}
|
||||||
if !useSnapshot {
|
if !useSnapshot {
|
||||||
snapshot, count = chunkList.Snapshot()
|
snapshot, count = chunkList.Snapshot()
|
||||||
|
snapshotRevision = inputRevision
|
||||||
}
|
}
|
||||||
total = count
|
total = count
|
||||||
terminal.UpdateCount(total, !reading, value.(*string))
|
terminal.UpdateCount(total, !reading, value.(*string))
|
||||||
if opts.Sync {
|
if opts.Sync {
|
||||||
opts.Sync = false
|
opts.Sync = false
|
||||||
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
|
terminal.UpdateList(PassMerger(&snapshot, opts.Tac, snapshotRevision))
|
||||||
}
|
}
|
||||||
if heightUnknown && !deferred {
|
if heightUnknown && !deferred {
|
||||||
determine(!reading)
|
determine(!reading)
|
||||||
}
|
}
|
||||||
reset := !useSnapshot && clearCache()
|
matcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision)
|
||||||
matcher.Reset(snapshot, input(reset), false, !reading, sort, reset)
|
|
||||||
|
|
||||||
case EvtSearchNew:
|
case EvtSearchNew:
|
||||||
var command *string
|
var command *string
|
||||||
@@ -320,17 +313,16 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
if !changed {
|
if !changed {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
reset := false
|
|
||||||
if !useSnapshot {
|
if !useSnapshot {
|
||||||
newSnapshot, _ := chunkList.Snapshot()
|
newSnapshot, _ := chunkList.Snapshot()
|
||||||
// We want to avoid showing empty list when reload is triggered
|
// We want to avoid showing empty list when reload is triggered
|
||||||
// and the query string is changed at the same time i.e. command != nil && changed
|
// and the query string is changed at the same time i.e. command != nil && changed
|
||||||
if command == nil || len(newSnapshot) > 0 {
|
if command == nil || len(newSnapshot) > 0 {
|
||||||
snapshot = newSnapshot
|
snapshot = newSnapshot
|
||||||
reset = clearCache()
|
snapshotRevision = inputRevision
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
matcher.Reset(snapshot, input(reset), true, !reading, sort, reset)
|
matcher.Reset(snapshot, input(), true, !reading, sort, snapshotRevision)
|
||||||
delay = false
|
delay = false
|
||||||
|
|
||||||
case EvtSearchProgress:
|
case EvtSearchProgress:
|
||||||
@@ -370,7 +362,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
determine(val.final)
|
determine(val.final)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
terminal.UpdateList(val, clearSelection())
|
terminal.UpdateList(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ import (
|
|||||||
|
|
||||||
// MatchRequest represents a search request
|
// MatchRequest represents a search request
|
||||||
type MatchRequest struct {
|
type MatchRequest struct {
|
||||||
chunks []*Chunk
|
chunks []*Chunk
|
||||||
pattern *Pattern
|
pattern *Pattern
|
||||||
final bool
|
final bool
|
||||||
sort bool
|
sort bool
|
||||||
clearCache bool
|
revision int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matcher is responsible for performing search
|
// Matcher is responsible for performing search
|
||||||
@@ -29,6 +29,7 @@ type Matcher struct {
|
|||||||
partitions int
|
partitions int
|
||||||
slab []*util.Slab
|
slab []*util.Slab
|
||||||
mergerCache map[string]*Merger
|
mergerCache map[string]*Merger
|
||||||
|
revision int
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -38,7 +39,7 @@ const (
|
|||||||
|
|
||||||
// NewMatcher returns a new Matcher
|
// NewMatcher returns a new Matcher
|
||||||
func NewMatcher(patternBuilder func([]rune) *Pattern,
|
func NewMatcher(patternBuilder func([]rune) *Pattern,
|
||||||
sort bool, tac bool, eventBox *util.EventBox) *Matcher {
|
sort bool, tac bool, eventBox *util.EventBox, revision int) *Matcher {
|
||||||
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
||||||
return &Matcher{
|
return &Matcher{
|
||||||
patternBuilder: patternBuilder,
|
patternBuilder: patternBuilder,
|
||||||
@@ -48,7 +49,8 @@ func NewMatcher(patternBuilder func([]rune) *Pattern,
|
|||||||
reqBox: util.NewEventBox(),
|
reqBox: util.NewEventBox(),
|
||||||
partitions: partitions,
|
partitions: partitions,
|
||||||
slab: make([]*util.Slab, partitions),
|
slab: make([]*util.Slab, partitions),
|
||||||
mergerCache: make(map[string]*Merger)}
|
mergerCache: make(map[string]*Merger),
|
||||||
|
revision: revision}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop puts Matcher in action
|
// Loop puts Matcher in action
|
||||||
@@ -70,8 +72,9 @@ func (m *Matcher) Loop() {
|
|||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
if request.sort != m.sort || request.clearCache {
|
if request.sort != m.sort || request.revision != m.revision {
|
||||||
m.sort = request.sort
|
m.sort = request.sort
|
||||||
|
m.revision = request.revision
|
||||||
m.mergerCache = make(map[string]*Merger)
|
m.mergerCache = make(map[string]*Merger)
|
||||||
clearChunkCache()
|
clearChunkCache()
|
||||||
}
|
}
|
||||||
@@ -140,11 +143,11 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
|
|
||||||
numChunks := len(request.chunks)
|
numChunks := len(request.chunks)
|
||||||
if numChunks == 0 {
|
if numChunks == 0 {
|
||||||
return EmptyMerger, false
|
return EmptyMerger(request.revision), false
|
||||||
}
|
}
|
||||||
pattern := request.pattern
|
pattern := request.pattern
|
||||||
if pattern.IsEmpty() {
|
if pattern.IsEmpty() {
|
||||||
return PassMerger(&request.chunks, m.tac), false
|
return PassMerger(&request.chunks, m.tac, request.revision), false
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelled := util.NewAtomicBool(false)
|
cancelled := util.NewAtomicBool(false)
|
||||||
@@ -218,11 +221,11 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
partialResult := <-resultChan
|
partialResult := <-resultChan
|
||||||
partialResults[partialResult.index] = partialResult.matches
|
partialResults[partialResult.index] = partialResult.matches
|
||||||
}
|
}
|
||||||
return NewMerger(pattern, partialResults, m.sort, m.tac), false
|
return NewMerger(pattern, partialResults, m.sort, m.tac, request.revision), false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset is called to interrupt/signal the ongoing search
|
// Reset is called to interrupt/signal the ongoing search
|
||||||
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, clearCache bool) {
|
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, revision int) {
|
||||||
pattern := m.patternBuilder(patternRunes)
|
pattern := m.patternBuilder(patternRunes)
|
||||||
|
|
||||||
var event util.EventType
|
var event util.EventType
|
||||||
@@ -231,5 +234,5 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
|
|||||||
} else {
|
} else {
|
||||||
event = reqRetry
|
event = reqRetry
|
||||||
}
|
}
|
||||||
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, clearCache})
|
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, revision})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,32 +3,36 @@ package fzf
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// EmptyMerger is a Merger with no data
|
// EmptyMerger is a Merger with no data
|
||||||
var EmptyMerger = NewMerger(nil, [][]Result{}, false, false)
|
func EmptyMerger(revision int) *Merger {
|
||||||
|
return NewMerger(nil, [][]Result{}, false, false, revision)
|
||||||
|
}
|
||||||
|
|
||||||
// Merger holds a set of locally sorted lists of items and provides the view of
|
// Merger holds a set of locally sorted lists of items and provides the view of
|
||||||
// a single, globally-sorted list
|
// a single, globally-sorted list
|
||||||
type Merger struct {
|
type Merger struct {
|
||||||
pattern *Pattern
|
pattern *Pattern
|
||||||
lists [][]Result
|
lists [][]Result
|
||||||
merged []Result
|
merged []Result
|
||||||
chunks *[]*Chunk
|
chunks *[]*Chunk
|
||||||
cursors []int
|
cursors []int
|
||||||
sorted bool
|
sorted bool
|
||||||
tac bool
|
tac bool
|
||||||
final bool
|
final bool
|
||||||
count int
|
count int
|
||||||
pass bool
|
pass bool
|
||||||
|
revision int
|
||||||
}
|
}
|
||||||
|
|
||||||
// PassMerger returns a new Merger that simply returns the items in the
|
// PassMerger returns a new Merger that simply returns the items in the
|
||||||
// original order
|
// original order
|
||||||
func PassMerger(chunks *[]*Chunk, tac bool) *Merger {
|
func PassMerger(chunks *[]*Chunk, tac bool, revision int) *Merger {
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
pattern: nil,
|
pattern: nil,
|
||||||
chunks: chunks,
|
chunks: chunks,
|
||||||
tac: tac,
|
tac: tac,
|
||||||
count: 0,
|
count: 0,
|
||||||
pass: true}
|
pass: true,
|
||||||
|
revision: revision}
|
||||||
|
|
||||||
for _, chunk := range *mg.chunks {
|
for _, chunk := range *mg.chunks {
|
||||||
mg.count += chunk.count
|
mg.count += chunk.count
|
||||||
@@ -37,17 +41,18 @@ func PassMerger(chunks *[]*Chunk, tac bool) *Merger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewMerger returns a new Merger
|
// NewMerger returns a new Merger
|
||||||
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool) *Merger {
|
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision int) *Merger {
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
pattern: pattern,
|
pattern: pattern,
|
||||||
lists: lists,
|
lists: lists,
|
||||||
merged: []Result{},
|
merged: []Result{},
|
||||||
chunks: nil,
|
chunks: nil,
|
||||||
cursors: make([]int, len(lists)),
|
cursors: make([]int, len(lists)),
|
||||||
sorted: sorted,
|
sorted: sorted,
|
||||||
tac: tac,
|
tac: tac,
|
||||||
final: false,
|
final: false,
|
||||||
count: 0}
|
count: 0,
|
||||||
|
revision: revision}
|
||||||
|
|
||||||
for _, list := range mg.lists {
|
for _, list := range mg.lists {
|
||||||
mg.count += len(list)
|
mg.count += len(list)
|
||||||
@@ -55,6 +60,11 @@ func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool) *Merge
|
|||||||
return &mg
|
return &mg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Revision returns revision number
|
||||||
|
func (mg *Merger) Revision() int {
|
||||||
|
return mg.revision
|
||||||
|
}
|
||||||
|
|
||||||
// Length returns the number of items
|
// Length returns the number of items
|
||||||
func (mg *Merger) Length() int {
|
func (mg *Merger) Length() int {
|
||||||
return mg.count
|
return mg.count
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ func randResult() Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyMerger(t *testing.T) {
|
func TestEmptyMerger(t *testing.T) {
|
||||||
assert(t, EmptyMerger.Length() == 0, "Not empty")
|
assert(t, EmptyMerger(0).Length() == 0, "Not empty")
|
||||||
assert(t, EmptyMerger.count == 0, "Invalid count")
|
assert(t, EmptyMerger(0).count == 0, "Invalid count")
|
||||||
assert(t, len(EmptyMerger.lists) == 0, "Invalid lists")
|
assert(t, len(EmptyMerger(0).lists) == 0, "Invalid lists")
|
||||||
assert(t, len(EmptyMerger.merged) == 0, "Invalid merged list")
|
assert(t, len(EmptyMerger(0).merged) == 0, "Invalid merged list")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
||||||
@@ -57,7 +57,7 @@ func TestMergerUnsorted(t *testing.T) {
|
|||||||
cnt := len(items)
|
cnt := len(items)
|
||||||
|
|
||||||
// Not sorted: same order
|
// Not sorted: same order
|
||||||
mg := NewMerger(nil, lists, false, false)
|
mg := NewMerger(nil, lists, false, false, 0)
|
||||||
assert(t, cnt == mg.Length(), "Invalid Length")
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
for i := 0; i < cnt; i++ {
|
for i := 0; i < cnt; i++ {
|
||||||
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
||||||
@@ -69,7 +69,7 @@ func TestMergerSorted(t *testing.T) {
|
|||||||
cnt := len(items)
|
cnt := len(items)
|
||||||
|
|
||||||
// Sorted sorted order
|
// Sorted sorted order
|
||||||
mg := NewMerger(nil, lists, true, false)
|
mg := NewMerger(nil, lists, true, false, 0)
|
||||||
assert(t, cnt == mg.Length(), "Invalid Length")
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
for i := 0; i < cnt; i++ {
|
for i := 0; i < cnt; i++ {
|
||||||
@@ -79,7 +79,7 @@ func TestMergerSorted(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inverse order
|
// Inverse order
|
||||||
mg2 := NewMerger(nil, lists, true, false)
|
mg2 := NewMerger(nil, lists, true, false, 0)
|
||||||
for i := cnt - 1; i >= 0; i-- {
|
for i := cnt - 1; i >= 0; i-- {
|
||||||
if items[i] != mg2.Get(i) {
|
if items[i] != mg2.Get(i) {
|
||||||
t.Error("Not sorted", items[i], mg2.Get(i))
|
t.Error("Not sorted", items[i], mg2.Get(i))
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const usage = `usage: fzf [options]
|
|||||||
(default: 10)
|
(default: 10)
|
||||||
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
||||||
--border[=STYLE] Draw border around the finder
|
--border[=STYLE] Draw border around the finder
|
||||||
[rounded|sharp|bold|block|double|horizontal|vertical|
|
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
|
||||||
top|bottom|left|right|none] (default: rounded)
|
top|bottom|left|right|none] (default: rounded)
|
||||||
--border-label=LABEL Label to print on the border
|
--border-label=LABEL Label to print on the border
|
||||||
--border-label-pos=COL Position of the border label
|
--border-label-pos=COL Position of the border label
|
||||||
@@ -72,7 +72,8 @@ const usage = `usage: fzf [options]
|
|||||||
(default: 0 or center)
|
(default: 0 or center)
|
||||||
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||||
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||||
--info=STYLE Finder info style [default|hidden|inline|inline:SEPARATOR]
|
--info=STYLE Finder info style
|
||||||
|
[default|right|hidden|inline[:SEPARATOR]|inline-right]
|
||||||
--separator=STR String to form horizontal separator on info line
|
--separator=STR String to form horizontal separator on info line
|
||||||
--no-separator Hide info line separator
|
--no-separator Hide info line separator
|
||||||
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
|
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
|
||||||
@@ -194,10 +195,16 @@ type infoStyle int
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
infoDefault infoStyle = iota
|
infoDefault infoStyle = iota
|
||||||
|
infoRight
|
||||||
infoInline
|
infoInline
|
||||||
|
infoInlineRight
|
||||||
infoHidden
|
infoHidden
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (s infoStyle) noExtraLine() bool {
|
||||||
|
return s == infoInline || s == infoInlineRight || s == infoHidden
|
||||||
|
}
|
||||||
|
|
||||||
type labelOpts struct {
|
type labelOpts struct {
|
||||||
label string
|
label string
|
||||||
column int
|
column int
|
||||||
@@ -546,6 +553,8 @@ func parseBorder(str string, optional bool) tui.BorderShape {
|
|||||||
return tui.BorderBold
|
return tui.BorderBold
|
||||||
case "block":
|
case "block":
|
||||||
return tui.BorderBlock
|
return tui.BorderBlock
|
||||||
|
case "thinblock":
|
||||||
|
return tui.BorderThinBlock
|
||||||
case "double":
|
case "double":
|
||||||
return tui.BorderDouble
|
return tui.BorderDouble
|
||||||
case "horizontal":
|
case "horizontal":
|
||||||
@@ -566,7 +575,7 @@ func parseBorder(str string, optional bool) tui.BorderShape {
|
|||||||
if optional && str == "" {
|
if optional && str == "" {
|
||||||
return tui.DefaultBorderShape
|
return tui.DefaultBorderShape
|
||||||
}
|
}
|
||||||
errorExit("invalid border style (expected: rounded|sharp|bold|block|double|horizontal|vertical|top|bottom|left|right|none)")
|
errorExit("invalid border style (expected: rounded|sharp|bold|block|thinblock|double|horizontal|vertical|top|bottom|left|right|none)")
|
||||||
}
|
}
|
||||||
return tui.BorderNone
|
return tui.BorderNone
|
||||||
}
|
}
|
||||||
@@ -1374,8 +1383,12 @@ func parseInfoStyle(str string) (infoStyle, string) {
|
|||||||
switch str {
|
switch str {
|
||||||
case "default":
|
case "default":
|
||||||
return infoDefault, ""
|
return infoDefault, ""
|
||||||
|
case "right":
|
||||||
|
return infoRight, ""
|
||||||
case "inline":
|
case "inline":
|
||||||
return infoInline, defaultInfoSep
|
return infoInline, defaultInfoSep
|
||||||
|
case "inline-right":
|
||||||
|
return infoInlineRight, ""
|
||||||
case "hidden":
|
case "hidden":
|
||||||
return infoHidden, ""
|
return infoHidden, ""
|
||||||
default:
|
default:
|
||||||
@@ -1383,7 +1396,7 @@ func parseInfoStyle(str string) (infoStyle, string) {
|
|||||||
if strings.HasPrefix(str, prefix) {
|
if strings.HasPrefix(str, prefix) {
|
||||||
return infoInline, strings.ReplaceAll(str[len(prefix):], "\n", " ")
|
return infoInline, strings.ReplaceAll(str[len(prefix):], "\n", " ")
|
||||||
}
|
}
|
||||||
errorExit("invalid info style (expected: default|hidden|inline|inline:SEPARATOR)")
|
errorExit("invalid info style (expected: default|right|hidden|inline[:SEPARATOR]|inline-right)")
|
||||||
}
|
}
|
||||||
return infoDefault, ""
|
return infoDefault, ""
|
||||||
}
|
}
|
||||||
@@ -1438,6 +1451,8 @@ func parsePreviewWindowImpl(opts *previewOpts, input string, exit func(string))
|
|||||||
opts.border = tui.BorderBold
|
opts.border = tui.BorderBold
|
||||||
case "border-block":
|
case "border-block":
|
||||||
opts.border = tui.BorderBlock
|
opts.border = tui.BorderBlock
|
||||||
|
case "border-thinblock":
|
||||||
|
opts.border = tui.BorderThinBlock
|
||||||
case "border-double":
|
case "border-double":
|
||||||
opts.border = tui.BorderDouble
|
opts.border = tui.BorderDouble
|
||||||
case "noborder", "border-none":
|
case "noborder", "border-none":
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ type Terminal struct {
|
|||||||
merger *Merger
|
merger *Merger
|
||||||
selected map[int32]selectedItem
|
selected map[int32]selectedItem
|
||||||
version int64
|
version int64
|
||||||
|
revision int
|
||||||
reqBox *util.EventBox
|
reqBox *util.EventBox
|
||||||
initialPreviewOpts previewOpts
|
initialPreviewOpts previewOpts
|
||||||
previewOpts previewOpts
|
previewOpts previewOpts
|
||||||
@@ -564,7 +565,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
if previewBox != nil && opts.Preview.aboveOrBelow() {
|
if previewBox != nil && opts.Preview.aboveOrBelow() {
|
||||||
effectiveMinHeight += 1 + borderLines(opts.Preview.border)
|
effectiveMinHeight += 1 + borderLines(opts.Preview.border)
|
||||||
}
|
}
|
||||||
if opts.InfoStyle != infoDefault {
|
if opts.InfoStyle.noExtraLine() {
|
||||||
effectiveMinHeight--
|
effectiveMinHeight--
|
||||||
}
|
}
|
||||||
effectiveMinHeight += borderLines(opts.BorderShape)
|
effectiveMinHeight += borderLines(opts.BorderShape)
|
||||||
@@ -644,7 +645,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
jumpLabels: opts.JumpLabels,
|
jumpLabels: opts.JumpLabels,
|
||||||
printer: opts.Printer,
|
printer: opts.Printer,
|
||||||
printsep: opts.PrintSep,
|
printsep: opts.PrintSep,
|
||||||
merger: EmptyMerger,
|
merger: EmptyMerger(0),
|
||||||
selected: make(map[int32]selectedItem),
|
selected: make(map[int32]selectedItem),
|
||||||
reqBox: util.NewEventBox(),
|
reqBox: util.NewEventBox(),
|
||||||
initialPreviewOpts: opts.Preview,
|
initialPreviewOpts: opts.Preview,
|
||||||
@@ -726,7 +727,7 @@ func (t *Terminal) environ() []string {
|
|||||||
|
|
||||||
func borderLines(shape tui.BorderShape) int {
|
func borderLines(shape tui.BorderShape) int {
|
||||||
switch shape {
|
switch shape {
|
||||||
case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderDouble:
|
case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
||||||
return 2
|
return 2
|
||||||
case tui.BorderTop, tui.BorderBottom:
|
case tui.BorderTop, tui.BorderBottom:
|
||||||
return 1
|
return 1
|
||||||
@@ -844,7 +845,7 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) noInfoLine() bool {
|
func (t *Terminal) noInfoLine() bool {
|
||||||
return t.infoStyle != infoDefault
|
return t.infoStyle.noExtraLine()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getScrollbar(total int, height int, offset int) (int, int) {
|
func getScrollbar(total int, height int, offset int) (int, int) {
|
||||||
@@ -930,9 +931,10 @@ func (t *Terminal) UpdateProgress(progress float32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateList updates Merger to display the list
|
// UpdateList updates Merger to display the list
|
||||||
func (t *Terminal) UpdateList(merger *Merger, reset bool) {
|
func (t *Terminal) UpdateList(merger *Merger) {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
var prevIndex int32 = -1
|
var prevIndex int32 = -1
|
||||||
|
reset := t.revision != merger.Revision()
|
||||||
if !reset && t.track != trackDisabled {
|
if !reset && t.track != trackDisabled {
|
||||||
if t.merger.Length() > 0 {
|
if t.merger.Length() > 0 {
|
||||||
prevIndex = t.merger.Get(t.cy).item.Index()
|
prevIndex = t.merger.Get(t.cy).item.Index()
|
||||||
@@ -944,6 +946,7 @@ func (t *Terminal) UpdateList(merger *Merger, reset bool) {
|
|||||||
t.merger = merger
|
t.merger = merger
|
||||||
if reset {
|
if reset {
|
||||||
t.selected = make(map[int32]selectedItem)
|
t.selected = make(map[int32]selectedItem)
|
||||||
|
t.revision = merger.Revision()
|
||||||
t.version++
|
t.version++
|
||||||
}
|
}
|
||||||
if t.triggerLoad {
|
if t.triggerLoad {
|
||||||
@@ -1082,7 +1085,7 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
|
|||||||
if idx == 3 {
|
if idx == 3 {
|
||||||
extraMargin[idx] += 1 + bw
|
extraMargin[idx] += 1 + bw
|
||||||
}
|
}
|
||||||
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderDouble:
|
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
||||||
extraMargin[idx] += 1 + bw*(idx%2)
|
extraMargin[idx] += 1 + bw*(idx%2)
|
||||||
}
|
}
|
||||||
marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
|
marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
|
||||||
@@ -1175,7 +1178,7 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
|
|||||||
t.border = t.tui.NewWindow(
|
t.border = t.tui.NewWindow(
|
||||||
marginInt[0], marginInt[3], width+(1+bw), height,
|
marginInt[0], marginInt[3], width+(1+bw), height,
|
||||||
false, tui.MakeBorderStyle(tui.BorderRight, t.unicode))
|
false, tui.MakeBorderStyle(tui.BorderRight, t.unicode))
|
||||||
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderDouble:
|
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
||||||
t.border = t.tui.NewWindow(
|
t.border = t.tui.NewWindow(
|
||||||
marginInt[0]-1, marginInt[3]-(1+bw), width+(1+bw)*2, height+2,
|
marginInt[0]-1, marginInt[3]-(1+bw), width+(1+bw)*2, height+2,
|
||||||
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
|
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
|
||||||
@@ -1209,7 +1212,7 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
|
|||||||
}
|
}
|
||||||
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
||||||
switch previewOpts.border {
|
switch previewOpts.border {
|
||||||
case tui.BorderSharp, tui.BorderRounded, tui.BorderBold, tui.BorderBlock, tui.BorderDouble:
|
case tui.BorderSharp, tui.BorderRounded, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
||||||
pwidth -= (1 + bw) * 2
|
pwidth -= (1 + bw) * 2
|
||||||
pheight -= 2
|
pheight -= 2
|
||||||
x += 1 + bw
|
x += 1 + bw
|
||||||
@@ -1353,7 +1356,7 @@ func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts label
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch borderShape {
|
switch borderShape {
|
||||||
case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderDouble:
|
case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
||||||
if redrawBorder {
|
if redrawBorder {
|
||||||
window.DrawHBorder()
|
window.DrawHBorder()
|
||||||
}
|
}
|
||||||
@@ -1461,9 +1464,7 @@ func (t *Terminal) trimMessage(message string, maxWidth int) string {
|
|||||||
func (t *Terminal) printInfo() {
|
func (t *Terminal) printInfo() {
|
||||||
pos := 0
|
pos := 0
|
||||||
line := t.promptLine()
|
line := t.promptLine()
|
||||||
switch t.infoStyle {
|
printSpinner := func() {
|
||||||
case infoDefault:
|
|
||||||
t.move(line+1, 0, t.separatorLen == 0)
|
|
||||||
if t.reading {
|
if t.reading {
|
||||||
duration := int64(spinnerDuration)
|
duration := int64(spinnerDuration)
|
||||||
idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration
|
idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration
|
||||||
@@ -1471,8 +1472,18 @@ func (t *Terminal) printInfo() {
|
|||||||
} else {
|
} else {
|
||||||
t.window.Print(" ") // Clear spinner
|
t.window.Print(" ") // Clear spinner
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
switch t.infoStyle {
|
||||||
|
case infoDefault:
|
||||||
|
t.move(line+1, 0, t.separatorLen == 0)
|
||||||
|
printSpinner()
|
||||||
t.move(line+1, 2, false)
|
t.move(line+1, 2, false)
|
||||||
pos = 2
|
pos = 2
|
||||||
|
case infoRight:
|
||||||
|
t.move(line+1, 0, false)
|
||||||
|
case infoInlineRight:
|
||||||
|
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||||
|
t.move(line, pos, true)
|
||||||
case infoInline:
|
case infoInline:
|
||||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||||
str := t.infoSep
|
str := t.infoSep
|
||||||
@@ -1520,14 +1531,55 @@ func (t *Terminal) printInfo() {
|
|||||||
if t.failed != nil && t.count == 0 {
|
if t.failed != nil && t.count == 0 {
|
||||||
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
|
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printSeparator := func(fillLength int, pad bool) {
|
||||||
|
// --------_
|
||||||
|
if t.separatorLen > 0 {
|
||||||
|
t.separator(t.window, fillLength)
|
||||||
|
t.window.Print(" ")
|
||||||
|
} else if pad {
|
||||||
|
t.window.Print(strings.Repeat(" ", fillLength+1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.infoStyle == infoRight {
|
||||||
|
maxWidth := t.window.Width()
|
||||||
|
if t.reading {
|
||||||
|
// Need space for spinner and a margin column
|
||||||
|
maxWidth -= 2
|
||||||
|
}
|
||||||
|
output = t.trimMessage(output, maxWidth)
|
||||||
|
fillLength := t.window.Width() - len(output) - 2
|
||||||
|
if t.reading {
|
||||||
|
if fillLength >= 2 {
|
||||||
|
printSeparator(fillLength-2, true)
|
||||||
|
}
|
||||||
|
printSpinner()
|
||||||
|
t.window.Print(" ")
|
||||||
|
} else if fillLength >= 0 {
|
||||||
|
printSeparator(fillLength, true)
|
||||||
|
}
|
||||||
|
t.window.CPrint(tui.ColInfo, output)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.infoStyle == infoInlineRight {
|
||||||
|
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
|
||||||
|
if pos >= t.window.Width() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.move(line, pos, false)
|
||||||
|
printSpinner()
|
||||||
|
t.window.Print(" ")
|
||||||
|
pos += 2
|
||||||
|
}
|
||||||
|
|
||||||
maxWidth := t.window.Width() - pos
|
maxWidth := t.window.Width() - pos
|
||||||
output = t.trimMessage(output, maxWidth)
|
output = t.trimMessage(output, maxWidth)
|
||||||
t.window.CPrint(tui.ColInfo, output)
|
t.window.CPrint(tui.ColInfo, output)
|
||||||
|
|
||||||
fillLength := maxWidth - len(output) - 2
|
fillLength := maxWidth - len(output) - 2
|
||||||
if t.separatorLen > 0 && fillLength > 0 {
|
if fillLength > 0 {
|
||||||
t.window.CPrint(tui.ColSeparator, " ")
|
t.window.CPrint(tui.ColSeparator, " ")
|
||||||
t.separator(t.window, fillLength)
|
printSeparator(fillLength, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1820,7 +1872,7 @@ func (t *Terminal) renderPreviewSpinner() {
|
|||||||
if !t.previewer.scrollable {
|
if !t.previewer.scrollable {
|
||||||
if maxWidth > 0 {
|
if maxWidth > 0 {
|
||||||
t.pwindow.Move(0, maxWidth-1)
|
t.pwindow.Move(0, maxWidth-1)
|
||||||
t.pwindow.CPrint(tui.ColSpinner, spin)
|
t.pwindow.CPrint(tui.ColPreviewSpinner, spin)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
offsetString := fmt.Sprintf("%d/%d", t.previewer.offset+1, numLines)
|
offsetString := fmt.Sprintf("%d/%d", t.previewer.offset+1, numLines)
|
||||||
@@ -1832,7 +1884,7 @@ func (t *Terminal) renderPreviewSpinner() {
|
|||||||
pos := maxWidth - t.displayWidth(offsetRunes)
|
pos := maxWidth - t.displayWidth(offsetRunes)
|
||||||
t.pwindow.Move(0, pos)
|
t.pwindow.Move(0, pos)
|
||||||
if maxWidth > 0 {
|
if maxWidth > 0 {
|
||||||
t.pwindow.CPrint(tui.ColSpinner, spin)
|
t.pwindow.CPrint(tui.ColPreviewSpinner, spin)
|
||||||
t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), string(offsetRunes))
|
t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), string(offsetRunes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -757,7 +757,7 @@ func (w *LightWindow) DrawHBorder() {
|
|||||||
|
|
||||||
func (w *LightWindow) drawBorder(onlyHorizontal bool) {
|
func (w *LightWindow) drawBorder(onlyHorizontal bool) {
|
||||||
switch w.border.shape {
|
switch w.border.shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
|
||||||
w.drawBorderAround(onlyHorizontal)
|
w.drawBorderAround(onlyHorizontal)
|
||||||
case BorderHorizontal:
|
case BorderHorizontal:
|
||||||
w.drawBorderHorizontal(true, true)
|
w.drawBorderHorizontal(true, true)
|
||||||
|
|||||||
@@ -715,7 +715,7 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
|||||||
|
|
||||||
hw := runewidth.RuneWidth(w.borderStyle.top)
|
hw := runewidth.RuneWidth(w.borderStyle.top)
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderHorizontal, BorderTop:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderTop:
|
||||||
max := right - 2*hw
|
max := right - 2*hw
|
||||||
if shape == BorderHorizontal || shape == BorderTop {
|
if shape == BorderHorizontal || shape == BorderTop {
|
||||||
max = right - hw
|
max = right - hw
|
||||||
@@ -730,7 +730,7 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderHorizontal, BorderBottom:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderBottom:
|
||||||
max := right - 2*hw
|
max := right - 2*hw
|
||||||
if shape == BorderHorizontal || shape == BorderBottom {
|
if shape == BorderHorizontal || shape == BorderBottom {
|
||||||
max = right - hw
|
max = right - hw
|
||||||
@@ -741,13 +741,13 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
|||||||
}
|
}
|
||||||
if !onlyHorizontal {
|
if !onlyHorizontal {
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderVertical, BorderLeft:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderLeft:
|
||||||
for y := top; y < bot; y++ {
|
for y := top; y < bot; y++ {
|
||||||
_screen.SetContent(left, y, w.borderStyle.left, nil, style)
|
_screen.SetContent(left, y, w.borderStyle.left, nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderVertical, BorderRight:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderRight:
|
||||||
vw := runewidth.RuneWidth(w.borderStyle.right)
|
vw := runewidth.RuneWidth(w.borderStyle.right)
|
||||||
for y := top; y < bot; y++ {
|
for y := top; y < bot; y++ {
|
||||||
_screen.SetContent(right-vw, y, w.borderStyle.right, nil, style)
|
_screen.SetContent(right-vw, y, w.borderStyle.right, nil, style)
|
||||||
@@ -755,7 +755,7 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
|
||||||
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
|
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
|
||||||
_screen.SetContent(right-runewidth.RuneWidth(w.borderStyle.topRight), top, w.borderStyle.topRight, nil, style)
|
_screen.SetContent(right-runewidth.RuneWidth(w.borderStyle.topRight), top, w.borderStyle.topRight, nil, style)
|
||||||
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
|
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
|
||||||
|
|||||||
@@ -315,6 +315,7 @@ const (
|
|||||||
BorderSharp
|
BorderSharp
|
||||||
BorderBold
|
BorderBold
|
||||||
BorderBlock
|
BorderBlock
|
||||||
|
BorderThinBlock
|
||||||
BorderDouble
|
BorderDouble
|
||||||
BorderHorizontal
|
BorderHorizontal
|
||||||
BorderVertical
|
BorderVertical
|
||||||
@@ -408,6 +409,23 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
|||||||
bottomLeft: '▙',
|
bottomLeft: '▙',
|
||||||
bottomRight: '▟',
|
bottomRight: '▟',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case BorderThinBlock:
|
||||||
|
// 🭽▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔🭾
|
||||||
|
// ▏ ▕
|
||||||
|
// 🭼▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁🭿
|
||||||
|
return BorderStyle{
|
||||||
|
shape: shape,
|
||||||
|
top: '▔',
|
||||||
|
bottom: '▁',
|
||||||
|
left: '▏',
|
||||||
|
right: '▕',
|
||||||
|
topLeft: '🭽',
|
||||||
|
topRight: '🭾',
|
||||||
|
bottomLeft: '🭼',
|
||||||
|
bottomRight: '🭿',
|
||||||
|
}
|
||||||
|
|
||||||
case BorderDouble:
|
case BorderDouble:
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: shape,
|
shape: shape,
|
||||||
@@ -538,6 +556,7 @@ var (
|
|||||||
ColBorderLabel ColorPair
|
ColBorderLabel ColorPair
|
||||||
ColPreviewLabel ColorPair
|
ColPreviewLabel ColorPair
|
||||||
ColPreviewScrollbar ColorPair
|
ColPreviewScrollbar ColorPair
|
||||||
|
ColPreviewSpinner ColorPair
|
||||||
)
|
)
|
||||||
|
|
||||||
func EmptyTheme() *ColorTheme {
|
func EmptyTheme() *ColorTheme {
|
||||||
@@ -769,4 +788,5 @@ func initPalette(theme *ColorTheme) {
|
|||||||
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
|
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
|
||||||
ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg)
|
ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg)
|
||||||
ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)
|
ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)
|
||||||
|
ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2707,6 +2707,16 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { assert(_1[-2] == ' 1/100') }
|
tmux.until { assert(_1[-2] == ' 1/100') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_info_right
|
||||||
|
tmux.send_keys "#{FZF} --info=right --separator x --bind 'start:reload:seq 100; sleep 10'", :Enter
|
||||||
|
tmux.until { assert_match(%r{xxx [⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100}, _1[-2]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_info_inline_right
|
||||||
|
tmux.send_keys "#{FZF} --info=inline-right --bind 'start:reload:seq 100; sleep 10'", :Enter
|
||||||
|
tmux.until { assert_match(%r{[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100}, _1[-1]) }
|
||||||
|
end
|
||||||
|
|
||||||
def test_prev_next_selected
|
def test_prev_next_selected
|
||||||
tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter
|
tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter
|
||||||
tmux.until { |lines| assert_equal 10, lines.item_count }
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
@@ -2935,6 +2945,14 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys "sleep 0.5 | #{FZF} --bind 'start:reload:ls' --bind 'load:become:tty'", :Enter
|
tmux.send_keys "sleep 0.5 | #{FZF} --bind 'start:reload:ls' --bind 'load:become:tty'", :Enter
|
||||||
tmux.until { |lines| assert_includes lines, '/dev/tty' }
|
tmux.until { |lines| assert_includes lines, '/dev/tty' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_disabled_preview_update
|
||||||
|
tmux.send_keys "echo bar | #{FZF} --disabled --bind 'change:reload:echo foo' --preview 'echo [{q}-{}]'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.until { |lines| assert(lines.any? { |line| line.include?('[-bar]') }) }
|
||||||
|
tmux.send_keys :x
|
||||||
|
tmux.until { |lines| assert(lines.any? { |line| line.include?('[x-foo]') }) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
|
|||||||
Reference in New Issue
Block a user