m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-14 14:23:47 -05:00

Compare commits

...

17 Commits

Author SHA1 Message Date
Junegunn Choi
2093667548 0.30.0 2022-04-04 23:01:43 +09:00
Junegunn Choi
3c868d7961 ADVANCED.md: Add rebind example 2022-04-04 22:59:26 +09:00
dependabot[bot]
707f4f5816 Bump github/codeql-action from 1.1.5 to 2.1.6 (#2782)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1.1.5 to 2.1.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](8834766498...28eead2408)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-04 22:44:10 +09:00
Junegunn Choi
b3ab6311c5 Hide cursor while rendering the screen
Fix #2781
Fix #2588
Fix #1805

Fix https://github.com/junegunn/fzf.vim/issues/1370
Fix https://github.com/junegunn/fzf.vim/issues/1060
2022-04-04 22:06:16 +09:00
Junegunn Choi
d56f605b63 Add rebind action for restoring bindings after unbind
Fix #2752
Close #2564
2022-04-04 21:54:22 +09:00
Junegunn Choi
f8b713f425 Remove redundant state update on reload
Related: 5209e95
2022-03-31 10:05:28 +09:00
Junegunn Choi
5209e95bc7 Make preview updated when reload and change-query are combined
Fix #2744
2022-03-29 22:27:03 +09:00
Junegunn Choi
ef67a45702 Add --ellipsis=.. option
Close #2432

Also see
- #1769
- https://github.com/junegunn/fzf/pull/1844#issuecomment-586663660
2022-03-29 21:35:36 +09:00
Junegunn Choi
b88eb72ac2 Modernize build tags 2022-03-29 21:23:45 +09:00
dependabot[bot]
32847f7254 Bump actions/setup-go from 2.2.0 to 3 (#2776)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2.2.0 to 3.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](bfdd3570ce...f6164bd8c8)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-29 21:17:44 +09:00
dependabot[bot]
71df93b534 Bump ruby/setup-ruby from 1.62.0 to 1.100.0 (#2775)
Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.62.0 to 1.100.0.
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Commits](5aaa89ff0d...bd94d6a504)

---
updated-dependencies:
- dependency-name: ruby/setup-ruby
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-29 21:17:29 +09:00
Naveen
bb028191f8 Set up dependabot for GitHub actions (#2764) 2022-03-29 21:08:41 +09:00
Naveen
19af8fc7d8 Pin actions to a full length commit SHA (#2765)
- Pinned actions by SHA https://github.com/ossf/scorecard/blob/main/docs/checks.md#pinned-dependencies
- Included permissions for the action. https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions

>Pin actions to a full length commit SHA

>Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload.

https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions

Also, dependabot supports upgrade based on SHA.

Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com>s
2022-03-29 21:08:01 +09:00
Junegunn Choi
a06671b47f Increase TTY buffer limit
Kitty's shell intergration generates a long sequence of key presses in
certain cases. As long as the length of the sequence is finite, fzf can
process it.

Close #2748
2022-03-09 17:02:06 +09:00
Junegunn Choi
5f385d88e0 [zsh] Set up bindings for all three keymaps: emacs, vicmd, and viins
Fix #2694
2022-02-23 15:36:49 +09:00
Junegunn Choi
9cb7a364a3 [install] Remove code that might delete user fish script
Fix #2703
2022-01-05 21:48:20 +09:00
Junegunn Choi
f68cbc577d Add link to ADVANCED.md
Related #2701
2022-01-03 13:52:46 +09:00
37 changed files with 245 additions and 85 deletions

View File

@@ -4,3 +4,7 @@ updates:
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -8,8 +8,15 @@ on:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
jobs:
analyze:
permissions:
actions: read # for github/codeql-action/init to get workflow details
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/autobuild to send a status report
name: Analyze
runs-on: ubuntu-latest
@@ -20,18 +27,18 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
with:
fetch-depth: 0
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@28eead240834b314f7def40f6fcba65d100d99b1 # v1
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@28eead240834b314f7def40f6fcba65d100d99b1 # v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@28eead240834b314f7def40f6fcba65d100d99b1 # v1

View File

@@ -8,24 +8,24 @@ on:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
go: [1.14, 1.16]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
uses: actions/setup-go@f6164bd8c8acb4a71fb2791a8b6c4024ff038dab # v2
with:
go-version: ${{ matrix.go }}
go-version: 1.18
- name: Setup Ruby
uses: ruby/setup-ruby@v1.62.0
uses: ruby/setup-ruby@bd94d6a504586da892a5753afdd1480096ed30df # v1.62.0
with:
ruby-version: 3.0.0

View File

@@ -8,24 +8,24 @@ on:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: macos-latest
strategy:
matrix:
go: [1.14, 1.16]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
uses: actions/setup-go@f6164bd8c8acb4a71fb2791a8b6c4024ff038dab # v2
with:
go-version: ${{ matrix.go }}
go-version: 1.18
- name: Setup Ruby
uses: ruby/setup-ruby@v1.62.0
uses: ruby/setup-ruby@bd94d6a504586da892a5753afdd1480096ed30df # v1.62.0
with:
ruby-version: 3.0.0

1
.tool-versions Normal file
View File

@@ -0,0 +1 @@
golang 1.18

View File

@@ -17,6 +17,7 @@ Advanced fzf examples
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
* [Using fzf as interative Ripgrep launcher](#using-fzf-as-interative-ripgrep-launcher)
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
* [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode)
* [Log tailing](#log-tailing)
* [Key bindings for git objects](#key-bindings-for-git-objects)
* [Files listed in `git status`](#files-listed-in-git-status)
@@ -405,6 +406,40 @@ IFS=: read -ra selected < <(
- We reverted `--color` option for customizing how the matching chunks are
displayed in the second phase
### Switching between Ripgrep mode and fzf mode
*(Requires fzf 0.30.0 or above)*
fzf 0.30.0 added `rebind` action so we can "rebind" the bindings that were
previously "unbound" via `unbind`.
This is an improved version of the previous example that allows us to switch
between Ripgrep launcher mode and fzf-only filtering mode via CTRL-R and
CTRL-F.
```sh
#!/usr/bin/env bash
# Switch between Ripgrep launcher mode (CTRL-R) and fzf filtering mode (CTRL-F)
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}"
IFS=: read -ra selected < <(
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
fzf --ansi \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--disabled --query "$INITIAL_QUERY" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--bind "ctrl-f:unbind(change,ctrl-f)+change-prompt(2. fzf> )+enable-search+clear-query+rebind(ctrl-r)" \
--bind "ctrl-r:unbind(ctrl-r)+change-prompt(1. ripgrep> )+disable-search+reload($RG_PREFIX {q} || true)+rebind(change,ctrl-f)" \
--prompt '1. Ripgrep> ' \
--delimiter : \
--header ' CTRL-R (Ripgrep mode) CTRL-F (fzf mode) ' \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
)
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
```
Log tailing
-----------

View File

@@ -1,6 +1,21 @@
CHANGELOG
=========
0.30.0
------
- Fixed cursor flickering over the screen by hiding it during rendering
- Added `--ellipsis` option. You can take advantage of it to make fzf
effectively search non-visible parts of the item.
```sh
# Search against hidden line numbers on the far right
nl /usr/share/dict/words |
awk '{printf "%s%1000s\n", $2, $1}' |
fzf --nth=-1 --no-hscroll --ellipsis='' |
awk '{print $2}'
```
- Added `rebind` action for restoring bindings after `unbind`
- Bug fixes and improvements
0.29.0
------
- Added `change-preview(...)` action to change the `--preview` command

View File

@@ -592,6 +592,9 @@ If ripgrep doesn't find any matches, it will exit with a non-zero exit status,
and fzf will warn you about it. To suppress the warning message, we added
`|| true` to the command, so that it always exits with 0.
See ["Using fzf as interative Ripgrep launcher"](https://github.com/junegunn/fzf/blob/master/ADVANCED.md#using-fzf-as-interative-ripgrep-launcher)
for a fuller example with preview window options.
### Preview window
When the `--preview` option is set, fzf automatically starts an external process

View File

@@ -2,7 +2,7 @@
set -u
version=0.29.0
version=0.30.0
auto_completion=
key_bindings=
update_config=2
@@ -280,11 +280,6 @@ EOF
[ $? -eq 0 ] && echo "OK" || echo "Failed"
mkdir -p "${fish_dir}/functions"
if [ -e "${fish_dir}/functions/fzf.fish" ]; then
echo -n "Remove unnecessary ${fish_dir}/functions/fzf.fish ... "
rm -f "${fish_dir}/functions/fzf.fish" && echo "OK" || echo "Failed"
fi
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
if [ $key_bindings -ne 0 ]; then
echo -n "Symlink $fish_binding ... "

View File

@@ -1,4 +1,4 @@
$version="0.29.0"
$version="0.30.0"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition

View File

@@ -5,7 +5,7 @@ import (
"github.com/junegunn/fzf/src/protector"
)
var version string = "0.29"
var version string = "0.30"
var revision string = "devel"
func main() {

View File

@@ -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
THE SOFTWARE.
..
.TH fzf-tmux 1 "Dec 2021" "fzf 0.29.0" "fzf-tmux - open fzf in tmux split pane"
.TH fzf-tmux 1 "Apr 2022" "fzf 0.30.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME
fzf-tmux - open fzf in tmux split pane

View File

@@ -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
THE SOFTWARE.
..
.TH fzf 1 "Dec 2021" "fzf 0.29.0" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Apr 2022" "fzf 0.30.0" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -302,6 +302,9 @@ lines that follow.
.TP
.B "--header-first"
Print header before the prompt line
.TP
.BI "--ellipsis=" "STR"
Ellipsis to show when line is truncated (default: '..')
.SS Display
.TP
.B "--ansi"
@@ -864,6 +867,7 @@ A key or an event can be bound to one or more of the following actions.
\fBprint-query\fR (print query and exit)
\fBput\fR (put the character to the prompt)
\fBrefresh-preview\fR
\fBrebind(...)\fR (rebind bindings after \fBunbind\fR)
\fBreload(...)\fR (see below for the details)
\fBreplace-query\fR (replace query string with the current selection)
\fBselect\fR

View File

@@ -66,7 +66,9 @@ fzf-file-widget() {
return $ret
}
zle -N fzf-file-widget
bindkey '^T' fzf-file-widget
bindkey -M emacs '^T' fzf-file-widget
bindkey -M vicmd '^T' fzf-file-widget
bindkey -M viins '^T' fzf-file-widget
# ALT-C - cd into the selected directory
fzf-cd-widget() {
@@ -87,7 +89,9 @@ fzf-cd-widget() {
return $ret
}
zle -N fzf-cd-widget
bindkey '\ec' fzf-cd-widget
bindkey -M emacs '\ec' fzf-cd-widget
bindkey -M vicmd '\ec' fzf-cd-widget
bindkey -M viins '\ec' fzf-cd-widget
# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
@@ -106,7 +110,9 @@ fzf-history-widget() {
return $ret
}
zle -N fzf-history-widget
bindkey '^R' fzf-history-widget
bindkey -M emacs '^R' fzf-history-widget
bindkey -M vicmd '^R' fzf-history-widget
bindkey -M viins '^R' fzf-history-widget
} always {
eval $__fzf_key_bindings_options

View File

@@ -242,9 +242,11 @@ func Run(opts *Options, version string, revision string) {
for {
delay := true
ticks++
input := func() []rune {
input := func(reloaded bool) []rune {
paused, input := terminal.Input()
if !paused {
if reloaded && paused {
query = []rune{}
} else if !paused {
query = input
}
return query
@@ -274,7 +276,8 @@ func Run(opts *Options, version string, revision string) {
opts.Sync = false
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
}
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache())
reset := clearCache()
matcher.Reset(snapshot, input(reset), false, !reading, sort, reset)
case EvtSearchNew:
var command *string
@@ -293,7 +296,8 @@ func Run(opts *Options, version string, revision string) {
break
}
snapshot, _ := chunkList.Snapshot()
matcher.Reset(snapshot, input(), true, !reading, sort, clearCache())
reset := clearCache()
matcher.Reset(snapshot, input(reset), true, !reading, sort, reset)
delay = false
case EvtSearchProgress:

View File

@@ -70,6 +70,7 @@ const usage = `usage: fzf [options]
--header=STR String to print as header
--header-lines=N The first N lines of the input are treated as header
--header-first Print header before the prompt line
--ellipsis=STR Ellipsis to show when line is truncated (default: '..')
Display
--ansi Enable processing of ANSI color codes
@@ -235,6 +236,7 @@ type Options struct {
Header []string
HeaderLines int
HeaderFirst bool
Ellipsis string
Margin [4]sizeSpec
Padding [4]sizeSpec
BorderShape tui.BorderShape
@@ -298,6 +300,7 @@ func defaultOptions() *Options {
Header: make([]string, 0),
HeaderLines: 0,
HeaderFirst: false,
Ellipsis: "..",
Margin: defaultMargin(),
Padding: defaultMargin(),
Unicode: true,
@@ -795,7 +798,7 @@ func init() {
// Backreferences are not supported.
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
executeRegexp = regexp.MustCompile(
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|unbind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|unbind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
}
func parseKeymap(keymap map[tui.Event][]*action, str string) {
@@ -815,6 +818,8 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) {
prefix = symbol + "preview"
} else if strings.HasPrefix(src[1:], "unbind") {
prefix = symbol + "unbind"
} else if strings.HasPrefix(src[1:], "rebind") {
prefix = symbol + "rebind"
} else if strings.HasPrefix(src[1:], "change-prompt") {
prefix = symbol + "change-prompt"
} else if src[len(prefix)] == '-' {
@@ -1022,6 +1027,8 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) {
offset = len("change-prompt")
case actUnbind:
offset = len("unbind")
case actRebind:
offset = len("rebind")
case actExecuteSilent:
offset = len("execute-silent")
case actExecuteMulti:
@@ -1042,8 +1049,8 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) {
actionArg = spec[offset+1 : len(spec)-1]
actions = append(actions, &action{t: t, a: actionArg})
}
if t == actUnbind {
parseKeyChords(actionArg, "unbind target required")
if t == actUnbind || t == actRebind {
parseKeyChords(actionArg, spec[0:offset]+" target required")
} else if t == actChangePreviewWindow {
opts := previewOpts{}
for _, arg := range strings.Split(actionArg, "|") {
@@ -1072,6 +1079,8 @@ func isExecuteAction(str string) actionType {
return actReload
case "unbind":
return actUnbind
case "rebind":
return actRebind
case "preview":
return actPreview
case "change-preview-window":
@@ -1280,6 +1289,7 @@ func parseOptions(opts *Options, allArgs []string) {
validateJumpLabels := false
validatePointer := false
validateMarker := false
validateEllipsis := false
for i := 0; i < len(allArgs); i++ {
arg := allArgs[i]
switch arg {
@@ -1465,6 +1475,9 @@ func parseOptions(opts *Options, allArgs []string) {
opts.HeaderFirst = true
case "--no-header-first":
opts.HeaderFirst = false
case "--ellipsis":
opts.Ellipsis = nextString(allArgs, &i, "ellipsis string required")
validateEllipsis = true
case "--preview":
opts.Preview.command = nextString(allArgs, &i, "preview command required")
case "--no-preview":
@@ -1562,6 +1575,9 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Header = strLines(value)
} else if match, value := optString(arg, "--header-lines="); match {
opts.HeaderLines = atoi(value)
} else if match, value := optString(arg, "--ellipsis="); match {
opts.Ellipsis = value
validateEllipsis = true
} else if match, value := optString(arg, "--preview="); match {
opts.Preview.command = value
} else if match, value := optString(arg, "--preview-window="); match {
@@ -1624,6 +1640,14 @@ func parseOptions(opts *Options, allArgs []string) {
errorExit(err.Error())
}
}
if validateEllipsis {
for _, r := range opts.Ellipsis {
if !unicode.IsGraphic(r) {
errorExit("invalid character in ellipsis")
}
}
}
}
func validateSign(sign string, signOptName string) error {

View File

@@ -1,4 +1,4 @@
// +build !openbsd
//go:build !openbsd
package protector

View File

@@ -1,4 +1,4 @@
// +build openbsd
//go:build openbsd
package protector

View File

@@ -1,4 +1,4 @@
// +build !386,!amd64
//go:build !386 && !amd64
package fzf

View File

@@ -1,5 +1,3 @@
// +build !tcell
package fzf
import (

View File

@@ -1,4 +1,4 @@
// +build 386 amd64
//go:build 386 || amd64
package fzf

View File

@@ -50,7 +50,6 @@ var offsetComponentRegex *regexp.Regexp
var offsetTrimCharsRegex *regexp.Regexp
var activeTempFiles []string
const ellipsis string = ".."
const clearCode string = "\x1b[2J"
func init() {
@@ -137,6 +136,7 @@ type Terminal struct {
delimiter Delimiter
expect map[tui.Event]string
keymap map[tui.Event][]*action
keymapOrg map[tui.Event][]*action
pressed string
printQuery bool
history *History
@@ -145,6 +145,7 @@ type Terminal struct {
headerLines int
header []string
header0 []string
ellipsis string
ansi bool
tabstop int
margin [4]sizeSpec
@@ -313,6 +314,7 @@ const (
actSelect
actDeselect
actUnbind
actRebind
)
type placeholderFlags struct {
@@ -501,6 +503,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
}
keymapCopy := make(map[tui.Event][]*action)
for key, action := range opts.Keymap {
keymapCopy[key] = action
}
t := Terminal{
initDelay: delay,
infoStyle: opts.InfoStyle,
@@ -526,6 +532,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
delimiter: opts.Delimiter,
expect: opts.Expect,
keymap: opts.Keymap,
keymapOrg: keymapCopy,
pressed: "",
printQuery: opts.PrintQuery,
history: opts.History,
@@ -541,6 +548,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
headerLines: opts.HeaderLines,
header: header,
header0: header,
ellipsis: opts.Ellipsis,
ansi: opts.Ansi,
tabstop: opts.Tabstop,
reading: true,
@@ -672,6 +680,7 @@ func (t *Terminal) UpdateList(merger *Merger, reset bool) {
t.merger = merger
if reset {
t.selected = make(map[int32]selectedItem)
t.version++
}
t.mutex.Unlock()
t.reqBox.Set(reqInfo, nil)
@@ -1261,47 +1270,54 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
offsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current)
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
ellipsis, ellipsisWidth := util.Truncate(t.ellipsis, maxWidth/2)
maxe = util.Constrain(maxe+util.Min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(text))
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
if displayWidth > maxWidth {
transformOffsets := func(diff int32) {
transformOffsets := func(diff int32, rightTrim bool) {
for idx, offset := range offsets {
b, e := offset.offset[0], offset.offset[1]
b += 2 - diff
e += 2 - diff
b = util.Max32(b, 2)
el := int32(len(ellipsis))
b += el - diff
e += el - diff
b = util.Max32(b, el)
if rightTrim {
e = util.Min32(e, int32(maxWidth-ellipsisWidth))
}
offsets[idx].offset[0] = b
offsets[idx].offset[1] = util.Max32(b, e)
}
}
if t.hscroll {
if t.keepRight && pos == nil {
trimmed, diff := t.trimLeft(text, maxWidth-2)
transformOffsets(diff)
text = append([]rune(ellipsis), trimmed...)
} else if !t.overflow(text[:maxe], maxWidth-2) {
trimmed, diff := t.trimLeft(text, maxWidth-ellipsisWidth)
transformOffsets(diff, false)
text = append(ellipsis, trimmed...)
} else if !t.overflow(text[:maxe], maxWidth-ellipsisWidth) {
// Stri..
text, _ = t.trimRight(text, maxWidth-2)
text = append(text, []rune(ellipsis)...)
text, _ = t.trimRight(text, maxWidth-ellipsisWidth)
text = append(text, ellipsis...)
} else {
// Stri..
if t.overflow(text[maxe:], 2) {
text = append(text[:maxe], []rune(ellipsis)...)
rightTrim := false
if t.overflow(text[maxe:], ellipsisWidth) {
text = append(text[:maxe], ellipsis...)
rightTrim = true
}
// ..ri..
var diff int32
text, diff = t.trimLeft(text, maxWidth-2)
text, diff = t.trimLeft(text, maxWidth-ellipsisWidth)
// Transform offsets
transformOffsets(diff)
text = append([]rune(ellipsis), text...)
transformOffsets(diff, rightTrim)
text = append(ellipsis, text...)
}
} else {
text, _ = t.trimRight(text, maxWidth-2)
text = append(text, []rune(ellipsis)...)
text, _ = t.trimRight(text, maxWidth-ellipsisWidth)
text = append(text, ellipsis...)
for idx, offset := range offsets {
offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-2))
offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-len(ellipsis)))
offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
}
}
@@ -2719,13 +2735,19 @@ func (t *Terminal) Loop() {
command := t.replacePlaceholder(a.a, false, string(t.input), list)
newCommand = &command
t.reading = true
t.version++
}
case actUnbind:
keys := parseKeyChords(a.a, "PANIC")
for key := range keys {
delete(t.keymap, key)
}
case actRebind:
keys := parseKeyChords(a.a, "PANIC")
for key := range keys {
if originalAction, found := t.keymapOrg[key]; found {
t.keymap[key] = originalAction
}
}
case actChangePreview:
if t.previewOpts.command != a.a {
togglePreview(len(a.a) > 0)

View File

@@ -1,4 +1,4 @@
// +build !windows
//go:build !windows
package fzf

View File

@@ -1,4 +1,4 @@
// +build windows
//go:build windows
package fzf

View File

@@ -1,6 +1,4 @@
// +build !ncurses
// +build !tcell
// +build !windows
//go:build !tcell && !windows
package tui

View File

@@ -23,7 +23,7 @@ const (
defaultEscDelay = 100
escPollInterval = 5
offsetPollTries = 10
maxInputBuffer = 10 * 1024
maxInputBuffer = 1024 * 1024
)
const consoleDevice string = "/dev/tty"
@@ -60,7 +60,7 @@ func (r *LightRenderer) csi(code string) {
func (r *LightRenderer) flush() {
if r.queued.Len() > 0 {
fmt.Fprint(os.Stderr, r.queued.String())
fmt.Fprint(os.Stderr, "\x1b[?25l"+r.queued.String()+"\x1b[?25h")
r.queued.Reset()
}
}

View File

@@ -1,4 +1,4 @@
// +build !windows
//go:build !windows
package tui

View File

@@ -1,4 +1,4 @@
//+build windows
//go:build windows
package tui

View File

@@ -1,4 +1,4 @@
// +build tcell windows
//go:build tcell || windows
package tui

View File

@@ -1,4 +1,4 @@
// +build tcell windows
//go:build tcell || windows
package tui

View File

@@ -1,4 +1,4 @@
// +build !windows
//go:build !windows
package tui

View File

@@ -1,4 +1,4 @@
// +build windows
//go:build windows
package tui

View File

@@ -34,6 +34,23 @@ func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int
return width, -1
}
// Truncate returns the truncated runes and its width
func Truncate(input string, limit int) ([]rune, int) {
runes := []rune{}
width := 0
gr := uniseg.NewGraphemes(input)
for gr.Next() {
rs := gr.Runes()
w := runewidth.StringWidth(string(rs))
if width+w > limit {
return runes, width
}
width += w
runes = append(runes, rs...)
}
return runes, width
}
// Max returns the largest integer
func Max(first int, second int) int {
if first >= second {

View File

@@ -54,3 +54,13 @@ func TestRunesWidth(t *testing.T) {
}
}
}
func TestTruncate(t *testing.T) {
truncated, width := Truncate("가나다라마", 7)
if string(truncated) != "가나다" {
t.Errorf("Expected: 가나다, actual: %s", string(truncated))
}
if width != 6 {
t.Errorf("Expected: 6, actual: %d", width)
}
}

View File

@@ -1,4 +1,4 @@
// +build !windows
//go:build !windows
package util

View File

@@ -1,4 +1,4 @@
// +build windows
//go:build windows
package util

View File

@@ -2043,8 +2043,8 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }
end
def test_unbind
tmux.send_keys "seq 100 | #{FZF} --bind 'c:clear-query,d:unbind(c,d)'", :Enter
def test_unbind_rebind
tmux.send_keys "seq 100 | #{FZF} --bind 'c:clear-query,d:unbind(c,d),e:rebind(c,d)'", :Enter
tmux.until { |lines| assert_equal 100, lines.item_count }
tmux.send_keys 'ab'
tmux.until { |lines| assert_equal '> ab', lines[-1] }
@@ -2052,6 +2052,8 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_equal '>', lines[-1] }
tmux.send_keys 'dabcd'
tmux.until { |lines| assert_equal '> abcd', lines[-1] }
tmux.send_keys 'ecabddc'
tmux.until { |lines| assert_equal '> abdc', lines[-1] }
end
def test_item_index_reset_on_reload
@@ -2077,6 +2079,15 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_includes lines[1], '4' }
end
def test_reload_and_change_preview_should_update_preview
tmux.send_keys "seq 3 | #{FZF} --bind 'ctrl-t:reload(echo 4)+change-preview(echo {})'", :Enter
tmux.until { |lines| assert_equal 3, lines.item_count }
tmux.until { |lines| refute_includes lines[1], '1' }
tmux.send_keys 'C-t'
tmux.until { |lines| assert_equal 1, lines.item_count }
tmux.until { |lines| assert_includes lines[1], '4' }
end
def test_scroll_off
tmux.send_keys "seq 1000 | #{FZF} --scroll-off=3 --bind l:last", :Enter
tmux.until { |lines| assert_equal 1000, lines.item_count }
@@ -2208,6 +2219,12 @@ class TestGoFZF < TestBase
tmux.send_keys 'a'
end
end
def test_ellipsis
tmux.send_keys 'seq 1000 | tr "\n" , | fzf --ellipsis=SNIPSNIP -e -q500', :Enter
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.until { |lines| assert_match(/^> SNIPSNIP.*SNIPSNIP$/, lines[-3]) }
end
end
module TestShell